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!)