diff --git a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/PatternMatching.cs b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/PatternMatching.cs index ed0168a5d..8471db9ca 100644 --- a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/PatternMatching.cs +++ b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/PatternMatching.cs @@ -387,6 +387,30 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty Console.WriteLine("not Test"); } } + + public void RecursivePattern_NonTypePattern(object obj) + { + if (obj is X { A: 42, B: { Length: 0 } } x) + { + Console.WriteLine("Test " + x); + } + else + { + Console.WriteLine("not Test"); + } + } + + public void RecursivePattern_VarLengthPattern(object obj) + { + if (obj is X { A: 42, B: { Length: var length } } x) + { + Console.WriteLine("Test " + x.A + ": " + length); + } + else + { + Console.WriteLine("not Test"); + } + } #endif private bool F() { diff --git a/ICSharpCode.Decompiler/IL/Instructions/MatchInstruction.cs b/ICSharpCode.Decompiler/IL/Instructions/MatchInstruction.cs index d30c30280..cb61f6613 100644 --- a/ICSharpCode.Decompiler/IL/Instructions/MatchInstruction.cs +++ b/ICSharpCode.Decompiler/IL/Instructions/MatchInstruction.cs @@ -127,9 +127,9 @@ namespace ICSharpCode.Decompiler.IL testedOperand = m.testedOperand; return true; case Comp comp: - if (comp.MatchLogicNot(out var operand)) + if (comp.MatchLogicNot(out var operand) && IsPatternMatch(operand, out testedOperand)) { - return IsPatternMatch(operand, out testedOperand); + return true; } else { diff --git a/ICSharpCode.Decompiler/IL/Transforms/PatternMatchingTransform.cs b/ICSharpCode.Decompiler/IL/Transforms/PatternMatchingTransform.cs index d5ac6e0a6..8b0737c9c 100644 --- a/ICSharpCode.Decompiler/IL/Transforms/PatternMatchingTransform.cs +++ b/ICSharpCode.Decompiler/IL/Transforms/PatternMatchingTransform.cs @@ -188,12 +188,22 @@ namespace ICSharpCode.Decompiler.IL.Transforms return true; } - private void DetectPropertySubPatterns(MatchInstruction parentPattern, Block block, ILInstruction parentFalseInst, ILTransformContext context, ref ControlFlowGraph? cfg) + private static void DetectPropertySubPatterns(MatchInstruction parentPattern, Block block, ILInstruction parentFalseInst, ILTransformContext context, ref ControlFlowGraph? cfg) { // if (match.notnull.type[System.String] (V_0 = callvirt get_C(ldloc V_2))) br IL_0022 // br IL_0037 if (MatchBlockContainingOneCondition(block, out var condition, out var trueInst, out var falseInst)) { + bool negate = false; + if (!DetectExitPoints.CompatibleExitInstruction(parentFalseInst, falseInst)) + { + if (!DetectExitPoints.CompatibleExitInstruction(parentFalseInst, trueInst)) + { + return; + } + ExtensionMethods.Swap(ref trueInst, ref falseInst); + negate = true; + } if (MatchInstruction.IsPatternMatch(condition, out var operand)) { if (!PropertyOrFieldAccess(operand, out var target, out _)) @@ -204,24 +214,32 @@ namespace ICSharpCode.Decompiler.IL.Transforms { return; } - if (!DetectExitPoints.CompatibleExitInstruction(parentFalseInst, falseInst)) + context.Step("Move property sub pattern", condition); + if (negate) { - if (!DetectExitPoints.CompatibleExitInstruction(parentFalseInst, trueInst)) - { - return; - } - ExtensionMethods.Swap(ref trueInst, ref falseInst); condition = Comp.LogicNot(condition); } - context.Step("Move property sub pattern", condition); parentPattern.SubPatterns.Add(condition); - block.Instructions.Clear(); - block.Instructions.Add(trueInst); - - if (trueInst.MatchBranch(out var trueBlock) && trueBlock.IncomingEdgeCount == 1 && trueBlock.Parent == block.Parent) + } + else if (PropertyOrFieldAccess(condition, out var target, out _)) + { + if (!target.MatchLdLocRef(parentPattern.Variable)) { - DetectPropertySubPatterns(parentPattern, trueBlock, falseInst, context, ref cfg); + return; } + context.Step("Move property sub pattern", condition); + parentPattern.SubPatterns.Add(new Comp(negate ? ComparisonKind.Equality : ComparisonKind.Inequality, Sign.None, condition, new LdcI4(0))); + } + else + { + return; + } + block.Instructions.Clear(); + block.Instructions.Add(trueInst); + + if (trueInst.MatchBranch(out var trueBlock) && trueBlock.IncomingEdgeCount == 1 && trueBlock.Parent == block.Parent) + { + DetectPropertySubPatterns(parentPattern, trueBlock, falseInst, context, ref cfg); } } else if (block.Instructions[0].MatchStLoc(out var targetVariable, out var operand)) @@ -248,10 +266,45 @@ namespace ICSharpCode.Decompiler.IL.Transforms parentPattern.SubPatterns.Add(varPattern); block.Instructions.RemoveAt(0); targetVariable.Kind = VariableKind.PatternLocal; - DetectPropertySubPatterns(parentPattern, block, parentFalseInst, context, ref cfg); + if (!MatchNullCheckPattern(block, varPattern, parentFalseInst, context, ref cfg)) + { + DetectPropertySubPatterns(parentPattern, block, parentFalseInst, context, ref cfg); + } } } + private static bool MatchNullCheckPattern(Block block, MatchInstruction varPattern, ILInstruction parentFalseInst, ILTransformContext context, ref ControlFlowGraph? cfg) + { + if (!MatchBlockContainingOneCondition(block, out var condition, out var trueInst, out var falseInst)) + { + return false; + } + if (condition.MatchCompEqualsNull(out var arg) && arg.MatchLdLoc(varPattern.Variable)) + { + ExtensionMethods.Swap(ref trueInst, ref falseInst); + } + else if (condition.MatchCompNotEqualsNull(out arg) && arg.MatchLdLoc(varPattern.Variable)) + { + } + else + { + return false; + } + if (!DetectExitPoints.CompatibleExitInstruction(falseInst, parentFalseInst)) + { + return false; + } + context.Step("Null check pattern", block); + varPattern.CheckNotNull = true; + block.Instructions.Clear(); + block.Instructions.Add(trueInst); + if (trueInst.MatchBranch(out var trueBlock) && trueBlock.IncomingEdgeCount == 1 && trueBlock.Parent == block.Parent) + { + DetectPropertySubPatterns(varPattern, trueBlock, falseInst, context, ref cfg); + } + return true; + } + private static bool PropertyOrFieldAccess(ILInstruction operand, [NotNullWhen(true)] out ILInstruction? target, [NotNullWhen(true)] out IMember? member) { if (operand is CallInstruction { @@ -300,7 +353,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms } } - private bool CheckAllUsesDominatedBy(ILVariable v, BlockContainer container, ILInstruction trueInst, + private static bool CheckAllUsesDominatedBy(ILVariable v, BlockContainer container, ILInstruction trueInst, ILInstruction storeToV, ILInstruction? loadInNullCheck, ILTransformContext context, ref ControlFlowGraph? cfg) { var targetBlock = trueInst as Block;