Skip to content

Commit

Permalink
#2048: Pattern matching: documentation and invariants for MatchInstru…
Browse files Browse the repository at this point in the history
…ction.
  • Loading branch information
dgrunwald committed Jul 25, 2020
1 parent 95f2ae7 commit cda56e7
Show file tree
Hide file tree
Showing 5 changed files with 154 additions and 16 deletions.
4 changes: 4 additions & 0 deletions ICSharpCode.Decompiler/IL/ILVariable.cs
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,10 @@ public enum VariableKind
/// Local variable that holds the display class used for lambdas within this function.
/// </summary>
DisplayClassLocal,
/// <summary>
/// Local variable declared within a pattern match.
/// </summary>
PatternLocal,
}

static class VariableKindExtensions
Expand Down
11 changes: 8 additions & 3 deletions ICSharpCode.Decompiler/IL/Instructions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6139,8 +6139,8 @@ protected override void Disconnected()
/// <summary>Returns the method operand.</summary>
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 {
Expand Down Expand Up @@ -6218,7 +6218,7 @@ public override T AcceptVisitor<C, T>(ILVisitor<C, T> visitor, C context)
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)
{
Expand Down Expand Up @@ -6597,6 +6597,11 @@ protected internal override bool PerformMatch(ILInstruction other, ref Patterns.
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
Expand Down
12 changes: 3 additions & 9 deletions ICSharpCode.Decompiler/IL/Instructions.tt
Original file line number Diff line number Diff line change
Expand Up @@ -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 }
Expand All @@ -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),
Expand Down Expand Up @@ -1179,13 +1180,6 @@ protected override void Disconnected()
};
}

static Action<OpCode> CustomInvariant(string code)
{
return opCode => {
opCode.Invariants.Add(code);
};
}

static Action<OpCode> Pattern = opCode => {
BaseClass("PatternInstruction")(opCode);
opCode.Namespace = "ICSharpCode.Decompiler.IL.Patterns";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -40,5 +43,26 @@ public override void WriteTo(ITextOutput output, ILAstWritingOptions options)
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");
}
}
}
119 changes: 115 additions & 4 deletions ICSharpCode.Decompiler/IL/Instructions/MatchInstruction.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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),
}
}
*/

/// <summary>
/// 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.
/// </summary>
public static bool IsPatternMatch(ILInstruction inst, out ILInstruction testedOperand)
{
switch (inst) {
case MatchInstruction m:
Expand All @@ -32,7 +106,7 @@ public bool IsPattern(ILInstruction inst, out ILInstruction testedOperand)
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;
Expand All @@ -53,18 +127,55 @@ private static bool IsConstant(ILInstruction inst)
};
}

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.");
}
}
}

public override void WriteTo(ITextOutput output, ILAstWritingOptions options)
{
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);
Expand Down

0 comments on commit cda56e7

Please sign in to comment.