diff --git a/ICSharpCode.Decompiler/CSharp/CSharpDecompiler.cs b/ICSharpCode.Decompiler/CSharp/CSharpDecompiler.cs index 70358a8bc..2918353fe 100644 --- a/ICSharpCode.Decompiler/CSharp/CSharpDecompiler.cs +++ b/ICSharpCode.Decompiler/CSharp/CSharpDecompiler.cs @@ -144,6 +144,7 @@ namespace ICSharpCode.Decompiler.CSharp new TransformCollectionAndObjectInitializers(), new TransformExpressionTrees(), new IndexRangeTransform(), + new DeconstructionTransform(), new NamedArgumentTransform(), new UserDefinedLogicTransform() ), diff --git a/ICSharpCode.Decompiler/CSharp/ExpressionBuilder.cs b/ICSharpCode.Decompiler/CSharp/ExpressionBuilder.cs index c0c583b00..97a6c984d 100644 --- a/ICSharpCode.Decompiler/CSharp/ExpressionBuilder.cs +++ b/ICSharpCode.Decompiler/CSharp/ExpressionBuilder.cs @@ -3321,6 +3321,49 @@ namespace ICSharpCode.Decompiler.CSharp return invocation.WithRR(new ResolveResult(inst.ReturnType)).WithILInstruction(inst); } + protected internal override TranslatedExpression VisitDeconstructInstruction(DeconstructInstruction inst, TranslationContext context) + { + IType rhsType = inst.Pattern.Variable.Type; + var rhs = Translate(inst.Pattern.TestedOperand, rhsType); + rhs = rhs.ConvertTo(rhsType, this); // TODO allowImplicitConversion + var assignments = inst.Assignments.Instructions; + int assignmentPos = 0; + var lhs = ConstructTuple(inst.Pattern); + return new AssignmentExpression(lhs, rhs) + .WithILInstruction(inst) + .WithRR(new ResolveResult(compilation.FindType(KnownTypeCode.Void))); + + TupleExpression ConstructTuple(MatchInstruction matchInstruction) + { + var expr = new TupleExpression(); + foreach (var subPattern in matchInstruction.SubPatterns.Cast()) { + if (subPattern.IsVar) { + if (subPattern.HasDesignator) { + expr.Elements.Add(ConstructAssignmentTarget(assignments[assignmentPos], subPattern.Variable)); + assignmentPos++; + } else + expr.Elements.Add(new IdentifierExpression("_")); + } else { + expr.Elements.Add(ConstructTuple(subPattern)); + } + } + return expr; + } + + TranslatedExpression ConstructAssignmentTarget(ILInstruction assignment, ILVariable value) + { + switch (assignment) { + case StLoc stloc: + Debug.Assert(stloc.Value.MatchLdLoc(value)); + break; + default: + throw new NotSupportedException(); + } + var expr = Translate(assignment); + return expr.UnwrapChild(((AssignmentExpression)expr).Left); + } + } + protected internal override TranslatedExpression VisitInvalidBranch(InvalidBranch inst, TranslationContext context) { string message = "Error"; diff --git a/ICSharpCode.Decompiler/ICSharpCode.Decompiler.csproj b/ICSharpCode.Decompiler/ICSharpCode.Decompiler.csproj index b17de8717..168ddc2a3 100644 --- a/ICSharpCode.Decompiler/ICSharpCode.Decompiler.csproj +++ b/ICSharpCode.Decompiler/ICSharpCode.Decompiler.csproj @@ -73,6 +73,7 @@ + diff --git a/ICSharpCode.Decompiler/IL/Transforms/DeconstructionTransform.cs b/ICSharpCode.Decompiler/IL/Transforms/DeconstructionTransform.cs new file mode 100644 index 000000000..b3322f31e --- /dev/null +++ b/ICSharpCode.Decompiler/IL/Transforms/DeconstructionTransform.cs @@ -0,0 +1,165 @@ +// Copyright (c) 2020 Siegfried Pammer +// +// Permission is hereby granted, free of charge, to any person obtaining a copy of this +// software and associated documentation files (the "Software"), to deal in the Software +// without restriction, including without limitation the rights to use, copy, modify, merge, +// publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons +// to whom the Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all copies or +// substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, +// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR +// PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE +// FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. + +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; +using System.Linq.Expressions; +using System.Text; + +using ICSharpCode.Decompiler.CSharp.Syntax.PatternMatching; +using ICSharpCode.Decompiler.TypeSystem; + +namespace ICSharpCode.Decompiler.IL.Transforms +{ + /// + /// + /// + class DeconstructionTransform : IStatementTransform + { + StatementTransformContext context; + readonly Dictionary deconstructionResultsLookup = new Dictionary(); + + /* + stloc tuple(call MakeIntIntTuple(ldloc this)) + ---- + stloc myInt(call op_Implicit(ldfld Item2(ldloca tuple))) + stloc a(ldfld Item1(ldloca tuple)) + stloc b(ldloc myInt) + ==> + deconstruct { + init: + + deconstruct: + match.deconstruct(temp = ldloca tuple) { + match(result0 = deconstruct.result 0(temp)), + match(result1 = deconstruct.result 1(temp)) + } + conversions: { + stloc conv2(call op_Implicit(ldloc result1)) + } + assignments: { + stloc a(ldloc result0) + stloc b(ldloc conv2) + } + } + * */ + void IStatementTransform.Run(Block block, int pos, StatementTransformContext context) + { + //if (!context.Settings.Deconstruction) + // return; + + try { + this.context = context; + this.deconstructionResultsLookup.Clear(); + int startPos = pos; + if (!MatchDeconstruction(block, ref pos, out var deconstructMethod, out var rootTestedOperand, out var deconstructionResults)) + return; + if (!MatchConversion(block, ref pos)) + return; + if (!MatchAssignments(block, ref pos, out var assignments)) + return; + context.Step("Deconstruction", block.Instructions[startPos]); + DeconstructInstruction replacement = new DeconstructInstruction(); + IType deconstructedType; + if (deconstructMethod.IsStatic) { + deconstructedType = deconstructMethod.Parameters[0].Type; + } else { + deconstructedType = deconstructMethod.DeclaringType; + } + var rootTempVariable = context.Function.RegisterVariable(VariableKind.PatternLocal, deconstructedType); + replacement.Pattern = new MatchInstruction(rootTempVariable, deconstructMethod, rootTestedOperand) { + IsDeconstructCall = true + }; + int index = 0; + foreach (var result in deconstructionResults) { + result.Kind = VariableKind.PatternLocal; + replacement.Pattern.SubPatterns.Add(new MatchInstruction(result, new DeconstructResultInstruction(index, result.StackType, new LdLoc(rootTempVariable)))); + index++; + } + replacement.Conversions = new Block(BlockKind.DeconstructionConversions); + replacement.Assignments = new Block(BlockKind.DeconstructionAssignments); + replacement.Assignments.Instructions.AddRange(assignments); + block.Instructions[startPos] = replacement; + block.Instructions.RemoveRange(startPos + 1, pos - startPos - 1); + } finally { + this.context = null; + this.deconstructionResultsLookup.Clear(); + } + } + + bool MatchDeconstruction(Block block, ref int pos, out IMethod deconstructMethod, out ILInstruction testedOperand, out List deconstructionResults) + { + testedOperand = null; + deconstructMethod = null; + deconstructionResults = null; + // TODO nested deconstruction / tuple deconstruction + if (!(block.Instructions[pos] is CallInstruction call)) + return false; + if (!MatchInstruction.IsDeconstructMethod(call.Method)) + return false; + if (call.Method.IsStatic == call is CallVirt) + return false; + if (call.Arguments.Count < 3) + return false; + deconstructionResults = new List(); + for (int i = 1; i < call.Arguments.Count; i++) { + if (!call.Arguments[i].MatchLdLoca(out var v)) + return false; + // TODO v.LoadCount may be 2 if the deconstruction is assigned to a tuple variable + // or 0? because of discards + if (!(v.StoreCount == 0 && v.AddressCount == 1 && v.LoadCount == 1)) + return false; + deconstructionResultsLookup.Add(v, i - 1); + deconstructionResults.Add(v); + } + testedOperand = call.Arguments[0]; + deconstructMethod = call.Method; + pos++; + return true; + } + + bool MatchConversion(Block block, ref int pos) + { + // TODO + return true; + } + + bool MatchAssignments(Block block, ref int pos, out List assignments) + { + assignments = new List(); + while (MatchAssignment(block.Instructions.ElementAtOrDefault(pos))) { + assignments.Add(block.Instructions[pos]); + pos++; + } + return assignments.Count > 0; + } + + bool MatchAssignment(ILInstruction inst) + { + if (!DeconstructInstruction.IsAssignment(inst, out var resultVariable)) + return false; + if (!deconstructionResultsLookup.ContainsKey(resultVariable)) + return false; + // TODO check order of use + + return true; + } + } +}