diff --git a/ICSharpCode.Decompiler/CSharp/ExpressionBuilder.cs b/ICSharpCode.Decompiler/CSharp/ExpressionBuilder.cs index 166fcd849..00155cb2a 100644 --- a/ICSharpCode.Decompiler/CSharp/ExpressionBuilder.cs +++ b/ICSharpCode.Decompiler/CSharp/ExpressionBuilder.cs @@ -4575,7 +4575,7 @@ namespace ICSharpCode.Decompiler.CSharp } foreach (var subPattern in matchInstruction.SubPatterns) { - if (!MatchInstruction.IsPatternMatch(subPattern, out var testedOperand)) + if (!MatchInstruction.IsPatternMatch(subPattern, out var testedOperand, settings)) { Debug.Fail("Invalid sub pattern"); continue; diff --git a/ICSharpCode.Decompiler/DecompilerSettings.cs b/ICSharpCode.Decompiler/DecompilerSettings.cs index ea0a7dde9..c39539000 100644 --- a/ICSharpCode.Decompiler/DecompilerSettings.cs +++ b/ICSharpCode.Decompiler/DecompilerSettings.cs @@ -130,6 +130,7 @@ namespace ICSharpCode.Decompiler staticLocalFunctions = false; ranges = false; switchExpressions = false; + recursivePatternMatching = false; } if (languageVersion < CSharp.LanguageVersion.CSharp9_0) { @@ -141,6 +142,8 @@ namespace ICSharpCode.Decompiler withExpressions = false; usePrimaryConstructorSyntax = false; covariantReturns = false; + relationalPatterns = false; + patternCombinators = false; } if (languageVersion < CSharp.LanguageVersion.CSharp10_0) { @@ -166,10 +169,11 @@ namespace ICSharpCode.Decompiler if (fileScopedNamespaces || recordStructs) return CSharp.LanguageVersion.CSharp10_0; if (nativeIntegers || initAccessors || functionPointers || forEachWithGetEnumeratorExtension - || recordClasses || withExpressions || usePrimaryConstructorSyntax || covariantReturns) + || recordClasses || withExpressions || usePrimaryConstructorSyntax || covariantReturns + || relationalPatterns || patternCombinators) return CSharp.LanguageVersion.CSharp9_0; if (nullableReferenceTypes || readOnlyMethods || asyncEnumerator || asyncUsingAndForEachStatement - || staticLocalFunctions || ranges || switchExpressions) + || staticLocalFunctions || ranges || switchExpressions || recursivePatternMatching) return CSharp.LanguageVersion.CSharp8_0; if (introduceUnmanagedConstraint || tupleComparisons || stackAllocInitializers || patternBasedFixedStatement) @@ -1678,6 +1682,60 @@ namespace ICSharpCode.Decompiler } } + bool recursivePatternMatching = true; + + /// + /// Gets/Sets whether C# 8.0 recursive patterns should be detected. + /// + [Category("C# 8.0 / VS 2019")] + [Description("DecompilerSettings.RecursivePatternMatching")] + public bool RecursivePatternMatching { + get { return recursivePatternMatching; } + set { + if (recursivePatternMatching != value) + { + recursivePatternMatching = value; + OnPropertyChanged(); + } + } + } + + bool patternCombinators = true; + + /// + /// Gets/Sets whether C# 9.0 and, or, not patterns should be detected. + /// + [Category("C# 9.0 / VS 2019.8")] + [Description("DecompilerSettings.PatternCombinators")] + public bool PatternCombinators { + get { return patternCombinators; } + set { + if (patternCombinators != value) + { + patternCombinators = value; + OnPropertyChanged(); + } + } + } + + bool relationalPatterns = true; + + /// + /// Gets/Sets whether C# 9.0 relational patterns should be detected. + /// + [Category("C# 9.0 / VS 2019.8")] + [Description("DecompilerSettings.RelationalPatterns")] + public bool RelationalPatterns { + get { return relationalPatterns; } + set { + if (relationalPatterns != value) + { + relationalPatterns = value; + OnPropertyChanged(); + } + } + } + bool staticLocalFunctions = true; /// diff --git a/ICSharpCode.Decompiler/IL/Instructions/MatchInstruction.cs b/ICSharpCode.Decompiler/IL/Instructions/MatchInstruction.cs index 33f4a0bb5..cab918cc7 100644 --- a/ICSharpCode.Decompiler/IL/Instructions/MatchInstruction.cs +++ b/ICSharpCode.Decompiler/IL/Instructions/MatchInstruction.cs @@ -119,7 +119,7 @@ namespace ICSharpCode.Decompiler.IL /// (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, [NotNullWhen(true)] out ILInstruction? testedOperand) + public static bool IsPatternMatch(ILInstruction? inst, [NotNullWhen(true)] out ILInstruction? testedOperand, DecompilerSettings? settings) { switch (inst) { @@ -127,13 +127,23 @@ namespace ICSharpCode.Decompiler.IL testedOperand = m.testedOperand; return true; case Comp comp: - if (comp.MatchLogicNot(out var operand) && IsPatternMatch(operand, out testedOperand)) + if (comp.MatchLogicNot(out var operand) && IsPatternMatch(operand, out testedOperand, settings)) { - return true; + return settings?.PatternCombinators ?? true; } else { testedOperand = comp.Left; + if (!(settings?.RelationalPatterns ?? true)) + { + if (comp.Kind is not (ComparisonKind.Equality or ComparisonKind.Inequality)) + return false; + } + if (!(settings?.PatternCombinators ?? true)) + { + if (comp.Kind is ComparisonKind.Inequality) + return false; + } return IsConstant(comp.Right); } case Call call when IsCallToString_op_Equality(call): @@ -201,7 +211,7 @@ namespace ICSharpCode.Decompiler.IL Debug.Assert(SubPatterns.Count >= NumPositionalPatterns); foreach (var subPattern in SubPatterns) { - if (!IsPatternMatch(subPattern, out ILInstruction? operand)) + if (!IsPatternMatch(subPattern, out ILInstruction? operand, null)) throw new InvalidOperationException("Sub-Pattern must be a valid pattern"); // the first child is TestedOperand int subPatternIndex = subPattern.ChildIndex - 1; diff --git a/ICSharpCode.Decompiler/IL/Transforms/ExpressionTransforms.cs b/ICSharpCode.Decompiler/IL/Transforms/ExpressionTransforms.cs index 8a6684c4e..1de0c6da1 100644 --- a/ICSharpCode.Decompiler/IL/Transforms/ExpressionTransforms.cs +++ b/ICSharpCode.Decompiler/IL/Transforms/ExpressionTransforms.cs @@ -557,7 +557,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms return; } } - if (MatchInstruction.IsPatternMatch(inst.Condition, out _) + if (MatchInstruction.IsPatternMatch(inst.Condition, out _, context.Settings) && inst.TrueInst.MatchLdcI4(1) && inst.FalseInst.MatchLdcI4(0)) { context.Step("match(x) ? true : false -> match(x)", inst); diff --git a/ICSharpCode.Decompiler/IL/Transforms/PatternMatchingTransform.cs b/ICSharpCode.Decompiler/IL/Transforms/PatternMatchingTransform.cs index 6b1ddc8dc..d1bf69567 100644 --- a/ICSharpCode.Decompiler/IL/Transforms/PatternMatchingTransform.cs +++ b/ICSharpCode.Decompiler/IL/Transforms/PatternMatchingTransform.cs @@ -189,6 +189,10 @@ namespace ICSharpCode.Decompiler.IL.Transforms private static ILInstruction DetectPropertySubPatterns(MatchInstruction parentPattern, ILInstruction trueInst, ILInstruction parentFalseInst, BlockContainer container, ILTransformContext context, ref ControlFlowGraph? cfg) { + if (!context.Settings.RecursivePatternMatching) + { + return trueInst; + } while (true) { Block? trueBlock = trueInst as Block; @@ -230,7 +234,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms ExtensionMethods.Swap(ref trueInst, ref falseInst); negate = true; } - if (MatchInstruction.IsPatternMatch(condition, out var operand)) + if (MatchInstruction.IsPatternMatch(condition, out var operand, context.Settings)) { if (!PropertyOrFieldAccess(operand, out var target, out _)) { @@ -240,6 +244,10 @@ namespace ICSharpCode.Decompiler.IL.Transforms { return null; } + if (negate && !context.Settings.PatternCombinators) + { + return null; + } context.Step("Move property sub pattern", condition); if (negate) { @@ -253,8 +261,13 @@ namespace ICSharpCode.Decompiler.IL.Transforms { return null; } - context.Step("Move property sub pattern", condition); - parentPattern.SubPatterns.Add(new Comp(negate ? ComparisonKind.Equality : ComparisonKind.Inequality, Sign.None, condition, new LdcI4(0))); + if (!negate && !context.Settings.PatternCombinators) + { + return null; + } + context.Step("Sub pattern: implicit != 0", condition); + parentPattern.SubPatterns.Add(new Comp(negate ? ComparisonKind.Equality : ComparisonKind.Inequality, + Sign.None, condition, new LdcI4(0))); } else { @@ -376,7 +389,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms } return null; } - if (varPattern.Variable.AddressCount == 1) + if (varPattern.Variable.AddressCount == 1 && context.Settings.PatternCombinators) { context.Step("Nullable.HasValue check -> not null pattern", block); varPattern.ReplaceWith(new Comp(ComparisonKind.Inequality, ComparisonLiftingKind.CSharp, StackType.O, Sign.None, varPattern.TestedOperand, new LdNull())); @@ -412,6 +425,10 @@ namespace ICSharpCode.Decompiler.IL.Transforms { return null; } + if (!(context.Settings.RelationalPatterns || comp.Kind is ComparisonKind.Equality or ComparisonKind.Inequality)) + { + return null; + } bool negated = false; if (!DetectExitPoints.CompatibleExitInstruction(falseInst, parentFalseInst)) { @@ -426,6 +443,10 @@ namespace ICSharpCode.Decompiler.IL.Transforms { return null; } + if (negated && !context.Settings.PatternCombinators) + { + return null; + } context.Step("Nullable.HasValue check + Nullable.GetValueOrDefault pattern", block); // varPattern: match (v = testedOperand) // comp: comp.i4(call GetValueOrDefault(ldloca v) != ldc.i4 42) diff --git a/ILSpy/Properties/Resources.Designer.cs b/ILSpy/Properties/Resources.Designer.cs index 54b1ce17b..18a93692f 100644 --- a/ILSpy/Properties/Resources.Designer.cs +++ b/ILSpy/Properties/Resources.Designer.cs @@ -1145,6 +1145,15 @@ namespace ICSharpCode.ILSpy.Properties { } } + /// + /// Looks up a localized string similar to Pattern combinators (and, or, not). + /// + public static string DecompilerSettings_PatternCombinators { + get { + return ResourceManager.GetString("DecompilerSettings.PatternCombinators", resourceCulture); + } + } + /// /// Looks up a localized string similar to Use pattern matching expressions. /// @@ -1199,6 +1208,24 @@ namespace ICSharpCode.ILSpy.Properties { } } + /// + /// Looks up a localized string similar to Recursive pattern matching. + /// + public static string DecompilerSettings_RecursivePatternMatching { + get { + return ResourceManager.GetString("DecompilerSettings.RecursivePatternMatching", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Relational patterns. + /// + public static string DecompilerSettings_RelationalPatterns { + get { + return ResourceManager.GetString("DecompilerSettings.RelationalPatterns", resourceCulture); + } + } + /// /// Looks up a localized string similar to Remove dead and side effect free code (use with caution!). /// diff --git a/ILSpy/Properties/Resources.resx b/ILSpy/Properties/Resources.resx index 518717922..f63a10682 100644 --- a/ILSpy/Properties/Resources.resx +++ b/ILSpy/Properties/Resources.resx @@ -405,6 +405,9 @@ Are you sure you want to continue? Use parameter null checking + + Pattern combinators (and, or, not) + Use pattern matching expressions @@ -423,6 +426,12 @@ Are you sure you want to continue? Record structs + + Recursive pattern matching + + + Relational patterns + Remove dead and side effect free code (use with caution!)