Browse Source

Merge pull request #3049 from icsharpcode/recursive-patterns

pull/3050/head
Siegfried Pammer 2 years ago committed by GitHub
parent
commit
25ed4b10f1
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 2
      .github/workflows/build-ilspy.yml
  2. 450
      ICSharpCode.Decompiler.Tests/TestCases/Pretty/PatternMatching.cs
  3. 97
      ICSharpCode.Decompiler/CSharp/ExpressionBuilder.cs
  4. 36
      ICSharpCode.Decompiler/CSharp/OutputVisitor/CSharpOutputVisitor.cs
  5. 15
      ICSharpCode.Decompiler/CSharp/Syntax/DepthFirstAstVisitor.cs
  6. 4
      ICSharpCode.Decompiler/CSharp/Syntax/Expressions/DeclarationExpression.cs
  7. 67
      ICSharpCode.Decompiler/CSharp/Syntax/Expressions/RecursivePatternExpression.cs
  8. 36
      ICSharpCode.Decompiler/CSharp/Syntax/Expressions/UnaryOperatorExpression.cs
  9. 3
      ICSharpCode.Decompiler/CSharp/Syntax/IAstVisitor.cs
  10. 62
      ICSharpCode.Decompiler/DecompilerSettings.cs
  11. 4
      ICSharpCode.Decompiler/ICSharpCode.Decompiler.csproj
  12. 2
      ICSharpCode.Decompiler/IL/Instructions/Comp.cs
  13. 39
      ICSharpCode.Decompiler/IL/Instructions/MatchInstruction.cs
  14. 21
      ICSharpCode.Decompiler/IL/Transforms/AssignVariableNames.cs
  15. 50
      ICSharpCode.Decompiler/IL/Transforms/EarlyExpressionTransforms.cs
  16. 45
      ICSharpCode.Decompiler/IL/Transforms/ExpressionTransforms.cs
  17. 364
      ICSharpCode.Decompiler/IL/Transforms/PatternMatchingTransform.cs
  18. 166
      ICSharpCode.Decompiler/Util/Index.cs
  19. 6
      ILSpy.Installer/ILSpy.Installer.csproj
  20. 27
      ILSpy/Properties/Resources.Designer.cs
  21. 9
      ILSpy/Properties/Resources.resx

2
.github/workflows/build-ilspy.yml

@ -95,7 +95,7 @@ jobs: @@ -95,7 +95,7 @@ jobs:
run: |
msbuild ILSpy.Installer.sln /t:Restore /p:Configuration="Release" /p:Platform="Any CPU"
msbuild ILSpy.Installer.sln /p:Configuration="Release" /p:Platform="Any CPU"
msbuild ILSpy.Installer.sln /p:Configuration="Release" /p:Platform="Any CPU" /p:DefineConstants="ARM64"
msbuild ILSpy.Installer.sln /p:Configuration="Release" /p:Platform="Any CPU" /p:PlatformForInstaller="ARM64"
- name: Build VS Extensions (for 2017-2019 and 2022)
if: matrix.configuration == 'release'

450
ICSharpCode.Decompiler.Tests/TestCases/Pretty/PatternMatching.cs

@ -4,6 +4,35 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty @@ -4,6 +4,35 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty
{
public class PatternMatching
{
public class X
{
public int? NullableIntField;
public S? NullableCustomStructField;
public int I { get; set; }
public string Text { get; set; }
public object Obj { get; set; }
public S CustomStruct { get; set; }
public int? NullableIntProp { get; set; }
public S? NullableCustomStructProp { get; set; }
}
public struct S
{
public int I;
public string Text { get; set; }
public object Obj { get; set; }
public S2 S2 { get; set; }
}
public struct S2
{
public int I;
public float F;
public decimal D;
public string Text { get; set; }
public object Obj { get; set; }
}
public void SimpleTypePattern(object x)
{
if (x is string value)
@ -303,6 +332,427 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty @@ -303,6 +332,427 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty
}
}
#if CS80
public void RecursivePattern_Type(object x)
{
if (x is X { Obj: string obj })
{
Console.WriteLine("Test " + obj);
}
else
{
Console.WriteLine("not Test");
}
}
public void RecursivePattern_TypeAndConst(object x)
{
if (x is X { Obj: string obj, I: 42 })
{
Console.WriteLine("Test " + obj);
}
else
{
Console.WriteLine("not Test");
}
}
public void RecursivePattern_Constant(object obj)
{
if (obj is X { Obj: null } x)
{
Console.WriteLine("Test " + x);
}
else
{
Console.WriteLine("not Test");
}
}
public void RecursivePattern_StringConstant(object obj)
{
if (obj is X { Text: "Hello" } x)
{
Console.WriteLine("Test " + x);
}
else
{
Console.WriteLine("not Test");
}
}
public void RecursivePattern_MultipleConstants(object obj)
{
if (obj is X { I: 42, Text: "Hello" } x)
{
Console.WriteLine("Test " + x);
}
else
{
Console.WriteLine("not Test");
}
}
public void RecursivePattern_ValueTypeWithField(object obj)
{
if (obj is S { I: 42 } s)
{
Console.WriteLine("Test " + s);
}
else
{
Console.WriteLine("not Test");
}
}
public void RecursivePattern_MultipleConstantsMixedWithVar(object x)
{
if (x is X { I: 42, Obj: var obj, Text: "Hello" })
{
Console.WriteLine("Test " + obj);
}
else
{
Console.WriteLine("not Test");
}
}
public void RecursivePattern_NonTypePattern(object obj)
{
if (obj is X { I: 42, Text: { Length: 0 } } x)
{
Console.WriteLine("Test " + x);
}
else
{
Console.WriteLine("not Test");
}
}
public void RecursivePatternValueType_NonTypePatternTwoProps(object obj)
{
if (obj is X { I: 42, CustomStruct: { I: 0, Text: "Test" } } x)
{
Console.WriteLine("Test " + x);
}
else
{
Console.WriteLine("not Test");
}
}
public void RecursivePattern_NonTypePatternNotNull(object o)
{
if (o is X { I: 42, Text: not null, Obj: var obj } x)
{
Console.WriteLine("Test " + x.I + " " + obj.GetType());
}
else
{
Console.WriteLine("not Test");
}
}
public void RecursivePattern_VarLengthPattern(object obj)
{
if (obj is X { I: 42, Text: { Length: var length } } x)
{
Console.WriteLine("Test " + x.I + ": " + length);
}
else
{
Console.WriteLine("not Test");
}
}
public void RecursivePatternValueType_VarLengthPattern(object obj)
{
if (obj is S { I: 42, Text: { Length: var length } } s)
{
Console.WriteLine("Test " + s.I + ": " + length);
}
else
{
Console.WriteLine("not Test");
}
}
public void RecursivePatternValueType_VarLengthPattern_SwappedProps(object obj)
{
if (obj is S { Text: { Length: var length }, I: 42 } s)
{
Console.WriteLine("Test " + s.I + ": " + length);
}
else
{
Console.WriteLine("not Test");
}
}
public void RecursivePattern_VarLengthPattern_SwappedProps(object obj)
{
if (obj is X { Text: { Length: var length }, I: 42 } x)
{
Console.WriteLine("Test " + x.I + ": " + length);
}
else
{
Console.WriteLine("not Test");
}
}
public void RecursivePattern_NullableIntField_Const(object obj)
{
if (obj is X { NullableIntField: 42 } x)
{
Console.WriteLine("Test " + x);
}
else
{
Console.WriteLine("not Test");
}
}
public void RecursivePattern_NullableIntField_Null(object obj)
{
if (obj is X { NullableIntField: null } x)
{
Console.WriteLine("Test " + x);
}
else
{
Console.WriteLine("not Test");
}
}
public void RecursivePattern_NullableIntField_NotNull(object obj)
{
if (obj is X { NullableIntField: not null } x)
{
Console.WriteLine("Test " + x);
}
else
{
Console.WriteLine("not Test");
}
}
public void RecursivePattern_NullableIntField_Var(object obj)
{
if (obj is X { NullableIntField: var nullableIntField })
{
Console.WriteLine("Test " + nullableIntField.Value);
}
else
{
Console.WriteLine("not Test");
}
}
public void RecursivePattern_NullableIntProp_Const(object obj)
{
if (obj is X { NullableIntProp: 42 } x)
{
Console.WriteLine("Test " + x);
}
else
{
Console.WriteLine("not Test");
}
}
public void RecursivePattern_NullableIntProp_Null(object obj)
{
if (obj is X { NullableIntProp: null } x)
{
Console.WriteLine("Test " + x);
}
else
{
Console.WriteLine("not Test");
}
}
public void RecursivePattern_NullableIntProp_NotNull(object obj)
{
if (obj is X { NullableIntProp: not null } x)
{
Console.WriteLine("Test " + x);
}
else
{
Console.WriteLine("not Test");
}
}
public void RecursivePattern_NullableIntProp_Var(object obj)
{
if (obj is X { NullableIntProp: var nullableIntProp })
{
Console.WriteLine("Test " + nullableIntProp.Value);
}
else
{
Console.WriteLine("not Test");
}
}
public void RecursivePattern_NullableCustomStructField_Const(object obj)
{
if (obj is X { NullableCustomStructField: { I: 42, Obj: not null } nullableCustomStructField })
{
Console.WriteLine("Test " + nullableCustomStructField.I);
}
else
{
Console.WriteLine("not Test");
}
}
public void RecursivePattern_NullableCustomStructField_Null(object obj)
{
if (obj is X { NullableCustomStructField: null } x)
{
Console.WriteLine("Test " + x);
}
else
{
Console.WriteLine("not Test");
}
}
public void RecursivePattern_NullableCustomStructField_NotNull(object obj)
{
if (obj is X { NullableCustomStructField: not null } x)
{
Console.WriteLine("Test " + x);
}
else
{
Console.WriteLine("not Test");
}
}
public void RecursivePattern_NullableCustomStructField_Var(object obj)
{
if (obj is X { NullableCustomStructField: var nullableCustomStructField, Obj: null })
{
Console.WriteLine("Test " + nullableCustomStructField.Value);
}
else
{
Console.WriteLine("not Test");
}
}
public void RecursivePattern_NullableCustomStructProp_Const(object obj)
{
if (obj is X { NullableCustomStructProp: { I: 42, Obj: not null } nullableCustomStructProp })
{
Console.WriteLine("Test " + nullableCustomStructProp.Text);
}
else
{
Console.WriteLine("not Test");
}
}
public void RecursivePattern_NullableCustomStructProp_Null(object obj)
{
if (obj is X { NullableCustomStructProp: null } x)
{
Console.WriteLine("Test " + x);
}
else
{
Console.WriteLine("not Test");
}
}
public void RecursivePattern_NullableCustomStructProp_NotNull(object obj)
{
if (obj is X { NullableCustomStructProp: not null } x)
{
Console.WriteLine("Test " + x);
}
else
{
Console.WriteLine("not Test");
}
}
public void RecursivePattern_NullableCustomStructProp_Var(object obj)
{
if (obj is X { NullableCustomStructProp: var nullableCustomStructProp, Obj: null })
{
Console.WriteLine("Test " + nullableCustomStructProp.Value);
}
else
{
Console.WriteLine("not Test");
}
}
public void RecursivePattern_CustomStructNested_Null(object obj)
{
if (obj is S { S2: { Obj: null } })
{
Console.WriteLine("Test " + obj);
}
else
{
Console.WriteLine("not Test");
}
}
public void RecursivePattern_CustomStructNested_TextLengthZero(object obj)
{
if (obj is S { S2: { Text: { Length: 0 } } })
{
Console.WriteLine("Test " + obj);
}
else
{
Console.WriteLine("not Test");
}
}
public void RecursivePattern_CustomStructNested_EmptyString(object obj)
{
if (obj is S { S2: { Text: "" } })
{
Console.WriteLine("Test " + obj);
}
else
{
Console.WriteLine("not Test");
}
}
public void RecursivePattern_CustomStructNested_Float(object obj)
{
if (obj is S { S2: { F: 3.141f, Obj: null } })
{
Console.WriteLine("Test " + obj);
}
else
{
Console.WriteLine("not Test");
}
}
public void RecursivePattern_CustomStructNested_Decimal(object obj)
{
if (obj is S { S2: { D: 3.141m, Obj: null } })
{
Console.WriteLine("Test " + obj);
}
else
{
Console.WriteLine("not Test");
}
}
#endif
private bool F()
{
return true;

97
ICSharpCode.Decompiler/CSharp/ExpressionBuilder.cs

@ -4546,40 +4546,121 @@ namespace ICSharpCode.Decompiler.CSharp @@ -4546,40 +4546,121 @@ 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)
{
case MatchInstruction matchInstruction:
if (!matchInstruction.CheckType)
throw new NotImplementedException();
if (matchInstruction.IsDeconstructCall)
throw new NotImplementedException();
if (matchInstruction.IsDeconstructTuple)
throw new NotImplementedException();
if (matchInstruction.SubPatterns.Any())
throw new NotImplementedException();
if (matchInstruction.HasDesignator)
if (matchInstruction.SubPatterns.Count > 0 || (matchInstruction.CheckNotNull && !matchInstruction.CheckType))
{
RecursivePatternExpression recursivePatternExpression = new();
if (matchInstruction.CheckType)
{
recursivePatternExpression.Type = ConvertType(matchInstruction.Variable.Type);
}
else
{
Debug.Assert(matchInstruction.CheckNotNull || matchInstruction.Variable.Type.IsReferenceType == false);
}
foreach (var subPattern in matchInstruction.SubPatterns)
{
if (!MatchInstruction.IsPatternMatch(subPattern, out var testedOperand, settings))
{
Debug.Fail("Invalid sub pattern");
continue;
}
IMember member;
if (testedOperand is CallInstruction call)
{
member = call.Method.AccessorOwner;
}
else if (testedOperand.MatchLdFld(out _, out var f))
{
member = f;
}
else
{
Debug.Fail("Invalid sub pattern");
continue;
}
recursivePatternExpression.SubPatterns.Add(
new NamedArgumentExpression { Name = member.Name, Expression = TranslatePattern(subPattern, member.ReturnType) }
.WithRR(new MemberResolveResult(null, member))
);
}
if (matchInstruction.HasDesignator)
{
SingleVariableDesignation designator = new SingleVariableDesignation { Identifier = matchInstruction.Variable.Name };
designator.AddAnnotation(new ILVariableResolveResult(matchInstruction.Variable));
recursivePatternExpression.Designation = designator;
}
return recursivePatternExpression.WithILInstruction(matchInstruction);
}
else if (matchInstruction.HasDesignator || !matchInstruction.CheckType)
{
SingleVariableDesignation designator = new SingleVariableDesignation { Identifier = matchInstruction.Variable.Name };
designator.AddAnnotation(new ILVariableResolveResult(matchInstruction.Variable));
AstType type;
if (matchInstruction.CheckType)
{
type = ConvertType(matchInstruction.Variable.Type);
}
else
{
Debug.Assert(matchInstruction.IsVar);
type = new SimpleType("var");
}
return new DeclarationExpression {
Type = ConvertType(matchInstruction.Variable.Type),
Type = type,
Designation = designator
}.WithILInstruction(matchInstruction);
}
else
{
Debug.Assert(matchInstruction.CheckType);
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);
}
case Call call when MatchInstruction.IsCallToOpEquality(call, KnownTypeCode.String):
return Translate(call.Arguments[1]);
case Call call when MatchInstruction.IsCallToOpEquality(call, KnownTypeCode.Decimal):
return Translate(call.Arguments[1]);
default:
throw new NotImplementedException();
}

36
ICSharpCode.Decompiler/CSharp/OutputVisitor/CSharpOutputVisitor.cs

@ -948,6 +948,40 @@ namespace ICSharpCode.Decompiler.CSharp.OutputVisitor @@ -948,6 +948,40 @@ namespace ICSharpCode.Decompiler.CSharp.OutputVisitor
EndNode(declarationExpression);
}
public virtual void VisitRecursivePatternExpression(RecursivePatternExpression recursivePatternExpression)
{
StartNode(recursivePatternExpression);
recursivePatternExpression.Type.AcceptVisitor(this);
Space();
if (recursivePatternExpression.IsPositional)
{
WriteToken(Roles.LPar);
}
else
{
WriteToken(Roles.LBrace);
}
Space();
WriteCommaSeparatedList(recursivePatternExpression.SubPatterns);
Space();
if (recursivePatternExpression.IsPositional)
{
WriteToken(Roles.RPar);
}
else
{
WriteToken(Roles.RBrace);
}
if (!recursivePatternExpression.Designation.IsNull)
{
Space();
recursivePatternExpression.Designation.AcceptVisitor(this);
}
EndNode(recursivePatternExpression);
}
public virtual void VisitOutVarDeclarationExpression(OutVarDeclarationExpression outVarDeclarationExpression)
{
StartNode(outVarDeclarationExpression);
@ -1268,7 +1302,7 @@ namespace ICSharpCode.Decompiler.CSharp.OutputVisitor @@ -1268,7 +1302,7 @@ namespace ICSharpCode.Decompiler.CSharp.OutputVisitor
StartNode(unaryOperatorExpression);
UnaryOperatorType opType = unaryOperatorExpression.Operator;
var opSymbol = UnaryOperatorExpression.GetOperatorRole(opType);
if (opType == UnaryOperatorType.Await)
if (opType is UnaryOperatorType.Await or UnaryOperatorType.PatternNot)
{
WriteKeyword(opSymbol);
Space();

15
ICSharpCode.Decompiler/CSharp/Syntax/DepthFirstAstVisitor.cs

@ -512,6 +512,11 @@ namespace ICSharpCode.Decompiler.CSharp.Syntax @@ -512,6 +512,11 @@ namespace ICSharpCode.Decompiler.CSharp.Syntax
VisitChildren(declarationExpression);
}
public virtual void VisitRecursivePatternExpression(RecursivePatternExpression recursivePatternExpression)
{
VisitChildren(recursivePatternExpression);
}
public virtual void VisitOutVarDeclarationExpression(OutVarDeclarationExpression outVarDeclarationExpression)
{
VisitChildren(outVarDeclarationExpression);
@ -1190,6 +1195,11 @@ namespace ICSharpCode.Decompiler.CSharp.Syntax @@ -1190,6 +1195,11 @@ namespace ICSharpCode.Decompiler.CSharp.Syntax
return VisitChildren(declarationExpression);
}
public virtual T VisitRecursivePatternExpression(RecursivePatternExpression recursivePatternExpression)
{
return VisitChildren(recursivePatternExpression);
}
public virtual T VisitOutVarDeclarationExpression(OutVarDeclarationExpression outVarDeclarationExpression)
{
return VisitChildren(outVarDeclarationExpression);
@ -1868,6 +1878,11 @@ namespace ICSharpCode.Decompiler.CSharp.Syntax @@ -1868,6 +1878,11 @@ namespace ICSharpCode.Decompiler.CSharp.Syntax
return VisitChildren(declarationExpression, data);
}
public virtual S VisitRecursivePatternExpression(RecursivePatternExpression recursivePatternExpression, T data)
{
return VisitChildren(recursivePatternExpression, data);
}
public virtual S VisitOutVarDeclarationExpression(OutVarDeclarationExpression outVarDeclarationExpression, T data)
{
return VisitChildren(outVarDeclarationExpression, data);

4
ICSharpCode.Decompiler/CSharp/Syntax/Expressions/DeclarationExpression.cs

@ -52,7 +52,9 @@ namespace ICSharpCode.Decompiler.CSharp.Syntax @@ -52,7 +52,9 @@ namespace ICSharpCode.Decompiler.CSharp.Syntax
protected internal override bool DoMatch(AstNode other, Match match)
{
return other is DeclarationExpression o && Designation.DoMatch(o.Designation, match);
return other is DeclarationExpression o
&& Type.DoMatch(o.Type, match)
&& Designation.DoMatch(o.Designation, match);
}
}
}

67
ICSharpCode.Decompiler/CSharp/Syntax/Expressions/RecursivePatternExpression.cs

@ -0,0 +1,67 @@ @@ -0,0 +1,67 @@
// Copyright (c) 2023 Daniel Grunwald
//
// Permission is hereby granted, free of charge, to any person obtaining a copy of this
// software and associated documentation files (the "Software"), to deal in the Software
// without restriction, including without limitation the rights to use, copy, modify, merge,
// publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons
// to whom the Software is furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all copies or
// substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
// PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE
// FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
// DEALINGS IN THE SOFTWARE.
using ICSharpCode.Decompiler.CSharp.Syntax.PatternMatching;
namespace ICSharpCode.Decompiler.CSharp.Syntax
{
public class RecursivePatternExpression : Expression
{
public static readonly Role<Expression> SubPatternRole = new Role<Expression>("SubPattern", Syntax.Expression.Null);
public AstType Type {
get { return GetChildByRole(Roles.Type); }
set { SetChildByRole(Roles.Type, value); }
}
public AstNodeCollection<Expression> SubPatterns {
get { return GetChildrenByRole(SubPatternRole); }
}
public VariableDesignation Designation {
get { return GetChildByRole(Roles.VariableDesignationRole); }
set { SetChildByRole(Roles.VariableDesignationRole, value); }
}
public bool IsPositional { get; set; }
public override void AcceptVisitor(IAstVisitor visitor)
{
visitor.VisitRecursivePatternExpression(this);
}
public override T AcceptVisitor<T>(IAstVisitor<T> visitor)
{
return visitor.VisitRecursivePatternExpression(this);
}
public override S AcceptVisitor<T, S>(IAstVisitor<T, S> visitor, T data)
{
return visitor.VisitRecursivePatternExpression(this, data);
}
protected internal override bool DoMatch(AstNode other, Match match)
{
return other is RecursivePatternExpression o
&& Type.DoMatch(o.Type, match)
&& IsPositional == o.IsPositional
&& SubPatterns.DoMatch(o.SubPatterns, match)
&& Designation.DoMatch(o.Designation, match);
}
}
}

36
ICSharpCode.Decompiler/CSharp/Syntax/Expressions/UnaryOperatorExpression.cs

@ -46,6 +46,7 @@ namespace ICSharpCode.Decompiler.CSharp.Syntax @@ -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 @@ -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 @@ -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 @@ -216,5 +232,25 @@ namespace ICSharpCode.Decompiler.CSharp.Syntax
/// C# 8 prefix ^ operator
/// </summary>
IndexFromEnd,
/// <summary>
/// C# 9 not pattern
/// </summary>
PatternNot,
/// <summary>
/// C# 9 relational pattern
/// </summary>
PatternRelationalLessThan,
/// <summary>
/// C# 9 relational pattern
/// </summary>
PatternRelationalLessThanOrEqual,
/// <summary>
/// C# 9 relational pattern
/// </summary>
PatternRelationalGreaterThan,
/// <summary>
/// C# 9 relational pattern
/// </summary>
PatternRelationalGreaterThanOrEqual,
}
}

3
ICSharpCode.Decompiler/CSharp/Syntax/IAstVisitor.cs

@ -36,6 +36,7 @@ namespace ICSharpCode.Decompiler.CSharp.Syntax @@ -36,6 +36,7 @@ namespace ICSharpCode.Decompiler.CSharp.Syntax
void VisitCheckedExpression(CheckedExpression checkedExpression);
void VisitConditionalExpression(ConditionalExpression conditionalExpression);
void VisitDeclarationExpression(DeclarationExpression declarationExpression);
void VisitRecursivePatternExpression(RecursivePatternExpression recursivePatternExpression);
void VisitDefaultValueExpression(DefaultValueExpression defaultValueExpression);
void VisitDirectionExpression(DirectionExpression directionExpression);
void VisitIdentifierExpression(IdentifierExpression identifierExpression);
@ -184,6 +185,7 @@ namespace ICSharpCode.Decompiler.CSharp.Syntax @@ -184,6 +185,7 @@ namespace ICSharpCode.Decompiler.CSharp.Syntax
S VisitCheckedExpression(CheckedExpression checkedExpression);
S VisitConditionalExpression(ConditionalExpression conditionalExpression);
S VisitDeclarationExpression(DeclarationExpression declarationExpression);
S VisitRecursivePatternExpression(RecursivePatternExpression recursivePatternExpression);
S VisitDefaultValueExpression(DefaultValueExpression defaultValueExpression);
S VisitDirectionExpression(DirectionExpression directionExpression);
S VisitIdentifierExpression(IdentifierExpression identifierExpression);
@ -332,6 +334,7 @@ namespace ICSharpCode.Decompiler.CSharp.Syntax @@ -332,6 +334,7 @@ namespace ICSharpCode.Decompiler.CSharp.Syntax
S VisitCheckedExpression(CheckedExpression checkedExpression, T data);
S VisitConditionalExpression(ConditionalExpression conditionalExpression, T data);
S VisitDeclarationExpression(DeclarationExpression declarationExpression, T data);
S VisitRecursivePatternExpression(RecursivePatternExpression recursivePatternExpression, T data);
S VisitDefaultValueExpression(DefaultValueExpression defaultValueExpression, T data);
S VisitDirectionExpression(DirectionExpression directionExpression, T data);
S VisitIdentifierExpression(IdentifierExpression identifierExpression, T data);

62
ICSharpCode.Decompiler/DecompilerSettings.cs

@ -130,6 +130,7 @@ namespace ICSharpCode.Decompiler @@ -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 @@ -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 @@ -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 @@ -1678,6 +1682,60 @@ namespace ICSharpCode.Decompiler
}
}
bool recursivePatternMatching = true;
/// <summary>
/// Gets/Sets whether C# 8.0 recursive patterns should be detected.
/// </summary>
[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;
/// <summary>
/// Gets/Sets whether C# 9.0 and, or, not patterns should be detected.
/// </summary>
[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;
/// <summary>
/// Gets/Sets whether C# 9.0 relational patterns should be detected.
/// </summary>
[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;
/// <summary>

4
ICSharpCode.Decompiler/ICSharpCode.Decompiler.csproj

@ -27,7 +27,7 @@ @@ -27,7 +27,7 @@
<GenerateAssemblyInformationalVersionAttribute>False</GenerateAssemblyInformationalVersionAttribute>
<EnableDefaultItems>false</EnableDefaultItems>
<LangVersion>10</LangVersion>
<LangVersion>11</LangVersion>
<GenerateDocumentationFile>true</GenerateDocumentationFile>
<SignAssembly>True</SignAssembly>
<AssemblyOriginatorKeyFile>ICSharpCode.Decompiler.snk</AssemblyOriginatorKeyFile>
@ -91,6 +91,7 @@ @@ -91,6 +91,7 @@
<Compile Include="CSharp\Annotations.cs" />
<Compile Include="CSharp\CallBuilder.cs" />
<Compile Include="CSharp\CSharpLanguageVersion.cs" />
<Compile Include="CSharp\Syntax\Expressions\RecursivePatternExpression.cs" />
<Compile Include="DecompilationProgress.cs" />
<Compile Include="Disassembler\IEntityProcessor.cs" />
<Compile Include="Disassembler\SortByNameProcessor.cs" />
@ -139,6 +140,7 @@ @@ -139,6 +140,7 @@
<Compile Include="Metadata\ReferenceLoadInfo.cs" />
<Compile Include="Properties\DecompilerVersionInfo.cs" />
<Compile Include="TypeSystem\ITypeDefinitionOrUnknown.cs" />
<Compile Include="Util\Index.cs" />
<None Include="Properties\DecompilerVersionInfo.template.cs" />
<Compile Include="Semantics\OutVarResolveResult.cs" />
<Compile Include="SingleFileBundle.cs" />

2
ICSharpCode.Decompiler/IL/Instructions/Comp.cs

@ -130,7 +130,7 @@ namespace ICSharpCode.Decompiler.IL @@ -130,7 +130,7 @@ namespace ICSharpCode.Decompiler.IL
}
}
public readonly ComparisonLiftingKind LiftingKind;
public ComparisonLiftingKind LiftingKind;
/// <summary>
/// Gets the stack type of the comparison inputs.

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

@ -22,6 +22,7 @@ using System.Diagnostics; @@ -22,6 +22,7 @@ using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using ICSharpCode.Decompiler.TypeSystem;
namespace ICSharpCode.Decompiler.IL
{
partial class MatchInstruction : ILInstruction
@ -118,7 +119,7 @@ namespace ICSharpCode.Decompiler.IL @@ -118,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.
/// </summary>
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)
{
@ -126,22 +127,45 @@ namespace ICSharpCode.Decompiler.IL @@ -126,22 +127,45 @@ 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, settings))
{
return IsPatternMatch(operand, out testedOperand);
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 IsCallToOpEquality(call, KnownTypeCode.String):
testedOperand = call.Arguments[0];
return call.Arguments[1].OpCode == OpCode.LdStr;
case Call call when IsCallToOpEquality(call, KnownTypeCode.Decimal):
testedOperand = call.Arguments[0];
return call.Arguments[1].OpCode == OpCode.LdcDecimal;
default:
testedOperand = null;
return false;
}
}
private static bool IsConstant(ILInstruction inst)
internal static bool IsCallToOpEquality(Call call, KnownTypeCode knownType)
{
return call.Method.IsOperator && call.Method.Name == "op_Equality"
&& call.Method.DeclaringType.IsKnownType(knownType)
&& call.Arguments.Count == 2;
}
internal static bool IsConstant(ILInstruction inst)
{
return inst.OpCode switch {
OpCode.LdcDecimal => true,
@ -150,7 +174,6 @@ namespace ICSharpCode.Decompiler.IL @@ -150,7 +174,6 @@ namespace ICSharpCode.Decompiler.IL
OpCode.LdcI4 => true,
OpCode.LdcI8 => true,
OpCode.LdNull => true,
OpCode.LdStr => true,
_ => false
};
}
@ -191,7 +214,7 @@ namespace ICSharpCode.Decompiler.IL @@ -191,7 +214,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;
@ -202,12 +225,12 @@ namespace ICSharpCode.Decompiler.IL @@ -202,12 +225,12 @@ namespace ICSharpCode.Decompiler.IL
}
else if (operand.MatchLdFld(out var target, out _))
{
Debug.Assert(target.MatchLdLoc(variable));
Debug.Assert(target.MatchLdLocRef(variable));
}
else if (operand is CallInstruction call)
{
Debug.Assert(call.Method.AccessorKind == System.Reflection.MethodSemanticsAttributes.Getter);
Debug.Assert(call.Arguments[0].MatchLdLoc(variable));
Debug.Assert(call.Arguments[0].MatchLdLocRef(variable));
}
else
{

21
ICSharpCode.Decompiler/IL/Transforms/AssignVariableNames.cs

@ -432,12 +432,25 @@ namespace ICSharpCode.Decompiler.IL.Transforms @@ -432,12 +432,25 @@ namespace ICSharpCode.Decompiler.IL.Transforms
}
if (string.IsNullOrEmpty(proposedName))
{
var proposedNameForStores = variable.StoreInstructions.OfType<StLoc>()
.Select(expr => GetNameFromInstruction(expr.Value))
.Except(currentLowerCaseTypeOrMemberNames).ToList();
var proposedNameForStores = new HashSet<string>();
foreach (var store in variable.StoreInstructions)
{
if (store is StLoc stloc)
{
var name = GetNameFromInstruction(stloc.Value);
if (!currentLowerCaseTypeOrMemberNames.Contains(name))
proposedNameForStores.Add(name);
}
else if (store is MatchInstruction match && match.SlotInfo == MatchInstruction.SubPatternsSlot)
{
var name = GetNameFromInstruction(match.TestedOperand);
if (!currentLowerCaseTypeOrMemberNames.Contains(name))
proposedNameForStores.Add(name);
}
}
if (proposedNameForStores.Count == 1)
{
proposedName = proposedNameForStores[0];
proposedName = proposedNameForStores.Single();
}
}
if (string.IsNullOrEmpty(proposedName))

50
ICSharpCode.Decompiler/IL/Transforms/EarlyExpressionTransforms.cs

@ -172,5 +172,55 @@ namespace ICSharpCode.Decompiler.IL.Transforms @@ -172,5 +172,55 @@ namespace ICSharpCode.Decompiler.IL.Transforms
temp.ReplaceWith(replacement);
}
}
protected internal override void VisitNewObj(NewObj inst)
{
if (TransformDecimalCtorToConstant(inst, out LdcDecimal decimalConstant))
{
context.Step("TransformDecimalCtorToConstant", inst);
inst.ReplaceWith(decimalConstant);
return;
}
base.VisitNewObj(inst);
}
bool TransformDecimalCtorToConstant(NewObj inst, out LdcDecimal result)
{
IType t = inst.Method.DeclaringType;
result = null;
if (!t.IsKnownType(KnownTypeCode.Decimal))
return false;
var args = inst.Arguments;
if (args.Count == 1)
{
long val;
if (args[0].MatchLdcI(out val))
{
var paramType = inst.Method.Parameters[0].Type.GetDefinition()?.KnownTypeCode;
result = paramType switch {
KnownTypeCode.Int32 => new LdcDecimal(new decimal(unchecked((int)val))),
KnownTypeCode.UInt32 => new LdcDecimal(new decimal(unchecked((uint)val))),
KnownTypeCode.Int64 => new LdcDecimal(new decimal(val)),
KnownTypeCode.UInt64 => new LdcDecimal(new decimal(unchecked((ulong)val))),
_ => null
};
return result is not null;
}
}
else if (args.Count == 5)
{
int lo, mid, hi, isNegative, scale;
if (args[0].MatchLdcI4(out lo) && args[1].MatchLdcI4(out mid) &&
args[2].MatchLdcI4(out hi) && args[3].MatchLdcI4(out isNegative) &&
args[4].MatchLdcI4(out scale))
{
result = new LdcDecimal(new decimal(lo, mid, hi, isNegative != 0, (byte)scale));
return true;
}
}
return false;
}
}
}

45
ICSharpCode.Decompiler/IL/Transforms/ExpressionTransforms.cs

@ -286,12 +286,6 @@ namespace ICSharpCode.Decompiler.IL.Transforms @@ -286,12 +286,6 @@ namespace ICSharpCode.Decompiler.IL.Transforms
protected internal override void VisitNewObj(NewObj inst)
{
if (TransformDecimalCtorToConstant(inst, out LdcDecimal decimalConstant))
{
context.Step("TransformDecimalCtorToConstant", inst);
inst.ReplaceWith(decimalConstant);
return;
}
Block block;
if (TransformSpanTCtorContainingStackAlloc(inst, out ILInstruction locallocSpan))
{
@ -419,43 +413,6 @@ namespace ICSharpCode.Decompiler.IL.Transforms @@ -419,43 +413,6 @@ namespace ICSharpCode.Decompiler.IL.Transforms
return true;
}
bool TransformDecimalCtorToConstant(NewObj inst, out LdcDecimal result)
{
IType t = inst.Method.DeclaringType;
result = null;
if (!t.IsKnownType(KnownTypeCode.Decimal))
return false;
var args = inst.Arguments;
if (args.Count == 1)
{
long val;
if (args[0].MatchLdcI(out val))
{
var paramType = inst.Method.Parameters[0].Type.GetDefinition()?.KnownTypeCode;
result = paramType switch {
KnownTypeCode.Int32 => new LdcDecimal(new decimal(unchecked((int)val))),
KnownTypeCode.UInt32 => new LdcDecimal(new decimal(unchecked((uint)val))),
KnownTypeCode.Int64 => new LdcDecimal(new decimal(val)),
KnownTypeCode.UInt64 => new LdcDecimal(new decimal(unchecked((ulong)val))),
_ => null
};
return result is not null;
}
}
else if (args.Count == 5)
{
int lo, mid, hi, isNegative, scale;
if (args[0].MatchLdcI4(out lo) && args[1].MatchLdcI4(out mid) &&
args[2].MatchLdcI4(out hi) && args[3].MatchLdcI4(out isNegative) &&
args[4].MatchLdcI4(out scale))
{
result = new LdcDecimal(new decimal(lo, mid, hi, isNegative != 0, (byte)scale));
return true;
}
}
return false;
}
bool TransformDecimalFieldToConstant(LdObj inst, out LdcDecimal result)
{
if (inst.MatchLdsFld(out var field) && field.DeclaringType.IsKnownType(KnownTypeCode.Decimal))
@ -557,7 +514,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms @@ -557,7 +514,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);

364
ICSharpCode.Decompiler/IL/Transforms/PatternMatchingTransform.cs

@ -19,9 +19,9 @@ @@ -19,9 +19,9 @@
#nullable enable
using System;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using System.Reflection;
using ICSharpCode.Decompiler.IL.ControlFlow;
using ICSharpCode.Decompiler.TypeSystem;
@ -39,7 +39,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms @@ -39,7 +39,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms
foreach (var container in function.Descendants.OfType<BlockContainer>())
{
ControlFlowGraph? cfg = null;
foreach (var block in container.Blocks)
foreach (var block in container.Blocks.Reverse())
{
if (PatternMatchValueTypes(block, container, context, ref cfg))
{
@ -50,6 +50,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms @@ -50,6 +50,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms
continue;
}
}
container.Blocks.RemoveAll(b => b.Instructions.Count == 0);
}
}
@ -179,10 +180,352 @@ namespace ICSharpCode.Decompiler.IL.Transforms @@ -179,10 +180,352 @@ namespace ICSharpCode.Decompiler.IL.Transforms
block.Instructions[block.Instructions.Count - 1] = falseInst;
block.Instructions.RemoveRange(pos, ifInst.ChildIndex - pos);
v.Kind = VariableKind.PatternLocal;
DetectPropertySubPatterns((MatchInstruction)ifInst.Condition, trueInst, falseInst, container, context, ref cfg);
return true;
}
private bool CheckAllUsesDominatedBy(ILVariable v, BlockContainer container, ILInstruction trueInst,
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;
if (!(trueBlock != null || trueInst.MatchBranch(out trueBlock)))
{
break;
}
if (!(trueBlock.IncomingEdgeCount == 1 && trueBlock.Parent == container))
{
break;
}
var nextTrueInst = DetectPropertySubPattern(parentPattern, trueBlock, parentFalseInst, context, ref cfg);
if (nextTrueInst != null)
{
trueInst = nextTrueInst;
}
else
{
break;
}
}
return trueInst;
}
private static ILInstruction? DetectPropertySubPattern(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 null;
}
ExtensionMethods.Swap(ref trueInst, ref falseInst);
negate = true;
}
if (MatchInstruction.IsPatternMatch(condition, out var operand, context.Settings))
{
if (!PropertyOrFieldAccess(operand, out var target, out _))
{
return null;
}
if (!target.MatchLdLocRef(parentPattern.Variable))
{
return null;
}
if (negate && !context.Settings.PatternCombinators)
{
return null;
}
context.Step("Move property sub pattern", condition);
if (negate)
{
condition = Comp.LogicNot(condition);
}
parentPattern.SubPatterns.Add(condition);
}
else if (PropertyOrFieldAccess(condition, out var target, out _))
{
if (!target.MatchLdLocRef(parentPattern.Variable))
{
return null;
}
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
{
return null;
}
block.Instructions.Clear();
block.Instructions.Add(trueInst);
return trueInst;
}
else if (block.Instructions[0].MatchStLoc(out var targetVariable, out var operand))
{
if (!PropertyOrFieldAccess(operand, out var target, out var member))
{
return null;
}
if (!target.MatchLdLocRef(parentPattern.Variable))
{
return null;
}
if (!targetVariable.Type.Equals(member.ReturnType))
{
return null;
}
if (!CheckAllUsesDominatedBy(targetVariable, (BlockContainer)block.Parent!, block, block.Instructions[0], null, context, ref cfg))
{
return null;
}
context.Step("Property var pattern", block);
var varPattern = new MatchInstruction(targetVariable, operand)
.WithILRange(block.Instructions[0]);
parentPattern.SubPatterns.Add(varPattern);
block.Instructions.RemoveAt(0);
targetVariable.Kind = VariableKind.PatternLocal;
if (targetVariable.Type.IsKnownType(KnownTypeCode.NullableOfT))
{
return MatchNullableHasValueCheckPattern(block, varPattern, parentFalseInst, context, ref cfg)
?? block;
}
var instructionAfterNullCheck = MatchNullCheckPattern(block, varPattern, parentFalseInst, context);
if (instructionAfterNullCheck != null)
{
return DetectPropertySubPatterns(varPattern, instructionAfterNullCheck, parentFalseInst, (BlockContainer)block.Parent!, context, ref cfg);
}
else if (targetVariable.Type.IsReferenceType == false)
{
return DetectPropertySubPatterns(varPattern, block, parentFalseInst, (BlockContainer)block.Parent!, context, ref cfg);
}
else
{
return block;
}
}
else
{
return null;
}
}
private static ILInstruction? MatchNullCheckPattern(Block block, MatchInstruction varPattern,
ILInstruction parentFalseInst, ILTransformContext context)
{
if (!MatchBlockContainingOneCondition(block, out var condition, out var trueInst, out var falseInst))
{
return null;
}
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 null;
}
if (!DetectExitPoints.CompatibleExitInstruction(falseInst, parentFalseInst))
{
return null;
}
context.Step("Null check pattern", block);
varPattern.CheckNotNull = true;
block.Instructions.Clear();
block.Instructions.Add(trueInst);
return trueInst;
}
private static ILInstruction? MatchNullableHasValueCheckPattern(Block block, MatchInstruction varPattern,
ILInstruction parentFalseInst, ILTransformContext context, ref ControlFlowGraph? cfg)
{
if (!(varPattern.Variable.StoreCount == 1 && varPattern.Variable.LoadCount == 0))
{
return null;
}
if (!MatchBlockContainingOneCondition(block, out var condition, out var trueInst, out var falseInst))
{
return null;
}
if (!NullableLiftingTransform.MatchHasValueCall(condition, varPattern.Variable))
{
return null;
}
if (!DetectExitPoints.CompatibleExitInstruction(falseInst, parentFalseInst))
{
if (DetectExitPoints.CompatibleExitInstruction(trueInst, parentFalseInst))
{
if (!(varPattern.Variable.AddressCount == 1))
{
return null;
}
context.Step("Nullable.HasValue check -> null pattern", block);
varPattern.ReplaceWith(new Comp(ComparisonKind.Equality, ComparisonLiftingKind.CSharp, StackType.O, Sign.None, varPattern.TestedOperand, new LdNull()));
block.Instructions.Clear();
block.Instructions.Add(falseInst);
return falseInst;
}
return null;
}
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()));
block.Instructions.Clear();
block.Instructions.Add(trueInst);
return trueInst;
}
else if (varPattern.Variable.AddressCount != 2)
{
return null;
}
if (!(trueInst.MatchBranch(out var trueBlock) && trueBlock.Parent == block.Parent && trueBlock.IncomingEdgeCount == 1))
{
return null;
}
if (trueBlock.Instructions[0].MatchStLoc(out var newTargetVariable, out var getValueOrDefaultCall)
&& NullableLiftingTransform.MatchGetValueOrDefault(getValueOrDefaultCall, varPattern.Variable))
{
context.Step("Nullable.HasValue check + Nullable.GetValueOrDefault pattern", block);
varPattern.CheckNotNull = true;
varPattern.Variable = newTargetVariable;
newTargetVariable.Kind = VariableKind.PatternLocal;
block.Instructions.Clear();
block.Instructions.Add(trueInst);
trueBlock.Instructions.RemoveAt(0);
return DetectPropertySubPatterns(varPattern, trueBlock, parentFalseInst, (BlockContainer)block.Parent!, context, ref cfg);
}
else if (MatchBlockContainingOneCondition(trueBlock, out condition, out trueInst, out falseInst))
{
if (!(condition is Comp comp
&& MatchInstruction.IsConstant(comp.Right)
&& NullableLiftingTransform.MatchGetValueOrDefault(comp.Left, varPattern.Variable)))
{
return null;
}
if (!(context.Settings.RelationalPatterns || comp.Kind is ComparisonKind.Equality or ComparisonKind.Inequality))
{
return null;
}
bool negated = false;
if (!DetectExitPoints.CompatibleExitInstruction(falseInst, parentFalseInst))
{
if (!DetectExitPoints.CompatibleExitInstruction(trueInst, parentFalseInst))
{
return null;
}
ExtensionMethods.Swap(ref trueInst, ref falseInst);
negated = true;
}
if (comp.Kind == (negated ? ComparisonKind.Equality : ComparisonKind.Inequality))
{
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)
// =>
// comp.i4.lifted(testedOperand != ldc.i4 42)
block.Instructions.Clear();
block.Instructions.Add(trueInst);
trueBlock.Instructions.Clear();
comp.Left = varPattern.TestedOperand;
comp.LiftingKind = ComparisonLiftingKind.CSharp;
if (negated)
{
comp = Comp.LogicNot(comp);
}
varPattern.ReplaceWith(comp);
return trueInst;
}
else
{
return null;
}
}
private static bool PropertyOrFieldAccess(ILInstruction operand, [NotNullWhen(true)] out ILInstruction? target, [NotNullWhen(true)] out IMember? member)
{
if (operand is CallInstruction {
Method: {
SymbolKind: SymbolKind.Accessor,
AccessorKind: MethodSemanticsAttributes.Getter,
AccessorOwner: { } _member
},
Arguments: [var _target]
})
{
target = _target;
member = _member;
return true;
}
else if (operand.MatchLdFld(out target, out var field))
{
member = field;
return true;
}
else
{
member = null;
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;
if (!block.Instructions[0].MatchStLoc(tempVar, out condition))
return false;
while (condition.MatchLogicNot(out var arg))
{
condition = arg;
ExtensionMethods.Swap(ref trueInst, ref falseInst);
}
return true;
default:
condition = null;
trueInst = null;
falseInst = null;
return false;
}
}
private static bool CheckAllUsesDominatedBy(ILVariable v, BlockContainer container, ILInstruction trueInst,
ILInstruction storeToV, ILInstruction? loadInNullCheck, ILTransformContext context, ref ControlFlowGraph? cfg)
{
var targetBlock = trueInst as Block;
@ -241,7 +584,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms @@ -241,7 +584,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms
private bool PatternMatchValueTypes(Block block, BlockContainer container, ILTransformContext context, ref ControlFlowGraph? cfg)
{
if (!MatchIsInstBlock(block, out var type, out var testedOperand, out var testedVariable,
out var boxType1, out var unboxBlock, out var falseBlock))
out var boxType1, out var unboxBlock, out var falseInst))
{
return false;
}
@ -279,8 +622,8 @@ namespace ICSharpCode.Decompiler.IL.Transforms @@ -279,8 +622,8 @@ namespace ICSharpCode.Decompiler.IL.Transforms
CheckNotNull = true,
CheckType = true
};
((Branch)ifInst.TrueInst).TargetBlock = unboxBlock;
((Branch)block.Instructions.Last()).TargetBlock = falseBlock;
ifInst.TrueInst = new Branch(unboxBlock);
block.Instructions[^1] = falseInst;
unboxBlock.Instructions.RemoveAt(0);
if (unboxOperand == tempStore?.Variable)
{
@ -291,6 +634,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms @@ -291,6 +634,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms
// the pattern matching machinery to an offset belonging to an instruction in the then-block.
unboxBlock.SetILRange(unboxBlock.Instructions[0]);
storeToV.Variable.Kind = VariableKind.PatternLocal;
DetectPropertySubPatterns((MatchInstruction)ifInst.Condition, unboxBlock, falseInst, container, context, ref cfg);
return true;
}
@ -307,15 +651,14 @@ namespace ICSharpCode.Decompiler.IL.Transforms @@ -307,15 +651,14 @@ namespace ICSharpCode.Decompiler.IL.Transforms
[NotNullWhen(true)] out ILVariable? testedVariable,
out IType? boxType,
[NotNullWhen(true)] out Block? unboxBlock,
[NotNullWhen(true)] out Block? falseBlock)
[NotNullWhen(true)] out ILInstruction? falseInst)
{
type = null;
testedOperand = null;
testedVariable = null;
boxType = null;
unboxBlock = null;
falseBlock = null;
if (!block.MatchIfAtEndOfBlock(out var condition, out var trueInst, out var falseInst))
if (!block.MatchIfAtEndOfBlock(out var condition, out var trueInst, out falseInst))
{
return false;
}
@ -343,8 +686,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms @@ -343,8 +686,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms
{
return false;
}
return trueInst.MatchBranch(out unboxBlock) && falseInst.MatchBranch(out falseBlock)
&& unboxBlock.Parent == block.Parent && falseBlock.Parent == block.Parent;
return trueInst.MatchBranch(out unboxBlock) && unboxBlock.Parent == block.Parent;
}
/// Block unboxBlock (incoming: 1) {

166
ICSharpCode.Decompiler/Util/Index.cs

@ -0,0 +1,166 @@ @@ -0,0 +1,166 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
#nullable enable
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Runtime.CompilerServices;
namespace System
{
/// <summary>Represent a type can be used to index a collection either from the start or the end.</summary>
/// <remarks>
/// Index is used by the C# compiler to support the new index syntax
/// <code>
/// int[] someArray = new int[5] { 1, 2, 3, 4, 5 } ;
/// int lastElement = someArray[^1]; // lastElement = 5
/// </code>
/// </remarks>
#if SYSTEM_PRIVATE_CORELIB
public
#else
internal
#endif
readonly struct Index : IEquatable<Index>
{
private readonly int _value;
/// <summary>Construct an Index using a value and indicating if the index is from the start or from the end.</summary>
/// <param name="value">The index value. it has to be zero or positive number.</param>
/// <param name="fromEnd">Indicating if the index is from the start or from the end.</param>
/// <remarks>
/// If the Index constructed from the end, index value 1 means pointing at the last element and index value 0 means pointing at beyond last element.
/// </remarks>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public Index(int value, bool fromEnd = false)
{
if (value < 0)
{
ThrowValueArgumentOutOfRange_NeedNonNegNumException();
}
if (fromEnd)
_value = ~value;
else
_value = value;
}
// The following private constructors mainly created for perf reason to avoid the checks
private Index(int value)
{
_value = value;
}
/// <summary>Create an Index pointing at first element.</summary>
public static Index Start => new Index(0);
/// <summary>Create an Index pointing at beyond last element.</summary>
public static Index End => new Index(~0);
/// <summary>Create an Index from the start at the position indicated by the value.</summary>
/// <param name="value">The index value from the start.</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Index FromStart(int value)
{
if (value < 0)
{
ThrowValueArgumentOutOfRange_NeedNonNegNumException();
}
return new Index(value);
}
/// <summary>Create an Index from the end at the position indicated by the value.</summary>
/// <param name="value">The index value from the end.</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Index FromEnd(int value)
{
if (value < 0)
{
ThrowValueArgumentOutOfRange_NeedNonNegNumException();
}
return new Index(~value);
}
/// <summary>Returns the index value.</summary>
public int Value {
get {
if (_value < 0)
return ~_value;
else
return _value;
}
}
/// <summary>Indicates whether the index is from the start or the end.</summary>
public bool IsFromEnd => _value < 0;
/// <summary>Calculate the offset from the start using the giving collection length.</summary>
/// <param name="length">The length of the collection that the Index will be used with. length has to be a positive value</param>
/// <remarks>
/// For performance reason, we don't validate the input length parameter and the returned offset value against negative values.
/// we don't validate either the returned offset is greater than the input length.
/// It is expected Index will be used with collections which always have non negative length/count. If the returned offset is negative and
/// then used to index a collection will get out of range exception which will be same affect as the validation.
/// </remarks>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public int GetOffset(int length)
{
int offset = _value;
if (IsFromEnd)
{
// offset = length - (~value)
// offset = length + (~(~value) + 1)
// offset = length + value + 1
offset += length + 1;
}
return offset;
}
/// <summary>Indicates whether the current Index object is equal to another object of the same type.</summary>
/// <param name="value">An object to compare with this object</param>
public override bool Equals([NotNullWhen(true)] object? value) => value is Index && _value == ((Index)value)._value;
/// <summary>Indicates whether the current Index object is equal to another Index object.</summary>
/// <param name="other">An object to compare with this object</param>
public bool Equals(Index other) => _value == other._value;
/// <summary>Returns the hash code for this instance.</summary>
public override int GetHashCode() => _value;
/// <summary>Converts integer number to an Index.</summary>
public static implicit operator Index(int value) => FromStart(value);
/// <summary>Converts the value of the current Index object to its equivalent string representation.</summary>
public override string ToString()
{
if (IsFromEnd)
return ToStringFromEnd();
return ((uint)Value).ToString();
}
private static void ThrowValueArgumentOutOfRange_NeedNonNegNumException()
{
#if SYSTEM_PRIVATE_CORELIB
throw new ArgumentOutOfRangeException("value", SR.ArgumentOutOfRange_NeedNonNegNum);
#else
throw new ArgumentOutOfRangeException("value", "value must be non-negative");
#endif
}
private string ToStringFromEnd()
{
#if (!NETSTANDARD2_0 && !NETFRAMEWORK)
Span<char> span = stackalloc char[11]; // 1 for ^ and 10 for longest possible uint value
bool formatted = ((uint)Value).TryFormat(span.Slice(1), out int charsWritten);
Debug.Assert(formatted);
span[0] = '^';
return new string(span.Slice(0, charsWritten + 1));
#else
return '^' + Value.ToString();
#endif
}
}
}

6
ILSpy.Installer/ILSpy.Installer.csproj

@ -1,4 +1,4 @@ @@ -1,4 +1,4 @@
<Project Sdk="Microsoft.NET.Sdk">
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
@ -6,6 +6,10 @@ @@ -6,6 +6,10 @@
<StartupObject>ILSpy.Installer.Builder</StartupObject>
</PropertyGroup>
<PropertyGroup>
<DefineConstants>$(DefineConstants);$(PlatformForInstaller)</DefineConstants>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="WixSharp" Version="1.22.0" />
<PackageReference Include="WixSharp.wix.bin" Version="3.14.0" />

27
ILSpy/Properties/Resources.Designer.cs generated

@ -1145,6 +1145,15 @@ namespace ICSharpCode.ILSpy.Properties { @@ -1145,6 +1145,15 @@ namespace ICSharpCode.ILSpy.Properties {
}
}
/// <summary>
/// Looks up a localized string similar to Pattern combinators (and, or, not).
/// </summary>
public static string DecompilerSettings_PatternCombinators {
get {
return ResourceManager.GetString("DecompilerSettings.PatternCombinators", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Use pattern matching expressions.
/// </summary>
@ -1199,6 +1208,24 @@ namespace ICSharpCode.ILSpy.Properties { @@ -1199,6 +1208,24 @@ namespace ICSharpCode.ILSpy.Properties {
}
}
/// <summary>
/// Looks up a localized string similar to Recursive pattern matching.
/// </summary>
public static string DecompilerSettings_RecursivePatternMatching {
get {
return ResourceManager.GetString("DecompilerSettings.RecursivePatternMatching", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Relational patterns.
/// </summary>
public static string DecompilerSettings_RelationalPatterns {
get {
return ResourceManager.GetString("DecompilerSettings.RelationalPatterns", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Remove dead and side effect free code (use with caution!).
/// </summary>

9
ILSpy/Properties/Resources.resx

@ -405,6 +405,9 @@ Are you sure you want to continue?</value> @@ -405,6 +405,9 @@ Are you sure you want to continue?</value>
<data name="DecompilerSettings.ParameterNullCheck" xml:space="preserve">
<value>Use parameter null checking</value>
</data>
<data name="DecompilerSettings.PatternCombinators" xml:space="preserve">
<value>Pattern combinators (and, or, not)</value>
</data>
<data name="DecompilerSettings.PatternMatching" xml:space="preserve">
<value>Use pattern matching expressions</value>
</data>
@ -423,6 +426,12 @@ Are you sure you want to continue?</value> @@ -423,6 +426,12 @@ Are you sure you want to continue?</value>
<data name="DecompilerSettings.RecordStructs" xml:space="preserve">
<value>Record structs</value>
</data>
<data name="DecompilerSettings.RecursivePatternMatching" xml:space="preserve">
<value>Recursive pattern matching</value>
</data>
<data name="DecompilerSettings.RelationalPatterns" xml:space="preserve">
<value>Relational patterns</value>
</data>
<data name="DecompilerSettings.RemoveDeadAndSideEffectFreeCodeUseWithCaution" xml:space="preserve">
<value>Remove dead and side effect free code (use with caution!)</value>
</data>

Loading…
Cancel
Save