Browse Source

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
pull/3807/head
Siegfried Pammer 3 weeks ago committed by Siegfried Pammer
parent
commit
785e36712d
  1. 12
      ICSharpCode.Decompiler.Generators/DecompilerSyntaxTreeGenerator.cs
  2. 5
      ICSharpCode.Decompiler/CSharp/Syntax/AstNodeCollection.cs
  3. 46
      ICSharpCode.Decompiler/CSharp/Transforms/AddCheckedBlocks.cs
  4. 6
      ICSharpCode.Decompiler/CSharp/Transforms/AddXmlDocumentationTransform.cs
  5. 12
      ICSharpCode.Decompiler/CSharp/Transforms/CombineQueryExpressions.cs
  6. 8
      ICSharpCode.Decompiler/CSharp/Transforms/ContextTrackingVisitor.cs
  7. 10
      ICSharpCode.Decompiler/CSharp/Transforms/CustomPatterns.cs
  8. 48
      ICSharpCode.Decompiler/CSharp/Transforms/DeclareVariables.cs
  9. 2
      ICSharpCode.Decompiler/CSharp/Transforms/EscapeInvalidIdentifiers.cs
  10. 4
      ICSharpCode.Decompiler/CSharp/Transforms/FixNameCollisions.cs
  11. 2
      ICSharpCode.Decompiler/CSharp/Transforms/FlattenSwitchBlocks.cs
  12. 2
      ICSharpCode.Decompiler/CSharp/Transforms/IAstTransform.cs
  13. 18
      ICSharpCode.Decompiler/CSharp/Transforms/IntroduceExtensionMethods.cs
  14. 76
      ICSharpCode.Decompiler/CSharp/Transforms/IntroduceQueryExpressions.cs
  15. 8
      ICSharpCode.Decompiler/CSharp/Transforms/IntroduceUnsafeModifier.cs
  16. 8
      ICSharpCode.Decompiler/CSharp/Transforms/IntroduceUsingDeclarations.cs
  17. 9
      ICSharpCode.Decompiler/CSharp/Transforms/NormalizeBlockStatements.cs
  18. 93
      ICSharpCode.Decompiler/CSharp/Transforms/PatternStatementTransform.cs
  19. 14
      ICSharpCode.Decompiler/CSharp/Transforms/PrettifyAssignments.cs
  20. 2
      ICSharpCode.Decompiler/CSharp/Transforms/RemoveCLSCompliantAttribute.cs
  21. 6
      ICSharpCode.Decompiler/CSharp/Transforms/ReplaceMethodCallsWithOperators.cs
  22. 8
      ICSharpCode.Decompiler/CSharp/Transforms/TransformContext.cs

12
ICSharpCode.Decompiler.Generators/DecompilerSyntaxTreeGenerator.cs

@ -197,10 +197,18 @@ internal class DecompilerSyntaxTreeGenerator : IIncrementalGenerator
if (source.NeedsPatternPlaceholder) 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( 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 sealed class PatternPlaceholder : {source.NodeName}, INode

5
ICSharpCode.Decompiler/CSharp/Syntax/AstNodeCollection.cs

@ -304,9 +304,10 @@ namespace ICSharpCode.Decompiler.CSharp.Syntax
return Pattern.DoMatchCollection(AsNodeList(), other.AsNodeList(), match); 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) public void InsertBefore(T existingItem, T newItem)

46
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 // OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
// DEALINGS IN THE SOFTWARE. // DEALINGS IN THE SOFTWARE.
#nullable enable
using System; using System;
using System.Diagnostics;
using System.Linq; using System.Linq;
using ICSharpCode.Decompiler.CSharp.Syntax; using ICSharpCode.Decompiler.CSharp.Syntax;
@ -143,7 +146,7 @@ namespace ICSharpCode.Decompiler.CSharp.Transforms
/// </summary> /// </summary>
abstract class InsertedNode abstract class InsertedNode
{ {
public static InsertedNode operator +(InsertedNode a, InsertedNode b) public static InsertedNode? operator +(InsertedNode? a, InsertedNode? b)
{ {
if (a == null) if (a == null)
return b; return b;
@ -194,11 +197,11 @@ namespace ICSharpCode.Decompiler.CSharp.Transforms
class InsertedBlock : InsertedNode class InsertedBlock : InsertedNode
{ {
readonly Statement firstStatement; // inclusive readonly Statement? firstStatement; // inclusive
readonly Statement lastStatement; // exclusive readonly Statement? lastStatement; // exclusive
readonly bool isChecked; readonly bool isChecked;
public InsertedBlock(Statement firstStatement, Statement lastStatement, bool isChecked) public InsertedBlock(Statement? firstStatement, Statement? lastStatement, bool isChecked)
{ {
this.firstStatement = firstStatement; this.firstStatement = firstStatement;
this.lastStatement = lastStatement; this.lastStatement = lastStatement;
@ -207,10 +210,13 @@ namespace ICSharpCode.Decompiler.CSharp.Transforms
public override void Insert() 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(); BlockStatement newBlock = new BlockStatement();
// Move all statements except for the first // Move all statements except for the first
Statement next; Statement? next;
for (Statement stmt = firstStatement.GetNextStatement(); stmt != lastStatement; stmt = next) for (Statement? stmt = firstStatement.GetNextStatement(); stmt != null && stmt != lastStatement; stmt = next)
{ {
next = stmt.GetNextStatement(); next = stmt.GetNextStatement();
newBlock.Add(stmt.Detach()); newBlock.Add(stmt.Detach());
@ -233,18 +239,18 @@ namespace ICSharpCode.Decompiler.CSharp.Transforms
class Result class Result
{ {
public Cost CostInCheckedContext; public Cost CostInCheckedContext;
public InsertedNode NodesToInsertInCheckedContext; public InsertedNode? NodesToInsertInCheckedContext;
public Cost CostInUncheckedContext; public Cost CostInUncheckedContext;
public InsertedNode NodesToInsertInUncheckedContext; public InsertedNode? NodesToInsertInUncheckedContext;
} }
#endregion #endregion
public void Run(AstNode node, TransformContext context) public void Run(AstNode node, TransformContext context)
{ {
BlockStatement block = node as BlockStatement; BlockStatement? block = node as BlockStatement;
if (block == null) 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); Run(child, context);
} }
@ -268,20 +274,20 @@ namespace ICSharpCode.Decompiler.CSharp.Transforms
// For a block, we are tracking 4 possibilities: // For a block, we are tracking 4 possibilities:
// a) context is checked, no unchecked block open // a) context is checked, no unchecked block open
Cost costCheckedContext = new Cost(0, 0); Cost costCheckedContext = new Cost(0, 0);
InsertedNode nodesCheckedContext = null; InsertedNode? nodesCheckedContext = null;
// b) context is checked, an unchecked block is open // b) context is checked, an unchecked block is open
Cost costCheckedContextUncheckedBlockOpen = Cost.Infinite; Cost costCheckedContextUncheckedBlockOpen = Cost.Infinite;
InsertedNode nodesCheckedContextUncheckedBlockOpen = null; InsertedNode? nodesCheckedContextUncheckedBlockOpen = null;
Statement uncheckedBlockStart = null; Statement? uncheckedBlockStart = null;
// c) context is unchecked, no checked block open // c) context is unchecked, no checked block open
Cost costUncheckedContext = new Cost(0, 0); Cost costUncheckedContext = new Cost(0, 0);
InsertedNode nodesUncheckedContext = null; InsertedNode? nodesUncheckedContext = null;
// d) context is unchecked, a checked block is open // d) context is unchecked, a checked block is open
Cost costUncheckedContextCheckedBlockOpen = Cost.Infinite; Cost costUncheckedContextCheckedBlockOpen = Cost.Infinite;
InsertedNode nodesUncheckedContextCheckedBlockOpen = null; InsertedNode? nodesUncheckedContextCheckedBlockOpen = null;
Statement checkedBlockStart = null; Statement? checkedBlockStart = null;
Statement statement = block.Statements.FirstOrDefault(); Statement? statement = block.Statements.FirstOrDefault();
while (true) while (true)
{ {
// Blocks can be closed 'for free'. We use '<=' so that blocks are closed as late as possible (goal 4b) // 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) if (node is BlockStatement)
return GetResultFromBlock((BlockStatement)node); return GetResultFromBlock((BlockStatement)node);
Result result = new Result(); 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 childResult = GetResult(child);
result.CostInCheckedContext += childResult.CostInCheckedContext; result.CostInCheckedContext += childResult.CostInCheckedContext;
@ -354,10 +360,10 @@ namespace ICSharpCode.Decompiler.CSharp.Transforms
result.CostInUncheckedContext += childResult.CostInUncheckedContext; result.CostInUncheckedContext += childResult.CostInUncheckedContext;
result.NodesToInsertInUncheckedContext += childResult.NodesToInsertInUncheckedContext; result.NodesToInsertInUncheckedContext += childResult.NodesToInsertInUncheckedContext;
} }
Expression expr = node as Expression; Expression? expr = node as Expression;
if (expr != null) if (expr != null)
{ {
CheckedUncheckedAnnotation annotation = expr.Annotation<CheckedUncheckedAnnotation>(); CheckedUncheckedAnnotation? annotation = expr.Annotation<CheckedUncheckedAnnotation>();
if (annotation != null) if (annotation != null)
{ {
if (annotation.IsExplicit) if (annotation.IsExplicit)

6
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 // OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
// DEALINGS IN THE SOFTWARE. // DEALINGS IN THE SOFTWARE.
#nullable enable
using System; using System;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
@ -62,7 +64,7 @@ namespace ICSharpCode.Decompiler.CSharp.Transforms
static void InsertXmlDocumentation(AstNode node, StringReader r) static void InsertXmlDocumentation(AstNode node, StringReader r)
{ {
// Find the first non-empty line: // Find the first non-empty line:
string firstLine; string? firstLine;
do do
{ {
firstLine = r.ReadLine(); firstLine = r.ReadLine();
@ -70,7 +72,7 @@ namespace ICSharpCode.Decompiler.CSharp.Transforms
return; return;
} while (string.IsNullOrWhiteSpace(firstLine)); } while (string.IsNullOrWhiteSpace(firstLine));
string indentation = firstLine.Substring(0, firstLine.Length - firstLine.TrimStart().Length); string indentation = firstLine.Substring(0, firstLine.Length - firstLine.TrimStart().Length);
string line = firstLine; string? line = firstLine;
int skippedWhitespaceLines = 0; int skippedWhitespaceLines = 0;
// Copy all lines from input to output, except for empty lines at the end. // Copy all lines from input to output, except for empty lines at the end.
while (line != null) while (line != null)

12
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 // OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
// DEALINGS IN THE SOFTWARE. // DEALINGS IN THE SOFTWARE.
#nullable enable
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
@ -47,8 +49,8 @@ namespace ICSharpCode.Decompiler.CSharp.Transforms
void CombineQueries(AstNode node, Dictionary<string, object> fromOrLetIdentifiers) void CombineQueries(AstNode node, Dictionary<string, object> fromOrLetIdentifiers)
{ {
AstNode next; AstNode? next;
for (AstNode child = node.FirstChild; child != null; child = next) for (AstNode? child = node.FirstChild; child != null; child = next)
{ {
// store reference to next child before transformation // store reference to next child before transformation
next = child.NextSibling; next = child.NextSibling;
@ -105,7 +107,9 @@ namespace ICSharpCode.Decompiler.CSharp.Transforms
{ {
if (!CSharpDecompiler.IsTransparentIdentifier(fromClause.Identifier)) if (!CSharpDecompiler.IsTransparentIdentifier(fromClause.Identifier))
return false; 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); Match match = selectTransparentIdentifierPattern.Match(selectClause);
if (!match.Success) if (!match.Success)
return false; return false;
@ -116,7 +120,7 @@ namespace ICSharpCode.Decompiler.CSharp.Transforms
fromClause.Remove(); fromClause.Remove();
selectClause.Remove(); selectClause.Remove();
// Move clauses from innerQuery to query // Move clauses from innerQuery to query
QueryClause insertionPos = null; QueryClause? insertionPos = null;
foreach (var clause in innerQuery.Clauses) foreach (var clause in innerQuery.Clauses)
{ {
query.Clauses.InsertAfter(insertionPos, insertionPos = clause.Detach()); query.Clauses.InsertAfter(insertionPos, insertionPos = clause.Detach());

8
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 // OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
// DEALINGS IN THE SOFTWARE. // DEALINGS IN THE SOFTWARE.
#nullable enable
using System.Diagnostics; using System.Diagnostics;
using ICSharpCode.Decompiler.CSharp.Syntax; using ICSharpCode.Decompiler.CSharp.Syntax;
@ -28,8 +30,8 @@ namespace ICSharpCode.Decompiler.CSharp.Transforms
/// </summary> /// </summary>
public abstract class ContextTrackingVisitor<TResult> : DepthFirstAstVisitor<TResult> public abstract class ContextTrackingVisitor<TResult> : DepthFirstAstVisitor<TResult>
{ {
protected ITypeDefinition currentTypeDefinition; protected ITypeDefinition? currentTypeDefinition;
protected IMethod currentMethod; protected IMethod? currentMethod;
protected void Initialize(TransformContext context) protected void Initialize(TransformContext context)
{ {
@ -45,7 +47,7 @@ namespace ICSharpCode.Decompiler.CSharp.Transforms
public override TResult VisitTypeDeclaration(TypeDeclaration typeDeclaration) public override TResult VisitTypeDeclaration(TypeDeclaration typeDeclaration)
{ {
ITypeDefinition oldType = currentTypeDefinition; ITypeDefinition? oldType = currentTypeDefinition;
try try
{ {
currentTypeDefinition = typeDeclaration.GetSymbol() as ITypeDefinition; currentTypeDefinition = typeDeclaration.GetSymbol() as ITypeDefinition;

10
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 // OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
// DEALINGS IN THE SOFTWARE. // DEALINGS IN THE SOFTWARE.
#nullable enable
using System; using System;
using System.Linq; using System.Linq;
@ -27,7 +29,7 @@ namespace ICSharpCode.Decompiler.CSharp.Transforms
{ {
sealed class TypePattern : Pattern sealed class TypePattern : Pattern
{ {
readonly string ns; readonly string? ns;
readonly string name; readonly string name;
public TypePattern(Type type) public TypePattern(Type type)
@ -38,8 +40,8 @@ namespace ICSharpCode.Decompiler.CSharp.Transforms
public override bool DoMatch(INode other, Match match) public override bool DoMatch(INode other, Match match)
{ {
ComposedType ct = other as ComposedType; ComposedType? ct = other as ComposedType;
AstType o; AstType? o;
if (ct != null && !ct.HasRefSpecifier && !ct.HasNullableSpecifier && ct.PointerRank == 0 && !ct.ArraySpecifiers.Any()) 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 // 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) public override bool DoMatch(INode other, Match match)
{ {
InvocationExpression ie = other as InvocationExpression; InvocationExpression? ie = other as InvocationExpression;
if (ie != null && ie.Annotation<LdTokenAnnotation>() != null && ie.Arguments.Count == 1) if (ie != null && ie.Annotation<LdTokenAnnotation>() != null && ie.Arguments.Count == 1)
{ {
return childNode.DoMatch(ie.Arguments.Single(), match); return childNode.DoMatch(ie.Arguments.Single(), match);

48
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 // OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
// DEALINGS IN THE SOFTWARE. // DEALINGS IN THE SOFTWARE.
#nullable enable
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Diagnostics; using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Linq; using System.Linq;
using ICSharpCode.Decompiler.CSharp.Syntax; using ICSharpCode.Decompiler.CSharp.Syntax;
@ -54,7 +57,8 @@ namespace ICSharpCode.Decompiler.CSharp.Transforms
{ {
return new InsertionPoint { return new InsertionPoint {
level = level - 1, 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; InsertionPoint result = this;
while (result.level > targetLevel) while (result.level > targetLevel)
{ {
result.nextNode = result.nextNode.Parent; result.nextNode = result.nextNode.Parent!;
result.level -= 1; result.level -= 1;
} }
return result; return result;
@ -82,7 +86,8 @@ namespace ICSharpCode.Decompiler.CSharp.Transforms
{ {
public readonly ILVariable ILVariable; public readonly ILVariable ILVariable;
public IType Type => ILVariable.Type; 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!;
/// <summary> /// <summary>
/// Whether the variable needs to be default-initialized. /// Whether the variable needs to be default-initialized.
@ -108,7 +113,7 @@ namespace ICSharpCode.Decompiler.CSharp.Transforms
/// </summary> /// </summary>
public IdentifierExpression FirstUse; public IdentifierExpression FirstUse;
public VariableToDeclare ReplacementDueToCollision; public VariableToDeclare? ReplacementDueToCollision;
public bool InvolvedInCollision; public bool InvolvedInCollision;
public bool RemovedDueToCollision => ReplacementDueToCollision != null; public bool RemovedDueToCollision => ReplacementDueToCollision != null;
public bool DeclaredInDeconstruction; public bool DeclaredInDeconstruction;
@ -138,6 +143,7 @@ namespace ICSharpCode.Decompiler.CSharp.Transforms
} }
readonly Dictionary<ILVariable, VariableToDeclare> variableDict = new Dictionary<ILVariable, VariableToDeclare>(); readonly Dictionary<ILVariable, VariableToDeclare> variableDict = new Dictionary<ILVariable, VariableToDeclare>();
[AllowNull]
TransformContext context; TransformContext context;
public void Run(AstNode rootNode, TransformContext context) public void Run(AstNode rootNode, TransformContext context)
@ -284,7 +290,7 @@ namespace ICSharpCode.Decompiler.CSharp.Transforms
/// </remarks> /// </remarks>
void FindInsertionPoints(AstNode node, int nodeLevel) void FindInsertionPoints(AstNode node, int nodeLevel)
{ {
BlockContainer scope = node.Annotation<BlockContainer>(); BlockContainer? scope = node.Annotation<BlockContainer>();
if (scope != null && IsRelevantScope(scope)) if (scope != null && IsRelevantScope(scope))
{ {
// track loops and function bodies as scopes, for comparison with CaptureScope. // track loops and function bodies as scopes, for comparison with CaptureScope.
@ -305,7 +311,7 @@ namespace ICSharpCode.Decompiler.CSharp.Transforms
} }
try 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); FindInsertionPoints(child, nodeLevel + 1);
} }
@ -329,7 +335,7 @@ namespace ICSharpCode.Decompiler.CSharp.Transforms
{ {
InsertionPoint newPoint; InsertionPoint newPoint;
int startIndex = scopeTracking.Count - 1; int startIndex = scopeTracking.Count - 1;
BlockContainer captureScope = variable.CaptureScope; BlockContainer? captureScope = variable.CaptureScope;
while (captureScope != null && !IsRelevantScope(captureScope)) while (captureScope != null && !IsRelevantScope(captureScope))
{ {
captureScope = BlockContainer.FindClosestContainer(captureScope.Parent); 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); 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; assignment = v.InsertionPoint.nextNode as AssignmentExpression;
if (assignment == null) if (assignment == null)
@ -595,7 +601,7 @@ namespace ICSharpCode.Decompiler.CSharp.Transforms
if (v.RemovedDueToCollision || v.DeclaredInDeconstruction) if (v.RemovedDueToCollision || v.DeclaredInDeconstruction)
continue; 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;' // 'int v; v = expr;' can be combined to 'int v = expr;'
AstType type; AstType type;
@ -674,7 +680,7 @@ namespace ICSharpCode.Decompiler.CSharp.Transforms
else else
{ {
// Insert a separate declaration statement. // Insert a separate declaration statement.
Expression initializer = null; Expression? initializer = null;
AstType type = context.TypeSystemAstBuilder.ConvertType(v.Type); AstType type = context.TypeSystemAstBuilder.ConvertType(v.Type);
if (v.DefaultInitialization == VariableInitKind.NeedsDefaultValue) if (v.DefaultInitialization == VariableInitKind.NeedsDefaultValue)
{ {
@ -694,6 +700,10 @@ namespace ICSharpCode.Decompiler.CSharp.Transforms
v.InsertionPoint = v.InsertionPoint.Up(); v.InsertionPoint = v.InsertionPoint.Up();
} }
Debug.Assert(v.InsertionPoint.nextNode.Slot?.Kind == SlotKind.Statement); 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) if (v.DefaultInitialization == VariableInitKind.NeedsSkipInit)
{ {
AstType unsafeType = context.TypeSystemAstBuilder.ConvertType( AstType unsafeType = context.TypeSystemAstBuilder.ConvertType(
@ -702,7 +712,7 @@ namespace ICSharpCode.Decompiler.CSharp.Transforms
{ {
var outVarDecl = new OutVarDeclarationExpression(type.Clone(), v.Name); var outVarDecl = new OutVarDeclarationExpression(type.Clone(), v.Name);
outVarDecl.Variable.AddAnnotation(new ILVariableResolveResult(ilVariable)); outVarDecl.Variable.AddAnnotation(new ILVariableResolveResult(ilVariable));
v.InsertionPoint.nextNode.Parent.InsertChildBefore( insertionParent.InsertChildBefore(
v.InsertionPoint.nextNode, v.InsertionPoint.nextNode,
new ExpressionStatement { new ExpressionStatement {
Expression = new InvocationExpression { Expression = new InvocationExpression {
@ -719,11 +729,11 @@ namespace ICSharpCode.Decompiler.CSharp.Transforms
} }
else else
{ {
v.InsertionPoint.nextNode.Parent.InsertChildBefore( insertionParent.InsertChildBefore(
v.InsertionPoint.nextNode, v.InsertionPoint.nextNode,
vds, vds,
BlockStatement.StatementRole); BlockStatement.StatementRole);
v.InsertionPoint.nextNode.Parent.InsertChildBefore( insertionParent.InsertChildBefore(
v.InsertionPoint.nextNode, v.InsertionPoint.nextNode,
new ExpressionStatement { new ExpressionStatement {
Expression = new InvocationExpression { Expression = new InvocationExpression {
@ -745,7 +755,7 @@ namespace ICSharpCode.Decompiler.CSharp.Transforms
} }
else else
{ {
v.InsertionPoint.nextNode.Parent.InsertChildBefore( insertionParent.InsertChildBefore(
v.InsertionPoint.nextNode, v.InsertionPoint.nextNode,
vds, vds,
BlockStatement.StatementRole); 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; dirExpr = v.FirstUse.Parent as DirectionExpression;
if (dirExpr == null || dirExpr.FieldDirection != FieldDirection.Out) if (dirExpr == null || dirExpr.FieldDirection != FieldDirection.Out)
@ -768,7 +778,7 @@ namespace ICSharpCode.Decompiler.CSharp.Transforms
return false; return false;
if (v.DefaultInitialization != VariableInitKind.None) if (v.DefaultInitialization != VariableInitKind.None)
return false; 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) if (node.Slot?.Kind == SlotKind.EmbeddedStatement)
{ {
@ -812,9 +822,9 @@ namespace ICSharpCode.Decompiler.CSharp.Transforms
var v = variableDict[ilVar]; var v = variableDict[ilVar];
if (!v.RemovedDueToCollision) if (!v.RemovedDueToCollision)
continue; continue;
while (v.RemovedDueToCollision) while (v.ReplacementDueToCollision is { } replacement)
{ {
v = v.ReplacementDueToCollision; v = replacement;
} }
node.RemoveAnnotations<ILVariableResolveResult>(); node.RemoveAnnotations<ILVariableResolveResult>();
node.AddAnnotation(new ILVariableResolveResult(v.ILVariable, v.Type)); node.AddAnnotation(new ILVariableResolveResult(v.ILVariable, v.Type));

2
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 // OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
// DEALINGS IN THE SOFTWARE. // DEALINGS IN THE SOFTWARE.
#nullable enable
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;

4
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 // OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
// DEALINGS IN THE SOFTWARE. // DEALINGS IN THE SOFTWARE.
#nullable enable
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
@ -68,7 +70,7 @@ namespace ICSharpCode.Decompiler.CSharp.Transforms
if (node is IdentifierExpression || node is MemberReferenceExpression) if (node is IdentifierExpression || node is MemberReferenceExpression)
{ {
ISymbol symbol = node.GetSymbol(); 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; node.GetChildByRole(Roles.Identifier).Name = newName;
} }

2
ICSharpCode.Decompiler/CSharp/Transforms/FlattenSwitchBlocks.cs

@ -1,3 +1,5 @@
#nullable enable
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;

2
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 // OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
// DEALINGS IN THE SOFTWARE. // DEALINGS IN THE SOFTWARE.
#nullable enable
using ICSharpCode.Decompiler.CSharp.Syntax; using ICSharpCode.Decompiler.CSharp.Syntax;
namespace ICSharpCode.Decompiler.CSharp.Transforms namespace ICSharpCode.Decompiler.CSharp.Transforms

18
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 // OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
// DEALINGS IN THE SOFTWARE. // DEALINGS IN THE SOFTWARE.
#nullable enable
using System.Collections.Generic; using System.Collections.Generic;
using System.Diagnostics; using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Linq; using System.Linq;
using ICSharpCode.Decompiler.CSharp.Resolver; using ICSharpCode.Decompiler.CSharp.Resolver;
@ -34,8 +37,11 @@ namespace ICSharpCode.Decompiler.CSharp.Transforms
/// </summary> /// </summary>
public class IntroduceExtensionMethods : DepthFirstAstVisitor, IAstTransform public class IntroduceExtensionMethods : DepthFirstAstVisitor, IAstTransform
{ {
[AllowNull]
TransformContext context; TransformContext context;
[AllowNull]
CSharpResolver resolver; CSharpResolver resolver;
[AllowNull]
CSharpConversions conversions; CSharpConversions conversions;
public void Run(AstNode rootNode, TransformContext context) public void Run(AstNode rootNode, TransformContext context)
@ -122,7 +128,9 @@ namespace ICSharpCode.Decompiler.CSharp.Transforms
} }
else 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) if (invocationExpression.GetResolveResult() is CSharpInvocationResolveResult irr)
{ {
@ -137,9 +145,9 @@ namespace ICSharpCode.Decompiler.CSharp.Transforms
} }
static bool CanTransformToExtensionMethodCall(CSharpResolver resolver, static bool CanTransformToExtensionMethodCall(CSharpResolver resolver,
InvocationExpression invocationExpression, out MemberReferenceExpression memberRefExpr, InvocationExpression invocationExpression, out MemberReferenceExpression? memberRefExpr,
out ResolveResult target, [NotNullWhen(true)] out ResolveResult? target,
out Expression firstArgument) [NotNullWhen(true)] out Expression? firstArgument)
{ {
var method = invocationExpression.GetSymbol() as IMethod; var method = invocationExpression.GetSymbol() as IMethod;
memberRefExpr = null; memberRefExpr = null;
@ -176,7 +184,7 @@ namespace ICSharpCode.Decompiler.CSharp.Transforms
} }
Debug.Assert(target != null); Debug.Assert(target != null);
ResolveResult[] args = new ResolveResult[invocationExpression.Arguments.Count - 1]; ResolveResult[] args = new ResolveResult[invocationExpression.Arguments.Count - 1];
string[] argNames = null; string[]? argNames = null;
int pos = 0; int pos = 0;
foreach (var arg in invocationExpression.Arguments.Skip(1)) foreach (var arg in invocationExpression.Arguments.Skip(1))
{ {

76
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 // OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
// DEALINGS IN THE SOFTWARE. // DEALINGS IN THE SOFTWARE.
#nullable enable
using System; using System;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Linq; using System.Linq;
using ICSharpCode.Decompiler.CSharp.Syntax; using ICSharpCode.Decompiler.CSharp.Syntax;
@ -30,6 +34,7 @@ namespace ICSharpCode.Decompiler.CSharp.Transforms
/// </summary> /// </summary>
public class IntroduceQueryExpressions : IAstTransform public class IntroduceQueryExpressions : IAstTransform
{ {
[AllowNull]
TransformContext context; TransformContext context;
public void Run(AstNode rootNode, 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, // See if the data source of this query is a degenerate query,
// and combine the queries if possible. // and combine the queries if possible.
QueryExpression innerQuery = fromClause.Expression as QueryExpression; QueryExpression? innerQuery = fromClause.Expression as QueryExpression;
while (IsDegenerateQuery(innerQuery)) while (innerQuery != null && IsDegenerateQuery(innerQuery))
{ {
QueryFromClause innerFromClause = (QueryFromClause)innerQuery.Clauses.First(); QueryFromClause innerFromClause = (QueryFromClause)innerQuery.Clauses.First();
ILVariable innerVariable = innerFromClause.Annotation<ILVariableResolveResult>()?.Variable; ILVariable? innerVariable = innerFromClause.Annotation<ILVariableResolveResult>()?.Variable;
ILVariable rangeVariable = fromClause.Annotation<ILVariableResolveResult>()?.Variable; ILVariable? rangeVariable = fromClause.Annotation<ILVariableResolveResult>()?.Variable;
// Replace the fromClause with all clauses from the inner query // Replace the fromClause with all clauses from the inner query
fromClause.Remove(); fromClause.Remove();
QueryClause insertionPos = null; QueryClause? insertionPos = null;
foreach (var clause in innerQuery.Clauses) foreach (var clause in innerQuery.Clauses)
{ {
CombineRangeVariables(clause, innerVariable, rangeVariable); 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<Identifier>()) foreach (var identifier in clause.DescendantNodes().OfType<Identifier>())
{ {
var variable = identifier.Parent.Annotation<ILVariableResolveResult>()?.Variable; var parent = identifier.Parent;
if (parent == null)
continue;
var variable = parent.Annotation<ILVariableResolveResult>()?.Variable;
if (variable == oldVariable) if (variable == oldVariable)
{ {
identifier.Parent.RemoveAnnotations<ILVariableResolveResult>(); parent.RemoveAnnotations<ILVariableResolveResult>();
identifier.Parent.AddAnnotation(new ILVariableResolveResult(newVariable)); parent.AddAnnotation(new ILVariableResolveResult(newVariable));
identifier.ReplaceWith(Identifier.Create(newVariable.Name)); identifier.ReplaceWith(Identifier.Create(newVariable.Name));
} }
} }
} }
bool IsDegenerateQuery(QueryExpression query) bool IsDegenerateQuery(QueryExpression? query)
{ {
if (query == null) if (query == null)
return false; return false;
@ -94,7 +104,7 @@ namespace ICSharpCode.Decompiler.CSharp.Transforms
void DecompileQueries(AstNode node) void DecompileQueries(AstNode node)
{ {
Expression query = DecompileQuery(node as InvocationExpression); Expression? query = DecompileQuery(node as InvocationExpression);
if (query != null) if (query != null)
{ {
if (node.Parent is ExpressionStatement && CanUseDiscardAssignment()) if (node.Parent is ExpressionStatement && CanUseDiscardAssignment())
@ -102,8 +112,8 @@ namespace ICSharpCode.Decompiler.CSharp.Transforms
node.ReplaceWith(query); node.ReplaceWith(query);
} }
AstNode next; AstNode? next;
for (AstNode child = (query ?? node).FirstChild; child != null; child = next) for (AstNode? child = (query ?? node).FirstChild; child != null; child = next)
{ {
// store reference to next child before transformation // store reference to next child before transformation
next = child.NextSibling; next = child.NextSibling;
@ -117,11 +127,11 @@ namespace ICSharpCode.Decompiler.CSharp.Transforms
return context.Settings.Discards; return context.Settings.Discards;
} }
QueryExpression DecompileQuery(InvocationExpression invocation) QueryExpression? DecompileQuery(InvocationExpression? invocation)
{ {
if (invocation == null) if (invocation == null)
return null; return null;
MemberReferenceExpression mre = invocation.Target as MemberReferenceExpression; MemberReferenceExpression? mre = invocation.Target as MemberReferenceExpression;
if (mre == null || IsNullConditional(mre.Target)) if (mre == null || IsNullConditional(mre.Target))
return null; return null;
switch (mre.MemberName) switch (mre.MemberName)
@ -133,7 +143,7 @@ namespace ICSharpCode.Decompiler.CSharp.Transforms
if (!IsComplexQuery(mre)) if (!IsComplexQuery(mre))
return null; return null;
Expression expr = invocation.Arguments.Single(); 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(); QueryExpression query = new QueryExpression();
query.Clauses.Add(MakeFromClause(parameter, mre.Target.Detach())); query.Clauses.Add(MakeFromClause(parameter, mre.Target.Detach()));
@ -148,8 +158,8 @@ namespace ICSharpCode.Decompiler.CSharp.Transforms
{ {
Expression keyLambda = invocation.Arguments.ElementAt(0); Expression keyLambda = invocation.Arguments.ElementAt(0);
Expression projectionLambda = invocation.Arguments.ElementAt(1); Expression projectionLambda = invocation.Arguments.ElementAt(1);
if (MatchSimpleLambda(keyLambda, out ParameterDeclaration parameter1, out Expression keySelector) if (MatchSimpleLambda(keyLambda, out var parameter1, out var keySelector)
&& MatchSimpleLambda(projectionLambda, out ParameterDeclaration parameter2, out Expression elementSelector) && MatchSimpleLambda(projectionLambda, out var parameter2, out var elementSelector)
&& parameter1.Name == parameter2.Name) && parameter1.Name == parameter2.Name)
{ {
QueryExpression query = new QueryExpression(); QueryExpression query = new QueryExpression();
@ -166,7 +176,7 @@ namespace ICSharpCode.Decompiler.CSharp.Transforms
else if (invocation.Arguments.Count == 1) else if (invocation.Arguments.Count == 1)
{ {
Expression lambda = invocation.Arguments.Single(); 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(); QueryExpression query = new QueryExpression();
query.Clauses.Add(MakeFromClause(parameter, mre.Target.Detach())); query.Clauses.Add(MakeFromClause(parameter, mre.Target.Detach()));
@ -181,11 +191,11 @@ namespace ICSharpCode.Decompiler.CSharp.Transforms
if (invocation.Arguments.Count != 2) if (invocation.Arguments.Count != 2)
return null; return null;
var fromExpressionLambda = invocation.Arguments.ElementAt(0); 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; return null;
if (IsNullConditional(collectionSelector)) if (IsNullConditional(collectionSelector))
return null; 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) if (lambda != null && lambda.Parameters.Count == 2 && lambda.Body is Expression)
{ {
ParameterDeclaration p1 = lambda.Parameters.ElementAt(0); ParameterDeclaration p1 = lambda.Parameters.ElementAt(0);
@ -208,7 +218,7 @@ namespace ICSharpCode.Decompiler.CSharp.Transforms
if (!IsComplexQuery(mre)) if (!IsComplexQuery(mre))
return null; return null;
Expression expr = invocation.Arguments.Single(); 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(); QueryExpression query = new QueryExpression();
query.Clauses.Add(MakeFromClause(parameter, mre.Target.Detach())); query.Clauses.Add(MakeFromClause(parameter, mre.Target.Detach()));
@ -227,7 +237,7 @@ namespace ICSharpCode.Decompiler.CSharp.Transforms
if (!IsComplexQuery(mre)) if (!IsComplexQuery(mre))
return null; return null;
var lambda = invocation.Arguments.Single(); 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)) if (ValidateThenByChain(invocation, parameter.Name))
{ {
@ -244,8 +254,14 @@ namespace ICSharpCode.Decompiler.CSharp.Transforms
InvocationExpression tmp = (InvocationExpression)mre.Target; InvocationExpression tmp = (InvocationExpression)mre.Target;
mre = (MemberReferenceExpression)tmp.Target; mre = (MemberReferenceExpression)tmp.Target;
lambda = tmp.Arguments.Single(); 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 // insert new ordering at beginning
orderClause.Orderings.InsertAfter( orderClause.Orderings.InsertAfter(
null, new QueryOrdering { null, new QueryOrdering {
@ -271,12 +287,12 @@ namespace ICSharpCode.Decompiler.CSharp.Transforms
if (IsNullConditional(source2)) if (IsNullConditional(source2))
return null; return null;
Expression outerLambda = invocation.Arguments.ElementAt(1); 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; return null;
Expression innerLambda = invocation.Arguments.ElementAt(2); 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; 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) if (lambda != null && lambda.Parameters.Count == 2 && lambda.Body is Expression)
{ {
ParameterDeclaration p1 = lambda.Parameters.ElementAt(0); ParameterDeclaration p1 = lambda.Parameters.ElementAt(0);
@ -365,13 +381,13 @@ namespace ICSharpCode.Decompiler.CSharp.Transforms
/// <summary> /// <summary>
/// Ensure that all ThenBy's are correct, and that the list of ThenBy's is terminated by an 'OrderBy' invocation. /// Ensure that all ThenBy's are correct, and that the list of ThenBy's is terminated by an 'OrderBy' invocation.
/// </summary> /// </summary>
bool ValidateThenByChain(InvocationExpression invocation, string expectedParameterName) bool ValidateThenByChain(InvocationExpression? invocation, string expectedParameterName)
{ {
if (invocation == null || invocation.Arguments.Count != 1) if (invocation == null || invocation.Arguments.Count != 1)
return false; return false;
if (!(invocation.Target is MemberReferenceExpression mre)) if (!(invocation.Target is MemberReferenceExpression mre))
return false; return false;
if (!MatchSimpleLambda(invocation.Arguments.Single(), out ParameterDeclaration parameter, out _)) if (!MatchSimpleLambda(invocation.Arguments.Single(), out var parameter, out _))
return false; return false;
if (parameter.Name != expectedParameterName) if (parameter.Name != expectedParameterName)
return false; return false;
@ -385,7 +401,7 @@ namespace ICSharpCode.Decompiler.CSharp.Transforms
} }
/// <summary>Matches simple lambdas of the form "a => b"</summary> /// <summary>Matches simple lambdas of the form "a => b"</summary>
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) if (expr is LambdaExpression lambda && lambda.Parameters.Count == 1 && lambda.Body is Expression)
{ {

8
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 // OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
// DEALINGS IN THE SOFTWARE. // DEALINGS IN THE SOFTWARE.
#nullable enable
using System.Linq; using System.Linq;
using ICSharpCode.Decompiler.CSharp.Resolver; using ICSharpCode.Decompiler.CSharp.Resolver;
@ -40,8 +42,8 @@ namespace ICSharpCode.Decompiler.CSharp.Transforms
protected override bool VisitChildren(AstNode node) protected override bool VisitChildren(AstNode node)
{ {
bool result = false; bool result = false;
AstNode next; AstNode? next;
for (AstNode child = node.FirstChild; child != null; child = next) for (AstNode? child = node.FirstChild; child != null; child = next)
{ {
// Store next to allow the loop to continue // Store next to allow the loop to continue
// if the visitor removes/replaces child. // if the visitor removes/replaces child.
@ -116,7 +118,7 @@ namespace ICSharpCode.Decompiler.CSharp.Transforms
public override bool VisitMemberReferenceExpression(MemberReferenceExpression memberReferenceExpression) public override bool VisitMemberReferenceExpression(MemberReferenceExpression memberReferenceExpression)
{ {
bool result = base.VisitMemberReferenceExpression(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) if (uoe != null && uoe.Operator == UnaryOperatorType.Dereference)
{ {
PointerReferenceExpression pre = new PointerReferenceExpression(); PointerReferenceExpression pre = new PointerReferenceExpression();

8
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 // OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
// DEALINGS IN THE SOFTWARE. // DEALINGS IN THE SOFTWARE.
#nullable enable
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Collections.Immutable; 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 // #define directives are leading trivia on the SyntaxTree, not children, so using
// declarations go at the very start of the member list. // declarations go at the very start of the member list.
AstNode insertionPoint = null; AstNode? insertionPoint = null;
// Now add using declarations for those namespaces: // Now add using declarations for those namespaces:
IOrderedEnumerable<string> sortedImports = requiredImports.ImportedNamespaces IOrderedEnumerable<string> sortedImports = requiredImports.ImportedNamespaces
@ -119,7 +121,7 @@ namespace ICSharpCode.Decompiler.CSharp.Transforms
base.VisitSimpleType(simpleType); // also visit type arguments base.VisitSimpleType(simpleType); // also visit type arguments
} }
private void AddImportedNamespace(IType type) private void AddImportedNamespace(IType? type)
{ {
if (type != null && !IsParentOfCurrentNamespace(type.Namespace)) if (type != null && !IsParentOfCurrentNamespace(type.Namespace))
{ {
@ -214,7 +216,7 @@ namespace ICSharpCode.Decompiler.CSharp.Transforms
this.astBuilder = CreateAstBuilder(resolver); this.astBuilder = CreateAstBuilder(resolver);
} }
TypeSystemAstBuilder CreateAstBuilder(CSharpResolver resolver, IL.ILFunction function = null) TypeSystemAstBuilder CreateAstBuilder(CSharpResolver resolver, IL.ILFunction? function = null)
{ {
if (function != null) if (function != null)
{ {

9
ICSharpCode.Decompiler/CSharp/Transforms/NormalizeBlockStatements.cs

@ -1,8 +1,12 @@
#nullable enable
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Text; using System.Text;
using System.Diagnostics.CodeAnalysis;
using ICSharpCode.Decompiler.CSharp.Syntax; using ICSharpCode.Decompiler.CSharp.Syntax;
using ICSharpCode.Decompiler.CSharp.Syntax.PatternMatching; using ICSharpCode.Decompiler.CSharp.Syntax.PatternMatching;
@ -10,9 +14,10 @@ namespace ICSharpCode.Decompiler.CSharp.Transforms
{ {
class NormalizeBlockStatements : DepthFirstAstVisitor, IAstTransform class NormalizeBlockStatements : DepthFirstAstVisitor, IAstTransform
{ {
[AllowNull]
TransformContext context; TransformContext context;
bool hasNamespace; bool hasNamespace;
NamespaceDeclaration singleNamespaceDeclaration; NamespaceDeclaration? singleNamespaceDeclaration;
public override void VisitSyntaxTree(SyntaxTree syntaxTree) public override void VisitSyntaxTree(SyntaxTree syntaxTree)
{ {
@ -196,7 +201,7 @@ namespace ICSharpCode.Decompiler.CSharp.Transforms
Attributes = { new Repeat(new AnyNode()) }, Attributes = { new Repeat(new AnyNode()) },
Modifiers = Modifiers.Any, Modifiers = Modifiers.Any,
PrivateImplementationType = new AnyNodeOrNull(), PrivateImplementationType = new AnyNodeOrNull(),
Parameters = { new Repeat(new AnyNode()) }, Parameters = { new Repeat(new AnyNode())! },
ReturnType = new AnyNode(), ReturnType = new AnyNode(),
Getter = new Accessor() { Getter = new Accessor() {
Modifiers = Modifiers.Any, Modifiers = Modifiers.Any,

93
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 // OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
// DEALINGS IN THE SOFTWARE. // DEALINGS IN THE SOFTWARE.
#nullable enable
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Diagnostics; using System.Diagnostics;
using System.Linq; using System.Linq;
using System.Reflection.Metadata; using System.Reflection.Metadata;
@ -35,6 +38,7 @@ namespace ICSharpCode.Decompiler.CSharp.Transforms
public sealed class PatternStatementTransform : ContextTrackingVisitor<AstNode>, IAstTransform public sealed class PatternStatementTransform : ContextTrackingVisitor<AstNode>, IAstTransform
{ {
readonly DeclareVariables declareVariables = new DeclareVariables(); readonly DeclareVariables declareVariables = new DeclareVariables();
[AllowNull]
TransformContext context; TransformContext context;
public void Run(AstNode rootNode, 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. // 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 // 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. // 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; AstNode oldChild;
do do
@ -77,7 +81,7 @@ namespace ICSharpCode.Decompiler.CSharp.Transforms
public override AstNode VisitExpressionStatement(ExpressionStatement expressionStatement) public override AstNode VisitExpressionStatement(ExpressionStatement expressionStatement)
{ {
AstNode result = TransformForeachOnMultiDimArray(expressionStatement); AstNode? result = TransformForeachOnMultiDimArray(expressionStatement);
if (result != null) if (result != null)
return result; return result;
result = TransformFor(expressionStatement); result = TransformFor(expressionStatement);
@ -88,7 +92,7 @@ namespace ICSharpCode.Decompiler.CSharp.Transforms
public override AstNode VisitForStatement(ForStatement forStatement) public override AstNode VisitForStatement(ForStatement forStatement)
{ {
AstNode result = TransformForeachOnArray(forStatement); AstNode? result = TransformForeachOnArray(forStatement);
if (result != null) if (result != null)
return result; return result;
return base.VisitForStatement(forStatement); return base.VisitForStatement(forStatement);
@ -96,7 +100,7 @@ namespace ICSharpCode.Decompiler.CSharp.Transforms
public override AstNode VisitIfElseStatement(IfElseStatement ifElseStatement) public override AstNode VisitIfElseStatement(IfElseStatement ifElseStatement)
{ {
AstNode simplifiedIfElse = SimplifyCascadingIfElseStatements(ifElseStatement); AstNode? simplifiedIfElse = SimplifyCascadingIfElseStatements(ifElseStatement);
if (simplifiedIfElse != null) if (simplifiedIfElse != null)
return simplifiedIfElse; return simplifiedIfElse;
return base.VisitIfElseStatement(ifElseStatement); return base.VisitIfElseStatement(ifElseStatement);
@ -107,7 +111,7 @@ namespace ICSharpCode.Decompiler.CSharp.Transforms
if (context.Settings.AutomaticProperties if (context.Settings.AutomaticProperties
&& (!propertyDeclaration.Setter.IsNull || context.Settings.GetterOnlyAutomaticProperties)) && (!propertyDeclaration.Setter.IsNull || context.Settings.GetterOnlyAutomaticProperties))
{ {
AstNode result = TransformAutomaticProperty(propertyDeclaration); AstNode? result = TransformAutomaticProperty(propertyDeclaration);
if (result != null) if (result != null)
return result; return result;
} }
@ -120,7 +124,7 @@ namespace ICSharpCode.Decompiler.CSharp.Transforms
base.VisitCustomEventDeclaration(eventDeclaration); base.VisitCustomEventDeclaration(eventDeclaration);
if (context.Settings.AutomaticEvents) if (context.Settings.AutomaticEvents)
{ {
AstNode result = TransformAutomaticEvents(eventDeclaration); AstNode? result = TransformAutomaticEvents(eventDeclaration);
if (result != null) if (result != null)
return result; 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) if (!context.Settings.ForStatement)
return null; return null;
@ -182,7 +186,9 @@ namespace ICSharpCode.Decompiler.CSharp.Transforms
if (!m1.Success) if (!m1.Success)
return null; return null;
var variable = m1.Get<IdentifierExpression>("variable").Single().GetILVariable(); var variable = m1.Get<IdentifierExpression>("variable").Single().GetILVariable();
AstNode next = node.NextSibling; AstNode? next = node.NextSibling;
if (next == null)
return null;
if (next is ForStatement forStatement && ForStatementUsesVariable(forStatement, variable)) if (next is ForStatement forStatement && ForStatementUsesVariable(forStatement, variable))
{ {
node.Remove(); node.Remove();
@ -339,7 +345,7 @@ namespace ICSharpCode.Decompiler.CSharp.Transforms
return false; return false;
} }
Statement TransformForeachOnArray(ForStatement forStatement) Statement? TransformForeachOnArray(ForStatement forStatement)
{ {
if (!context.Settings.ForEachStatement) if (!context.Settings.ForEachStatement)
return null; 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; index = null;
var m = variableAssignLowerBoundPattern.Match(statement); var m = variableAssignLowerBoundPattern.Match(statement);
@ -444,7 +450,7 @@ namespace ICSharpCode.Decompiler.CSharp.Transforms
return m.Get<IdentifierExpression>("collection").Single().GetILVariable() == collection; return m.Get<IdentifierExpression>("collection").Single().GetILVariable() == collection;
} }
bool MatchForeachOnMultiDimArray(IL.ILVariable[] upperBounds, IL.ILVariable collection, Statement firstInitializerStatement, out IdentifierExpression foreachVariable, out IList<Statement> statements, out IL.ILVariable[] lowerBounds) bool MatchForeachOnMultiDimArray(IL.ILVariable[] upperBounds, IL.ILVariable collection, Statement firstInitializerStatement, [NotNullWhen(true)] out IdentifierExpression? foreachVariable, [NotNullWhen(true)] out IList<Statement>? statements, out IL.ILVariable[] lowerBounds)
{ {
int i = 0; int i = 0;
foreachVariable = null; foreachVariable = null;
@ -452,7 +458,7 @@ namespace ICSharpCode.Decompiler.CSharp.Transforms
lowerBounds = new IL.ILVariable[upperBounds.Length]; lowerBounds = new IL.ILVariable[upperBounds.Length];
Statement stmt = firstInitializerStatement; Statement stmt = firstInitializerStatement;
Match m = default(Match); 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()); m = forOnArrayMultiDimPattern.Match(stmt.GetNextStatement());
if (!m.Success) if (!m.Success)
@ -477,14 +483,14 @@ namespace ICSharpCode.Decompiler.CSharp.Transforms
return true; return true;
} }
Statement TransformForeachOnMultiDimArray(ExpressionStatement expressionStatement) Statement? TransformForeachOnMultiDimArray(ExpressionStatement expressionStatement)
{ {
if (!context.Settings.ForEachStatement) if (!context.Settings.ForEachStatement)
return null; return null;
Match m; Match m;
Statement stmt = expressionStatement; Statement? stmt = expressionStatement;
IL.ILVariable collection = null; IL.ILVariable? collection = null;
IL.ILVariable[] upperBounds = null; IL.ILVariable[]? upperBounds = null;
List<Statement> statementsToDelete = new List<Statement>(); List<Statement> statementsToDelete = new List<Statement>();
int i = 0; int i = 0;
// first we look for all the upper bound initializations // first we look for all the upper bound initializations
@ -512,9 +518,9 @@ namespace ICSharpCode.Decompiler.CSharp.Transforms
upperBounds[i] = m.Get<IdentifierExpression>("variable").Single().GetILVariable(); upperBounds[i] = m.Get<IdentifierExpression>("variable").Single().GetILVariable();
stmt = stmt.GetNextStatement(); stmt = stmt.GetNextStatement();
i++; 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; return null;
if (!MatchForeachOnMultiDimArray(upperBounds, collection, stmt, out var foreachVariable, out var statements, out var lowerBounds)) if (!MatchForeachOnMultiDimArray(upperBounds, collection, stmt, out var foreachVariable, out var statements, out var lowerBounds))
return null; return null;
@ -609,12 +615,14 @@ namespace ICSharpCode.Decompiler.CSharp.Transforms
return true; return true;
} }
PropertyDeclaration TransformAutomaticProperty(PropertyDeclaration propertyDeclaration) PropertyDeclaration? TransformAutomaticProperty(PropertyDeclaration propertyDeclaration)
{ {
IProperty property = propertyDeclaration.GetSymbol() as IProperty; IProperty? property = propertyDeclaration.GetSymbol() as IProperty;
if (!CanTransformToAutomaticProperty(property, !property.DeclaringTypeDefinition.Fields.Any(f => f.Name == "_" + property.Name && f.IsCompilerGenerated()))) if (property == null)
return null;
if (!CanTransformToAutomaticProperty(property, !(property.DeclaringTypeDefinition?.Fields.Any(f => f.Name == "_" + property.Name && f.IsCompilerGenerated()) ?? false)))
return null; return null;
IField field = null; IField? field = null;
Match m = automaticPropertyPattern.Match(propertyDeclaration); Match m = automaticPropertyPattern.Match(propertyDeclaration);
if (m.Success) if (m.Success)
{ {
@ -636,8 +644,10 @@ namespace ICSharpCode.Decompiler.CSharp.Transforms
{ {
RemoveCompilerGeneratedAttribute(propertyDeclaration.Getter.Attributes); RemoveCompilerGeneratedAttribute(propertyDeclaration.Getter.Attributes);
RemoveCompilerGeneratedAttribute(propertyDeclaration.Setter.Attributes); RemoveCompilerGeneratedAttribute(propertyDeclaration.Setter.Attributes);
propertyDeclaration.Getter.Body = null; // Clearing the accessor body turns it into an auto-property accessor; the slot setter
propertyDeclaration.Setter.Body = null; // tolerates null until optional slots become nullable in the AST declarations.
propertyDeclaration.Getter.Body = null!;
propertyDeclaration.Setter.Body = null!;
propertyDeclaration.Modifiers &= ~Modifiers.Readonly; propertyDeclaration.Modifiers &= ~Modifiers.Readonly;
propertyDeclaration.Getter.Modifiers &= ~Modifiers.Readonly; propertyDeclaration.Getter.Modifiers &= ~Modifiers.Readonly;
@ -703,14 +713,14 @@ namespace ICSharpCode.Decompiler.CSharp.Transforms
return base.VisitIdentifier(identifier); 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; property = null;
if (!NameCouldBeBackingFieldOfAutomaticProperty(field.Name, out string propertyName)) if (!NameCouldBeBackingFieldOfAutomaticProperty(field.Name, out var propertyName))
return false; return false;
if (!field.IsCompilerGenerated()) if (!field.IsCompilerGenerated())
return false; return false;
property = field.DeclaringTypeDefinition property = field.DeclaringTypeDefinition?
.GetProperties(p => p.Name == propertyName, GetMemberOptions.IgnoreInheritedMembers) .GetProperties(p => p.Name == propertyName, GetMemberOptions.IgnoreInheritedMembers)
.FirstOrDefault(); .FirstOrDefault();
return property != null; return property != null;
@ -726,7 +736,7 @@ namespace ICSharpCode.Decompiler.CSharp.Transforms
static readonly System.Text.RegularExpressions.Regex automaticPropertyBackingFieldNameRegex static readonly System.Text.RegularExpressions.Regex automaticPropertyBackingFieldNameRegex
= new System.Text.RegularExpressions.Regex(@"^(<(?<name>.+)>k__BackingField|_(?<name>.+))$"); = new System.Text.RegularExpressions.Regex(@"^(<(?<name>.+)>k__BackingField|_(?<name>.+))$");
static bool NameCouldBeBackingFieldOfAutomaticProperty(string name, out string propertyName) static bool NameCouldBeBackingFieldOfAutomaticProperty(string name, [NotNullWhen(true)] out string? propertyName)
{ {
propertyName = null; propertyName = null;
var m = automaticPropertyBackingFieldNameRegex.Match(name); var m = automaticPropertyBackingFieldNameRegex.Match(name);
@ -736,16 +746,17 @@ namespace ICSharpCode.Decompiler.CSharp.Transforms
return true; return true;
} }
Identifier ReplaceBackingFieldUsage(Identifier identifier) Identifier? ReplaceBackingFieldUsage(Identifier identifier)
{ {
if (NameCouldBeBackingFieldOfAutomaticProperty(identifier.Name, out _)) if (NameCouldBeBackingFieldOfAutomaticProperty(identifier.Name, out _))
{ {
var parent = identifier.Parent; var parent = identifier.Parent;
if (parent == null)
return null;
var mrr = parent.Annotation<MemberResolveResult>(); var mrr = parent.Annotation<MemberResolveResult>();
var field = mrr?.Member as IField; if (mrr?.Member is IField field && IsBackingFieldOfAutomaticProperty(field, out var property)
if (field != null && IsBackingFieldOfAutomaticProperty(field, out var property)
&& CanTransformToAutomaticProperty(property, !(field.IsCompilerGenerated() && field.Name == "_" + property.Name)) && CanTransformToAutomaticProperty(property, !(field.IsCompilerGenerated() && field.Name == "_" + property.Name))
&& currentMethod.AccessorOwner != property) && currentMethod?.AccessorOwner != property)
{ {
if (!property.CanSet && !context.Settings.GetterOnlyAutomaticProperties) if (!property.CanSet && !context.Settings.GetterOnlyAutomaticProperties)
return null; return null;
@ -757,9 +768,11 @@ namespace ICSharpCode.Decompiler.CSharp.Transforms
return null; return null;
} }
Identifier ReplaceEventFieldAnnotation(Identifier identifier) Identifier? ReplaceEventFieldAnnotation(Identifier identifier)
{ {
var parent = identifier.Parent; var parent = identifier.Parent;
if (parent == null)
return null;
var mrr = parent.Annotation<MemberResolveResult>(); var mrr = parent.Annotation<MemberResolveResult>();
if (mrr?.Member is not IField field || field.Accessibility != Accessibility.Private) if (mrr?.Member is not IField field || field.Accessibility != Accessibility.Private)
return null; return null;
@ -769,7 +782,7 @@ namespace ICSharpCode.Decompiler.CSharp.Transforms
if (module.MetadataFile.PropertyAndEventBackingFieldLookup.IsEventBackingField((FieldDefinitionHandle)field.MetadataToken, out var eventHandle)) if (module.MetadataFile.PropertyAndEventBackingFieldLookup.IsEventBackingField((FieldDefinitionHandle)field.MetadataToken, out var eventHandle))
{ {
var eventDef = module.ResolveEntity(eventHandle) as IEvent; var eventDef = module.ResolveEntity(eventHandle) as IEvent;
if (eventDef != null && currentMethod.AccessorOwner != eventDef) if (eventDef != null && currentMethod?.AccessorOwner != eventDef)
{ {
parent.RemoveAnnotations<MemberResolveResult>(); parent.RemoveAnnotations<MemberResolveResult>();
parent.AddAnnotation(new MemberResolveResult(mrr.TargetResult, eventDef)); parent.AddAnnotation(new MemberResolveResult(mrr.TargetResult, eventDef));
@ -914,7 +927,7 @@ namespace ICSharpCode.Decompiler.CSharp.Transforms
if (!m.Success) if (!m.Success)
return false; return false;
Expression fieldExpression = m.Get<Expression>("field").Single(); Expression fieldExpression = m.Get<Expression>("field").Single();
IField eventField = fieldExpression.GetSymbol() as IField; IField? eventField = fieldExpression.GetSymbol() as IField;
if (eventField == null) if (eventField == null)
return false; return false;
var module = eventField.ParentModule as MetadataModule; var module = eventField.ParentModule as MetadataModule;
@ -990,7 +1003,7 @@ namespace ICSharpCode.Decompiler.CSharp.Transforms
return true; return true;
} }
EventDeclaration TransformAutomaticEvents(CustomEventDeclaration ev) EventDeclaration? TransformAutomaticEvents(CustomEventDeclaration ev)
{ {
if (!ev.PrivateImplementationType.IsNull) if (!ev.PrivateImplementationType.IsNull)
return null; return null;
@ -1065,7 +1078,7 @@ namespace ICSharpCode.Decompiler.CSharp.Transforms
Body = destructorBodyPattern Body = destructorBodyPattern
}; };
DestructorDeclaration TransformDestructor(MethodDeclaration methodDef) DestructorDeclaration? TransformDestructor(MethodDeclaration methodDef)
{ {
Match m = destructorPattern.Match(methodDef); Match m = destructorPattern.Match(methodDef);
if (m.Success) if (m.Success)
@ -1082,7 +1095,7 @@ namespace ICSharpCode.Decompiler.CSharp.Transforms
return null; return null;
} }
DestructorDeclaration TransformDestructorBody(DestructorDeclaration dtorDef) DestructorDeclaration? TransformDestructorBody(DestructorDeclaration dtorDef)
{ {
Match m = destructorBodyPattern.Match(dtorDef.Body); Match m = destructorBodyPattern.Match(dtorDef.Body);
if (m.Success) if (m.Success)
@ -1109,7 +1122,7 @@ namespace ICSharpCode.Decompiler.CSharp.Transforms
/// Simplify nested 'try { try {} catch {} } finally {}'. /// Simplify nested 'try { try {} catch {} } finally {}'.
/// This transformation must run after the using/lock tranformations. /// This transformation must run after the using/lock tranformations.
/// </summary> /// </summary>
TryCatchStatement TransformTryCatchFinally(TryCatchStatement tryFinally) TryCatchStatement? TransformTryCatchFinally(TryCatchStatement tryFinally)
{ {
if (tryCatchFinallyPattern.IsMatch(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); Match m = cascadingIfElsePattern.Match(node);
if (m.Success) if (m.Success)

14
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 // OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
// DEALINGS IN THE SOFTWARE. // DEALINGS IN THE SOFTWARE.
#nullable enable
using System; using System;
using System.Diagnostics.CodeAnalysis;
using System.Linq; using System.Linq;
using ICSharpCode.Decompiler.CSharp.Resolver; using ICSharpCode.Decompiler.CSharp.Resolver;
@ -39,6 +42,7 @@ namespace ICSharpCode.Decompiler.CSharp.Transforms
/// </remarks> /// </remarks>
class PrettifyAssignments : DepthFirstAstVisitor, IAstTransform class PrettifyAssignments : DepthFirstAstVisitor, IAstTransform
{ {
[AllowNull]
TransformContext context; TransformContext context;
public override void VisitAssignmentExpression(AssignmentExpression assignment) 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 // Also supports "x = (T)(x op y)" -> "x op= y", if x.GetType() == T
// and y is implicitly convertible to T. // and y is implicitly convertible to T.
Expression rhs = assignment.Right; Expression rhs = assignment.Right;
IType expectedType = null; IType? expectedType = null;
if (assignment.Right is CastExpression { Type: var astType } cast) if (assignment.Right is CastExpression { Type: var astType } cast)
{ {
rhs = cast.Expression; 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) if (expectedType == null)
return true; return true;
@ -132,13 +136,13 @@ namespace ICSharpCode.Decompiler.CSharp.Transforms
static bool CanConvertToCompoundAssignment(Expression left) static bool CanConvertToCompoundAssignment(Expression left)
{ {
MemberReferenceExpression mre = left as MemberReferenceExpression; MemberReferenceExpression? mre = left as MemberReferenceExpression;
if (mre != null) if (mre != null)
return IsWithoutSideEffects(mre.Target); return IsWithoutSideEffects(mre.Target);
IndexerExpression ie = left as IndexerExpression; IndexerExpression? ie = left as IndexerExpression;
if (ie != null) if (ie != null)
return IsWithoutSideEffects(ie.Target) && ie.Arguments.All(IsWithoutSideEffects); 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) if (uoe != null && uoe.Operator == UnaryOperatorType.Dereference)
return IsWithoutSideEffects(uoe.Expression); return IsWithoutSideEffects(uoe.Expression);
return IsWithoutSideEffects(left); return IsWithoutSideEffects(left);

2
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 // OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
// DEALINGS IN THE SOFTWARE. // DEALINGS IN THE SOFTWARE.
#nullable enable
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;

6
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 // OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
// DEALINGS IN THE SOFTWARE. // DEALINGS IN THE SOFTWARE.
#nullable enable
using System; using System;
using System.Diagnostics.CodeAnalysis;
using System.Linq; using System.Linq;
using System.Reflection; using System.Reflection;
@ -41,6 +44,7 @@ namespace ICSharpCode.Decompiler.CSharp.Transforms
MemberName = "TypeHandle" MemberName = "TypeHandle"
}; };
[AllowNull]
TransformContext context; TransformContext context;
public override void VisitInvocationExpression(InvocationExpression invocationExpression) public override void VisitInvocationExpression(InvocationExpression invocationExpression)
@ -508,7 +512,7 @@ namespace ICSharpCode.Decompiler.CSharp.Transforms
Match m = getMethodOrConstructorFromHandlePattern.Match(castExpression); Match m = getMethodOrConstructorFromHandlePattern.Match(castExpression);
if (m.Success) if (m.Success)
{ {
IMethod method = m.Get<AstNode>("method").Single().GetSymbol() as IMethod; IMethod? method = m.Get<AstNode>("method").Single().GetSymbol() as IMethod;
if (m.Has("declaringType") && method != null) if (m.Has("declaringType") && method != null)
{ {
Expression newNode = new MemberReferenceExpression(new TypeReferenceExpression(m.Get<AstType>("declaringType").Single().Detach()), method.Name); Expression newNode = new MemberReferenceExpression(new TypeReferenceExpression(m.Get<AstType>("declaringType").Single().Detach()), method.Name);

8
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 // OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
// DEALINGS IN THE SOFTWARE. // DEALINGS IN THE SOFTWARE.
#nullable enable
using System.Collections.Immutable; using System.Collections.Immutable;
using System.Threading; using System.Threading;
@ -40,17 +42,17 @@ namespace ICSharpCode.Decompiler.CSharp.Transforms
/// <summary> /// <summary>
/// Returns the current member; or null if a whole type or module is being decompiled. /// Returns the current member; or null if a whole type or module is being decompiled.
/// </summary> /// </summary>
public IMember CurrentMember => decompilationContext.CurrentMember; public IMember? CurrentMember => decompilationContext.CurrentMember;
/// <summary> /// <summary>
/// Returns the current type definition; or null if a module is being decompiled. /// Returns the current type definition; or null if a module is being decompiled.
/// </summary> /// </summary>
public ITypeDefinition CurrentTypeDefinition => decompilationContext.CurrentTypeDefinition; public ITypeDefinition? CurrentTypeDefinition => decompilationContext.CurrentTypeDefinition;
/// <summary> /// <summary>
/// Returns the module that is being decompiled. /// Returns the module that is being decompiled.
/// </summary> /// </summary>
public IModule CurrentModule => decompilationContext.CurrentModule; public IModule? CurrentModule => decompilationContext.CurrentModule;
/// <summary> /// <summary>
/// Returns the max possible set of namespaces that will be used during decompilation. /// Returns the max possible set of namespaces that will be used during decompilation.

Loading…
Cancel
Save