// 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 ICSharpCode.Decompiler.TypeSystem; namespace ICSharpCode.Decompiler.IL { partial class DeconstructInstruction { public static readonly SlotInfo InitSlot = new SlotInfo("Init", canInlineInto: true, isCollection: true); public static readonly SlotInfo PatternSlot = new SlotInfo("Pattern", canInlineInto: true); public static readonly SlotInfo ConversionsSlot = new SlotInfo("Conversions"); public static readonly SlotInfo AssignmentsSlot = new SlotInfo("Assignments"); public DeconstructInstruction() : base(OpCode.DeconstructInstruction) { this.Init = new InstructionCollection(this, 0); } public readonly InstructionCollection Init; MatchInstruction pattern; public MatchInstruction Pattern { get { return this.pattern; } set { ValidateChild(value); SetChildInstruction(ref this.pattern, value, Init.Count); } } Block conversions; public Block Conversions { get { return this.conversions; } set { ValidateChild(value); SetChildInstruction(ref this.conversions, value, Init.Count + 1); } } Block assignments; public Block Assignments { get { return this.assignments; } set { ValidateChild(value); SetChildInstruction(ref this.assignments, value, Init.Count + 2); } } protected sealed override int GetChildCount() { return Init.Count + 3; } protected sealed override ILInstruction GetChild(int index) { switch (index - Init.Count) { case 0: return this.pattern; case 1: return this.conversions; case 2: return this.assignments; default: return this.Init[index]; } } protected sealed override void SetChild(int index, ILInstruction value) { switch (index - Init.Count) { case 0: this.Pattern = (MatchInstruction)value; break; case 1: this.Conversions = (Block)value; break; case 2: this.Assignments = (Block)value; break; default: this.Init[index] = (StLoc)value; break; } } protected sealed override SlotInfo GetChildSlot(int index) { switch (index - Init.Count) { case 0: return PatternSlot; case 1: return ConversionsSlot; case 2: return AssignmentsSlot; default: return InitSlot; } } public sealed override ILInstruction Clone() { var clone = new DeconstructInstruction(); clone.Init.AddRange(this.Init.Select(inst => (StLoc)inst.Clone())); clone.Pattern = (MatchInstruction)this.pattern.Clone(); clone.Conversions = (Block)this.conversions.Clone(); clone.Assignments = (Block)this.assignments.Clone(); return clone; } protected override InstructionFlags ComputeFlags() { var flags = InstructionFlags.None; foreach (var inst in Init) { flags |= inst.Flags; } flags |= pattern.Flags | conversions.Flags | assignments.Flags; return flags; } public override InstructionFlags DirectFlags { get { return InstructionFlags.None; } } protected internal override void InstructionCollectionUpdateComplete() { base.InstructionCollectionUpdateComplete(); if (pattern.Parent == this) pattern.ChildIndex = Init.Count; if (conversions.Parent == this) conversions.ChildIndex = Init.Count + 1; if (assignments.Parent == this) assignments.ChildIndex = Init.Count + 2; } public override void WriteTo(ITextOutput output, ILAstWritingOptions options) { WriteILRange(output, options); output.Write("deconstruct "); output.MarkFoldStart("{...}"); output.WriteLine("{"); output.Indent(); output.WriteLine("init:"); output.Indent(); foreach (var inst in this.Init) { inst.WriteTo(output, options); output.WriteLine(); } output.Unindent(); output.WriteLine("pattern:"); output.Indent(); pattern.WriteTo(output, options); output.Unindent(); output.WriteLine(); output.Write("conversions: "); conversions.WriteTo(output, options); output.WriteLine(); output.Write("assignments: "); assignments.WriteTo(output, options); output.Unindent(); output.WriteLine(); output.Write('}'); output.MarkFoldEnd(); } internal static bool IsConversionStLoc(ILInstruction inst, out ILVariable variable, out ILVariable inputVariable) { inputVariable = null; if (!inst.MatchStLoc(out variable, out var value)) return false; ILInstruction input; switch (value) { case Conv conv: input = conv.Argument; break; //case Call { Method: { IsOperator: true, Name: "op_Implicit" }, Arguments: { Count: 1 } } call: // input = call.Arguments[0]; // break; default: return false; } return input.MatchLdLoc(out inputVariable) || input.MatchLdLoca(out inputVariable); } internal static bool IsAssignment(ILInstruction inst, ICompilation typeSystem, out IType expectedType, out ILInstruction value) { expectedType = null; value = null; switch (inst) { case CallInstruction call: if (call.Method.AccessorKind != System.Reflection.MethodSemanticsAttributes.Setter) return false; for (int i = 0; i < call.Arguments.Count - 1; i++) { ILInstruction arg = call.Arguments[i]; if (arg.Flags == InstructionFlags.None) { // OK - we accept integer literals, etc. } else if (arg.MatchLdLoc(out var v)) { } else { return false; } } expectedType = call.Method.Parameters.Last().Type; value = call.Arguments.Last(); return true; case StLoc stloc: expectedType = stloc.Variable.Type; value = stloc.Value; return true; case StObj stobj: var target = stobj.Target; while (target.MatchLdFlda(out var nestedTarget, out _)) target = nestedTarget; if (target.Flags == InstructionFlags.None) { // OK - we accept integer literals, etc. } else if (target.MatchLdLoc(out var v)) { } else { return false; } if (stobj.Target.InferType(typeSystem) is ByReferenceType brt) expectedType = brt.ElementType; else expectedType = SpecialType.UnknownType; value = stobj.Value; return true; default: return false; } } internal override void CheckInvariant(ILPhase phase) { base.CheckInvariant(phase); var patternVariables = new HashSet(); var conversionVariables = new HashSet(); foreach (StLoc init in this.Init) { Debug.Assert(init.Variable.IsSingleDefinition && init.Variable.LoadCount == 1); Debug.Assert(init.Variable.LoadInstructions[0].IsDescendantOf(assignments)); } ValidatePattern(pattern); foreach (var inst in this.conversions.Instructions) { if (!IsConversionStLoc(inst, out var variable, out var inputVariable)) Debug.Fail("inst is not a conversion stloc!"); Debug.Assert(variable.IsSingleDefinition && variable.LoadCount == 1); Debug.Assert(variable.LoadInstructions[0].IsDescendantOf(assignments)); Debug.Assert(patternVariables.Contains(inputVariable)); conversionVariables.Add(variable); } Debug.Assert(this.conversions.FinalInstruction is Nop); foreach (var inst in assignments.Instructions) { if (!(IsAssignment(inst, typeSystem: null, out _, out var value) && value.MatchLdLoc(out var inputVariable))) throw new InvalidOperationException("inst is not an assignment!"); Debug.Assert(patternVariables.Contains(inputVariable) || conversionVariables.Contains(inputVariable)); } Debug.Assert(this.assignments.FinalInstruction is Nop); void ValidatePattern(MatchInstruction inst) { Debug.Assert(inst.IsDeconstructCall || inst.IsDeconstructTuple); Debug.Assert(!inst.CheckNotNull && !inst.CheckType); Debug.Assert(!inst.HasDesignator); foreach (var subPattern in inst.SubPatterns.Cast()) { if (subPattern.IsVar) { Debug.Assert(subPattern.Variable.IsSingleDefinition && subPattern.Variable.LoadCount <= 1); if (subPattern.Variable.LoadCount == 1) Debug.Assert(subPattern.Variable.LoadInstructions[0].IsDescendantOf(this)); patternVariables.Add(subPattern.Variable); } else { ValidatePattern(subPattern); } } } } } }