From 785e36712d592c0585cd71cd70ae4785b225ed23 Mon Sep 17 00:00:00 2001 From: Siegfried Pammer Date: Sun, 14 Jun 2026 17:23:40 +0200 Subject: [PATCH] Enable nullable reference types across the C# transforms Turn on #nullable enable across the AST transform pipeline, ahead of annotating the slot properties themselves. TransformContext now exposes the nullable CurrentMember/CurrentTypeDefinition/CurrentModule contract already declared by ITypeResolveContext, and the generated pattern-to-node conversion returns a non-null node so patterns can be used in collection initializers without warnings. No IL changes. Assisted-by: Claude:claude-opus-4-8:Claude Code --- .../DecompilerSyntaxTreeGenerator.cs | 12 ++- .../CSharp/Syntax/AstNodeCollection.cs | 5 +- .../CSharp/Transforms/AddCheckedBlocks.cs | 46 +++++---- .../AddXmlDocumentationTransform.cs | 6 +- .../Transforms/CombineQueryExpressions.cs | 12 ++- .../Transforms/ContextTrackingVisitor.cs | 8 +- .../CSharp/Transforms/CustomPatterns.cs | 10 +- .../CSharp/Transforms/DeclareVariables.cs | 48 ++++++---- .../Transforms/EscapeInvalidIdentifiers.cs | 2 + .../CSharp/Transforms/FixNameCollisions.cs | 4 +- .../CSharp/Transforms/FlattenSwitchBlocks.cs | 2 + .../CSharp/Transforms/IAstTransform.cs | 2 + .../Transforms/IntroduceExtensionMethods.cs | 18 +++- .../Transforms/IntroduceQueryExpressions.cs | 76 +++++++++------ .../Transforms/IntroduceUnsafeModifier.cs | 8 +- .../Transforms/IntroduceUsingDeclarations.cs | 8 +- .../Transforms/NormalizeBlockStatements.cs | 9 +- .../Transforms/PatternStatementTransform.cs | 93 +++++++++++-------- .../CSharp/Transforms/PrettifyAssignments.cs | 14 ++- .../Transforms/RemoveCLSCompliantAttribute.cs | 2 + .../ReplaceMethodCallsWithOperators.cs | 6 +- .../CSharp/Transforms/TransformContext.cs | 8 +- 22 files changed, 250 insertions(+), 149 deletions(-) diff --git a/ICSharpCode.Decompiler.Generators/DecompilerSyntaxTreeGenerator.cs b/ICSharpCode.Decompiler.Generators/DecompilerSyntaxTreeGenerator.cs index 0ba2b5c54..e2b0ca609 100644 --- a/ICSharpCode.Decompiler.Generators/DecompilerSyntaxTreeGenerator.cs +++ b/ICSharpCode.Decompiler.Generators/DecompilerSyntaxTreeGenerator.cs @@ -197,10 +197,18 @@ internal class DecompilerSyntaxTreeGenerator : IIncrementalGenerator if (source.NeedsPatternPlaceholder) { + // The placeholder conversion is part of the pattern-construction DSL, where a non-null + // pattern is the invariant; the result is therefore non-nullable for the specific node + // types. AstNode (the base) and ParameterDeclaration keep the nullable contract, and only + // AstNode also accepts a nullable pattern. + bool nullableReturn = source.NodeName is "AstNode" or "ParameterDeclaration"; + string returnQ = nullableReturn ? "?" : ""; + string paramQ = source.NodeName == "AstNode" ? "?" : ""; + string forgive = nullableReturn ? "" : "!"; builder.Append( - $@" public static implicit operator {source.NodeName}?(PatternMatching.Pattern? pattern) + $@" public static implicit operator {source.NodeName}{returnQ}(PatternMatching.Pattern{paramQ} pattern) {{ - return pattern != null ? new PatternPlaceholder(pattern) : null; + return pattern != null ? new PatternPlaceholder(pattern) : null{forgive}; }} sealed class PatternPlaceholder : {source.NodeName}, INode diff --git a/ICSharpCode.Decompiler/CSharp/Syntax/AstNodeCollection.cs b/ICSharpCode.Decompiler/CSharp/Syntax/AstNodeCollection.cs index 314bfaf4f..cb92d87db 100644 --- a/ICSharpCode.Decompiler/CSharp/Syntax/AstNodeCollection.cs +++ b/ICSharpCode.Decompiler/CSharp/Syntax/AstNodeCollection.cs @@ -304,9 +304,10 @@ namespace ICSharpCode.Decompiler.CSharp.Syntax return Pattern.DoMatchCollection(AsNodeList(), other.AsNodeList(), match); } - public void InsertAfter(T existingItem, T newItem) + public void InsertAfter(T? existingItem, T newItem) { - Insert(IndexOf(existingItem) + 1, newItem); + // A null existingItem yields IndexOf == -1, so the new item is inserted at the front. + Insert(IndexOf(existingItem!) + 1, newItem); } public void InsertBefore(T existingItem, T newItem) diff --git a/ICSharpCode.Decompiler/CSharp/Transforms/AddCheckedBlocks.cs b/ICSharpCode.Decompiler/CSharp/Transforms/AddCheckedBlocks.cs index 2d9fdd0db..ebf2e3fc3 100644 --- a/ICSharpCode.Decompiler/CSharp/Transforms/AddCheckedBlocks.cs +++ b/ICSharpCode.Decompiler/CSharp/Transforms/AddCheckedBlocks.cs @@ -16,7 +16,10 @@ // OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // DEALINGS IN THE SOFTWARE. +#nullable enable + using System; +using System.Diagnostics; using System.Linq; using ICSharpCode.Decompiler.CSharp.Syntax; @@ -143,7 +146,7 @@ namespace ICSharpCode.Decompiler.CSharp.Transforms /// abstract class InsertedNode { - public static InsertedNode operator +(InsertedNode a, InsertedNode b) + public static InsertedNode? operator +(InsertedNode? a, InsertedNode? b) { if (a == null) return b; @@ -194,11 +197,11 @@ namespace ICSharpCode.Decompiler.CSharp.Transforms class InsertedBlock : InsertedNode { - readonly Statement firstStatement; // inclusive - readonly Statement lastStatement; // exclusive + readonly Statement? firstStatement; // inclusive + readonly Statement? lastStatement; // exclusive readonly bool isChecked; - public InsertedBlock(Statement firstStatement, Statement lastStatement, bool isChecked) + public InsertedBlock(Statement? firstStatement, Statement? lastStatement, bool isChecked) { this.firstStatement = firstStatement; this.lastStatement = lastStatement; @@ -207,10 +210,13 @@ namespace ICSharpCode.Decompiler.CSharp.Transforms public override void Insert() { + // An InsertedBlock with a null start has infinite cost in the search and is never + // selected for insertion, so by the time Insert runs firstStatement is non-null. + Debug.Assert(firstStatement != null); BlockStatement newBlock = new BlockStatement(); // Move all statements except for the first - Statement next; - for (Statement stmt = firstStatement.GetNextStatement(); stmt != lastStatement; stmt = next) + Statement? next; + for (Statement? stmt = firstStatement.GetNextStatement(); stmt != null && stmt != lastStatement; stmt = next) { next = stmt.GetNextStatement(); newBlock.Add(stmt.Detach()); @@ -233,18 +239,18 @@ namespace ICSharpCode.Decompiler.CSharp.Transforms class Result { public Cost CostInCheckedContext; - public InsertedNode NodesToInsertInCheckedContext; + public InsertedNode? NodesToInsertInCheckedContext; public Cost CostInUncheckedContext; - public InsertedNode NodesToInsertInUncheckedContext; + public InsertedNode? NodesToInsertInUncheckedContext; } #endregion public void Run(AstNode node, TransformContext context) { - BlockStatement block = node as BlockStatement; + BlockStatement? block = node as BlockStatement; if (block == null) { - for (AstNode child = node.FirstChild; child != null; child = child.NextSibling) + for (AstNode? child = node.FirstChild; child != null; child = child.NextSibling) { Run(child, context); } @@ -268,20 +274,20 @@ namespace ICSharpCode.Decompiler.CSharp.Transforms // For a block, we are tracking 4 possibilities: // a) context is checked, no unchecked block open Cost costCheckedContext = new Cost(0, 0); - InsertedNode nodesCheckedContext = null; + InsertedNode? nodesCheckedContext = null; // b) context is checked, an unchecked block is open Cost costCheckedContextUncheckedBlockOpen = Cost.Infinite; - InsertedNode nodesCheckedContextUncheckedBlockOpen = null; - Statement uncheckedBlockStart = null; + InsertedNode? nodesCheckedContextUncheckedBlockOpen = null; + Statement? uncheckedBlockStart = null; // c) context is unchecked, no checked block open Cost costUncheckedContext = new Cost(0, 0); - InsertedNode nodesUncheckedContext = null; + InsertedNode? nodesUncheckedContext = null; // d) context is unchecked, a checked block is open Cost costUncheckedContextCheckedBlockOpen = Cost.Infinite; - InsertedNode nodesUncheckedContextCheckedBlockOpen = null; - Statement checkedBlockStart = null; + InsertedNode? nodesUncheckedContextCheckedBlockOpen = null; + Statement? checkedBlockStart = null; - Statement statement = block.Statements.FirstOrDefault(); + Statement? statement = block.Statements.FirstOrDefault(); while (true) { // Blocks can be closed 'for free'. We use '<=' so that blocks are closed as late as possible (goal 4b) @@ -346,7 +352,7 @@ namespace ICSharpCode.Decompiler.CSharp.Transforms if (node is BlockStatement) return GetResultFromBlock((BlockStatement)node); Result result = new Result(); - for (AstNode child = node.FirstChild; child != null; child = child.NextSibling) + for (AstNode? child = node.FirstChild; child != null; child = child.NextSibling) { Result childResult = GetResult(child); result.CostInCheckedContext += childResult.CostInCheckedContext; @@ -354,10 +360,10 @@ namespace ICSharpCode.Decompiler.CSharp.Transforms result.CostInUncheckedContext += childResult.CostInUncheckedContext; result.NodesToInsertInUncheckedContext += childResult.NodesToInsertInUncheckedContext; } - Expression expr = node as Expression; + Expression? expr = node as Expression; if (expr != null) { - CheckedUncheckedAnnotation annotation = expr.Annotation(); + CheckedUncheckedAnnotation? annotation = expr.Annotation(); if (annotation != null) { if (annotation.IsExplicit) diff --git a/ICSharpCode.Decompiler/CSharp/Transforms/AddXmlDocumentationTransform.cs b/ICSharpCode.Decompiler/CSharp/Transforms/AddXmlDocumentationTransform.cs index e18a248be..ddc869610 100644 --- a/ICSharpCode.Decompiler/CSharp/Transforms/AddXmlDocumentationTransform.cs +++ b/ICSharpCode.Decompiler/CSharp/Transforms/AddXmlDocumentationTransform.cs @@ -16,6 +16,8 @@ // OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // DEALINGS IN THE SOFTWARE. +#nullable enable + using System; using System.IO; using System.Linq; @@ -62,7 +64,7 @@ namespace ICSharpCode.Decompiler.CSharp.Transforms static void InsertXmlDocumentation(AstNode node, StringReader r) { // Find the first non-empty line: - string firstLine; + string? firstLine; do { firstLine = r.ReadLine(); @@ -70,7 +72,7 @@ namespace ICSharpCode.Decompiler.CSharp.Transforms return; } while (string.IsNullOrWhiteSpace(firstLine)); string indentation = firstLine.Substring(0, firstLine.Length - firstLine.TrimStart().Length); - string line = firstLine; + string? line = firstLine; int skippedWhitespaceLines = 0; // Copy all lines from input to output, except for empty lines at the end. while (line != null) diff --git a/ICSharpCode.Decompiler/CSharp/Transforms/CombineQueryExpressions.cs b/ICSharpCode.Decompiler/CSharp/Transforms/CombineQueryExpressions.cs index 1f7af0863..ccac408b8 100644 --- a/ICSharpCode.Decompiler/CSharp/Transforms/CombineQueryExpressions.cs +++ b/ICSharpCode.Decompiler/CSharp/Transforms/CombineQueryExpressions.cs @@ -16,6 +16,8 @@ // OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // DEALINGS IN THE SOFTWARE. +#nullable enable + using System.Collections.Generic; using System.Linq; @@ -47,8 +49,8 @@ namespace ICSharpCode.Decompiler.CSharp.Transforms void CombineQueries(AstNode node, Dictionary fromOrLetIdentifiers) { - AstNode next; - for (AstNode child = node.FirstChild; child != null; child = next) + AstNode? next; + for (AstNode? child = node.FirstChild; child != null; child = next) { // store reference to next child before transformation next = child.NextSibling; @@ -105,7 +107,9 @@ namespace ICSharpCode.Decompiler.CSharp.Transforms { if (!CSharpDecompiler.IsTransparentIdentifier(fromClause.Identifier)) return false; - QuerySelectClause selectClause = innerQuery.Clauses.Last() as QuerySelectClause; + QuerySelectClause? selectClause = innerQuery.Clauses.Last() as QuerySelectClause; + if (selectClause == null) + return false; Match match = selectTransparentIdentifierPattern.Match(selectClause); if (!match.Success) return false; @@ -116,7 +120,7 @@ namespace ICSharpCode.Decompiler.CSharp.Transforms fromClause.Remove(); selectClause.Remove(); // Move clauses from innerQuery to query - QueryClause insertionPos = null; + QueryClause? insertionPos = null; foreach (var clause in innerQuery.Clauses) { query.Clauses.InsertAfter(insertionPos, insertionPos = clause.Detach()); diff --git a/ICSharpCode.Decompiler/CSharp/Transforms/ContextTrackingVisitor.cs b/ICSharpCode.Decompiler/CSharp/Transforms/ContextTrackingVisitor.cs index 4de2c3c1c..58a110f50 100644 --- a/ICSharpCode.Decompiler/CSharp/Transforms/ContextTrackingVisitor.cs +++ b/ICSharpCode.Decompiler/CSharp/Transforms/ContextTrackingVisitor.cs @@ -16,6 +16,8 @@ // OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // DEALINGS IN THE SOFTWARE. +#nullable enable + using System.Diagnostics; using ICSharpCode.Decompiler.CSharp.Syntax; @@ -28,8 +30,8 @@ namespace ICSharpCode.Decompiler.CSharp.Transforms /// public abstract class ContextTrackingVisitor : DepthFirstAstVisitor { - protected ITypeDefinition currentTypeDefinition; - protected IMethod currentMethod; + protected ITypeDefinition? currentTypeDefinition; + protected IMethod? currentMethod; protected void Initialize(TransformContext context) { @@ -45,7 +47,7 @@ namespace ICSharpCode.Decompiler.CSharp.Transforms public override TResult VisitTypeDeclaration(TypeDeclaration typeDeclaration) { - ITypeDefinition oldType = currentTypeDefinition; + ITypeDefinition? oldType = currentTypeDefinition; try { currentTypeDefinition = typeDeclaration.GetSymbol() as ITypeDefinition; diff --git a/ICSharpCode.Decompiler/CSharp/Transforms/CustomPatterns.cs b/ICSharpCode.Decompiler/CSharp/Transforms/CustomPatterns.cs index ace3b95b2..54d0adeff 100644 --- a/ICSharpCode.Decompiler/CSharp/Transforms/CustomPatterns.cs +++ b/ICSharpCode.Decompiler/CSharp/Transforms/CustomPatterns.cs @@ -16,6 +16,8 @@ // OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // DEALINGS IN THE SOFTWARE. +#nullable enable + using System; using System.Linq; @@ -27,7 +29,7 @@ namespace ICSharpCode.Decompiler.CSharp.Transforms { sealed class TypePattern : Pattern { - readonly string ns; + readonly string? ns; readonly string name; public TypePattern(Type type) @@ -38,8 +40,8 @@ namespace ICSharpCode.Decompiler.CSharp.Transforms public override bool DoMatch(INode other, Match match) { - ComposedType ct = other as ComposedType; - AstType o; + ComposedType? ct = other as ComposedType; + AstType? o; if (ct != null && !ct.HasRefSpecifier && !ct.HasNullableSpecifier && ct.PointerRank == 0 && !ct.ArraySpecifiers.Any()) { // Special case: ILSpy sometimes produces a ComposedType but then removed all array specifiers @@ -73,7 +75,7 @@ namespace ICSharpCode.Decompiler.CSharp.Transforms public override bool DoMatch(INode other, Match match) { - InvocationExpression ie = other as InvocationExpression; + InvocationExpression? ie = other as InvocationExpression; if (ie != null && ie.Annotation() != null && ie.Arguments.Count == 1) { return childNode.DoMatch(ie.Arguments.Single(), match); diff --git a/ICSharpCode.Decompiler/CSharp/Transforms/DeclareVariables.cs b/ICSharpCode.Decompiler/CSharp/Transforms/DeclareVariables.cs index 20ab03a6e..fe154fc3f 100644 --- a/ICSharpCode.Decompiler/CSharp/Transforms/DeclareVariables.cs +++ b/ICSharpCode.Decompiler/CSharp/Transforms/DeclareVariables.cs @@ -16,9 +16,12 @@ // OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // DEALINGS IN THE SOFTWARE. +#nullable enable + using System; using System.Collections.Generic; using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; using System.Linq; using ICSharpCode.Decompiler.CSharp.Syntax; @@ -54,7 +57,8 @@ namespace ICSharpCode.Decompiler.CSharp.Transforms { return new InsertionPoint { level = level - 1, - nextNode = nextNode.Parent + // Insertion points live inside a method body, so walking up always finds a parent. + nextNode = nextNode.Parent! }; } @@ -63,7 +67,7 @@ namespace ICSharpCode.Decompiler.CSharp.Transforms InsertionPoint result = this; while (result.level > targetLevel) { - result.nextNode = result.nextNode.Parent; + result.nextNode = result.nextNode.Parent!; result.level -= 1; } return result; @@ -82,7 +86,8 @@ namespace ICSharpCode.Decompiler.CSharp.Transforms { public readonly ILVariable ILVariable; public IType Type => ILVariable.Type; - public string Name => ILVariable.Name; + // Variables reaching this transform have already been assigned a name. + public string Name => ILVariable.Name!; /// /// Whether the variable needs to be default-initialized. @@ -108,7 +113,7 @@ namespace ICSharpCode.Decompiler.CSharp.Transforms /// public IdentifierExpression FirstUse; - public VariableToDeclare ReplacementDueToCollision; + public VariableToDeclare? ReplacementDueToCollision; public bool InvolvedInCollision; public bool RemovedDueToCollision => ReplacementDueToCollision != null; public bool DeclaredInDeconstruction; @@ -138,6 +143,7 @@ namespace ICSharpCode.Decompiler.CSharp.Transforms } readonly Dictionary variableDict = new Dictionary(); + [AllowNull] TransformContext context; public void Run(AstNode rootNode, TransformContext context) @@ -284,7 +290,7 @@ namespace ICSharpCode.Decompiler.CSharp.Transforms /// void FindInsertionPoints(AstNode node, int nodeLevel) { - BlockContainer scope = node.Annotation(); + BlockContainer? scope = node.Annotation(); if (scope != null && IsRelevantScope(scope)) { // track loops and function bodies as scopes, for comparison with CaptureScope. @@ -305,7 +311,7 @@ namespace ICSharpCode.Decompiler.CSharp.Transforms } try { - for (AstNode child = node.FirstChild; child != null; child = child.NextSibling) + for (AstNode? child = node.FirstChild; child != null; child = child.NextSibling) { FindInsertionPoints(child, nodeLevel + 1); } @@ -329,7 +335,7 @@ namespace ICSharpCode.Decompiler.CSharp.Transforms { InsertionPoint newPoint; int startIndex = scopeTracking.Count - 1; - BlockContainer captureScope = variable.CaptureScope; + BlockContainer? captureScope = variable.CaptureScope; while (captureScope != null && !IsRelevantScope(captureScope)) { captureScope = BlockContainer.FindClosestContainer(captureScope.Parent); @@ -364,7 +370,7 @@ namespace ICSharpCode.Decompiler.CSharp.Transforms } } } - if (variableDict.TryGetValue(variable, out VariableToDeclare v)) + if (variableDict.TryGetValue(variable, out var v)) { v.InsertionPoint = FindCommonParent(v.InsertionPoint, newPoint); } @@ -561,7 +567,7 @@ namespace ICSharpCode.Decompiler.CSharp.Transforms } } - bool IsMatchingAssignment(VariableToDeclare v, out AssignmentExpression assignment) + bool IsMatchingAssignment(VariableToDeclare v, [NotNullWhen(true)] out AssignmentExpression? assignment) { assignment = v.InsertionPoint.nextNode as AssignmentExpression; if (assignment == null) @@ -595,7 +601,7 @@ namespace ICSharpCode.Decompiler.CSharp.Transforms if (v.RemovedDueToCollision || v.DeclaredInDeconstruction) continue; - if (CombineDeclarationAndInitializer(v, context) && IsMatchingAssignment(v, out AssignmentExpression assignment)) + if (CombineDeclarationAndInitializer(v, context) && IsMatchingAssignment(v, out var assignment)) { // 'int v; v = expr;' can be combined to 'int v = expr;' AstType type; @@ -674,7 +680,7 @@ namespace ICSharpCode.Decompiler.CSharp.Transforms else { // Insert a separate declaration statement. - Expression initializer = null; + Expression? initializer = null; AstType type = context.TypeSystemAstBuilder.ConvertType(v.Type); if (v.DefaultInitialization == VariableInitKind.NeedsDefaultValue) { @@ -694,6 +700,10 @@ namespace ICSharpCode.Decompiler.CSharp.Transforms v.InsertionPoint = v.InsertionPoint.Up(); } Debug.Assert(v.InsertionPoint.nextNode.Slot?.Kind == SlotKind.Statement); + // The insertion point is a statement within a block, so it always has a parent. + AstNode insertionNode = v.InsertionPoint.nextNode; + AstNode insertionParent = insertionNode.Parent + ?? throw new InvalidOperationException("Variable insertion point has no parent."); if (v.DefaultInitialization == VariableInitKind.NeedsSkipInit) { AstType unsafeType = context.TypeSystemAstBuilder.ConvertType( @@ -702,7 +712,7 @@ namespace ICSharpCode.Decompiler.CSharp.Transforms { var outVarDecl = new OutVarDeclarationExpression(type.Clone(), v.Name); outVarDecl.Variable.AddAnnotation(new ILVariableResolveResult(ilVariable)); - v.InsertionPoint.nextNode.Parent.InsertChildBefore( + insertionParent.InsertChildBefore( v.InsertionPoint.nextNode, new ExpressionStatement { Expression = new InvocationExpression { @@ -719,11 +729,11 @@ namespace ICSharpCode.Decompiler.CSharp.Transforms } else { - v.InsertionPoint.nextNode.Parent.InsertChildBefore( + insertionParent.InsertChildBefore( v.InsertionPoint.nextNode, vds, BlockStatement.StatementRole); - v.InsertionPoint.nextNode.Parent.InsertChildBefore( + insertionParent.InsertChildBefore( v.InsertionPoint.nextNode, new ExpressionStatement { Expression = new InvocationExpression { @@ -745,7 +755,7 @@ namespace ICSharpCode.Decompiler.CSharp.Transforms } else { - v.InsertionPoint.nextNode.Parent.InsertChildBefore( + insertionParent.InsertChildBefore( v.InsertionPoint.nextNode, vds, BlockStatement.StatementRole); @@ -759,7 +769,7 @@ namespace ICSharpCode.Decompiler.CSharp.Transforms } } - private bool CanBeDeclaredAsOutVariable(VariableToDeclare v, out DirectionExpression dirExpr) + private bool CanBeDeclaredAsOutVariable(VariableToDeclare v, [NotNullWhen(true)] out DirectionExpression? dirExpr) { dirExpr = v.FirstUse.Parent as DirectionExpression; if (dirExpr == null || dirExpr.FieldDirection != FieldDirection.Out) @@ -768,7 +778,7 @@ namespace ICSharpCode.Decompiler.CSharp.Transforms return false; if (v.DefaultInitialization != VariableInitKind.None) return false; - for (AstNode node = v.FirstUse; node != null; node = node.Parent) + for (AstNode? node = v.FirstUse; node != null; node = node.Parent) { if (node.Slot?.Kind == SlotKind.EmbeddedStatement) { @@ -812,9 +822,9 @@ namespace ICSharpCode.Decompiler.CSharp.Transforms var v = variableDict[ilVar]; if (!v.RemovedDueToCollision) continue; - while (v.RemovedDueToCollision) + while (v.ReplacementDueToCollision is { } replacement) { - v = v.ReplacementDueToCollision; + v = replacement; } node.RemoveAnnotations(); node.AddAnnotation(new ILVariableResolveResult(v.ILVariable, v.Type)); diff --git a/ICSharpCode.Decompiler/CSharp/Transforms/EscapeInvalidIdentifiers.cs b/ICSharpCode.Decompiler/CSharp/Transforms/EscapeInvalidIdentifiers.cs index c56dde2aa..cf8a10fa7 100644 --- a/ICSharpCode.Decompiler/CSharp/Transforms/EscapeInvalidIdentifiers.cs +++ b/ICSharpCode.Decompiler/CSharp/Transforms/EscapeInvalidIdentifiers.cs @@ -16,6 +16,8 @@ // OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // DEALINGS IN THE SOFTWARE. +#nullable enable + using System.Collections.Generic; using System.Linq; diff --git a/ICSharpCode.Decompiler/CSharp/Transforms/FixNameCollisions.cs b/ICSharpCode.Decompiler/CSharp/Transforms/FixNameCollisions.cs index 80c164631..88b793763 100644 --- a/ICSharpCode.Decompiler/CSharp/Transforms/FixNameCollisions.cs +++ b/ICSharpCode.Decompiler/CSharp/Transforms/FixNameCollisions.cs @@ -16,6 +16,8 @@ // OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // DEALINGS IN THE SOFTWARE. +#nullable enable + using System.Collections.Generic; using System.Linq; @@ -68,7 +70,7 @@ namespace ICSharpCode.Decompiler.CSharp.Transforms if (node is IdentifierExpression || node is MemberReferenceExpression) { ISymbol symbol = node.GetSymbol(); - if (symbol != null && renamedSymbols.TryGetValue(symbol, out string newName)) + if (symbol != null && renamedSymbols.TryGetValue(symbol, out string? newName)) { node.GetChildByRole(Roles.Identifier).Name = newName; } diff --git a/ICSharpCode.Decompiler/CSharp/Transforms/FlattenSwitchBlocks.cs b/ICSharpCode.Decompiler/CSharp/Transforms/FlattenSwitchBlocks.cs index 74e3cee84..52764aa7a 100644 --- a/ICSharpCode.Decompiler/CSharp/Transforms/FlattenSwitchBlocks.cs +++ b/ICSharpCode.Decompiler/CSharp/Transforms/FlattenSwitchBlocks.cs @@ -1,3 +1,5 @@ +#nullable enable + using System; using System.Collections.Generic; using System.Linq; diff --git a/ICSharpCode.Decompiler/CSharp/Transforms/IAstTransform.cs b/ICSharpCode.Decompiler/CSharp/Transforms/IAstTransform.cs index 1d7193a99..91b05d0e7 100644 --- a/ICSharpCode.Decompiler/CSharp/Transforms/IAstTransform.cs +++ b/ICSharpCode.Decompiler/CSharp/Transforms/IAstTransform.cs @@ -16,6 +16,8 @@ // OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // DEALINGS IN THE SOFTWARE. +#nullable enable + using ICSharpCode.Decompiler.CSharp.Syntax; namespace ICSharpCode.Decompiler.CSharp.Transforms diff --git a/ICSharpCode.Decompiler/CSharp/Transforms/IntroduceExtensionMethods.cs b/ICSharpCode.Decompiler/CSharp/Transforms/IntroduceExtensionMethods.cs index 460d85996..587429a8b 100644 --- a/ICSharpCode.Decompiler/CSharp/Transforms/IntroduceExtensionMethods.cs +++ b/ICSharpCode.Decompiler/CSharp/Transforms/IntroduceExtensionMethods.cs @@ -16,8 +16,11 @@ // OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // DEALINGS IN THE SOFTWARE. +#nullable enable + using System.Collections.Generic; using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; using System.Linq; using ICSharpCode.Decompiler.CSharp.Resolver; @@ -34,8 +37,11 @@ namespace ICSharpCode.Decompiler.CSharp.Transforms /// public class IntroduceExtensionMethods : DepthFirstAstVisitor, IAstTransform { + [AllowNull] TransformContext context; + [AllowNull] CSharpResolver resolver; + [AllowNull] CSharpConversions conversions; public void Run(AstNode rootNode, TransformContext context) @@ -122,7 +128,9 @@ namespace ICSharpCode.Decompiler.CSharp.Transforms } else { - memberRefExpr.Target = firstArgument.Detach(); + // The target is not an IdentifierExpression, so CanTransformToExtensionMethodCall + // matched the MemberReferenceExpression case and memberRefExpr is non-null. + memberRefExpr!.Target = firstArgument.Detach(); } if (invocationExpression.GetResolveResult() is CSharpInvocationResolveResult irr) { @@ -137,9 +145,9 @@ namespace ICSharpCode.Decompiler.CSharp.Transforms } static bool CanTransformToExtensionMethodCall(CSharpResolver resolver, - InvocationExpression invocationExpression, out MemberReferenceExpression memberRefExpr, - out ResolveResult target, - out Expression firstArgument) + InvocationExpression invocationExpression, out MemberReferenceExpression? memberRefExpr, + [NotNullWhen(true)] out ResolveResult? target, + [NotNullWhen(true)] out Expression? firstArgument) { var method = invocationExpression.GetSymbol() as IMethod; memberRefExpr = null; @@ -176,7 +184,7 @@ namespace ICSharpCode.Decompiler.CSharp.Transforms } Debug.Assert(target != null); ResolveResult[] args = new ResolveResult[invocationExpression.Arguments.Count - 1]; - string[] argNames = null; + string[]? argNames = null; int pos = 0; foreach (var arg in invocationExpression.Arguments.Skip(1)) { diff --git a/ICSharpCode.Decompiler/CSharp/Transforms/IntroduceQueryExpressions.cs b/ICSharpCode.Decompiler/CSharp/Transforms/IntroduceQueryExpressions.cs index 636957905..badf5395a 100644 --- a/ICSharpCode.Decompiler/CSharp/Transforms/IntroduceQueryExpressions.cs +++ b/ICSharpCode.Decompiler/CSharp/Transforms/IntroduceQueryExpressions.cs @@ -16,7 +16,11 @@ // OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // DEALINGS IN THE SOFTWARE. +#nullable enable + using System; +using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; using System.Linq; using ICSharpCode.Decompiler.CSharp.Syntax; @@ -30,6 +34,7 @@ namespace ICSharpCode.Decompiler.CSharp.Transforms /// public class IntroduceQueryExpressions : IAstTransform { + [AllowNull] TransformContext context; public void Run(AstNode rootNode, TransformContext context) @@ -50,15 +55,15 @@ namespace ICSharpCode.Decompiler.CSharp.Transforms } // See if the data source of this query is a degenerate query, // and combine the queries if possible. - QueryExpression innerQuery = fromClause.Expression as QueryExpression; - while (IsDegenerateQuery(innerQuery)) + QueryExpression? innerQuery = fromClause.Expression as QueryExpression; + while (innerQuery != null && IsDegenerateQuery(innerQuery)) { QueryFromClause innerFromClause = (QueryFromClause)innerQuery.Clauses.First(); - ILVariable innerVariable = innerFromClause.Annotation()?.Variable; - ILVariable rangeVariable = fromClause.Annotation()?.Variable; + ILVariable? innerVariable = innerFromClause.Annotation()?.Variable; + ILVariable? rangeVariable = fromClause.Annotation()?.Variable; // Replace the fromClause with all clauses from the inner query fromClause.Remove(); - QueryClause insertionPos = null; + QueryClause? insertionPos = null; foreach (var clause in innerQuery.Clauses) { CombineRangeVariables(clause, innerVariable, rangeVariable); @@ -70,21 +75,26 @@ namespace ICSharpCode.Decompiler.CSharp.Transforms } } - private void CombineRangeVariables(QueryClause clause, ILVariable oldVariable, ILVariable newVariable) + private void CombineRangeVariables(QueryClause clause, ILVariable? oldVariable, ILVariable? newVariable) { + if (oldVariable == null || newVariable == null) + return; foreach (var identifier in clause.DescendantNodes().OfType()) { - var variable = identifier.Parent.Annotation()?.Variable; + var parent = identifier.Parent; + if (parent == null) + continue; + var variable = parent.Annotation()?.Variable; if (variable == oldVariable) { - identifier.Parent.RemoveAnnotations(); - identifier.Parent.AddAnnotation(new ILVariableResolveResult(newVariable)); + parent.RemoveAnnotations(); + parent.AddAnnotation(new ILVariableResolveResult(newVariable)); identifier.ReplaceWith(Identifier.Create(newVariable.Name)); } } } - bool IsDegenerateQuery(QueryExpression query) + bool IsDegenerateQuery(QueryExpression? query) { if (query == null) return false; @@ -94,7 +104,7 @@ namespace ICSharpCode.Decompiler.CSharp.Transforms void DecompileQueries(AstNode node) { - Expression query = DecompileQuery(node as InvocationExpression); + Expression? query = DecompileQuery(node as InvocationExpression); if (query != null) { if (node.Parent is ExpressionStatement && CanUseDiscardAssignment()) @@ -102,8 +112,8 @@ namespace ICSharpCode.Decompiler.CSharp.Transforms node.ReplaceWith(query); } - AstNode next; - for (AstNode child = (query ?? node).FirstChild; child != null; child = next) + AstNode? next; + for (AstNode? child = (query ?? node).FirstChild; child != null; child = next) { // store reference to next child before transformation next = child.NextSibling; @@ -117,11 +127,11 @@ namespace ICSharpCode.Decompiler.CSharp.Transforms return context.Settings.Discards; } - QueryExpression DecompileQuery(InvocationExpression invocation) + QueryExpression? DecompileQuery(InvocationExpression? invocation) { if (invocation == null) return null; - MemberReferenceExpression mre = invocation.Target as MemberReferenceExpression; + MemberReferenceExpression? mre = invocation.Target as MemberReferenceExpression; if (mre == null || IsNullConditional(mre.Target)) return null; switch (mre.MemberName) @@ -133,7 +143,7 @@ namespace ICSharpCode.Decompiler.CSharp.Transforms if (!IsComplexQuery(mre)) return null; Expression expr = invocation.Arguments.Single(); - if (MatchSimpleLambda(expr, out ParameterDeclaration parameter, out Expression body)) + if (MatchSimpleLambda(expr, out var parameter, out var body)) { QueryExpression query = new QueryExpression(); query.Clauses.Add(MakeFromClause(parameter, mre.Target.Detach())); @@ -148,8 +158,8 @@ namespace ICSharpCode.Decompiler.CSharp.Transforms { Expression keyLambda = invocation.Arguments.ElementAt(0); Expression projectionLambda = invocation.Arguments.ElementAt(1); - if (MatchSimpleLambda(keyLambda, out ParameterDeclaration parameter1, out Expression keySelector) - && MatchSimpleLambda(projectionLambda, out ParameterDeclaration parameter2, out Expression elementSelector) + if (MatchSimpleLambda(keyLambda, out var parameter1, out var keySelector) + && MatchSimpleLambda(projectionLambda, out var parameter2, out var elementSelector) && parameter1.Name == parameter2.Name) { QueryExpression query = new QueryExpression(); @@ -166,7 +176,7 @@ namespace ICSharpCode.Decompiler.CSharp.Transforms else if (invocation.Arguments.Count == 1) { Expression lambda = invocation.Arguments.Single(); - if (MatchSimpleLambda(lambda, out ParameterDeclaration parameter, out Expression keySelector)) + if (MatchSimpleLambda(lambda, out var parameter, out var keySelector)) { QueryExpression query = new QueryExpression(); query.Clauses.Add(MakeFromClause(parameter, mre.Target.Detach())); @@ -181,11 +191,11 @@ namespace ICSharpCode.Decompiler.CSharp.Transforms if (invocation.Arguments.Count != 2) return null; var fromExpressionLambda = invocation.Arguments.ElementAt(0); - if (!MatchSimpleLambda(fromExpressionLambda, out ParameterDeclaration parameter, out Expression collectionSelector)) + if (!MatchSimpleLambda(fromExpressionLambda, out var parameter, out var collectionSelector)) return null; if (IsNullConditional(collectionSelector)) return null; - LambdaExpression lambda = invocation.Arguments.ElementAt(1) as LambdaExpression; + LambdaExpression? lambda = invocation.Arguments.ElementAt(1) as LambdaExpression; if (lambda != null && lambda.Parameters.Count == 2 && lambda.Body is Expression) { ParameterDeclaration p1 = lambda.Parameters.ElementAt(0); @@ -208,7 +218,7 @@ namespace ICSharpCode.Decompiler.CSharp.Transforms if (!IsComplexQuery(mre)) return null; Expression expr = invocation.Arguments.Single(); - if (MatchSimpleLambda(expr, out ParameterDeclaration parameter, out Expression body)) + if (MatchSimpleLambda(expr, out var parameter, out var body)) { QueryExpression query = new QueryExpression(); query.Clauses.Add(MakeFromClause(parameter, mre.Target.Detach())); @@ -227,7 +237,7 @@ namespace ICSharpCode.Decompiler.CSharp.Transforms if (!IsComplexQuery(mre)) return null; var lambda = invocation.Arguments.Single(); - if (MatchSimpleLambda(lambda, out ParameterDeclaration parameter, out Expression orderExpression)) + if (MatchSimpleLambda(lambda, out var parameter, out var orderExpression)) { if (ValidateThenByChain(invocation, parameter.Name)) { @@ -244,8 +254,14 @@ namespace ICSharpCode.Decompiler.CSharp.Transforms InvocationExpression tmp = (InvocationExpression)mre.Target; mre = (MemberReferenceExpression)tmp.Target; lambda = tmp.Arguments.Single(); - MatchSimpleLambda(lambda, out parameter, out orderExpression); + // ValidateThenByChain already confirmed every clause in the chain is a simple + // lambda, so this match always succeeds. + bool matched = MatchSimpleLambda(lambda, out parameter, out orderExpression); + Debug.Assert(matched); } + // The chain was validated and every iteration re-matched a simple lambda, + // so the final range-variable parameter is set. + Debug.Assert(parameter != null); // insert new ordering at beginning orderClause.Orderings.InsertAfter( null, new QueryOrdering { @@ -271,12 +287,12 @@ namespace ICSharpCode.Decompiler.CSharp.Transforms if (IsNullConditional(source2)) return null; Expression outerLambda = invocation.Arguments.ElementAt(1); - if (!MatchSimpleLambda(outerLambda, out ParameterDeclaration element1, out Expression key1)) + if (!MatchSimpleLambda(outerLambda, out var element1, out var key1)) return null; Expression innerLambda = invocation.Arguments.ElementAt(2); - if (!MatchSimpleLambda(innerLambda, out ParameterDeclaration element2, out Expression key2)) + if (!MatchSimpleLambda(innerLambda, out var element2, out var key2)) return null; - LambdaExpression lambda = invocation.Arguments.ElementAt(3) as LambdaExpression; + LambdaExpression? lambda = invocation.Arguments.ElementAt(3) as LambdaExpression; if (lambda != null && lambda.Parameters.Count == 2 && lambda.Body is Expression) { ParameterDeclaration p1 = lambda.Parameters.ElementAt(0); @@ -365,13 +381,13 @@ namespace ICSharpCode.Decompiler.CSharp.Transforms /// /// Ensure that all ThenBy's are correct, and that the list of ThenBy's is terminated by an 'OrderBy' invocation. /// - bool ValidateThenByChain(InvocationExpression invocation, string expectedParameterName) + bool ValidateThenByChain(InvocationExpression? invocation, string expectedParameterName) { if (invocation == null || invocation.Arguments.Count != 1) return false; if (!(invocation.Target is MemberReferenceExpression mre)) return false; - if (!MatchSimpleLambda(invocation.Arguments.Single(), out ParameterDeclaration parameter, out _)) + if (!MatchSimpleLambda(invocation.Arguments.Single(), out var parameter, out _)) return false; if (parameter.Name != expectedParameterName) return false; @@ -385,7 +401,7 @@ namespace ICSharpCode.Decompiler.CSharp.Transforms } /// Matches simple lambdas of the form "a => b" - bool MatchSimpleLambda(Expression expr, out ParameterDeclaration parameter, out Expression body) + bool MatchSimpleLambda(Expression expr, [NotNullWhen(true)] out ParameterDeclaration? parameter, [NotNullWhen(true)] out Expression? body) { if (expr is LambdaExpression lambda && lambda.Parameters.Count == 1 && lambda.Body is Expression) { diff --git a/ICSharpCode.Decompiler/CSharp/Transforms/IntroduceUnsafeModifier.cs b/ICSharpCode.Decompiler/CSharp/Transforms/IntroduceUnsafeModifier.cs index c95a7a9c0..4785f4019 100644 --- a/ICSharpCode.Decompiler/CSharp/Transforms/IntroduceUnsafeModifier.cs +++ b/ICSharpCode.Decompiler/CSharp/Transforms/IntroduceUnsafeModifier.cs @@ -16,6 +16,8 @@ // OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // DEALINGS IN THE SOFTWARE. +#nullable enable + using System.Linq; using ICSharpCode.Decompiler.CSharp.Resolver; @@ -40,8 +42,8 @@ namespace ICSharpCode.Decompiler.CSharp.Transforms protected override bool VisitChildren(AstNode node) { bool result = false; - AstNode next; - for (AstNode child = node.FirstChild; child != null; child = next) + AstNode? next; + for (AstNode? child = node.FirstChild; child != null; child = next) { // Store next to allow the loop to continue // if the visitor removes/replaces child. @@ -116,7 +118,7 @@ namespace ICSharpCode.Decompiler.CSharp.Transforms public override bool VisitMemberReferenceExpression(MemberReferenceExpression memberReferenceExpression) { bool result = base.VisitMemberReferenceExpression(memberReferenceExpression); - UnaryOperatorExpression uoe = memberReferenceExpression.Target as UnaryOperatorExpression; + UnaryOperatorExpression? uoe = memberReferenceExpression.Target as UnaryOperatorExpression; if (uoe != null && uoe.Operator == UnaryOperatorType.Dereference) { PointerReferenceExpression pre = new PointerReferenceExpression(); diff --git a/ICSharpCode.Decompiler/CSharp/Transforms/IntroduceUsingDeclarations.cs b/ICSharpCode.Decompiler/CSharp/Transforms/IntroduceUsingDeclarations.cs index 880cbd502..06dbd1099 100644 --- a/ICSharpCode.Decompiler/CSharp/Transforms/IntroduceUsingDeclarations.cs +++ b/ICSharpCode.Decompiler/CSharp/Transforms/IntroduceUsingDeclarations.cs @@ -16,6 +16,8 @@ // OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // DEALINGS IN THE SOFTWARE. +#nullable enable + using System; using System.Collections.Generic; using System.Collections.Immutable; @@ -49,7 +51,7 @@ namespace ICSharpCode.Decompiler.CSharp.Transforms { // #define directives are leading trivia on the SyntaxTree, not children, so using // declarations go at the very start of the member list. - AstNode insertionPoint = null; + AstNode? insertionPoint = null; // Now add using declarations for those namespaces: IOrderedEnumerable sortedImports = requiredImports.ImportedNamespaces @@ -119,7 +121,7 @@ namespace ICSharpCode.Decompiler.CSharp.Transforms base.VisitSimpleType(simpleType); // also visit type arguments } - private void AddImportedNamespace(IType type) + private void AddImportedNamespace(IType? type) { if (type != null && !IsParentOfCurrentNamespace(type.Namespace)) { @@ -214,7 +216,7 @@ namespace ICSharpCode.Decompiler.CSharp.Transforms this.astBuilder = CreateAstBuilder(resolver); } - TypeSystemAstBuilder CreateAstBuilder(CSharpResolver resolver, IL.ILFunction function = null) + TypeSystemAstBuilder CreateAstBuilder(CSharpResolver resolver, IL.ILFunction? function = null) { if (function != null) { diff --git a/ICSharpCode.Decompiler/CSharp/Transforms/NormalizeBlockStatements.cs b/ICSharpCode.Decompiler/CSharp/Transforms/NormalizeBlockStatements.cs index 4e65fc2c7..11bd66aaf 100644 --- a/ICSharpCode.Decompiler/CSharp/Transforms/NormalizeBlockStatements.cs +++ b/ICSharpCode.Decompiler/CSharp/Transforms/NormalizeBlockStatements.cs @@ -1,8 +1,12 @@ +#nullable enable + using System; using System.Collections.Generic; using System.Linq; using System.Text; +using System.Diagnostics.CodeAnalysis; + using ICSharpCode.Decompiler.CSharp.Syntax; using ICSharpCode.Decompiler.CSharp.Syntax.PatternMatching; @@ -10,9 +14,10 @@ namespace ICSharpCode.Decompiler.CSharp.Transforms { class NormalizeBlockStatements : DepthFirstAstVisitor, IAstTransform { + [AllowNull] TransformContext context; bool hasNamespace; - NamespaceDeclaration singleNamespaceDeclaration; + NamespaceDeclaration? singleNamespaceDeclaration; public override void VisitSyntaxTree(SyntaxTree syntaxTree) { @@ -196,7 +201,7 @@ namespace ICSharpCode.Decompiler.CSharp.Transforms Attributes = { new Repeat(new AnyNode()) }, Modifiers = Modifiers.Any, PrivateImplementationType = new AnyNodeOrNull(), - Parameters = { new Repeat(new AnyNode()) }, + Parameters = { new Repeat(new AnyNode())! }, ReturnType = new AnyNode(), Getter = new Accessor() { Modifiers = Modifiers.Any, diff --git a/ICSharpCode.Decompiler/CSharp/Transforms/PatternStatementTransform.cs b/ICSharpCode.Decompiler/CSharp/Transforms/PatternStatementTransform.cs index 9ce9ea72b..cb99802b6 100644 --- a/ICSharpCode.Decompiler/CSharp/Transforms/PatternStatementTransform.cs +++ b/ICSharpCode.Decompiler/CSharp/Transforms/PatternStatementTransform.cs @@ -16,8 +16,11 @@ // OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // DEALINGS IN THE SOFTWARE. +#nullable enable + using System; using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; using System.Diagnostics; using System.Linq; using System.Reflection.Metadata; @@ -35,6 +38,7 @@ namespace ICSharpCode.Decompiler.CSharp.Transforms public sealed class PatternStatementTransform : ContextTrackingVisitor, IAstTransform { readonly DeclareVariables declareVariables = new DeclareVariables(); + [AllowNull] TransformContext context; public void Run(AstNode rootNode, TransformContext context) @@ -62,7 +66,7 @@ namespace ICSharpCode.Decompiler.CSharp.Transforms // Go through the children, and keep visiting a node as long as it changes. // Because some transforms delete/replace nodes before and after the node being transformed, we rely // on the transform's return value to know where we need to keep iterating. - for (AstNode child = node.FirstChild; child != null; child = child.NextSibling) + for (AstNode? child = node.FirstChild; child != null; child = child.NextSibling) { AstNode oldChild; do @@ -77,7 +81,7 @@ namespace ICSharpCode.Decompiler.CSharp.Transforms public override AstNode VisitExpressionStatement(ExpressionStatement expressionStatement) { - AstNode result = TransformForeachOnMultiDimArray(expressionStatement); + AstNode? result = TransformForeachOnMultiDimArray(expressionStatement); if (result != null) return result; result = TransformFor(expressionStatement); @@ -88,7 +92,7 @@ namespace ICSharpCode.Decompiler.CSharp.Transforms public override AstNode VisitForStatement(ForStatement forStatement) { - AstNode result = TransformForeachOnArray(forStatement); + AstNode? result = TransformForeachOnArray(forStatement); if (result != null) return result; return base.VisitForStatement(forStatement); @@ -96,7 +100,7 @@ namespace ICSharpCode.Decompiler.CSharp.Transforms public override AstNode VisitIfElseStatement(IfElseStatement ifElseStatement) { - AstNode simplifiedIfElse = SimplifyCascadingIfElseStatements(ifElseStatement); + AstNode? simplifiedIfElse = SimplifyCascadingIfElseStatements(ifElseStatement); if (simplifiedIfElse != null) return simplifiedIfElse; return base.VisitIfElseStatement(ifElseStatement); @@ -107,7 +111,7 @@ namespace ICSharpCode.Decompiler.CSharp.Transforms if (context.Settings.AutomaticProperties && (!propertyDeclaration.Setter.IsNull || context.Settings.GetterOnlyAutomaticProperties)) { - AstNode result = TransformAutomaticProperty(propertyDeclaration); + AstNode? result = TransformAutomaticProperty(propertyDeclaration); if (result != null) return result; } @@ -120,7 +124,7 @@ namespace ICSharpCode.Decompiler.CSharp.Transforms base.VisitCustomEventDeclaration(eventDeclaration); if (context.Settings.AutomaticEvents) { - AstNode result = TransformAutomaticEvents(eventDeclaration); + AstNode? result = TransformAutomaticEvents(eventDeclaration); if (result != null) return result; } @@ -174,7 +178,7 @@ namespace ICSharpCode.Decompiler.CSharp.Transforms } }; - public ForStatement TransformFor(ExpressionStatement node) + public ForStatement? TransformFor(ExpressionStatement node) { if (!context.Settings.ForStatement) return null; @@ -182,7 +186,9 @@ namespace ICSharpCode.Decompiler.CSharp.Transforms if (!m1.Success) return null; var variable = m1.Get("variable").Single().GetILVariable(); - AstNode next = node.NextSibling; + AstNode? next = node.NextSibling; + if (next == null) + return null; if (next is ForStatement forStatement && ForStatementUsesVariable(forStatement, variable)) { node.Remove(); @@ -339,7 +345,7 @@ namespace ICSharpCode.Decompiler.CSharp.Transforms return false; } - Statement TransformForeachOnArray(ForStatement forStatement) + Statement? TransformForeachOnArray(ForStatement forStatement) { if (!context.Settings.ForEachStatement) return null; @@ -432,7 +438,7 @@ namespace ICSharpCode.Decompiler.CSharp.Transforms ) ))); - bool MatchLowerBound(int indexNum, out IL.ILVariable index, IL.ILVariable collection, Statement statement) + bool MatchLowerBound(int indexNum, [NotNullWhen(true)] out IL.ILVariable? index, IL.ILVariable collection, Statement statement) { index = null; var m = variableAssignLowerBoundPattern.Match(statement); @@ -444,7 +450,7 @@ namespace ICSharpCode.Decompiler.CSharp.Transforms return m.Get("collection").Single().GetILVariable() == collection; } - bool MatchForeachOnMultiDimArray(IL.ILVariable[] upperBounds, IL.ILVariable collection, Statement firstInitializerStatement, out IdentifierExpression foreachVariable, out IList statements, out IL.ILVariable[] lowerBounds) + bool MatchForeachOnMultiDimArray(IL.ILVariable[] upperBounds, IL.ILVariable collection, Statement firstInitializerStatement, [NotNullWhen(true)] out IdentifierExpression? foreachVariable, [NotNullWhen(true)] out IList? statements, out IL.ILVariable[] lowerBounds) { int i = 0; foreachVariable = null; @@ -452,7 +458,7 @@ namespace ICSharpCode.Decompiler.CSharp.Transforms lowerBounds = new IL.ILVariable[upperBounds.Length]; Statement stmt = firstInitializerStatement; Match m = default(Match); - while (i < upperBounds.Length && MatchLowerBound(i, out IL.ILVariable indexVariable, collection, stmt)) + while (i < upperBounds.Length && MatchLowerBound(i, out var indexVariable, collection, stmt)) { m = forOnArrayMultiDimPattern.Match(stmt.GetNextStatement()); if (!m.Success) @@ -477,14 +483,14 @@ namespace ICSharpCode.Decompiler.CSharp.Transforms return true; } - Statement TransformForeachOnMultiDimArray(ExpressionStatement expressionStatement) + Statement? TransformForeachOnMultiDimArray(ExpressionStatement expressionStatement) { if (!context.Settings.ForEachStatement) return null; Match m; - Statement stmt = expressionStatement; - IL.ILVariable collection = null; - IL.ILVariable[] upperBounds = null; + Statement? stmt = expressionStatement; + IL.ILVariable? collection = null; + IL.ILVariable[]? upperBounds = null; List statementsToDelete = new List(); int i = 0; // first we look for all the upper bound initializations @@ -512,9 +518,9 @@ namespace ICSharpCode.Decompiler.CSharp.Transforms upperBounds[i] = m.Get("variable").Single().GetILVariable(); stmt = stmt.GetNextStatement(); i++; - } while (stmt != null && i < upperBounds.Length); + } while (stmt != null && upperBounds != null && i < upperBounds.Length); - if (upperBounds?.LastOrDefault() == null || collection == null) + if (upperBounds?.LastOrDefault() == null || collection == null || stmt == null) return null; if (!MatchForeachOnMultiDimArray(upperBounds, collection, stmt, out var foreachVariable, out var statements, out var lowerBounds)) return null; @@ -609,12 +615,14 @@ namespace ICSharpCode.Decompiler.CSharp.Transforms return true; } - PropertyDeclaration TransformAutomaticProperty(PropertyDeclaration propertyDeclaration) + PropertyDeclaration? TransformAutomaticProperty(PropertyDeclaration propertyDeclaration) { - IProperty property = propertyDeclaration.GetSymbol() as IProperty; - if (!CanTransformToAutomaticProperty(property, !property.DeclaringTypeDefinition.Fields.Any(f => f.Name == "_" + property.Name && f.IsCompilerGenerated()))) + IProperty? property = propertyDeclaration.GetSymbol() as IProperty; + if (property == null) + return null; + if (!CanTransformToAutomaticProperty(property, !(property.DeclaringTypeDefinition?.Fields.Any(f => f.Name == "_" + property.Name && f.IsCompilerGenerated()) ?? false))) return null; - IField field = null; + IField? field = null; Match m = automaticPropertyPattern.Match(propertyDeclaration); if (m.Success) { @@ -636,8 +644,10 @@ namespace ICSharpCode.Decompiler.CSharp.Transforms { RemoveCompilerGeneratedAttribute(propertyDeclaration.Getter.Attributes); RemoveCompilerGeneratedAttribute(propertyDeclaration.Setter.Attributes); - propertyDeclaration.Getter.Body = null; - propertyDeclaration.Setter.Body = null; + // Clearing the accessor body turns it into an auto-property accessor; the slot setter + // tolerates null until optional slots become nullable in the AST declarations. + propertyDeclaration.Getter.Body = null!; + propertyDeclaration.Setter.Body = null!; propertyDeclaration.Modifiers &= ~Modifiers.Readonly; propertyDeclaration.Getter.Modifiers &= ~Modifiers.Readonly; @@ -703,14 +713,14 @@ namespace ICSharpCode.Decompiler.CSharp.Transforms return base.VisitIdentifier(identifier); } - internal static bool IsBackingFieldOfAutomaticProperty(IField field, out IProperty property) + internal static bool IsBackingFieldOfAutomaticProperty(IField field, [NotNullWhen(true)] out IProperty? property) { property = null; - if (!NameCouldBeBackingFieldOfAutomaticProperty(field.Name, out string propertyName)) + if (!NameCouldBeBackingFieldOfAutomaticProperty(field.Name, out var propertyName)) return false; if (!field.IsCompilerGenerated()) return false; - property = field.DeclaringTypeDefinition + property = field.DeclaringTypeDefinition? .GetProperties(p => p.Name == propertyName, GetMemberOptions.IgnoreInheritedMembers) .FirstOrDefault(); return property != null; @@ -726,7 +736,7 @@ namespace ICSharpCode.Decompiler.CSharp.Transforms static readonly System.Text.RegularExpressions.Regex automaticPropertyBackingFieldNameRegex = new System.Text.RegularExpressions.Regex(@"^(<(?.+)>k__BackingField|_(?.+))$"); - static bool NameCouldBeBackingFieldOfAutomaticProperty(string name, out string propertyName) + static bool NameCouldBeBackingFieldOfAutomaticProperty(string name, [NotNullWhen(true)] out string? propertyName) { propertyName = null; var m = automaticPropertyBackingFieldNameRegex.Match(name); @@ -736,16 +746,17 @@ namespace ICSharpCode.Decompiler.CSharp.Transforms return true; } - Identifier ReplaceBackingFieldUsage(Identifier identifier) + Identifier? ReplaceBackingFieldUsage(Identifier identifier) { if (NameCouldBeBackingFieldOfAutomaticProperty(identifier.Name, out _)) { var parent = identifier.Parent; + if (parent == null) + return null; var mrr = parent.Annotation(); - var field = mrr?.Member as IField; - if (field != null && IsBackingFieldOfAutomaticProperty(field, out var property) + if (mrr?.Member is IField field && IsBackingFieldOfAutomaticProperty(field, out var property) && CanTransformToAutomaticProperty(property, !(field.IsCompilerGenerated() && field.Name == "_" + property.Name)) - && currentMethod.AccessorOwner != property) + && currentMethod?.AccessorOwner != property) { if (!property.CanSet && !context.Settings.GetterOnlyAutomaticProperties) return null; @@ -757,9 +768,11 @@ namespace ICSharpCode.Decompiler.CSharp.Transforms return null; } - Identifier ReplaceEventFieldAnnotation(Identifier identifier) + Identifier? ReplaceEventFieldAnnotation(Identifier identifier) { var parent = identifier.Parent; + if (parent == null) + return null; var mrr = parent.Annotation(); if (mrr?.Member is not IField field || field.Accessibility != Accessibility.Private) return null; @@ -769,7 +782,7 @@ namespace ICSharpCode.Decompiler.CSharp.Transforms if (module.MetadataFile.PropertyAndEventBackingFieldLookup.IsEventBackingField((FieldDefinitionHandle)field.MetadataToken, out var eventHandle)) { var eventDef = module.ResolveEntity(eventHandle) as IEvent; - if (eventDef != null && currentMethod.AccessorOwner != eventDef) + if (eventDef != null && currentMethod?.AccessorOwner != eventDef) { parent.RemoveAnnotations(); parent.AddAnnotation(new MemberResolveResult(mrr.TargetResult, eventDef)); @@ -914,7 +927,7 @@ namespace ICSharpCode.Decompiler.CSharp.Transforms if (!m.Success) return false; Expression fieldExpression = m.Get("field").Single(); - IField eventField = fieldExpression.GetSymbol() as IField; + IField? eventField = fieldExpression.GetSymbol() as IField; if (eventField == null) return false; var module = eventField.ParentModule as MetadataModule; @@ -990,7 +1003,7 @@ namespace ICSharpCode.Decompiler.CSharp.Transforms return true; } - EventDeclaration TransformAutomaticEvents(CustomEventDeclaration ev) + EventDeclaration? TransformAutomaticEvents(CustomEventDeclaration ev) { if (!ev.PrivateImplementationType.IsNull) return null; @@ -1065,7 +1078,7 @@ namespace ICSharpCode.Decompiler.CSharp.Transforms Body = destructorBodyPattern }; - DestructorDeclaration TransformDestructor(MethodDeclaration methodDef) + DestructorDeclaration? TransformDestructor(MethodDeclaration methodDef) { Match m = destructorPattern.Match(methodDef); if (m.Success) @@ -1082,7 +1095,7 @@ namespace ICSharpCode.Decompiler.CSharp.Transforms return null; } - DestructorDeclaration TransformDestructorBody(DestructorDeclaration dtorDef) + DestructorDeclaration? TransformDestructorBody(DestructorDeclaration dtorDef) { Match m = destructorBodyPattern.Match(dtorDef.Body); if (m.Success) @@ -1109,7 +1122,7 @@ namespace ICSharpCode.Decompiler.CSharp.Transforms /// Simplify nested 'try { try {} catch {} } finally {}'. /// This transformation must run after the using/lock tranformations. /// - TryCatchStatement TransformTryCatchFinally(TryCatchStatement tryFinally) + TryCatchStatement? TransformTryCatchFinally(TryCatchStatement tryFinally) { if (tryCatchFinallyPattern.IsMatch(tryFinally)) { @@ -1140,7 +1153,7 @@ namespace ICSharpCode.Decompiler.CSharp.Transforms } }; - AstNode SimplifyCascadingIfElseStatements(IfElseStatement node) + AstNode? SimplifyCascadingIfElseStatements(IfElseStatement node) { Match m = cascadingIfElsePattern.Match(node); if (m.Success) diff --git a/ICSharpCode.Decompiler/CSharp/Transforms/PrettifyAssignments.cs b/ICSharpCode.Decompiler/CSharp/Transforms/PrettifyAssignments.cs index b69fc9e26..a394fe32d 100644 --- a/ICSharpCode.Decompiler/CSharp/Transforms/PrettifyAssignments.cs +++ b/ICSharpCode.Decompiler/CSharp/Transforms/PrettifyAssignments.cs @@ -16,7 +16,10 @@ // OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // DEALINGS IN THE SOFTWARE. +#nullable enable + using System; +using System.Diagnostics.CodeAnalysis; using System.Linq; using ICSharpCode.Decompiler.CSharp.Resolver; @@ -39,6 +42,7 @@ namespace ICSharpCode.Decompiler.CSharp.Transforms /// class PrettifyAssignments : DepthFirstAstVisitor, IAstTransform { + [AllowNull] TransformContext context; public override void VisitAssignmentExpression(AssignmentExpression assignment) @@ -48,7 +52,7 @@ namespace ICSharpCode.Decompiler.CSharp.Transforms // Also supports "x = (T)(x op y)" -> "x op= y", if x.GetType() == T // and y is implicitly convertible to T. Expression rhs = assignment.Right; - IType expectedType = null; + IType? expectedType = null; if (assignment.Right is CastExpression { Type: var astType } cast) { rhs = cast.Expression; @@ -89,7 +93,7 @@ namespace ICSharpCode.Decompiler.CSharp.Transforms } } - bool IsImplicitlyConvertible(Expression rhs, IType expectedType) + bool IsImplicitlyConvertible(Expression rhs, IType? expectedType) { if (expectedType == null) return true; @@ -132,13 +136,13 @@ namespace ICSharpCode.Decompiler.CSharp.Transforms static bool CanConvertToCompoundAssignment(Expression left) { - MemberReferenceExpression mre = left as MemberReferenceExpression; + MemberReferenceExpression? mre = left as MemberReferenceExpression; if (mre != null) return IsWithoutSideEffects(mre.Target); - IndexerExpression ie = left as IndexerExpression; + IndexerExpression? ie = left as IndexerExpression; if (ie != null) return IsWithoutSideEffects(ie.Target) && ie.Arguments.All(IsWithoutSideEffects); - UnaryOperatorExpression uoe = left as UnaryOperatorExpression; + UnaryOperatorExpression? uoe = left as UnaryOperatorExpression; if (uoe != null && uoe.Operator == UnaryOperatorType.Dereference) return IsWithoutSideEffects(uoe.Expression); return IsWithoutSideEffects(left); diff --git a/ICSharpCode.Decompiler/CSharp/Transforms/RemoveCLSCompliantAttribute.cs b/ICSharpCode.Decompiler/CSharp/Transforms/RemoveCLSCompliantAttribute.cs index 94adca45d..f2d9d2b9b 100644 --- a/ICSharpCode.Decompiler/CSharp/Transforms/RemoveCLSCompliantAttribute.cs +++ b/ICSharpCode.Decompiler/CSharp/Transforms/RemoveCLSCompliantAttribute.cs @@ -16,6 +16,8 @@ // OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // DEALINGS IN THE SOFTWARE. +#nullable enable + using System; using System.Collections.Generic; using System.Linq; diff --git a/ICSharpCode.Decompiler/CSharp/Transforms/ReplaceMethodCallsWithOperators.cs b/ICSharpCode.Decompiler/CSharp/Transforms/ReplaceMethodCallsWithOperators.cs index 7c7d469f8..fcb5aa1ba 100644 --- a/ICSharpCode.Decompiler/CSharp/Transforms/ReplaceMethodCallsWithOperators.cs +++ b/ICSharpCode.Decompiler/CSharp/Transforms/ReplaceMethodCallsWithOperators.cs @@ -16,7 +16,10 @@ // OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // DEALINGS IN THE SOFTWARE. +#nullable enable + using System; +using System.Diagnostics.CodeAnalysis; using System.Linq; using System.Reflection; @@ -41,6 +44,7 @@ namespace ICSharpCode.Decompiler.CSharp.Transforms MemberName = "TypeHandle" }; + [AllowNull] TransformContext context; public override void VisitInvocationExpression(InvocationExpression invocationExpression) @@ -508,7 +512,7 @@ namespace ICSharpCode.Decompiler.CSharp.Transforms Match m = getMethodOrConstructorFromHandlePattern.Match(castExpression); if (m.Success) { - IMethod method = m.Get("method").Single().GetSymbol() as IMethod; + IMethod? method = m.Get("method").Single().GetSymbol() as IMethod; if (m.Has("declaringType") && method != null) { Expression newNode = new MemberReferenceExpression(new TypeReferenceExpression(m.Get("declaringType").Single().Detach()), method.Name); diff --git a/ICSharpCode.Decompiler/CSharp/Transforms/TransformContext.cs b/ICSharpCode.Decompiler/CSharp/Transforms/TransformContext.cs index 3ed54f468..505587ef8 100644 --- a/ICSharpCode.Decompiler/CSharp/Transforms/TransformContext.cs +++ b/ICSharpCode.Decompiler/CSharp/Transforms/TransformContext.cs @@ -16,6 +16,8 @@ // OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // DEALINGS IN THE SOFTWARE. +#nullable enable + using System.Collections.Immutable; using System.Threading; @@ -40,17 +42,17 @@ namespace ICSharpCode.Decompiler.CSharp.Transforms /// /// Returns the current member; or null if a whole type or module is being decompiled. /// - public IMember CurrentMember => decompilationContext.CurrentMember; + public IMember? CurrentMember => decompilationContext.CurrentMember; /// /// Returns the current type definition; or null if a module is being decompiled. /// - public ITypeDefinition CurrentTypeDefinition => decompilationContext.CurrentTypeDefinition; + public ITypeDefinition? CurrentTypeDefinition => decompilationContext.CurrentTypeDefinition; /// /// Returns the module that is being decompiled. /// - public IModule CurrentModule => decompilationContext.CurrentModule; + public IModule? CurrentModule => decompilationContext.CurrentModule; /// /// Returns the max possible set of namespaces that will be used during decompilation.