diff --git a/ICSharpCode.Decompiler/IL/ILVariable.cs b/ICSharpCode.Decompiler/IL/ILVariable.cs index 77b6dbae5..a5cf53cad 100644 --- a/ICSharpCode.Decompiler/IL/ILVariable.cs +++ b/ICSharpCode.Decompiler/IL/ILVariable.cs @@ -71,6 +71,10 @@ namespace ICSharpCode.Decompiler.IL /// Local variable that holds the display class used for lambdas within this function. /// DisplayClassLocal, + /// + /// Local variable declared within a pattern match. + /// + PatternLocal, } static class VariableKindExtensions diff --git a/ICSharpCode.Decompiler/IL/Instructions.cs b/ICSharpCode.Decompiler/IL/Instructions.cs index b6b6888c0..4158ed9e1 100644 --- a/ICSharpCode.Decompiler/IL/Instructions.cs +++ b/ICSharpCode.Decompiler/IL/Instructions.cs @@ -6139,8 +6139,8 @@ namespace ICSharpCode.Decompiler.IL /// Returns the method operand. public IMethod Method { get { return method; } } public bool Deconstruct; - public bool MatchType; - public bool MatchesNull; + public bool CheckType; + public bool CheckNotNull; public static readonly SlotInfo TestedOperandSlot = new SlotInfo("TestedOperand", canInlineInto: true); ILInstruction testedOperand; public ILInstruction TestedOperand { @@ -6218,7 +6218,7 @@ namespace ICSharpCode.Decompiler.IL protected internal override bool PerformMatch(ILInstruction other, ref Patterns.Match match) { var o = other as MatchInstruction; - return o != null && variable == o.variable && object.Equals(method, o.method) && this.Deconstruct == o.Deconstruct && this.MatchType == o.MatchType && this.MatchesNull == o.MatchesNull && this.testedOperand.PerformMatch(o.testedOperand, ref match) && Patterns.ListMatch.DoMatch(this.SubPatterns, o.SubPatterns, ref match); + return o != null && variable == o.variable && object.Equals(method, o.method) && this.Deconstruct == o.Deconstruct && this.CheckType == o.CheckType && this.CheckNotNull == o.CheckNotNull && this.testedOperand.PerformMatch(o.testedOperand, ref match) && Patterns.ListMatch.DoMatch(this.SubPatterns, o.SubPatterns, ref match); } internal override void CheckInvariant(ILPhase phase) { @@ -6597,6 +6597,11 @@ namespace ICSharpCode.Decompiler.IL var o = other as DeconstructResultInstruction; return o != null && this.Argument.PerformMatch(o.Argument, ref match) && type.Equals(o.type); } + internal override void CheckInvariant(ILPhase phase) + { + base.CheckInvariant(phase); + AdditionalInvariants(); + } } } namespace ICSharpCode.Decompiler.IL.Patterns diff --git a/ICSharpCode.Decompiler/IL/Instructions.tt b/ICSharpCode.Decompiler/IL/Instructions.tt index 34448f37e..f8c55d72c 100644 --- a/ICSharpCode.Decompiler/IL/Instructions.tt +++ b/ICSharpCode.Decompiler/IL/Instructions.tt @@ -336,7 +336,7 @@ new OpCode("match", "ILAst representation of C# patterns", CustomClassName("MatchInstruction"), HasVariableOperand("Store"), HasMethodOperand, - BoolFlag("Deconstruct"), BoolFlag("MatchType"), BoolFlag("MatchesNull"), + BoolFlag("Deconstruct"), BoolFlag("CheckType"), BoolFlag("CheckNotNull"), CustomChildren(new []{ new ChildInfo("testedOperand") { CanInlineInto = true, ExpectedTypes = new[] { "O" } }, new ChildInfo("subPatterns") { IsCollection = true } @@ -361,7 +361,8 @@ new OpCode("deconstruct", "Deconstruction statement", CustomClassName("DeconstructInstruction"), CustomConstructor, ResultType("O"), CustomWriteTo), new OpCode("deconstruct.result", "Represents a deconstructed value", - CustomClassName("DeconstructResultInstruction"), CustomConstructor, Unary, HasTypeOperand, ResultType("type.GetStackType()"), CustomWriteTo), + CustomClassName("DeconstructResultInstruction"), CustomConstructor, CustomInvariant("AdditionalInvariants();"), + Unary, HasTypeOperand, ResultType("type.GetStackType()"), CustomWriteTo), // patterns new OpCode("AnyNode", "Matches any node", Pattern, CustomArguments(), CustomConstructor), @@ -1179,13 +1180,6 @@ protected override void Disconnected() }; } - static Action CustomInvariant(string code) - { - return opCode => { - opCode.Invariants.Add(code); - }; - } - static Action Pattern = opCode => { BaseClass("PatternInstruction")(opCode); opCode.Namespace = "ICSharpCode.Decompiler.IL.Patterns"; diff --git a/ICSharpCode.Decompiler/IL/Instructions/DeconstructResultInstruction.cs b/ICSharpCode.Decompiler/IL/Instructions/DeconstructResultInstruction.cs index 19134db99..b3b32c8d0 100644 --- a/ICSharpCode.Decompiler/IL/Instructions/DeconstructResultInstruction.cs +++ b/ICSharpCode.Decompiler/IL/Instructions/DeconstructResultInstruction.cs @@ -16,6 +16,9 @@ // OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // DEALINGS IN THE SOFTWARE. +using System.Diagnostics; +using ICSharpCode.Decompiler.TypeSystem; + namespace ICSharpCode.Decompiler.IL { partial class DeconstructResultInstruction @@ -40,5 +43,26 @@ namespace ICSharpCode.Decompiler.IL this.Argument.WriteTo(output, options); output.Write(')'); } + + MatchInstruction FindMatch() + { + for (ILInstruction inst = this; inst != null; inst = inst.Parent) { + if (inst.Parent is MatchInstruction match && inst != match.TestedOperand) + return match; + } + return null; + } + + void AdditionalInvariants() + { + var matchInst = FindMatch(); + Debug.Assert(matchInst != null && matchInst.Deconstruct); + Debug.Assert(Argument.MatchLdLoc(matchInst.Variable)); + var outParamType = matchInst.GetDeconstructResult(this.Index).Type; + if (outParamType is ByReferenceType brt) + Debug.Assert(brt.ElementType.Equals(this.Type)); + else + Debug.Fail("deconstruct out param must be by reference"); + } } } diff --git a/ICSharpCode.Decompiler/IL/Instructions/MatchInstruction.cs b/ICSharpCode.Decompiler/IL/Instructions/MatchInstruction.cs index e5307b5a7..53e04593e 100644 --- a/ICSharpCode.Decompiler/IL/Instructions/MatchInstruction.cs +++ b/ICSharpCode.Decompiler/IL/Instructions/MatchInstruction.cs @@ -17,12 +17,86 @@ // DEALINGS IN THE SOFTWARE. using System.Diagnostics; +using System.Linq; +using ICSharpCode.Decompiler.TypeSystem; namespace ICSharpCode.Decompiler.IL { partial class MatchInstruction : ILInstruction { - public bool IsPattern(ILInstruction inst, out ILInstruction testedOperand) + /* Pseudo-Code for interpreting a MatchInstruction: + bool Eval() + { + var value = this.TestedOperand.Eval(); + if (this.CheckNotNull && value == null) + return false; + if (this.CheckType && !(value is this.Variable.Type)) + return false; + if (this.Deconstruct) { + deconstructResult = new[numArgs]; + EvalCall(this.Method, value, out deconstructResult[0], .., out deconstructResult[numArgs-1]); + // any occurrences of 'deconstruct.result' in the subPatterns will refer + // to the values provided by evaluating the call. + } + Variable.Value = value; + foreach (var subPattern in this.SubPatterns) { + if (!subPattern.Eval()) + return false; + } + return true; + } + */ + /* Examples of MatchInstructions: + expr is var x: + match(x = expr) + + expr is {} x: + match.notnull(x = expr + + expr is T x: + match.type[T](x = expr) + + expr is C { A: var x } z: + match.type[C](z = expr) { + match(x = z.A) + } + + expr is C { A: var x, B: 42, C: { A: 4 } } z: + match.type[C](z = expr) { + match(x = z.A), + comp (z.B == 42), + match.notnull(temp2 = z.C) { + comp (temp2.A == 4) + } + } + + expr is C(var x, var y, <4): + match.type[C].deconstruct[C.Deconstruct](tmp1 = expr) { + match(x = deconstruct.result0(tmp1)), + match(y = deconstruct.result1(tmp1)), + comp(deconstruct.result2(tmp1) < 4), + } + + expr is C(1, D(2, 3)): + match.type[C].deconstruct(c = expr) { + comp(deconstruct.result0(c) == 1), + match.type[D].deconstruct(d = deconstruct.result1(c)) { + comp(deconstruct.result0(d) == 2), + comp(deconstruct.result1(d) == 2), + } + } + */ + + /// + /// Checks whether the input instruction can represent a pattern matching operation. + /// + /// Any pattern matching instruction will first evaluate the `testedOperand` (a descendant of `inst`), + /// and then match the value of that operand against the pattern encoded in the instruction. + /// The matching may have side-effects on the newly-initialized pattern variables + /// (even if the pattern fails to match!). + /// The pattern matching instruction evaluates to 1 (as I4) if the pattern matches, or 0 otherwise. + /// + public static bool IsPatternMatch(ILInstruction inst, out ILInstruction testedOperand) { switch (inst) { case MatchInstruction m: @@ -32,7 +106,7 @@ namespace ICSharpCode.Decompiler.IL testedOperand = comp.Left; return IsConstant(comp.Right); case ILInstruction logicNot when logicNot.MatchLogicNot(out var operand): - return IsPattern(operand, out testedOperand); + return IsPatternMatch(operand, out testedOperand); default: testedOperand = null; return false; @@ -53,11 +127,35 @@ namespace ICSharpCode.Decompiler.IL }; } + internal IParameter GetDeconstructResult(int index) + { + Debug.Assert(this.Deconstruct); + int firstOutParam = (method.IsStatic ? 1 : 0); + return this.Method.Parameters[firstOutParam + index]; + } + void AdditionalInvariants() { + Debug.Assert(variable.Kind == VariableKind.PatternLocal); + if (this.Deconstruct) { + Debug.Assert(method.Name == "Deconstruct"); + int firstOutParam = (method.IsStatic ? 1 : 0); + Debug.Assert(method.Parameters.Count >= firstOutParam); + Debug.Assert(method.Parameters.Skip(firstOutParam).All(p => p.IsOut)); + } foreach (var subPattern in SubPatterns) { - ILInstruction operand; - Debug.Assert(IsPattern(subPattern, out operand)); + if (!IsPatternMatch(subPattern, out ILInstruction operand)) + Debug.Fail("Sub-Pattern must be a valid pattern"); + if (operand.MatchLdFld(out var target, out _)) { + Debug.Assert(target.MatchLdLoc(variable)); + } else if (operand is CallInstruction call) { + Debug.Assert(call.Method.AccessorKind == System.Reflection.MethodSemanticsAttributes.Getter); + Debug.Assert(call.Arguments[0].MatchLdLoc(variable)); + } else if (operand is DeconstructResultInstruction resultInstruction) { + Debug.Assert(this.Deconstruct); + } else { + Debug.Fail("Tested operand of sub-pattern is invalid."); + } } } @@ -65,6 +163,19 @@ namespace ICSharpCode.Decompiler.IL { WriteILRange(output, options); output.Write(OpCode); + if (CheckNotNull) { + output.Write(".notnull"); + } + if (CheckType) { + output.Write(".type["); + variable.Type.WriteTo(output); + output.Write(']'); + } + if (Deconstruct) { + output.Write(".deconstruct["); + method.WriteTo(output); + output.Write(']'); + } output.Write(' '); output.Write('('); Variable.WriteTo(output);