diff --git a/.github/workflows/build-ilspy.yml b/.github/workflows/build-ilspy.yml index 2b6b3ed2c..89291c79b 100644 --- a/.github/workflows/build-ilspy.yml +++ b/.github/workflows/build-ilspy.yml @@ -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' diff --git a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/PatternMatching.cs b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/PatternMatching.cs index 064db32bc..165e4e4d6 100644 --- a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/PatternMatching.cs +++ b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/PatternMatching.cs @@ -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) @@ -231,6 +260,499 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty #endif } + public void GenericTypePatternInt(T x) + { + if (x is int value) + { + Console.WriteLine(value); + } + else + { + Console.WriteLine("not an int"); + } + } + + public void GenericValueTypePatternInt(T x) where T : struct + { + if (x is int value) + { + Console.WriteLine(value); + } + else + { + Console.WriteLine("not an int"); + } + } + + public void GenericRefTypePatternInt(T x) where T : class + { + if (x is int value) + { + Console.WriteLine(value); + } + else + { + Console.WriteLine("not an int"); + } + } + + public void GenericTypePatternString(T x) + { + if (x is string value) + { + Console.WriteLine(value); + } + else + { + Console.WriteLine("not a string"); + } + } + + public void GenericRefTypePatternString(T x) where T : class + { + if (x is string value) + { + Console.WriteLine(value); + } + else + { + Console.WriteLine("not a string"); + } + } + + public void GenericValueTypePatternStringRequiresCastToObject(T x) where T : struct + { + if ((object)x is string value) + { + Console.WriteLine(value); + } + else + { + Console.WriteLine("not a string"); + } + } + +#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; diff --git a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/StaticAbstractInterfaceMembers.cs b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/StaticAbstractInterfaceMembers.cs index 553ea0e53..7d0b6ed5e 100644 --- a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/StaticAbstractInterfaceMembers.cs +++ b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/StaticAbstractInterfaceMembers.cs @@ -2,16 +2,151 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty.StaticAbstractInterfaceMembers { - public interface I + internal class C : I + { + private string _s; + + static C I.P { get; set; } + static event Action I.E { + add { + + } + remove { + + } + } + public C(string s) + { + _s = s; + } + + static void I.M(object x) + { + Console.WriteLine("Implementation"); + } + static C I.operator +(C l, C r) + { + return new C(l._s + " " + r._s); + } + + static bool I.operator ==(C l, C r) + { + return l._s == r._s; + } + + static bool I.operator !=(C l, C r) + { + return l._s != r._s; + } + + static implicit I.operator C(string s) + { + return new C(s); + } + + static explicit I.operator string(C c) + { + return c._s; + } + } + + internal interface I where T : I + { + static abstract T P { get; set; } + static abstract event Action E; + static abstract void M(object x); + static abstract T operator +(T l, T r); + static abstract bool operator ==(T l, T r); + static abstract bool operator !=(T l, T r); + static abstract implicit operator T(string s); + static abstract explicit operator string(T t); + } + + public interface IAmSimple { static abstract int Capacity { get; } static abstract int Count { get; set; } static abstract int SetterOnly { set; } static abstract event EventHandler E; - static abstract I CreateI(); + static abstract IAmSimple CreateI(); + } + + internal interface IAmStatic where T : IAmStatic + { + static int f; + static T P { get; set; } + static event Action E; + static void M(object x) + { + } + static IAmStatic operator +(IAmStatic l, IAmStatic r) + { + throw new NotImplementedException(); + } + static IAmStatic() + { + f = 42; + } + } + + internal interface IAmVirtual where T : IAmVirtual + { + static virtual T P { get; set; } + static virtual event Action E; + static virtual void M(object x) + { + } + static virtual T operator +(T l, T r) + { + throw new NotImplementedException(); + } + static virtual implicit operator T(string s) + { + return default(T); + } + static virtual explicit operator string(T t) + { + return null; + } + } + + internal class Uses + { + public static T TestVirtualStaticUse(T a, T b) where T : IAmVirtual + { + T.P = a; + a = "World"; + T.E += null; + T.E -= null; + T.M("Hello"); + UseString((string)b); + return a + b; + } + public static IAmStatic TestStaticUse(T a, T b) where T : IAmStatic + { + IAmStatic.f = 11; + IAmStatic.P = a; + IAmStatic.E += null; + IAmStatic.E -= null; + IAmStatic.M("Hello"); + return a + b; + } + public static I TestAbstractStaticUse(T a, T b) where T : I + { + T.P = a; + a = "World"; + T.E += null; + T.E -= null; + T.M("Hello"); + UseString((string)b); + return a + b; + } + private static void UseString(string a) + { + } } - public class X : I + public class X : IAmSimple { public static int Capacity { get; } @@ -24,13 +159,13 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty.StaticAbstractInterfaceM public static event EventHandler E; - public static I CreateI() + public static IAmSimple CreateI() { return new X(); } } - public class X2 : I + public class X2 : IAmSimple { public static int Capacity { get { @@ -61,7 +196,7 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty.StaticAbstractInterfaceM } } - public static I CreateI() + public static IAmSimple CreateI() { throw new NotImplementedException(); } diff --git a/ICSharpCode.Decompiler/CSharp/CSharpDecompiler.cs b/ICSharpCode.Decompiler/CSharp/CSharpDecompiler.cs index 266540cb9..bd9ecd13e 100644 --- a/ICSharpCode.Decompiler/CSharp/CSharpDecompiler.cs +++ b/ICSharpCode.Decompiler/CSharp/CSharpDecompiler.cs @@ -1585,7 +1585,7 @@ namespace ICSharpCode.Decompiler.CSharp var typeSystemAstBuilder = CreateAstBuilder(decompileRun.Settings); var methodDecl = typeSystemAstBuilder.ConvertEntity(method); int lastDot = method.Name.LastIndexOf('.'); - if (method.IsExplicitInterfaceImplementation && lastDot >= 0) + if (methodDecl is not OperatorDeclaration && method.IsExplicitInterfaceImplementation && lastDot >= 0) { methodDecl.Name = method.Name.Substring(lastDot + 1); } diff --git a/ICSharpCode.Decompiler/CSharp/ExpressionBuilder.cs b/ICSharpCode.Decompiler/CSharp/ExpressionBuilder.cs index 2001cbea1..b03f38b4a 100644 --- a/ICSharpCode.Decompiler/CSharp/ExpressionBuilder.cs +++ b/ICSharpCode.Decompiler/CSharp/ExpressionBuilder.cs @@ -4537,40 +4537,130 @@ namespace ICSharpCode.Decompiler.CSharp protected internal override TranslatedExpression VisitMatchInstruction(MatchInstruction inst, TranslationContext context) { var left = Translate(inst.TestedOperand); - var right = TranslatePattern(inst); + // remove boxing conversion if possible, however, we still need a cast in + // test case PatternMatching.GenericValueTypePatternStringRequiresCastToObject + if (left.ResolveResult is ConversionResolveResult crr + && crr.Conversion.IsBoxingConversion + && left.Expression is CastExpression castExpr + && (crr.Input.Type.IsReferenceType != false || inst.Variable.Type.IsReferenceType == false)) + { + left = left.UnwrapChild(castExpr.Expression); + } + 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(); } diff --git a/ICSharpCode.Decompiler/CSharp/OutputVisitor/CSharpOutputVisitor.cs b/ICSharpCode.Decompiler/CSharp/OutputVisitor/CSharpOutputVisitor.cs index 3e428e5ef..66e6a2acf 100644 --- a/ICSharpCode.Decompiler/CSharp/OutputVisitor/CSharpOutputVisitor.cs +++ b/ICSharpCode.Decompiler/CSharp/OutputVisitor/CSharpOutputVisitor.cs @@ -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 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(); @@ -2540,6 +2574,8 @@ namespace ICSharpCode.Decompiler.CSharp.OutputVisitor { operatorDeclaration.ReturnType.AcceptVisitor(this); } + Space(); + WritePrivateImplementationType(operatorDeclaration.PrivateImplementationType); WriteKeyword(OperatorDeclaration.OperatorKeywordRole); Space(); if (OperatorDeclaration.IsChecked(operatorDeclaration.OperatorType)) diff --git a/ICSharpCode.Decompiler/CSharp/Syntax/DepthFirstAstVisitor.cs b/ICSharpCode.Decompiler/CSharp/Syntax/DepthFirstAstVisitor.cs index 41870ba48..2016b429c 100644 --- a/ICSharpCode.Decompiler/CSharp/Syntax/DepthFirstAstVisitor.cs +++ b/ICSharpCode.Decompiler/CSharp/Syntax/DepthFirstAstVisitor.cs @@ -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 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 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); diff --git a/ICSharpCode.Decompiler/CSharp/Syntax/Expressions/DeclarationExpression.cs b/ICSharpCode.Decompiler/CSharp/Syntax/Expressions/DeclarationExpression.cs index 0e8585233..558456524 100644 --- a/ICSharpCode.Decompiler/CSharp/Syntax/Expressions/DeclarationExpression.cs +++ b/ICSharpCode.Decompiler/CSharp/Syntax/Expressions/DeclarationExpression.cs @@ -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); } } } diff --git a/ICSharpCode.Decompiler/CSharp/Syntax/Expressions/RecursivePatternExpression.cs b/ICSharpCode.Decompiler/CSharp/Syntax/Expressions/RecursivePatternExpression.cs new file mode 100644 index 000000000..3a26d735b --- /dev/null +++ b/ICSharpCode.Decompiler/CSharp/Syntax/Expressions/RecursivePatternExpression.cs @@ -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 SubPatternRole = new Role("SubPattern", Syntax.Expression.Null); + + public AstType Type { + get { return GetChildByRole(Roles.Type); } + set { SetChildByRole(Roles.Type, value); } + } + + public AstNodeCollection 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(IAstVisitor visitor) + { + return visitor.VisitRecursivePatternExpression(this); + } + + public override S AcceptVisitor(IAstVisitor 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); + } + } +} 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/CSharp/Syntax/IAstVisitor.cs b/ICSharpCode.Decompiler/CSharp/Syntax/IAstVisitor.cs index 25fa54d02..37dfd40a3 100644 --- a/ICSharpCode.Decompiler/CSharp/Syntax/IAstVisitor.cs +++ b/ICSharpCode.Decompiler/CSharp/Syntax/IAstVisitor.cs @@ -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 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 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); diff --git a/ICSharpCode.Decompiler/CSharp/Syntax/TypeMembers/OperatorDeclaration.cs b/ICSharpCode.Decompiler/CSharp/Syntax/TypeMembers/OperatorDeclaration.cs index de3915b83..9ffc9058b 100644 --- a/ICSharpCode.Decompiler/CSharp/Syntax/TypeMembers/OperatorDeclaration.cs +++ b/ICSharpCode.Decompiler/CSharp/Syntax/TypeMembers/OperatorDeclaration.cs @@ -159,6 +159,15 @@ namespace ICSharpCode.Decompiler.CSharp.Syntax get { return SymbolKind.Operator; } } + /// + /// Gets/Sets the type reference of the interface that is explicitly implemented. + /// Null node if this member is not an explicit interface implementation. + /// + public AstType PrivateImplementationType { + get { return GetChildByRole(PrivateImplementationTypeRole); } + set { SetChildByRole(PrivateImplementationTypeRole, value); } + } + OperatorType operatorType; public OperatorType OperatorType { @@ -347,7 +356,9 @@ namespace ICSharpCode.Decompiler.CSharp.Syntax protected internal override bool DoMatch(AstNode other, PatternMatching.Match match) { OperatorDeclaration o = other as OperatorDeclaration; - return o != null && this.MatchAttributesAndModifiers(o, match) && this.OperatorType == o.OperatorType + return o != null && this.MatchAttributesAndModifiers(o, match) + && this.PrivateImplementationType.DoMatch(o.PrivateImplementationType, match) + && this.OperatorType == o.OperatorType && this.ReturnType.DoMatch(o.ReturnType, match) && this.Parameters.DoMatch(o.Parameters, match) && this.Body.DoMatch(o.Body, match); } diff --git a/ICSharpCode.Decompiler/CSharp/Syntax/TypeSystemAstBuilder.cs b/ICSharpCode.Decompiler/CSharp/Syntax/TypeSystemAstBuilder.cs index 708ff92e6..799e79035 100644 --- a/ICSharpCode.Decompiler/CSharp/Syntax/TypeSystemAstBuilder.cs +++ b/ICSharpCode.Decompiler/CSharp/Syntax/TypeSystemAstBuilder.cs @@ -1740,6 +1740,10 @@ namespace ICSharpCode.Decompiler.CSharp.Syntax case SymbolKind.Event: return ConvertEvent((IEvent)entity); case SymbolKind.Method: + if (entity.Name.Contains(".op_")) + { + goto case SymbolKind.Operator; + } return ConvertMethod((IMethod)entity); case SymbolKind.Operator: return ConvertOperator((IMethod)entity); @@ -2225,7 +2229,9 @@ namespace ICSharpCode.Decompiler.CSharp.Syntax EntityDeclaration ConvertOperator(IMethod op) { - OperatorType? opType = OperatorDeclaration.GetOperatorType(op.Name); + int dot = op.Name.LastIndexOf('.'); + string name = op.Name.Substring(dot + 1); + OperatorType? opType = OperatorDeclaration.GetOperatorType(name); if (opType == null) return ConvertMethod(op); if (opType == OperatorType.UnsignedRightShift && !SupportUnsignedRightShift) @@ -2255,6 +2261,7 @@ namespace ICSharpCode.Decompiler.CSharp.Syntax decl.AddAnnotation(new MemberResolveResult(null, op)); } decl.Body = GenerateBodyBlock(); + decl.PrivateImplementationType = GetExplicitInterfaceType(op); return decl; } @@ -2373,8 +2380,14 @@ namespace ICSharpCode.Decompiler.CSharp.Syntax { m |= Modifiers.Sealed; } - if (member.IsAbstract && member.IsStatic) - m |= Modifiers.Abstract; + if (member.IsStatic) + { + // modifiers of static members in interfaces: + if (member.IsAbstract) + m |= Modifiers.Abstract; + else if (member.IsVirtual && !member.IsOverride) + m |= Modifiers.Virtual; + } } else { diff --git a/ICSharpCode.Decompiler/DebugInfo/DebugInfoGenerator.cs b/ICSharpCode.Decompiler/DebugInfo/DebugInfoGenerator.cs index 8d8eb8557..84de80d2a 100644 --- a/ICSharpCode.Decompiler/DebugInfo/DebugInfoGenerator.cs +++ b/ICSharpCode.Decompiler/DebugInfo/DebugInfoGenerator.cs @@ -256,7 +256,7 @@ namespace ICSharpCode.Decompiler.DebugInfo if (v.Index != null && v.Kind.IsLocal()) { #if DEBUG - Debug.Assert(v.Index < types.Length && v.Type.Equals(types[v.Index.Value])); + Debug.Assert(v.Index < types.Length && NormalizeTypeVisitor.TypeErasure.EquivalentTypes(v.Type, types[v.Index.Value])); #endif localVariables.Add(v); } 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/ICSharpCode.Decompiler.csproj b/ICSharpCode.Decompiler/ICSharpCode.Decompiler.csproj index 5ee433d8a..3f8c72492 100644 --- a/ICSharpCode.Decompiler/ICSharpCode.Decompiler.csproj +++ b/ICSharpCode.Decompiler/ICSharpCode.Decompiler.csproj @@ -27,7 +27,7 @@ False false - 10 + 11 true True ICSharpCode.Decompiler.snk @@ -91,6 +91,7 @@ + @@ -139,6 +140,7 @@ + diff --git a/ICSharpCode.Decompiler/IL/Instructions/Comp.cs b/ICSharpCode.Decompiler/IL/Instructions/Comp.cs index 227eef95c..406d3847e 100644 --- a/ICSharpCode.Decompiler/IL/Instructions/Comp.cs +++ b/ICSharpCode.Decompiler/IL/Instructions/Comp.cs @@ -130,7 +130,7 @@ namespace ICSharpCode.Decompiler.IL } } - public readonly ComparisonLiftingKind LiftingKind; + public ComparisonLiftingKind LiftingKind; /// /// Gets the stack type of the comparison inputs. diff --git a/ICSharpCode.Decompiler/IL/Instructions/MatchInstruction.cs b/ICSharpCode.Decompiler/IL/Instructions/MatchInstruction.cs index f12f06cf6..af9074f12 100644 --- a/ICSharpCode.Decompiler/IL/Instructions/MatchInstruction.cs +++ b/ICSharpCode.Decompiler/IL/Instructions/MatchInstruction.cs @@ -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 /// (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) { @@ -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 OpCode.LdcI4 => true, OpCode.LdcI8 => true, OpCode.LdNull => true, - OpCode.LdStr => true, _ => false }; } @@ -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 } 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 { diff --git a/ICSharpCode.Decompiler/IL/Transforms/AssignVariableNames.cs b/ICSharpCode.Decompiler/IL/Transforms/AssignVariableNames.cs index e4331c74b..2fd3f7782 100644 --- a/ICSharpCode.Decompiler/IL/Transforms/AssignVariableNames.cs +++ b/ICSharpCode.Decompiler/IL/Transforms/AssignVariableNames.cs @@ -432,12 +432,25 @@ namespace ICSharpCode.Decompiler.IL.Transforms } if (string.IsNullOrEmpty(proposedName)) { - var proposedNameForStores = variable.StoreInstructions.OfType() - .Select(expr => GetNameFromInstruction(expr.Value)) - .Except(currentLowerCaseTypeOrMemberNames).ToList(); + var proposedNameForStores = new HashSet(); + 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)) diff --git a/ICSharpCode.Decompiler/IL/Transforms/EarlyExpressionTransforms.cs b/ICSharpCode.Decompiler/IL/Transforms/EarlyExpressionTransforms.cs index 12daf764c..3d99d0400 100644 --- a/ICSharpCode.Decompiler/IL/Transforms/EarlyExpressionTransforms.cs +++ b/ICSharpCode.Decompiler/IL/Transforms/EarlyExpressionTransforms.cs @@ -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; + } + } } diff --git a/ICSharpCode.Decompiler/IL/Transforms/ExpressionTransforms.cs b/ICSharpCode.Decompiler/IL/Transforms/ExpressionTransforms.cs index 8a6684c4e..7f494cad6 100644 --- a/ICSharpCode.Decompiler/IL/Transforms/ExpressionTransforms.cs +++ b/ICSharpCode.Decompiler/IL/Transforms/ExpressionTransforms.cs @@ -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 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 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 97edacb66..d1bf69567 100644 --- a/ICSharpCode.Decompiler/IL/Transforms/PatternMatchingTransform.cs +++ b/ICSharpCode.Decompiler/IL/Transforms/PatternMatchingTransform.cs @@ -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 foreach (var container in function.Descendants.OfType()) { 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 continue; } } + container.Blocks.RemoveAll(b => b.Instructions.Count == 0); } } @@ -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; @@ -240,21 +583,25 @@ 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 unboxBlock, out var falseBlock)) + if (!MatchIsInstBlock(block, out var type, out var testedOperand, out var testedVariable, + out var boxType1, out var unboxBlock, out var falseInst)) { return false; } StLoc? tempStore = block.Instructions.ElementAtOrDefault(block.Instructions.Count - 3) as StLoc; - if (tempStore == null || !tempStore.Value.MatchLdLoc(testedOperand.Variable)) + if (tempStore == null || !tempStore.Value.MatchLdLoc(testedVariable)) { tempStore = null; } - if (!MatchUnboxBlock(unboxBlock, type, out var unboxOperand, out var v, out var storeToV)) + if (!MatchUnboxBlock(unboxBlock, type, out var unboxOperand, out var boxType2, out var storeToV)) + { + return false; + } + if (!object.Equals(boxType1, boxType2)) { return false; } - if (unboxOperand == testedOperand.Variable) + if (unboxOperand == testedVariable) { // do nothing } @@ -267,16 +614,16 @@ namespace ICSharpCode.Decompiler.IL.Transforms { return false; } - if (!CheckAllUsesDominatedBy(v, container, unboxBlock, storeToV, null, context, ref cfg)) + if (!CheckAllUsesDominatedBy(storeToV.Variable, container, unboxBlock, storeToV, null, context, ref cfg)) return false; - context.Step($"PatternMatching with {v.Name}", block); + context.Step($"PatternMatching with {storeToV.Variable.Name}", block); var ifInst = (IfInstruction)block.Instructions.SecondToLastOrDefault()!; - ifInst.Condition = new MatchInstruction(v, testedOperand) { + ifInst.Condition = new MatchInstruction(storeToV.Variable, testedOperand) { 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) { @@ -286,25 +633,35 @@ namespace ICSharpCode.Decompiler.IL.Transforms // should become the then-branch. Change the unboxBlock StartILOffset from an offset inside // the pattern matching machinery to an offset belonging to an instruction in the then-block. unboxBlock.SetILRange(unboxBlock.Instructions[0]); - v.Kind = VariableKind.PatternLocal; + storeToV.Variable.Kind = VariableKind.PatternLocal; + DetectPropertySubPatterns((MatchInstruction)ifInst.Condition, unboxBlock, falseInst, container, context, ref cfg); return true; } /// ... /// if (comp.o(isinst T(ldloc testedOperand) == ldnull)) br falseBlock /// br unboxBlock + /// - or - + /// ... + /// if (comp.o(isinst T(box ``0(ldloc testedOperand)) == ldnull)) br falseBlock + /// br unboxBlock private bool MatchIsInstBlock(Block block, [NotNullWhen(true)] out IType? type, - [NotNullWhen(true)] out LdLoc? testedOperand, + [NotNullWhen(true)] out ILInstruction? testedOperand, + [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; + } if (condition.MatchCompEqualsNull(out var arg)) { ExtensionMethods.Swap(ref trueInst, ref falseInst); @@ -317,33 +674,58 @@ namespace ICSharpCode.Decompiler.IL.Transforms { return false; } - if (!arg.MatchIsInst(out arg, out type)) + if (!arg.MatchIsInst(out testedOperand, out type)) + { return false; - testedOperand = arg as LdLoc; - if (testedOperand == null) + } + if (!(testedOperand.MatchBox(out var boxArg, out boxType) && boxType.Kind == TypeKind.TypeParameter)) + { + boxArg = testedOperand; + } + if (!boxArg.MatchLdLoc(out testedVariable)) + { 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) { /// stloc V(unbox.any T(ldloc testedOperand)) /// ... + /// - or - + /// stloc V(unbox.any T(isinst T(box ``0(ldloc testedOperand)))) + /// ... /// } - private bool MatchUnboxBlock(Block unboxBlock, IType type, [NotNullWhen(true)] out ILVariable? testedOperand, - [NotNullWhen(true)] out ILVariable? v, [NotNullWhen(true)] out ILInstruction? storeToV) + private bool MatchUnboxBlock(Block unboxBlock, IType type, [NotNullWhen(true)] out ILVariable? testedVariable, + out IType? boxType, [NotNullWhen(true)] out StLoc? storeToV) { - v = null; + boxType = null; storeToV = null; - testedOperand = null; + testedVariable = null; if (unboxBlock.IncomingEdgeCount != 1) return false; - storeToV = unboxBlock.Instructions[0]; - if (!storeToV.MatchStLoc(out v, out var value)) + storeToV = unboxBlock.Instructions[0] as StLoc; + if (storeToV == null) return false; - if (!(value.MatchUnboxAny(out var arg, out var t) && t.Equals(type) && arg.MatchLdLoc(out testedOperand))) + var value = storeToV.Value; + if (!(value.MatchUnboxAny(out var arg, out var t) && t.Equals(type))) return false; - + if (arg.MatchIsInst(out var isinstArg, out var isinstType) && isinstType.Equals(type)) + { + arg = isinstArg; + } + if (arg.MatchBox(out var boxArg, out boxType) && boxType.Kind == TypeKind.TypeParameter) + { + arg = boxArg; + } + if (!arg.MatchLdLoc(out testedVariable)) + { + return false; + } + if (boxType != null && !boxType.Equals(testedVariable.Type)) + { + return false; + } return true; } } diff --git a/ICSharpCode.Decompiler/TypeSystem/Implementation/AbstractTypeParameter.cs b/ICSharpCode.Decompiler/TypeSystem/Implementation/AbstractTypeParameter.cs index 0660573f0..289fc35e7 100644 --- a/ICSharpCode.Decompiler/TypeSystem/Implementation/AbstractTypeParameter.cs +++ b/ICSharpCode.Decompiler/TypeSystem/Implementation/AbstractTypeParameter.cs @@ -378,10 +378,8 @@ namespace ICSharpCode.Decompiler.TypeSystem.Implementation static Predicate FilterNonStatic(Predicate filter) where T : class, IMember { - if (filter == null) - return member => !member.IsStatic; - else - return member => !member.IsStatic && filter(member); + return member => (!member.IsStatic || member.SymbolKind == SymbolKind.Operator || member.IsVirtual) + && (filter == null || filter(member)); } public sealed override bool Equals(object obj) diff --git a/ICSharpCode.Decompiler/TypeSystem/Implementation/MetadataMethod.cs b/ICSharpCode.Decompiler/TypeSystem/Implementation/MetadataMethod.cs index 3ebd754d7..84be4ccb6 100644 --- a/ICSharpCode.Decompiler/TypeSystem/Implementation/MetadataMethod.cs +++ b/ICSharpCode.Decompiler/TypeSystem/Implementation/MetadataMethod.cs @@ -576,8 +576,23 @@ namespace ICSharpCode.Decompiler.TypeSystem.Implementation public bool IsStatic => (attributes & MethodAttributes.Static) != 0; public bool IsAbstract => (attributes & MethodAttributes.Abstract) != 0; public bool IsSealed => (attributes & (MethodAttributes.Abstract | MethodAttributes.Final | MethodAttributes.NewSlot | MethodAttributes.Static)) == MethodAttributes.Final; - public bool IsVirtual => (attributes & (MethodAttributes.Abstract | MethodAttributes.Virtual | MethodAttributes.NewSlot | MethodAttributes.Final)) == (MethodAttributes.Virtual | MethodAttributes.NewSlot); - public bool IsOverride => (attributes & (MethodAttributes.NewSlot | MethodAttributes.Virtual)) == MethodAttributes.Virtual; + + public bool IsVirtual { + get { + if (IsStatic) + { + return (attributes & (MethodAttributes.Abstract | MethodAttributes.Virtual)) == MethodAttributes.Virtual; + } + else + { + const MethodAttributes mask = MethodAttributes.Abstract | MethodAttributes.Virtual + | MethodAttributes.NewSlot | MethodAttributes.Final; + return (attributes & mask) == (MethodAttributes.Virtual | MethodAttributes.NewSlot); + } + } + } + + public bool IsOverride => (attributes & (MethodAttributes.NewSlot | MethodAttributes.Virtual | MethodAttributes.Static)) == MethodAttributes.Virtual; public bool IsOverridable => (attributes & (MethodAttributes.Abstract | MethodAttributes.Virtual)) != 0 && (attributes & MethodAttributes.Final) == 0; diff --git a/ICSharpCode.Decompiler/Util/Index.cs b/ICSharpCode.Decompiler/Util/Index.cs new file mode 100644 index 000000000..6da2439e7 --- /dev/null +++ b/ICSharpCode.Decompiler/Util/Index.cs @@ -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 +{ + /// Represent a type can be used to index a collection either from the start or the end. + /// + /// Index is used by the C# compiler to support the new index syntax + /// + /// int[] someArray = new int[5] { 1, 2, 3, 4, 5 } ; + /// int lastElement = someArray[^1]; // lastElement = 5 + /// + /// +#if SYSTEM_PRIVATE_CORELIB + public +#else + internal +#endif + readonly struct Index : IEquatable + { + private readonly int _value; + + /// Construct an Index using a value and indicating if the index is from the start or from the end. + /// The index value. it has to be zero or positive number. + /// Indicating if the index is from the start or from the end. + /// + /// 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. + /// + [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; + } + + /// Create an Index pointing at first element. + public static Index Start => new Index(0); + + /// Create an Index pointing at beyond last element. + public static Index End => new Index(~0); + + /// Create an Index from the start at the position indicated by the value. + /// The index value from the start. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Index FromStart(int value) + { + if (value < 0) + { + ThrowValueArgumentOutOfRange_NeedNonNegNumException(); + } + + return new Index(value); + } + + /// Create an Index from the end at the position indicated by the value. + /// The index value from the end. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Index FromEnd(int value) + { + if (value < 0) + { + ThrowValueArgumentOutOfRange_NeedNonNegNumException(); + } + + return new Index(~value); + } + + /// Returns the index value. + public int Value { + get { + if (_value < 0) + return ~_value; + else + return _value; + } + } + + /// Indicates whether the index is from the start or the end. + public bool IsFromEnd => _value < 0; + + /// Calculate the offset from the start using the giving collection length. + /// The length of the collection that the Index will be used with. length has to be a positive value + /// + /// 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. + /// + [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; + } + + /// Indicates whether the current Index object is equal to another object of the same type. + /// An object to compare with this object + public override bool Equals([NotNullWhen(true)] object? value) => value is Index && _value == ((Index)value)._value; + + /// Indicates whether the current Index object is equal to another Index object. + /// An object to compare with this object + public bool Equals(Index other) => _value == other._value; + + /// Returns the hash code for this instance. + public override int GetHashCode() => _value; + + /// Converts integer number to an Index. + public static implicit operator Index(int value) => FromStart(value); + + /// Converts the value of the current Index object to its equivalent string representation. + 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 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 + } + } +} \ No newline at end of file diff --git a/ILSpy.Installer/ILSpy.Installer.csproj b/ILSpy.Installer/ILSpy.Installer.csproj index 6567b67ae..0697d3872 100644 --- a/ILSpy.Installer/ILSpy.Installer.csproj +++ b/ILSpy.Installer/ILSpy.Installer.csproj @@ -1,4 +1,4 @@ - + Exe @@ -6,6 +6,10 @@ ILSpy.Installer.Builder + + $(DefineConstants);$(PlatformForInstaller) + + 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!) diff --git a/decompiler-nuget-demos.ipynb b/decompiler-nuget-demos.ipynb index 18956d2a5..09c593071 100644 --- a/decompiler-nuget-demos.ipynb +++ b/decompiler-nuget-demos.ipynb @@ -11,17 +11,20 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 10, "metadata": { "dotnet_interactive": { "language": "csharp" + }, + "vscode": { + "languageId": "polyglot-notebook" } }, "outputs": [ { "data": { "text/html": [ - "
Installed Packages
  • ICSharpCode.Decompiler, 7.2.0.6791-preview3
" + "
Installed Packages
  • ICSharpCode.Decompiler, 8.1.0.7455
" ] }, "metadata": {}, @@ -31,12 +34,12 @@ "name": "stdout", "output_type": "stream", "text": [ - "ICSharpCode.Decompiler, Version=7.2.0.6791, Culture=neutral, PublicKeyToken=d4bfe873e7598c49\r\n" + "ICSharpCode.Decompiler, Version=8.1.0.7455, Culture=neutral, PublicKeyToken=d4bfe873e7598c49\r\n" ] } ], "source": [ - "#r \"nuget: ICSharpCode.Decompiler, 7.2.0.6791-preview3\"\n", + "#r \"nuget: ICSharpCode.Decompiler, 8.1.0.7455\"\n", "\n", "using System.Reflection.Metadata;\n", "using ICSharpCode.Decompiler;\n", @@ -56,10 +59,13 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 11, "metadata": { "dotnet_interactive": { "language": "csharp" + }, + "vscode": { + "languageId": "polyglot-notebook" } }, "outputs": [], @@ -79,10 +85,13 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 12, "metadata": { "dotnet_interactive": { "language": "csharp" + }, + "vscode": { + "languageId": "polyglot-notebook" } }, "outputs": [ @@ -90,7 +99,7 @@ "name": "stdout", "output_type": "stream", "text": [ - "1480\r\n" + "1532\r\n" ] } ], @@ -108,10 +117,13 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 13, "metadata": { "dotnet_interactive": { "language": "csharp" + }, + "vscode": { + "languageId": "polyglot-notebook" } }, "outputs": [ @@ -146,10 +158,13 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 14, "metadata": { "dotnet_interactive": { "language": "csharp" + }, + "vscode": { + "languageId": "polyglot-notebook" } }, "outputs": [ @@ -196,10 +211,13 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 15, "metadata": { "dotnet_interactive": { "language": "csharp" + }, + "vscode": { + "languageId": "polyglot-notebook" } }, "outputs": [], @@ -217,10 +235,13 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 16, "metadata": { "dotnet_interactive": { "language": "csharp" + }, + "vscode": { + "languageId": "polyglot-notebook" } }, "outputs": [ @@ -228,15 +249,14 @@ "name": "stdout", "output_type": "stream", "text": [ - "Microsoft\r\n", - "System\r\n", - "LightJson\r\n", - "Humanizer\r\n", - "ICSharpCode\r\n", - "FxResources\r\n", - "Internal\r\n", - "MS\r\n", - "Windows\r\n" + "Microsoft\n", + "System\n", + "LightJson\n", + "Humanizer\n", + "ICSharpCode\n", + "FxResources\n", + "Internal\n", + "MS\n" ] } ], @@ -254,10 +274,13 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 17, "metadata": { "dotnet_interactive": { "language": "csharp" + }, + "vscode": { + "languageId": "polyglot-notebook" } }, "outputs": [ @@ -276,10 +299,13 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 18, "metadata": { "dotnet_interactive": { "language": "csharp" + }, + "vscode": { + "languageId": "polyglot-notebook" } }, "outputs": [],