diff --git a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/PatternMatching.cs b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/PatternMatching.cs index cbc56794b..81ac1c17a 100644 --- a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/PatternMatching.cs +++ b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/PatternMatching.cs @@ -310,6 +310,7 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty } } +#if CS80 public void RecursivePattern_Type(object x) { if (x is X { C: string text }) @@ -322,6 +323,19 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty } } + public void RecursivePattern_Constant(object obj) + { + if (obj is X { C: null } x) + { + Console.WriteLine("Test " + x); + } + else + { + Console.WriteLine("not Test"); + } + } + +#endif private bool F() { return true; diff --git a/ICSharpCode.Decompiler/CSharp/ExpressionBuilder.cs b/ICSharpCode.Decompiler/CSharp/ExpressionBuilder.cs index c73084370..feafd7199 100644 --- a/ICSharpCode.Decompiler/CSharp/ExpressionBuilder.cs +++ b/ICSharpCode.Decompiler/CSharp/ExpressionBuilder.cs @@ -4546,14 +4546,14 @@ namespace ICSharpCode.Decompiler.CSharp { left = left.UnwrapChild(castExpr.Expression); } - var right = TranslatePattern(inst); + var right = TranslatePattern(inst, left.Type); return new BinaryOperatorExpression(left, BinaryOperatorType.IsPattern, right) .WithRR(new ResolveResult(compilation.FindType(KnownTypeCode.Boolean))) .WithILInstruction(inst); } - ExpressionWithILInstruction TranslatePattern(ILInstruction pattern) + ExpressionWithILInstruction TranslatePattern(ILInstruction pattern, IType leftHandType) { switch (pattern) { @@ -4590,7 +4590,7 @@ namespace ICSharpCode.Decompiler.CSharp continue; } recursivePatternExpression.SubPatterns.Add( - new NamedArgumentExpression { Name = member.Name, Expression = TranslatePattern(subPattern) } + new NamedArgumentExpression { Name = member.Name, Expression = TranslatePattern(subPattern, member.ReturnType) } .WithRR(new MemberResolveResult(null, member)) ); } @@ -4616,6 +4616,31 @@ namespace ICSharpCode.Decompiler.CSharp return new TypeReferenceExpression(ConvertType(matchInstruction.Variable.Type)) .WithILInstruction(matchInstruction); } + case Comp comp: + var constantValue = Translate(comp.Right, leftHandType); + switch (comp.Kind) + { + case ComparisonKind.Equality: + return constantValue + .WithILInstruction(comp); + case ComparisonKind.Inequality: + return new UnaryOperatorExpression(UnaryOperatorType.PatternNot, constantValue) + .WithILInstruction(comp); + case ComparisonKind.LessThan: + return new UnaryOperatorExpression(UnaryOperatorType.PatternRelationalLessThan, constantValue) + .WithILInstruction(comp); + case ComparisonKind.LessThanOrEqual: + return new UnaryOperatorExpression(UnaryOperatorType.PatternRelationalLessThanOrEqual, constantValue) + .WithILInstruction(comp); + case ComparisonKind.GreaterThan: + return new UnaryOperatorExpression(UnaryOperatorType.PatternRelationalGreaterThan, constantValue) + .WithILInstruction(comp); + case ComparisonKind.GreaterThanOrEqual: + return new UnaryOperatorExpression(UnaryOperatorType.PatternRelationalGreaterThanOrEqual, constantValue) + .WithILInstruction(comp); + default: + throw new InvalidOperationException("Unexpected comparison kind: " + comp.Kind); + } default: throw new NotImplementedException(); } diff --git a/ICSharpCode.Decompiler/CSharp/Syntax/Expressions/UnaryOperatorExpression.cs b/ICSharpCode.Decompiler/CSharp/Syntax/Expressions/UnaryOperatorExpression.cs index af925fc14..460f9a421 100644 --- a/ICSharpCode.Decompiler/CSharp/Syntax/Expressions/UnaryOperatorExpression.cs +++ b/ICSharpCode.Decompiler/CSharp/Syntax/Expressions/UnaryOperatorExpression.cs @@ -46,6 +46,7 @@ namespace ICSharpCode.Decompiler.CSharp.Syntax public readonly static TokenRole NullConditionalRole = new TokenRole("?"); public readonly static TokenRole SuppressNullableWarningRole = new TokenRole("!"); public readonly static TokenRole IndexFromEndRole = new TokenRole("^"); + public readonly static TokenRole PatternNotRole = new TokenRole("not"); public UnaryOperatorExpression() { @@ -126,6 +127,16 @@ namespace ICSharpCode.Decompiler.CSharp.Syntax return SuppressNullableWarningRole; case UnaryOperatorType.IndexFromEnd: return IndexFromEndRole; + case UnaryOperatorType.PatternNot: + return PatternNotRole; + case UnaryOperatorType.PatternRelationalLessThan: + return BinaryOperatorExpression.LessThanRole; + case UnaryOperatorType.PatternRelationalLessThanOrEqual: + return BinaryOperatorExpression.LessThanOrEqualRole; + case UnaryOperatorType.PatternRelationalGreaterThan: + return BinaryOperatorExpression.GreaterThanRole; + case UnaryOperatorType.PatternRelationalGreaterThanOrEqual: + return BinaryOperatorExpression.GreaterThanOrEqualRole; default: throw new NotSupportedException("Invalid value for UnaryOperatorType"); } @@ -156,6 +167,11 @@ namespace ICSharpCode.Decompiler.CSharp.Syntax case UnaryOperatorType.Await: case UnaryOperatorType.SuppressNullableWarning: case UnaryOperatorType.IndexFromEnd: + case UnaryOperatorType.PatternNot: + case UnaryOperatorType.PatternRelationalLessThan: + case UnaryOperatorType.PatternRelationalLessThanOrEqual: + case UnaryOperatorType.PatternRelationalGreaterThan: + case UnaryOperatorType.PatternRelationalGreaterThanOrEqual: return ExpressionType.Extension; default: throw new NotSupportedException("Invalid value for UnaryOperatorType"); @@ -216,5 +232,25 @@ namespace ICSharpCode.Decompiler.CSharp.Syntax /// C# 8 prefix ^ operator /// IndexFromEnd, + /// + /// C# 9 not pattern + /// + PatternNot, + /// + /// C# 9 relational pattern + /// + PatternRelationalLessThan, + /// + /// C# 9 relational pattern + /// + PatternRelationalLessThanOrEqual, + /// + /// C# 9 relational pattern + /// + PatternRelationalGreaterThan, + /// + /// C# 9 relational pattern + /// + PatternRelationalGreaterThanOrEqual, } } diff --git a/ICSharpCode.Decompiler/IL/Transforms/PatternMatchingTransform.cs b/ICSharpCode.Decompiler/IL/Transforms/PatternMatchingTransform.cs index 7cdc090db..c99a429a8 100644 --- a/ICSharpCode.Decompiler/IL/Transforms/PatternMatchingTransform.cs +++ b/ICSharpCode.Decompiler/IL/Transforms/PatternMatchingTransform.cs @@ -183,17 +183,17 @@ namespace ICSharpCode.Decompiler.IL.Transforms if (trueInst.MatchBranch(out var trueBlock) && trueBlock.IncomingEdgeCount == 1 && trueBlock.Parent == container) { - DetectPropertySubPatterns((MatchInstruction)ifInst.Condition, trueBlock, falseInst); + DetectPropertySubPatterns((MatchInstruction)ifInst.Condition, trueBlock, falseInst, context); } return true; } - private bool DetectPropertySubPatterns(MatchInstruction parentPattern, Block block, ILInstruction parentFalseInst) + private bool DetectPropertySubPatterns(MatchInstruction parentPattern, Block block, ILInstruction parentFalseInst, ILTransformContext context) { // if (match.notnull.type[System.String] (V_0 = callvirt get_C(ldloc V_2))) br IL_0022 // br IL_0037 - if (block.Instructions.Count == 2 && block.MatchIfAtEndOfBlock(out var condition, out var trueInst, out var falseInst)) + if (MatchBlockContainingOneCondition(block, out var condition, out var trueInst, out var falseInst)) { if (MatchInstruction.IsPatternMatch(condition, out var operand)) { @@ -213,17 +213,44 @@ namespace ICSharpCode.Decompiler.IL.Transforms } if (!DetectExitPoints.CompatibleExitInstruction(parentFalseInst, falseInst)) { - return false; + if (!DetectExitPoints.CompatibleExitInstruction(parentFalseInst, trueInst)) + { + return false; + } + ExtensionMethods.Swap(ref trueInst, ref falseInst); + condition = Comp.LogicNot(condition); } + context.Step("Move property sub pattern", condition); parentPattern.SubPatterns.Add(condition); - block.Instructions.RemoveAt(0); - block.Instructions[0] = trueInst; + block.Instructions.Clear(); + block.Instructions.Add(trueInst); return true; } } return false; } + private static bool MatchBlockContainingOneCondition(Block block, [NotNullWhen(true)] out ILInstruction? condition, [NotNullWhen(true)] out ILInstruction? trueInst, [NotNullWhen(true)] out ILInstruction? falseInst) + { + switch (block.Instructions.Count) + { + case 2: + return block.MatchIfAtEndOfBlock(out condition, out trueInst, out falseInst); + case 3: + condition = null; + if (!block.MatchIfAtEndOfBlock(out var loadTemp, out trueInst, out falseInst)) + return false; + if (!(loadTemp.MatchLdLoc(out var tempVar) && tempVar.IsSingleDefinition && tempVar.LoadCount == 1)) + return false; + return block.Instructions[0].MatchStLoc(tempVar, out condition); + default: + condition = null; + trueInst = null; + falseInst = null; + return false; + } + } + private bool CheckAllUsesDominatedBy(ILVariable v, BlockContainer container, ILInstruction trueInst, ILInstruction storeToV, ILInstruction? loadInNullCheck, ILTransformContext context, ref ControlFlowGraph? cfg) {