diff --git a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/DeconstructionTests.cs b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/DeconstructionTests.cs index 2f7b344c1..5c2e604c2 100644 --- a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/DeconstructionTests.cs +++ b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/DeconstructionTests.cs @@ -149,63 +149,49 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty public void LocalVariable_NoConversion() { - MyInt? myInt3; - MyInt x; - (myInt3, x) = GetSource(); + var (myInt3, x) = GetSource(); Console.WriteLine(myInt3); Console.WriteLine(x); } public void LocalVariable_NoConversion_DiscardFirst() { - MyInt x; - int value; - (_, x, value) = GetSource(); + var (_, x, value) = GetSource(); Console.WriteLine(x); Console.WriteLine(value); } public void LocalVariable_NoConversion_DiscardLast() { - MyInt? myInt3; - MyInt x; - (myInt3, x, _) = GetSource(); + var (myInt3, x, _) = GetSource(); Console.WriteLine(myInt3); Console.WriteLine(x); } public void LocalVariable_NoConversion_DiscardSecond() { - MyInt? myInt3; - int value; - (myInt3, _, value) = GetSource(); + var (myInt3, _, value) = GetSource(); Console.WriteLine(myInt3); Console.WriteLine(value); } public void LocalVariable_NoConversion_ReferenceTypes() { - string value; - string value2; - (value, value2) = GetSource(); + var (value, value2) = GetSource(); Console.WriteLine(value); Console.WriteLine(value2); } public void LocalVariable_IntToLongConversion() { - int value; - long value2; - (value, value2) = GetSource(); + var (value, value2) = GetSource(); Console.WriteLine(value); Console.WriteLine(value2); } public void LocalVariable_NoConversion_ComplexValue() { - MyInt? myInt3; - MyInt x; - (myInt3, x) = new DeconstructionSource { + var (myInt3, x) = new DeconstructionSource { Dummy = 3 }; Console.WriteLine(myInt3); diff --git a/ICSharpCode.Decompiler/CSharp/StatementBuilder.cs b/ICSharpCode.Decompiler/CSharp/StatementBuilder.cs index 34109e106..9dbefdacd 100644 --- a/ICSharpCode.Decompiler/CSharp/StatementBuilder.cs +++ b/ICSharpCode.Decompiler/CSharp/StatementBuilder.cs @@ -567,7 +567,7 @@ namespace ICSharpCode.Decompiler.CSharp break; case RequiredGetCurrentTransformation.Deconstruction: useVar = true; - designation = TranslateForeachDeconstructionDesignation((DeconstructInstruction)body.Instructions[0]); + designation = TranslateDeconstructionDesignation((DeconstructInstruction)body.Instructions[0], isForeach: true); break; } @@ -614,7 +614,7 @@ namespace ICSharpCode.Decompiler.CSharp return foreachStmt; } - VariableDesignation TranslateForeachDeconstructionDesignation(DeconstructInstruction inst) + internal static VariableDesignation TranslateDeconstructionDesignation(DeconstructInstruction inst, bool isForeach) { var assignments = inst.Assignments.Instructions; int assignmentPos = 0; @@ -629,7 +629,8 @@ namespace ICSharpCode.Decompiler.CSharp var designation = new SingleVariableDesignation(); if (subPattern.HasDesignator) { ILVariable v = ((StLoc)assignments[assignmentPos]).Variable; - v.Kind = VariableKind.ForeachLocal; + if (isForeach) + v.Kind = VariableKind.ForeachLocal; designation.Identifier = v.Name; designation.AddAnnotation(new ILVariableResolveResult(v)); assignmentPos++; diff --git a/ICSharpCode.Decompiler/CSharp/Transforms/DeclareVariables.cs b/ICSharpCode.Decompiler/CSharp/Transforms/DeclareVariables.cs index 272042d83..5caf14f88 100644 --- a/ICSharpCode.Decompiler/CSharp/Transforms/DeclareVariables.cs +++ b/ICSharpCode.Decompiler/CSharp/Transforms/DeclareVariables.cs @@ -102,6 +102,7 @@ namespace ICSharpCode.Decompiler.CSharp.Transforms public VariableToDeclare ReplacementDueToCollision; public bool InvolvedInCollision; public bool RemovedDueToCollision => ReplacementDueToCollision != null; + public bool DeclaredInDeconstruction; public VariableToDeclare(ILVariable variable, bool defaultInitialization, InsertionPoint insertionPoint, IdentifierExpression firstUse, int sourceOrder) { @@ -126,6 +127,7 @@ namespace ICSharpCode.Decompiler.CSharp.Transforms EnsureExpressionStatementsAreValid(rootNode); FindInsertionPoints(rootNode, 0); ResolveCollisions(); + InsertDeconstructionVariableDeclarations(); InsertVariableDeclarations(context); UpdateAnnotations(rootNode); } finally { @@ -133,7 +135,6 @@ namespace ICSharpCode.Decompiler.CSharp.Transforms variableDict.Clear(); } } - /// /// Analyze the input AST (containing undeclared variables) /// for where those variables would be declared by this transform. @@ -425,6 +426,44 @@ namespace ICSharpCode.Decompiler.CSharp.Transforms } } + private void InsertDeconstructionVariableDeclarations() + { + var usedVariables = new HashSet(); + foreach (var g in variableDict.Values.GroupBy(v => v.InsertionPoint.nextNode)) { + if (!(g.Key is ExpressionStatement { Expression: AssignmentExpression { Left: TupleExpression left, Operator: AssignmentOperatorType.Assign } assignment })) + continue; + usedVariables.Clear(); + var deconstruct = assignment.Annotation(); + if (deconstruct == null || deconstruct.Init.Count > 0 || deconstruct.Conversions.Instructions.Count > 0) + continue; + if (!deconstruct.Assignments.Instructions.All(IsDeclarableVariable)) + continue; + + var designation = StatementBuilder.TranslateDeconstructionDesignation(deconstruct, isForeach: false); + left.ReplaceWith(new DeclarationExpression { Type = new SimpleType("var"), Designation = designation }); + + foreach (var v in usedVariables) { + variableDict[v].DeclaredInDeconstruction = true; + } + + bool IsDeclarableVariable(ILInstruction inst) + { + if (!inst.MatchStLoc(out var v, out var value)) + return false; + if (!g.Any(vd => vd.ILVariable == v)) + return false; + if (!usedVariables.Add(v)) + return false; + var expectedType = ((LdLoc)value).Variable.Type; + if (!v.Type.Equals(expectedType)) + return false; + if (!(v.Kind == VariableKind.StackSlot || v.Kind == VariableKind.Local)) + return false; + return true; + } + } + } + bool IsMatchingAssignment(VariableToDeclare v, out AssignmentExpression assignment) { assignment = v.InsertionPoint.nextNode as AssignmentExpression; @@ -454,7 +493,7 @@ namespace ICSharpCode.Decompiler.CSharp.Transforms { var replacements = new List<(AstNode, AstNode)>(); foreach (var (ilVariable, v) in variableDict) { - if (v.RemovedDueToCollision) + if (v.RemovedDueToCollision || v.DeclaredInDeconstruction) continue; if (CombineDeclarationAndInitializer(v, context) && IsMatchingAssignment(v, out AssignmentExpression assignment)) {