Browse Source

#2048: Pattern matching: documentation and invariants for MatchInstruction.

pull/2119/head
Daniel Grunwald 6 years ago
parent
commit
cda56e7f7e
  1. 4
      ICSharpCode.Decompiler/IL/ILVariable.cs
  2. 11
      ICSharpCode.Decompiler/IL/Instructions.cs
  3. 12
      ICSharpCode.Decompiler/IL/Instructions.tt
  4. 24
      ICSharpCode.Decompiler/IL/Instructions/DeconstructResultInstruction.cs
  5. 119
      ICSharpCode.Decompiler/IL/Instructions/MatchInstruction.cs

4
ICSharpCode.Decompiler/IL/ILVariable.cs

@ -71,6 +71,10 @@ namespace ICSharpCode.Decompiler.IL @@ -71,6 +71,10 @@ namespace ICSharpCode.Decompiler.IL
/// 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

11
ICSharpCode.Decompiler/IL/Instructions.cs

@ -6139,8 +6139,8 @@ namespace ICSharpCode.Decompiler.IL @@ -6139,8 +6139,8 @@ namespace ICSharpCode.Decompiler.IL
/// <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 {
@ -6218,7 +6218,7 @@ namespace ICSharpCode.Decompiler.IL @@ -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 @@ -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

12
ICSharpCode.Decompiler/IL/Instructions.tt

@ -336,7 +336,7 @@ @@ -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 @@ @@ -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() @@ -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";

24
ICSharpCode.Decompiler/IL/Instructions/DeconstructResultInstruction.cs

@ -16,6 +16,9 @@ @@ -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 @@ -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");
}
}
}

119
ICSharpCode.Decompiler/IL/Instructions/MatchInstruction.cs

@ -17,12 +17,86 @@ @@ -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:
@ -32,7 +106,7 @@ namespace ICSharpCode.Decompiler.IL @@ -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 @@ -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 @@ -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);

Loading…
Cancel
Save