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);