From 3d8cda5f882ab04e86ee839e95efb3ffaa369165 Mon Sep 17 00:00:00 2001 From: Siegfried Pammer Date: Fri, 4 Aug 2023 11:44:43 +0200 Subject: [PATCH 01/22] Fix #2613: Detect pattern matching on variables of generic type with value types. --- .../TestCases/Pretty/PatternMatching.cs | 72 ++++++++++++++++ .../CSharp/ExpressionBuilder.cs | 9 ++ .../IL/Transforms/PatternMatchingTransform.cs | 82 ++++++++++++++----- 3 files changed, 142 insertions(+), 21 deletions(-) diff --git a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/PatternMatching.cs b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/PatternMatching.cs index 064db32bc..5b9f38787 100644 --- a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/PatternMatching.cs +++ b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/PatternMatching.cs @@ -231,6 +231,78 @@ 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"); + } + } + private bool F() { return true; diff --git a/ICSharpCode.Decompiler/CSharp/ExpressionBuilder.cs b/ICSharpCode.Decompiler/CSharp/ExpressionBuilder.cs index 2001cbea1..707377b47 100644 --- a/ICSharpCode.Decompiler/CSharp/ExpressionBuilder.cs +++ b/ICSharpCode.Decompiler/CSharp/ExpressionBuilder.cs @@ -4537,6 +4537,15 @@ namespace ICSharpCode.Decompiler.CSharp protected internal override TranslatedExpression VisitMatchInstruction(MatchInstruction inst, TranslationContext context) { var left = Translate(inst.TestedOperand); + // 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); return new BinaryOperatorExpression(left, BinaryOperatorType.IsPattern, right) diff --git a/ICSharpCode.Decompiler/IL/Transforms/PatternMatchingTransform.cs b/ICSharpCode.Decompiler/IL/Transforms/PatternMatchingTransform.cs index 97edacb66..9a02d9c7e 100644 --- a/ICSharpCode.Decompiler/IL/Transforms/PatternMatchingTransform.cs +++ b/ICSharpCode.Decompiler/IL/Transforms/PatternMatchingTransform.cs @@ -240,21 +240,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 falseBlock)) { 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 (unboxOperand == testedOperand.Variable) + if (!object.Equals(boxType1, boxType2)) + { + return false; + } + if (unboxOperand == testedVariable) { // do nothing } @@ -267,11 +271,11 @@ 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 }; @@ -286,25 +290,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; 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) { type = null; testedOperand = null; + testedVariable = null; + boxType = null; unboxBlock = null; falseBlock = null; if (!block.MatchIfAtEndOfBlock(out var condition, out var trueInst, out var falseInst)) + { return false; + } if (condition.MatchCompEqualsNull(out var arg)) { ExtensionMethods.Swap(ref trueInst, ref falseInst); @@ -317,11 +331,18 @@ 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; } @@ -329,21 +350,40 @@ namespace ICSharpCode.Decompiler.IL.Transforms /// 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; } } From 7b58f79d9c05ad96ecee613af81bf3a3f8bf7c6e Mon Sep 17 00:00:00 2001 From: Christoph Wille Date: Fri, 4 Aug 2023 14:33:25 +0200 Subject: [PATCH 02/22] Update Notebook for 8.1 --- decompiler-nuget-demos.ipynb | 70 ++++++++++++++++++++++++------------ 1 file changed, 48 insertions(+), 22 deletions(-) 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": [], From f7343c75d09cbd9f73b88bd50b451bb7ce3d4664 Mon Sep 17 00:00:00 2001 From: Siegfried Pammer Date: Fri, 4 Aug 2023 15:17:54 +0200 Subject: [PATCH 03/22] Add support for simple recursive patterns where the sub pattern is a simple type pattern. --- .../TestCases/Pretty/PatternMatching.cs | 19 ++ .../CSharp/ExpressionBuilder.cs | 42 ++++- .../OutputVisitor/CSharpOutputVisitor.cs | 34 ++++ .../CSharp/Syntax/DepthFirstAstVisitor.cs | 15 ++ .../Expressions/DeclarationExpression.cs | 4 +- .../Expressions/RecursivePatternExpression.cs | 67 +++++++ .../CSharp/Syntax/IAstVisitor.cs | 3 + .../ICSharpCode.Decompiler.csproj | 4 +- .../IL/Transforms/PatternMatchingTransform.cs | 44 ++++- ICSharpCode.Decompiler/Util/Index.cs | 166 ++++++++++++++++++ 10 files changed, 392 insertions(+), 6 deletions(-) create mode 100644 ICSharpCode.Decompiler/CSharp/Syntax/Expressions/RecursivePatternExpression.cs create mode 100644 ICSharpCode.Decompiler/Util/Index.cs diff --git a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/PatternMatching.cs b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/PatternMatching.cs index 5b9f38787..cbc56794b 100644 --- a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/PatternMatching.cs +++ b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/PatternMatching.cs @@ -4,6 +4,13 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty { public class PatternMatching { + public class X + { + public int A { get; set; } + public string B { get; set; } + public object C { get; set; } + } + public void SimpleTypePattern(object x) { if (x is string value) @@ -303,6 +310,18 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty } } + public void RecursivePattern_Type(object x) + { + if (x is X { C: string text }) + { + Console.WriteLine("Test " + text); + } + else + { + Console.WriteLine("not Test"); + } + } + private bool F() { return true; diff --git a/ICSharpCode.Decompiler/CSharp/ExpressionBuilder.cs b/ICSharpCode.Decompiler/CSharp/ExpressionBuilder.cs index 707377b47..c73084370 100644 --- a/ICSharpCode.Decompiler/CSharp/ExpressionBuilder.cs +++ b/ICSharpCode.Decompiler/CSharp/ExpressionBuilder.cs @@ -4564,9 +4564,45 @@ namespace ICSharpCode.Decompiler.CSharp throw new NotImplementedException(); if (matchInstruction.IsDeconstructTuple) throw new NotImplementedException(); - if (matchInstruction.SubPatterns.Any()) - throw new NotImplementedException(); - if (matchInstruction.HasDesignator) + if (matchInstruction.SubPatterns.Count > 0) + { + RecursivePatternExpression recursivePatternExpression = new(); + recursivePatternExpression.Type = ConvertType(matchInstruction.Variable.Type); + foreach (var subPattern in matchInstruction.SubPatterns) + { + if (!MatchInstruction.IsPatternMatch(subPattern, out var testedOperand)) + { + 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) } + .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) { SingleVariableDesignation designator = new SingleVariableDesignation { Identifier = matchInstruction.Variable.Name }; designator.AddAnnotation(new ILVariableResolveResult(matchInstruction.Variable)); diff --git a/ICSharpCode.Decompiler/CSharp/OutputVisitor/CSharpOutputVisitor.cs b/ICSharpCode.Decompiler/CSharp/OutputVisitor/CSharpOutputVisitor.cs index 3e428e5ef..152f9fdd8 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); 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/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/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/Transforms/PatternMatchingTransform.cs b/ICSharpCode.Decompiler/IL/Transforms/PatternMatchingTransform.cs index 9a02d9c7e..7cdc090db 100644 --- a/ICSharpCode.Decompiler/IL/Transforms/PatternMatchingTransform.cs +++ b/ICSharpCode.Decompiler/IL/Transforms/PatternMatchingTransform.cs @@ -22,6 +22,7 @@ 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 +40,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)) { @@ -179,9 +180,50 @@ namespace ICSharpCode.Decompiler.IL.Transforms block.Instructions[block.Instructions.Count - 1] = falseInst; block.Instructions.RemoveRange(pos, ifInst.ChildIndex - pos); v.Kind = VariableKind.PatternLocal; + + if (trueInst.MatchBranch(out var trueBlock) && trueBlock.IncomingEdgeCount == 1 && trueBlock.Parent == container) + { + DetectPropertySubPatterns((MatchInstruction)ifInst.Condition, trueBlock, falseInst); + } + return true; } + private bool DetectPropertySubPatterns(MatchInstruction parentPattern, Block block, ILInstruction parentFalseInst) + { + // if (match.notnull.type[System.String] (V_0 = callvirt get_C(ldloc V_2))) br IL_0022 + // br IL_0037 + if (block.Instructions.Count == 2 && block.MatchIfAtEndOfBlock(out var condition, out var trueInst, out var falseInst)) + { + if (MatchInstruction.IsPatternMatch(condition, out var operand)) + { + if (operand is not CallInstruction { + Method: { + SymbolKind: SymbolKind.Accessor, + AccessorKind: MethodSemanticsAttributes.Getter + }, + Arguments: [LdLoc ldloc] + } call) + { + return false; + } + if (ldloc.Variable != parentPattern.Variable) + { + return false; + } + if (!DetectExitPoints.CompatibleExitInstruction(parentFalseInst, falseInst)) + { + return false; + } + parentPattern.SubPatterns.Add(condition); + block.Instructions.RemoveAt(0); + block.Instructions[0] = trueInst; + return true; + } + } + return false; + } + private bool CheckAllUsesDominatedBy(ILVariable v, BlockContainer container, ILInstruction trueInst, ILInstruction storeToV, ILInstruction? loadInNullCheck, ILTransformContext context, ref ControlFlowGraph? cfg) { 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 From 65b4c928c0c98974c13da9fc8a0e8e968da8159f Mon Sep 17 00:00:00 2001 From: Siegfried Pammer Date: Fri, 4 Aug 2023 16:29:12 +0200 Subject: [PATCH 04/22] Add support for simple constant patterns. --- .../TestCases/Pretty/PatternMatching.cs | 14 +++++++ .../CSharp/ExpressionBuilder.cs | 31 +++++++++++++-- .../Expressions/UnaryOperatorExpression.cs | 36 +++++++++++++++++ .../IL/Transforms/PatternMatchingTransform.cs | 39 ++++++++++++++++--- 4 files changed, 111 insertions(+), 9 deletions(-) diff --git a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/PatternMatching.cs b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/PatternMatching.cs index cbc56794b..81ac1c17a 100644 --- a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/PatternMatching.cs +++ b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/PatternMatching.cs @@ -310,6 +310,7 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty } } +#if CS80 public void RecursivePattern_Type(object x) { if (x is X { C: string text }) @@ -322,6 +323,19 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty } } + public void RecursivePattern_Constant(object obj) + { + if (obj is X { C: null } x) + { + Console.WriteLine("Test " + x); + } + else + { + Console.WriteLine("not Test"); + } + } + +#endif private bool F() { return true; diff --git a/ICSharpCode.Decompiler/CSharp/ExpressionBuilder.cs b/ICSharpCode.Decompiler/CSharp/ExpressionBuilder.cs index c73084370..feafd7199 100644 --- a/ICSharpCode.Decompiler/CSharp/ExpressionBuilder.cs +++ b/ICSharpCode.Decompiler/CSharp/ExpressionBuilder.cs @@ -4546,14 +4546,14 @@ namespace ICSharpCode.Decompiler.CSharp { left = left.UnwrapChild(castExpr.Expression); } - var right = TranslatePattern(inst); + var right = TranslatePattern(inst, left.Type); return new BinaryOperatorExpression(left, BinaryOperatorType.IsPattern, right) .WithRR(new ResolveResult(compilation.FindType(KnownTypeCode.Boolean))) .WithILInstruction(inst); } - ExpressionWithILInstruction TranslatePattern(ILInstruction pattern) + ExpressionWithILInstruction TranslatePattern(ILInstruction pattern, IType leftHandType) { switch (pattern) { @@ -4590,7 +4590,7 @@ namespace ICSharpCode.Decompiler.CSharp continue; } recursivePatternExpression.SubPatterns.Add( - new NamedArgumentExpression { Name = member.Name, Expression = TranslatePattern(subPattern) } + new NamedArgumentExpression { Name = member.Name, Expression = TranslatePattern(subPattern, member.ReturnType) } .WithRR(new MemberResolveResult(null, member)) ); } @@ -4616,6 +4616,31 @@ namespace ICSharpCode.Decompiler.CSharp return new TypeReferenceExpression(ConvertType(matchInstruction.Variable.Type)) .WithILInstruction(matchInstruction); } + case Comp comp: + var constantValue = Translate(comp.Right, leftHandType); + switch (comp.Kind) + { + case ComparisonKind.Equality: + return constantValue + .WithILInstruction(comp); + case ComparisonKind.Inequality: + return new UnaryOperatorExpression(UnaryOperatorType.PatternNot, constantValue) + .WithILInstruction(comp); + case ComparisonKind.LessThan: + return new UnaryOperatorExpression(UnaryOperatorType.PatternRelationalLessThan, constantValue) + .WithILInstruction(comp); + case ComparisonKind.LessThanOrEqual: + return new UnaryOperatorExpression(UnaryOperatorType.PatternRelationalLessThanOrEqual, constantValue) + .WithILInstruction(comp); + case ComparisonKind.GreaterThan: + return new UnaryOperatorExpression(UnaryOperatorType.PatternRelationalGreaterThan, constantValue) + .WithILInstruction(comp); + case ComparisonKind.GreaterThanOrEqual: + return new UnaryOperatorExpression(UnaryOperatorType.PatternRelationalGreaterThanOrEqual, constantValue) + .WithILInstruction(comp); + default: + throw new InvalidOperationException("Unexpected comparison kind: " + comp.Kind); + } default: throw new NotImplementedException(); } diff --git a/ICSharpCode.Decompiler/CSharp/Syntax/Expressions/UnaryOperatorExpression.cs b/ICSharpCode.Decompiler/CSharp/Syntax/Expressions/UnaryOperatorExpression.cs index af925fc14..460f9a421 100644 --- a/ICSharpCode.Decompiler/CSharp/Syntax/Expressions/UnaryOperatorExpression.cs +++ b/ICSharpCode.Decompiler/CSharp/Syntax/Expressions/UnaryOperatorExpression.cs @@ -46,6 +46,7 @@ namespace ICSharpCode.Decompiler.CSharp.Syntax public readonly static TokenRole NullConditionalRole = new TokenRole("?"); public readonly static TokenRole SuppressNullableWarningRole = new TokenRole("!"); public readonly static TokenRole IndexFromEndRole = new TokenRole("^"); + public readonly static TokenRole PatternNotRole = new TokenRole("not"); public UnaryOperatorExpression() { @@ -126,6 +127,16 @@ namespace ICSharpCode.Decompiler.CSharp.Syntax return SuppressNullableWarningRole; case UnaryOperatorType.IndexFromEnd: return IndexFromEndRole; + case UnaryOperatorType.PatternNot: + return PatternNotRole; + case UnaryOperatorType.PatternRelationalLessThan: + return BinaryOperatorExpression.LessThanRole; + case UnaryOperatorType.PatternRelationalLessThanOrEqual: + return BinaryOperatorExpression.LessThanOrEqualRole; + case UnaryOperatorType.PatternRelationalGreaterThan: + return BinaryOperatorExpression.GreaterThanRole; + case UnaryOperatorType.PatternRelationalGreaterThanOrEqual: + return BinaryOperatorExpression.GreaterThanOrEqualRole; default: throw new NotSupportedException("Invalid value for UnaryOperatorType"); } @@ -156,6 +167,11 @@ namespace ICSharpCode.Decompiler.CSharp.Syntax case UnaryOperatorType.Await: case UnaryOperatorType.SuppressNullableWarning: case UnaryOperatorType.IndexFromEnd: + case UnaryOperatorType.PatternNot: + case UnaryOperatorType.PatternRelationalLessThan: + case UnaryOperatorType.PatternRelationalLessThanOrEqual: + case UnaryOperatorType.PatternRelationalGreaterThan: + case UnaryOperatorType.PatternRelationalGreaterThanOrEqual: return ExpressionType.Extension; default: throw new NotSupportedException("Invalid value for UnaryOperatorType"); @@ -216,5 +232,25 @@ namespace ICSharpCode.Decompiler.CSharp.Syntax /// C# 8 prefix ^ operator /// IndexFromEnd, + /// + /// C# 9 not pattern + /// + PatternNot, + /// + /// C# 9 relational pattern + /// + PatternRelationalLessThan, + /// + /// C# 9 relational pattern + /// + PatternRelationalLessThanOrEqual, + /// + /// C# 9 relational pattern + /// + PatternRelationalGreaterThan, + /// + /// C# 9 relational pattern + /// + PatternRelationalGreaterThanOrEqual, } } diff --git a/ICSharpCode.Decompiler/IL/Transforms/PatternMatchingTransform.cs b/ICSharpCode.Decompiler/IL/Transforms/PatternMatchingTransform.cs index 7cdc090db..c99a429a8 100644 --- a/ICSharpCode.Decompiler/IL/Transforms/PatternMatchingTransform.cs +++ b/ICSharpCode.Decompiler/IL/Transforms/PatternMatchingTransform.cs @@ -183,17 +183,17 @@ namespace ICSharpCode.Decompiler.IL.Transforms if (trueInst.MatchBranch(out var trueBlock) && trueBlock.IncomingEdgeCount == 1 && trueBlock.Parent == container) { - DetectPropertySubPatterns((MatchInstruction)ifInst.Condition, trueBlock, falseInst); + DetectPropertySubPatterns((MatchInstruction)ifInst.Condition, trueBlock, falseInst, context); } return true; } - private bool DetectPropertySubPatterns(MatchInstruction parentPattern, Block block, ILInstruction parentFalseInst) + private bool DetectPropertySubPatterns(MatchInstruction parentPattern, Block block, ILInstruction parentFalseInst, ILTransformContext context) { // if (match.notnull.type[System.String] (V_0 = callvirt get_C(ldloc V_2))) br IL_0022 // br IL_0037 - if (block.Instructions.Count == 2 && block.MatchIfAtEndOfBlock(out var condition, out var trueInst, out var falseInst)) + if (MatchBlockContainingOneCondition(block, out var condition, out var trueInst, out var falseInst)) { if (MatchInstruction.IsPatternMatch(condition, out var operand)) { @@ -213,17 +213,44 @@ namespace ICSharpCode.Decompiler.IL.Transforms } if (!DetectExitPoints.CompatibleExitInstruction(parentFalseInst, falseInst)) { - return false; + if (!DetectExitPoints.CompatibleExitInstruction(parentFalseInst, trueInst)) + { + return false; + } + ExtensionMethods.Swap(ref trueInst, ref falseInst); + condition = Comp.LogicNot(condition); } + context.Step("Move property sub pattern", condition); parentPattern.SubPatterns.Add(condition); - block.Instructions.RemoveAt(0); - block.Instructions[0] = trueInst; + block.Instructions.Clear(); + block.Instructions.Add(trueInst); return true; } } return false; } + private static bool MatchBlockContainingOneCondition(Block block, [NotNullWhen(true)] out ILInstruction? condition, [NotNullWhen(true)] out ILInstruction? trueInst, [NotNullWhen(true)] out ILInstruction? falseInst) + { + switch (block.Instructions.Count) + { + case 2: + return block.MatchIfAtEndOfBlock(out condition, out trueInst, out falseInst); + case 3: + condition = null; + if (!block.MatchIfAtEndOfBlock(out var loadTemp, out trueInst, out falseInst)) + return false; + if (!(loadTemp.MatchLdLoc(out var tempVar) && tempVar.IsSingleDefinition && tempVar.LoadCount == 1)) + return false; + return block.Instructions[0].MatchStLoc(tempVar, out condition); + default: + condition = null; + trueInst = null; + falseInst = null; + return false; + } + } + private bool CheckAllUsesDominatedBy(ILVariable v, BlockContainer container, ILInstruction trueInst, ILInstruction storeToV, ILInstruction? loadInNullCheck, ILTransformContext context, ref ControlFlowGraph? cfg) { From 3218a0639610f42a840b776be419d3fc533ea3dd Mon Sep 17 00:00:00 2001 From: Siegfried Pammer Date: Fri, 4 Aug 2023 17:04:51 +0200 Subject: [PATCH 05/22] Add support for string constant patterns. --- .../TestCases/Pretty/PatternMatching.cs | 11 +++++++++++ ICSharpCode.Decompiler/CSharp/ExpressionBuilder.cs | 2 ++ .../IL/Instructions/MatchInstruction.cs | 11 ++++++++++- 3 files changed, 23 insertions(+), 1 deletion(-) diff --git a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/PatternMatching.cs b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/PatternMatching.cs index 81ac1c17a..038c50395 100644 --- a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/PatternMatching.cs +++ b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/PatternMatching.cs @@ -335,6 +335,17 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty } } + public void RecursivePattern_StringConstant(object obj) + { + if (obj is X { B: "Hello" } x) + { + Console.WriteLine("Test " + x); + } + else + { + Console.WriteLine("not Test"); + } + } #endif private bool F() { diff --git a/ICSharpCode.Decompiler/CSharp/ExpressionBuilder.cs b/ICSharpCode.Decompiler/CSharp/ExpressionBuilder.cs index feafd7199..2729a27ad 100644 --- a/ICSharpCode.Decompiler/CSharp/ExpressionBuilder.cs +++ b/ICSharpCode.Decompiler/CSharp/ExpressionBuilder.cs @@ -4641,6 +4641,8 @@ namespace ICSharpCode.Decompiler.CSharp default: throw new InvalidOperationException("Unexpected comparison kind: " + comp.Kind); } + case Call call when MatchInstruction.IsCallToString_op_Equality(call): + return Translate(call.Arguments[1]); default: throw new NotImplementedException(); } diff --git a/ICSharpCode.Decompiler/IL/Instructions/MatchInstruction.cs b/ICSharpCode.Decompiler/IL/Instructions/MatchInstruction.cs index f12f06cf6..84a625096 100644 --- a/ICSharpCode.Decompiler/IL/Instructions/MatchInstruction.cs +++ b/ICSharpCode.Decompiler/IL/Instructions/MatchInstruction.cs @@ -135,12 +135,22 @@ namespace ICSharpCode.Decompiler.IL testedOperand = comp.Left; return IsConstant(comp.Right); } + case Call call when IsCallToString_op_Equality(call): + testedOperand = call.Arguments[0]; + return call.Arguments[1].OpCode == OpCode.LdStr; default: testedOperand = null; return false; } } + internal static bool IsCallToString_op_Equality(Call call) + { + return call.Method.IsOperator && call.Method.Name == "op_Equality" + && call.Method.DeclaringType.IsKnownType(KnownTypeCode.String) + && call.Arguments.Count == 2; + } + private static bool IsConstant(ILInstruction inst) { return inst.OpCode switch { @@ -150,7 +160,6 @@ namespace ICSharpCode.Decompiler.IL OpCode.LdcI4 => true, OpCode.LdcI8 => true, OpCode.LdNull => true, - OpCode.LdStr => true, _ => false }; } From 4e62fea07ab797d927f9a72d20f114ce280bb965 Mon Sep 17 00:00:00 2001 From: Siegfried Pammer Date: Fri, 4 Aug 2023 17:53:58 +0200 Subject: [PATCH 06/22] Fix pattern matching with fields and value types. --- .../TestCases/Pretty/PatternMatching.cs | 29 +++++++++ .../IL/Instructions/MatchInstruction.cs | 5 +- .../IL/Transforms/PatternMatchingTransform.cs | 64 ++++++++++++------- 3 files changed, 73 insertions(+), 25 deletions(-) diff --git a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/PatternMatching.cs b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/PatternMatching.cs index 038c50395..d125a98ef 100644 --- a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/PatternMatching.cs +++ b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/PatternMatching.cs @@ -11,6 +11,11 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty public object C { get; set; } } + public struct S + { + public int I; + } + public void SimpleTypePattern(object x) { if (x is string value) @@ -346,6 +351,30 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty Console.WriteLine("not Test"); } } + + public void RecursivePattern_MultipleConstants(object obj) + { + if (obj is X { A: 42, B: "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"); + } + } #endif private bool F() { diff --git a/ICSharpCode.Decompiler/IL/Instructions/MatchInstruction.cs b/ICSharpCode.Decompiler/IL/Instructions/MatchInstruction.cs index 84a625096..d30c30280 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 @@ -211,12 +212,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/PatternMatchingTransform.cs b/ICSharpCode.Decompiler/IL/Transforms/PatternMatchingTransform.cs index c99a429a8..0b867b8e6 100644 --- a/ICSharpCode.Decompiler/IL/Transforms/PatternMatchingTransform.cs +++ b/ICSharpCode.Decompiler/IL/Transforms/PatternMatchingTransform.cs @@ -19,7 +19,6 @@ #nullable enable using System; -using System.Diagnostics; using System.Diagnostics.CodeAnalysis; using System.Linq; using System.Reflection; @@ -189,7 +188,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms return true; } - private bool DetectPropertySubPatterns(MatchInstruction parentPattern, Block block, ILInstruction parentFalseInst, ILTransformContext context) + private void DetectPropertySubPatterns(MatchInstruction parentPattern, Block block, ILInstruction parentFalseInst, ILTransformContext context) { // if (match.notnull.type[System.String] (V_0 = callvirt get_C(ldloc V_2))) br IL_0022 // br IL_0037 @@ -197,25 +196,19 @@ namespace ICSharpCode.Decompiler.IL.Transforms { if (MatchInstruction.IsPatternMatch(condition, out var operand)) { - if (operand is not CallInstruction { - Method: { - SymbolKind: SymbolKind.Accessor, - AccessorKind: MethodSemanticsAttributes.Getter - }, - Arguments: [LdLoc ldloc] - } call) + if (!PropertyOrFieldAccess(operand, out var target)) { - return false; + return; } - if (ldloc.Variable != parentPattern.Variable) + if (!target.MatchLdLocRef(parentPattern.Variable)) { - return false; + return; } if (!DetectExitPoints.CompatibleExitInstruction(parentFalseInst, falseInst)) { if (!DetectExitPoints.CompatibleExitInstruction(parentFalseInst, trueInst)) { - return false; + return; } ExtensionMethods.Swap(ref trueInst, ref falseInst); condition = Comp.LogicNot(condition); @@ -224,10 +217,36 @@ namespace ICSharpCode.Decompiler.IL.Transforms parentPattern.SubPatterns.Add(condition); block.Instructions.Clear(); block.Instructions.Add(trueInst); - return true; + + if (trueInst.MatchBranch(out var trueBlock) && trueBlock.IncomingEdgeCount == 1 && trueBlock.Parent == block.Parent) + { + DetectPropertySubPatterns(parentPattern, trueBlock, falseInst, context); + } } } - return false; + } + + private static bool PropertyOrFieldAccess(ILInstruction operand, [NotNullWhen(true)] out ILInstruction? target) + { + if (operand is CallInstruction { + Method: { + SymbolKind: SymbolKind.Accessor, + AccessorKind: MethodSemanticsAttributes.Getter + }, + Arguments: [var _target] + }) + { + target = _target; + return true; + } + else if (operand.MatchLdFld(out target, out _)) + { + return true; + } + else + { + return false; + } } private static bool MatchBlockContainingOneCondition(Block block, [NotNullWhen(true)] out ILInstruction? condition, [NotNullWhen(true)] out ILInstruction? trueInst, [NotNullWhen(true)] out ILInstruction? falseInst) @@ -310,7 +329,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; } @@ -348,8 +367,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) { @@ -360,6 +379,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, context); return true; } @@ -376,15 +396,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; } @@ -412,8 +431,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) { From 51a8eb28f1804c4b2d69f9ddbe97cbb808e95d94 Mon Sep 17 00:00:00 2001 From: Siegfried Pammer Date: Fri, 4 Aug 2023 18:58:52 +0200 Subject: [PATCH 07/22] Add support for var sub patterns. --- .../TestCases/Pretty/PatternMatching.cs | 16 ++++++- .../CSharp/ExpressionBuilder.cs | 28 ++++++++--- .../IL/Transforms/AssignVariableNames.cs | 21 +++++++-- .../IL/Transforms/PatternMatchingTransform.cs | 46 +++++++++++++++---- 4 files changed, 91 insertions(+), 20 deletions(-) diff --git a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/PatternMatching.cs b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/PatternMatching.cs index d125a98ef..ed0168a5d 100644 --- a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/PatternMatching.cs +++ b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/PatternMatching.cs @@ -318,9 +318,9 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty #if CS80 public void RecursivePattern_Type(object x) { - if (x is X { C: string text }) + if (x is X { C: string c }) { - Console.WriteLine("Test " + text); + Console.WriteLine("Test " + c); } else { @@ -375,6 +375,18 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty Console.WriteLine("not Test"); } } + + public void RecursivePattern_MultipleConstantsMixedWithVar(object x) + { + if (x is X { A: 42, C: var c, B: "Hello" }) + { + Console.WriteLine("Test " + c); + } + else + { + Console.WriteLine("not Test"); + } + } #endif private bool F() { diff --git a/ICSharpCode.Decompiler/CSharp/ExpressionBuilder.cs b/ICSharpCode.Decompiler/CSharp/ExpressionBuilder.cs index 2729a27ad..f3a789cbf 100644 --- a/ICSharpCode.Decompiler/CSharp/ExpressionBuilder.cs +++ b/ICSharpCode.Decompiler/CSharp/ExpressionBuilder.cs @@ -4558,16 +4558,21 @@ namespace ICSharpCode.Decompiler.CSharp 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.Count > 0) + if (matchInstruction.SubPatterns.Count > 0 || (matchInstruction.CheckNotNull && !matchInstruction.CheckType)) { RecursivePatternExpression recursivePatternExpression = new(); - recursivePatternExpression.Type = ConvertType(matchInstruction.Variable.Type); + if (matchInstruction.CheckType) + { + recursivePatternExpression.Type = ConvertType(matchInstruction.Variable.Type); + } + else + { + Debug.Assert(matchInstruction.CheckNotNull); + } foreach (var subPattern in matchInstruction.SubPatterns) { if (!MatchInstruction.IsPatternMatch(subPattern, out var testedOperand)) @@ -4602,17 +4607,28 @@ namespace ICSharpCode.Decompiler.CSharp } return recursivePatternExpression.WithILInstruction(matchInstruction); } - else if (matchInstruction.HasDesignator) + 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); } 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/PatternMatchingTransform.cs b/ICSharpCode.Decompiler/IL/Transforms/PatternMatchingTransform.cs index 0b867b8e6..d5ac6e0a6 100644 --- a/ICSharpCode.Decompiler/IL/Transforms/PatternMatchingTransform.cs +++ b/ICSharpCode.Decompiler/IL/Transforms/PatternMatchingTransform.cs @@ -182,13 +182,13 @@ namespace ICSharpCode.Decompiler.IL.Transforms if (trueInst.MatchBranch(out var trueBlock) && trueBlock.IncomingEdgeCount == 1 && trueBlock.Parent == container) { - DetectPropertySubPatterns((MatchInstruction)ifInst.Condition, trueBlock, falseInst, context); + DetectPropertySubPatterns((MatchInstruction)ifInst.Condition, trueBlock, falseInst, context, ref cfg); } return true; } - private void DetectPropertySubPatterns(MatchInstruction parentPattern, Block block, ILInstruction parentFalseInst, ILTransformContext context) + private void DetectPropertySubPatterns(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 @@ -196,7 +196,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms { if (MatchInstruction.IsPatternMatch(condition, out var operand)) { - if (!PropertyOrFieldAccess(operand, out var target)) + if (!PropertyOrFieldAccess(operand, out var target, out _)) { return; } @@ -220,31 +220,61 @@ namespace ICSharpCode.Decompiler.IL.Transforms if (trueInst.MatchBranch(out var trueBlock) && trueBlock.IncomingEdgeCount == 1 && trueBlock.Parent == block.Parent) { - DetectPropertySubPatterns(parentPattern, trueBlock, falseInst, context); + DetectPropertySubPatterns(parentPattern, trueBlock, falseInst, context, ref cfg); } } } + else if (block.Instructions[0].MatchStLoc(out var targetVariable, out var operand)) + { + if (!PropertyOrFieldAccess(operand, out var target, out var member)) + { + return; + } + if (!target.MatchLdLocRef(parentPattern.Variable)) + { + return; + } + if (!targetVariable.Type.Equals(member.ReturnType)) + { + return; + } + if (!CheckAllUsesDominatedBy(targetVariable, (BlockContainer)block.Parent!, block, block.Instructions[0], null, context, ref cfg)) + { + return; + } + 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; + DetectPropertySubPatterns(parentPattern, block, parentFalseInst, context, ref cfg); + } } - private static bool PropertyOrFieldAccess(ILInstruction operand, [NotNullWhen(true)] out ILInstruction? target) + 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 + AccessorKind: MethodSemanticsAttributes.Getter, + AccessorOwner: { } _member }, Arguments: [var _target] }) { target = _target; + member = _member; return true; } - else if (operand.MatchLdFld(out target, out _)) + else if (operand.MatchLdFld(out target, out var field)) { + member = field; return true; } else { + member = null; return false; } } @@ -379,7 +409,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, context); + DetectPropertySubPatterns((MatchInstruction)ifInst.Condition, unboxBlock, falseInst, context, ref cfg); return true; } From e475af78221f4cdf65e172117439a2d8093d653f Mon Sep 17 00:00:00 2001 From: Siegfried Pammer Date: Fri, 4 Aug 2023 20:08:00 +0200 Subject: [PATCH 08/22] Support null check without type check in sub patterns. --- .../TestCases/Pretty/PatternMatching.cs | 24 ++++++ .../IL/Instructions/MatchInstruction.cs | 4 +- .../IL/Transforms/PatternMatchingTransform.cs | 83 +++++++++++++++---- 3 files changed, 94 insertions(+), 17 deletions(-) diff --git a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/PatternMatching.cs b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/PatternMatching.cs index ed0168a5d..8471db9ca 100644 --- a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/PatternMatching.cs +++ b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/PatternMatching.cs @@ -387,6 +387,30 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty Console.WriteLine("not Test"); } } + + public void RecursivePattern_NonTypePattern(object obj) + { + if (obj is X { A: 42, B: { Length: 0 } } x) + { + Console.WriteLine("Test " + x); + } + else + { + Console.WriteLine("not Test"); + } + } + + public void RecursivePattern_VarLengthPattern(object obj) + { + if (obj is X { A: 42, B: { Length: var length } } x) + { + Console.WriteLine("Test " + x.A + ": " + length); + } + else + { + Console.WriteLine("not Test"); + } + } #endif private bool F() { diff --git a/ICSharpCode.Decompiler/IL/Instructions/MatchInstruction.cs b/ICSharpCode.Decompiler/IL/Instructions/MatchInstruction.cs index d30c30280..cb61f6613 100644 --- a/ICSharpCode.Decompiler/IL/Instructions/MatchInstruction.cs +++ b/ICSharpCode.Decompiler/IL/Instructions/MatchInstruction.cs @@ -127,9 +127,9 @@ 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)) { - return IsPatternMatch(operand, out testedOperand); + return true; } else { diff --git a/ICSharpCode.Decompiler/IL/Transforms/PatternMatchingTransform.cs b/ICSharpCode.Decompiler/IL/Transforms/PatternMatchingTransform.cs index d5ac6e0a6..8b0737c9c 100644 --- a/ICSharpCode.Decompiler/IL/Transforms/PatternMatchingTransform.cs +++ b/ICSharpCode.Decompiler/IL/Transforms/PatternMatchingTransform.cs @@ -188,12 +188,22 @@ namespace ICSharpCode.Decompiler.IL.Transforms return true; } - private void DetectPropertySubPatterns(MatchInstruction parentPattern, Block block, ILInstruction parentFalseInst, ILTransformContext context, ref ControlFlowGraph? cfg) + private static void DetectPropertySubPatterns(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; + } + ExtensionMethods.Swap(ref trueInst, ref falseInst); + negate = true; + } if (MatchInstruction.IsPatternMatch(condition, out var operand)) { if (!PropertyOrFieldAccess(operand, out var target, out _)) @@ -204,24 +214,32 @@ namespace ICSharpCode.Decompiler.IL.Transforms { return; } - if (!DetectExitPoints.CompatibleExitInstruction(parentFalseInst, falseInst)) + context.Step("Move property sub pattern", condition); + if (negate) { - if (!DetectExitPoints.CompatibleExitInstruction(parentFalseInst, trueInst)) - { - return; - } - ExtensionMethods.Swap(ref trueInst, ref falseInst); condition = Comp.LogicNot(condition); } - context.Step("Move property sub pattern", condition); parentPattern.SubPatterns.Add(condition); - block.Instructions.Clear(); - block.Instructions.Add(trueInst); - - if (trueInst.MatchBranch(out var trueBlock) && trueBlock.IncomingEdgeCount == 1 && trueBlock.Parent == block.Parent) + } + else if (PropertyOrFieldAccess(condition, out var target, out _)) + { + if (!target.MatchLdLocRef(parentPattern.Variable)) { - DetectPropertySubPatterns(parentPattern, trueBlock, falseInst, context, ref cfg); + return; } + context.Step("Move property sub pattern", condition); + parentPattern.SubPatterns.Add(new Comp(negate ? ComparisonKind.Equality : ComparisonKind.Inequality, Sign.None, condition, new LdcI4(0))); + } + else + { + return; + } + block.Instructions.Clear(); + block.Instructions.Add(trueInst); + + if (trueInst.MatchBranch(out var trueBlock) && trueBlock.IncomingEdgeCount == 1 && trueBlock.Parent == block.Parent) + { + DetectPropertySubPatterns(parentPattern, trueBlock, falseInst, context, ref cfg); } } else if (block.Instructions[0].MatchStLoc(out var targetVariable, out var operand)) @@ -248,10 +266,45 @@ namespace ICSharpCode.Decompiler.IL.Transforms parentPattern.SubPatterns.Add(varPattern); block.Instructions.RemoveAt(0); targetVariable.Kind = VariableKind.PatternLocal; - DetectPropertySubPatterns(parentPattern, block, parentFalseInst, context, ref cfg); + if (!MatchNullCheckPattern(block, varPattern, parentFalseInst, context, ref cfg)) + { + DetectPropertySubPatterns(parentPattern, block, parentFalseInst, context, ref cfg); + } } } + private static bool MatchNullCheckPattern(Block block, MatchInstruction varPattern, ILInstruction parentFalseInst, ILTransformContext context, ref ControlFlowGraph? cfg) + { + if (!MatchBlockContainingOneCondition(block, out var condition, out var trueInst, out var falseInst)) + { + return false; + } + 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 false; + } + if (!DetectExitPoints.CompatibleExitInstruction(falseInst, parentFalseInst)) + { + return false; + } + context.Step("Null check pattern", block); + varPattern.CheckNotNull = true; + block.Instructions.Clear(); + block.Instructions.Add(trueInst); + if (trueInst.MatchBranch(out var trueBlock) && trueBlock.IncomingEdgeCount == 1 && trueBlock.Parent == block.Parent) + { + DetectPropertySubPatterns(varPattern, trueBlock, falseInst, context, ref cfg); + } + return true; + } + private static bool PropertyOrFieldAccess(ILInstruction operand, [NotNullWhen(true)] out ILInstruction? target, [NotNullWhen(true)] out IMember? member) { if (operand is CallInstruction { @@ -300,7 +353,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms } } - private bool CheckAllUsesDominatedBy(ILVariable v, BlockContainer container, ILInstruction trueInst, + private static bool CheckAllUsesDominatedBy(ILVariable v, BlockContainer container, ILInstruction trueInst, ILInstruction storeToV, ILInstruction? loadInNullCheck, ILTransformContext context, ref ControlFlowGraph? cfg) { var targetBlock = trueInst as Block; From 8cb3a67c0c208fd9adf7bdf515f902cb0ea5a19d Mon Sep 17 00:00:00 2001 From: Siegfried Pammer Date: Sat, 5 Aug 2023 09:28:32 +0200 Subject: [PATCH 09/22] Support recursive pattern for value types --- .../TestCases/Pretty/PatternMatching.cs | 65 +++++++++++++++---- .../CSharp/ExpressionBuilder.cs | 2 +- .../OutputVisitor/CSharpOutputVisitor.cs | 2 +- .../IL/Transforms/PatternMatchingTransform.cs | 4 ++ 4 files changed, 58 insertions(+), 15 deletions(-) diff --git a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/PatternMatching.cs b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/PatternMatching.cs index 8471db9ca..00d6f2a82 100644 --- a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/PatternMatching.cs +++ b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/PatternMatching.cs @@ -6,14 +6,17 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty { public class X { - public int A { get; set; } - public string B { get; set; } - public object C { get; set; } + public int I { get; set; } + public string Text { get; set; } + public object Obj { get; set; } + public S CustomStruct { get; set; } } public struct S { public int I; + public string Text { get; set; } + public object Obj { get; set; } } public void SimpleTypePattern(object x) @@ -318,9 +321,9 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty #if CS80 public void RecursivePattern_Type(object x) { - if (x is X { C: string c }) + if (x is X { Obj: string obj }) { - Console.WriteLine("Test " + c); + Console.WriteLine("Test " + obj); } else { @@ -330,7 +333,7 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty public void RecursivePattern_Constant(object obj) { - if (obj is X { C: null } x) + if (obj is X { Obj: null } x) { Console.WriteLine("Test " + x); } @@ -342,7 +345,7 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty public void RecursivePattern_StringConstant(object obj) { - if (obj is X { B: "Hello" } x) + if (obj is X { Text: "Hello" } x) { Console.WriteLine("Test " + x); } @@ -354,7 +357,7 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty public void RecursivePattern_MultipleConstants(object obj) { - if (obj is X { A: 42, B: "Hello" } x) + if (obj is X { I: 42, Text: "Hello" } x) { Console.WriteLine("Test " + x); } @@ -378,9 +381,9 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty public void RecursivePattern_MultipleConstantsMixedWithVar(object x) { - if (x is X { A: 42, C: var c, B: "Hello" }) + if (x is X { I: 42, Obj: var obj, Text: "Hello" }) { - Console.WriteLine("Test " + c); + Console.WriteLine("Test " + obj); } else { @@ -390,7 +393,7 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty public void RecursivePattern_NonTypePattern(object obj) { - if (obj is X { A: 42, B: { Length: 0 } } x) + if (obj is X { I: 42, Text: { Length: 0 } } x) { Console.WriteLine("Test " + x); } @@ -400,11 +403,47 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty } } + 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 { A: 42, B: { Length: var length } } x) + 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 " + x.A + ": " + length); + Console.WriteLine("Test " + s.I + ": " + length); } else { diff --git a/ICSharpCode.Decompiler/CSharp/ExpressionBuilder.cs b/ICSharpCode.Decompiler/CSharp/ExpressionBuilder.cs index f3a789cbf..166fcd849 100644 --- a/ICSharpCode.Decompiler/CSharp/ExpressionBuilder.cs +++ b/ICSharpCode.Decompiler/CSharp/ExpressionBuilder.cs @@ -4571,7 +4571,7 @@ namespace ICSharpCode.Decompiler.CSharp } else { - Debug.Assert(matchInstruction.CheckNotNull); + Debug.Assert(matchInstruction.CheckNotNull || matchInstruction.Variable.Type.IsReferenceType == false); } foreach (var subPattern in matchInstruction.SubPatterns) { diff --git a/ICSharpCode.Decompiler/CSharp/OutputVisitor/CSharpOutputVisitor.cs b/ICSharpCode.Decompiler/CSharp/OutputVisitor/CSharpOutputVisitor.cs index 152f9fdd8..7fee876ca 100644 --- a/ICSharpCode.Decompiler/CSharp/OutputVisitor/CSharpOutputVisitor.cs +++ b/ICSharpCode.Decompiler/CSharp/OutputVisitor/CSharpOutputVisitor.cs @@ -1302,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(); diff --git a/ICSharpCode.Decompiler/IL/Transforms/PatternMatchingTransform.cs b/ICSharpCode.Decompiler/IL/Transforms/PatternMatchingTransform.cs index 8b0737c9c..6d9ebaf50 100644 --- a/ICSharpCode.Decompiler/IL/Transforms/PatternMatchingTransform.cs +++ b/ICSharpCode.Decompiler/IL/Transforms/PatternMatchingTransform.cs @@ -268,6 +268,10 @@ namespace ICSharpCode.Decompiler.IL.Transforms targetVariable.Kind = VariableKind.PatternLocal; if (!MatchNullCheckPattern(block, varPattern, parentFalseInst, context, ref cfg)) { + if (targetVariable.Type.IsReferenceType == false) + { + DetectPropertySubPatterns(varPattern, block, parentFalseInst, context, ref cfg); + } DetectPropertySubPatterns(parentPattern, block, parentFalseInst, context, ref cfg); } } From 1cb4e77f0651d88a9bb18e9b10971ef5b7dc6a87 Mon Sep 17 00:00:00 2001 From: Siegfried Pammer Date: Sat, 5 Aug 2023 10:26:34 +0200 Subject: [PATCH 10/22] Refactor sub pattern detection into loop to allow continuations of outer patterns. --- .../TestCases/Pretty/PatternMatching.cs | 36 +++++++ .../IL/Transforms/PatternMatchingTransform.cs | 95 ++++++++++++------- 2 files changed, 96 insertions(+), 35 deletions(-) diff --git a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/PatternMatching.cs b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/PatternMatching.cs index 00d6f2a82..73dac3b28 100644 --- a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/PatternMatching.cs +++ b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/PatternMatching.cs @@ -331,6 +331,18 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty } } + 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) @@ -450,6 +462,30 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty 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"); + } + } #endif private bool F() { diff --git a/ICSharpCode.Decompiler/IL/Transforms/PatternMatchingTransform.cs b/ICSharpCode.Decompiler/IL/Transforms/PatternMatchingTransform.cs index 6d9ebaf50..4c5dfcbdd 100644 --- a/ICSharpCode.Decompiler/IL/Transforms/PatternMatchingTransform.cs +++ b/ICSharpCode.Decompiler/IL/Transforms/PatternMatchingTransform.cs @@ -180,15 +180,37 @@ namespace ICSharpCode.Decompiler.IL.Transforms block.Instructions.RemoveRange(pos, ifInst.ChildIndex - pos); v.Kind = VariableKind.PatternLocal; - if (trueInst.MatchBranch(out var trueBlock) && trueBlock.IncomingEdgeCount == 1 && trueBlock.Parent == container) - { - DetectPropertySubPatterns((MatchInstruction)ifInst.Condition, trueBlock, falseInst, context, ref cfg); - } + DetectPropertySubPatterns((MatchInstruction)ifInst.Condition, trueInst, falseInst, container, context, ref cfg); return true; } - private static void DetectPropertySubPatterns(MatchInstruction parentPattern, Block block, ILInstruction parentFalseInst, ILTransformContext context, ref ControlFlowGraph? cfg) + private static ILInstruction? DetectPropertySubPatterns(MatchInstruction parentPattern, ILInstruction? trueInst, + ILInstruction parentFalseInst, BlockContainer container, ILTransformContext context, ref ControlFlowGraph? cfg) + { + ILInstruction? prevTrueInst = trueInst; + while (trueInst != null) + { + Block? trueBlock = trueInst as Block; + if (!(trueBlock != null || trueInst.MatchBranch(out trueBlock))) + { + break; + } + if (!(trueBlock.IncomingEdgeCount == 1 && trueBlock.Parent == container)) + { + break; + } + trueInst = DetectPropertySubPattern(parentPattern, trueBlock, parentFalseInst, context, ref cfg); + if (trueInst != null) + { + prevTrueInst = trueInst; + } + } + return prevTrueInst; + } + + 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 @@ -199,7 +221,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms { if (!DetectExitPoints.CompatibleExitInstruction(parentFalseInst, trueInst)) { - return; + return null; } ExtensionMethods.Swap(ref trueInst, ref falseInst); negate = true; @@ -208,11 +230,11 @@ namespace ICSharpCode.Decompiler.IL.Transforms { if (!PropertyOrFieldAccess(operand, out var target, out _)) { - return; + return null; } if (!target.MatchLdLocRef(parentPattern.Variable)) { - return; + return null; } context.Step("Move property sub pattern", condition); if (negate) @@ -225,40 +247,36 @@ namespace ICSharpCode.Decompiler.IL.Transforms { if (!target.MatchLdLocRef(parentPattern.Variable)) { - return; + return null; } context.Step("Move property sub pattern", condition); parentPattern.SubPatterns.Add(new Comp(negate ? ComparisonKind.Equality : ComparisonKind.Inequality, Sign.None, condition, new LdcI4(0))); } else { - return; + return null; } block.Instructions.Clear(); block.Instructions.Add(trueInst); - - if (trueInst.MatchBranch(out var trueBlock) && trueBlock.IncomingEdgeCount == 1 && trueBlock.Parent == block.Parent) - { - DetectPropertySubPatterns(parentPattern, trueBlock, falseInst, context, ref cfg); - } + return trueInst; } else if (block.Instructions[0].MatchStLoc(out var targetVariable, out var operand)) { if (!PropertyOrFieldAccess(operand, out var target, out var member)) { - return; + return null; } if (!target.MatchLdLocRef(parentPattern.Variable)) { - return; + return null; } if (!targetVariable.Type.Equals(member.ReturnType)) { - return; + return null; } if (!CheckAllUsesDominatedBy(targetVariable, (BlockContainer)block.Parent!, block, block.Instructions[0], null, context, ref cfg)) { - return; + return null; } context.Step("Property var pattern", block); var varPattern = new MatchInstruction(targetVariable, operand) @@ -266,22 +284,33 @@ namespace ICSharpCode.Decompiler.IL.Transforms parentPattern.SubPatterns.Add(varPattern); block.Instructions.RemoveAt(0); targetVariable.Kind = VariableKind.PatternLocal; - if (!MatchNullCheckPattern(block, varPattern, parentFalseInst, context, ref cfg)) + + var instructionAfterNullCheck = MatchNullCheckPattern(block, varPattern, parentFalseInst, context, ref cfg); + if (instructionAfterNullCheck != null) { - if (targetVariable.Type.IsReferenceType == false) - { - DetectPropertySubPatterns(varPattern, block, parentFalseInst, context, ref cfg); - } - DetectPropertySubPatterns(parentPattern, block, parentFalseInst, context, ref cfg); + 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 bool MatchNullCheckPattern(Block block, MatchInstruction varPattern, ILInstruction parentFalseInst, ILTransformContext context, ref ControlFlowGraph? cfg) + private static ILInstruction? MatchNullCheckPattern(Block block, MatchInstruction varPattern, + ILInstruction parentFalseInst, ILTransformContext context, ref ControlFlowGraph? cfg) { if (!MatchBlockContainingOneCondition(block, out var condition, out var trueInst, out var falseInst)) { - return false; + return null; } if (condition.MatchCompEqualsNull(out var arg) && arg.MatchLdLoc(varPattern.Variable)) { @@ -292,21 +321,17 @@ namespace ICSharpCode.Decompiler.IL.Transforms } else { - return false; + return null; } if (!DetectExitPoints.CompatibleExitInstruction(falseInst, parentFalseInst)) { - return false; + return null; } context.Step("Null check pattern", block); varPattern.CheckNotNull = true; block.Instructions.Clear(); block.Instructions.Add(trueInst); - if (trueInst.MatchBranch(out var trueBlock) && trueBlock.IncomingEdgeCount == 1 && trueBlock.Parent == block.Parent) - { - DetectPropertySubPatterns(varPattern, trueBlock, falseInst, context, ref cfg); - } - return true; + return trueInst; } private static bool PropertyOrFieldAccess(ILInstruction operand, [NotNullWhen(true)] out ILInstruction? target, [NotNullWhen(true)] out IMember? member) @@ -466,7 +491,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, context, ref cfg); + DetectPropertySubPatterns((MatchInstruction)ifInst.Condition, unboxBlock, falseInst, container, context, ref cfg); return true; } From a93731ad3ae492b82bb4960a31c89104e61dc511 Mon Sep 17 00:00:00 2001 From: Siegfried Pammer Date: Sat, 5 Aug 2023 11:28:53 +0200 Subject: [PATCH 11/22] Add support for nullable structs --- .../TestCases/Pretty/PatternMatching.cs | 16 ++++++ .../IL/Transforms/PatternMatchingTransform.cs | 50 ++++++++++++++++++- 2 files changed, 64 insertions(+), 2 deletions(-) diff --git a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/PatternMatching.cs b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/PatternMatching.cs index 73dac3b28..2cb188567 100644 --- a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/PatternMatching.cs +++ b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/PatternMatching.cs @@ -6,10 +6,14 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty { 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 @@ -486,6 +490,18 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty 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"); + } + } #endif private bool F() { diff --git a/ICSharpCode.Decompiler/IL/Transforms/PatternMatchingTransform.cs b/ICSharpCode.Decompiler/IL/Transforms/PatternMatchingTransform.cs index 4c5dfcbdd..70e920072 100644 --- a/ICSharpCode.Decompiler/IL/Transforms/PatternMatchingTransform.cs +++ b/ICSharpCode.Decompiler/IL/Transforms/PatternMatchingTransform.cs @@ -285,7 +285,13 @@ namespace ICSharpCode.Decompiler.IL.Transforms block.Instructions.RemoveAt(0); targetVariable.Kind = VariableKind.PatternLocal; - var instructionAfterNullCheck = MatchNullCheckPattern(block, varPattern, parentFalseInst, context, ref cfg); + if (targetVariable.Type.IsKnownType(KnownTypeCode.NullableOfT)) + { + var instructionAfterHasValueCheck = MatchNullableHasValueCheckPattern(block, varPattern, parentFalseInst, context); + return DetectPropertySubPatterns(varPattern, instructionAfterHasValueCheck, parentFalseInst, (BlockContainer)block.Parent!, context, ref cfg); + } + + var instructionAfterNullCheck = MatchNullCheckPattern(block, varPattern, parentFalseInst, context); if (instructionAfterNullCheck != null) { return DetectPropertySubPatterns(varPattern, instructionAfterNullCheck, parentFalseInst, (BlockContainer)block.Parent!, context, ref cfg); @@ -306,7 +312,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms } private static ILInstruction? MatchNullCheckPattern(Block block, MatchInstruction varPattern, - ILInstruction parentFalseInst, ILTransformContext context, ref ControlFlowGraph? cfg) + ILInstruction parentFalseInst, ILTransformContext context) { if (!MatchBlockContainingOneCondition(block, out var condition, out var trueInst, out var falseInst)) { @@ -333,6 +339,46 @@ namespace ICSharpCode.Decompiler.IL.Transforms block.Instructions.Add(trueInst); return trueInst; } + private static ILInstruction? MatchNullableHasValueCheckPattern(Block block, MatchInstruction varPattern, + ILInstruction parentFalseInst, ILTransformContext context) + { + 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)) + { + 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)) + { + return null; + } + if (!NullableLiftingTransform.MatchGetValueOrDefault(getValueOrDefaultCall, varPattern.Variable)) + { + return null; + } + if (!(varPattern.Variable.StoreCount == 1 && varPattern.Variable.LoadCount == 0 && varPattern.Variable.AddressCount == 2)) + { + return null; + } + 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 trueBlock; + } private static bool PropertyOrFieldAccess(ILInstruction operand, [NotNullWhen(true)] out ILInstruction? target, [NotNullWhen(true)] out IMember? member) { From cb62cac9d3372342951d3427f199157c6ddbd9af Mon Sep 17 00:00:00 2001 From: Siegfried Pammer Date: Sat, 5 Aug 2023 12:18:52 +0200 Subject: [PATCH 12/22] Add support for nullable int const patterns --- .../TestCases/Pretty/PatternMatching.cs | 12 ++++ .../IL/Instructions/Comp.cs | 2 +- .../IL/Instructions/MatchInstruction.cs | 2 +- .../IL/Transforms/PatternMatchingTransform.cs | 69 +++++++++++++++---- 4 files changed, 68 insertions(+), 17 deletions(-) diff --git a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/PatternMatching.cs b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/PatternMatching.cs index 2cb188567..a2815b736 100644 --- a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/PatternMatching.cs +++ b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/PatternMatching.cs @@ -491,6 +491,18 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty } } + 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_NullableCustomStructProp_Const(object obj) { if (obj is X { NullableCustomStructProp: { I: 42, Obj: not null } nullableCustomStructProp }) 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 cb61f6613..33f4a0bb5 100644 --- a/ICSharpCode.Decompiler/IL/Instructions/MatchInstruction.cs +++ b/ICSharpCode.Decompiler/IL/Instructions/MatchInstruction.cs @@ -152,7 +152,7 @@ namespace ICSharpCode.Decompiler.IL && call.Arguments.Count == 2; } - private static bool IsConstant(ILInstruction inst) + internal static bool IsConstant(ILInstruction inst) { return inst.OpCode switch { OpCode.LdcDecimal => true, diff --git a/ICSharpCode.Decompiler/IL/Transforms/PatternMatchingTransform.cs b/ICSharpCode.Decompiler/IL/Transforms/PatternMatchingTransform.cs index 70e920072..4771a13ae 100644 --- a/ICSharpCode.Decompiler/IL/Transforms/PatternMatchingTransform.cs +++ b/ICSharpCode.Decompiler/IL/Transforms/PatternMatchingTransform.cs @@ -50,6 +50,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms continue; } } + container.Blocks.RemoveAll(b => b.Instructions.Count == 0); } } @@ -287,8 +288,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms if (targetVariable.Type.IsKnownType(KnownTypeCode.NullableOfT)) { - var instructionAfterHasValueCheck = MatchNullableHasValueCheckPattern(block, varPattern, parentFalseInst, context); - return DetectPropertySubPatterns(varPattern, instructionAfterHasValueCheck, parentFalseInst, (BlockContainer)block.Parent!, context, ref cfg); + return MatchNullableHasValueCheckPattern(block, varPattern, parentFalseInst, context, ref cfg); } var instructionAfterNullCheck = MatchNullCheckPattern(block, varPattern, parentFalseInst, context); @@ -340,7 +340,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms return trueInst; } private static ILInstruction? MatchNullableHasValueCheckPattern(Block block, MatchInstruction varPattern, - ILInstruction parentFalseInst, ILTransformContext context) + ILInstruction parentFalseInst, ILTransformContext context, ref ControlFlowGraph? cfg) { if (!MatchBlockContainingOneCondition(block, out var condition, out var trueInst, out var falseInst)) { @@ -358,26 +358,65 @@ namespace ICSharpCode.Decompiler.IL.Transforms { return null; } - if (!trueBlock.Instructions[0].MatchStLoc(out var newTargetVariable, out var getValueOrDefaultCall)) + if (!(varPattern.Variable.StoreCount == 1 && varPattern.Variable.LoadCount == 0 && varPattern.Variable.AddressCount == 2)) { return null; } - if (!NullableLiftingTransform.MatchGetValueOrDefault(getValueOrDefaultCall, varPattern.Variable)) + if (trueBlock.Instructions[0].MatchStLoc(out var newTargetVariable, out var getValueOrDefaultCall) + && NullableLiftingTransform.MatchGetValueOrDefault(getValueOrDefaultCall, varPattern.Variable)) { - return null; + 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); } - if (!(varPattern.Variable.StoreCount == 1 && varPattern.Variable.LoadCount == 0 && varPattern.Variable.AddressCount == 2)) + 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; + } + 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; + } + 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; } - 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 trueBlock; } private static bool PropertyOrFieldAccess(ILInstruction operand, [NotNullWhen(true)] out ILInstruction? target, [NotNullWhen(true)] out IMember? member) From 800067e488c8b077eff95e783358d44e80aaaaad Mon Sep 17 00:00:00 2001 From: Siegfried Pammer Date: Sat, 5 Aug 2023 13:11:44 +0200 Subject: [PATCH 13/22] Pattern Matching: Ensure that we always return a non-null instruction after successfully matching a pattern. --- .../TestCases/Pretty/PatternMatching.cs | 72 +++++++++++++++++++ .../IL/Transforms/PatternMatchingTransform.cs | 21 +++--- 2 files changed, 85 insertions(+), 8 deletions(-) diff --git a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/PatternMatching.cs b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/PatternMatching.cs index a2815b736..777066a1b 100644 --- a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/PatternMatching.cs +++ b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/PatternMatching.cs @@ -503,6 +503,66 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty } } + + 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_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_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 }) @@ -514,6 +574,18 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty 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"); + } + } #endif private bool F() { diff --git a/ICSharpCode.Decompiler/IL/Transforms/PatternMatchingTransform.cs b/ICSharpCode.Decompiler/IL/Transforms/PatternMatchingTransform.cs index 4771a13ae..4125bdf8c 100644 --- a/ICSharpCode.Decompiler/IL/Transforms/PatternMatchingTransform.cs +++ b/ICSharpCode.Decompiler/IL/Transforms/PatternMatchingTransform.cs @@ -186,11 +186,10 @@ namespace ICSharpCode.Decompiler.IL.Transforms return true; } - private static ILInstruction? DetectPropertySubPatterns(MatchInstruction parentPattern, ILInstruction? trueInst, + private static ILInstruction DetectPropertySubPatterns(MatchInstruction parentPattern, ILInstruction trueInst, ILInstruction parentFalseInst, BlockContainer container, ILTransformContext context, ref ControlFlowGraph? cfg) { - ILInstruction? prevTrueInst = trueInst; - while (trueInst != null) + while (true) { Block? trueBlock = trueInst as Block; if (!(trueBlock != null || trueInst.MatchBranch(out trueBlock))) @@ -201,13 +200,17 @@ namespace ICSharpCode.Decompiler.IL.Transforms { break; } - trueInst = DetectPropertySubPattern(parentPattern, trueBlock, parentFalseInst, context, ref cfg); - if (trueInst != null) + var nextTrueInst = DetectPropertySubPattern(parentPattern, trueBlock, parentFalseInst, context, ref cfg); + if (nextTrueInst != null) { - prevTrueInst = trueInst; + trueInst = nextTrueInst; + } + else + { + break; } } - return prevTrueInst; + return trueInst; } private static ILInstruction? DetectPropertySubPattern(MatchInstruction parentPattern, Block block, @@ -288,7 +291,8 @@ namespace ICSharpCode.Decompiler.IL.Transforms if (targetVariable.Type.IsKnownType(KnownTypeCode.NullableOfT)) { - return MatchNullableHasValueCheckPattern(block, varPattern, parentFalseInst, context, ref cfg); + return MatchNullableHasValueCheckPattern(block, varPattern, parentFalseInst, context, ref cfg) + ?? block; } var instructionAfterNullCheck = MatchNullCheckPattern(block, varPattern, parentFalseInst, context); @@ -339,6 +343,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms block.Instructions.Add(trueInst); return trueInst; } + private static ILInstruction? MatchNullableHasValueCheckPattern(Block block, MatchInstruction varPattern, ILInstruction parentFalseInst, ILTransformContext context, ref ControlFlowGraph? cfg) { From 8e63d9288634ff668a247e72a1c1381670f3c2b0 Mon Sep 17 00:00:00 2001 From: Siegfried Pammer Date: Sat, 5 Aug 2023 13:38:43 +0200 Subject: [PATCH 14/22] Add null and not null patterns for nullable value types --- .../TestCases/Pretty/PatternMatching.cs | 96 +++++++++++++++++++ .../IL/Transforms/PatternMatchingTransform.cs | 38 +++++++- 2 files changed, 131 insertions(+), 3 deletions(-) diff --git a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/PatternMatching.cs b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/PatternMatching.cs index 777066a1b..4ee763441 100644 --- a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/PatternMatching.cs +++ b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/PatternMatching.cs @@ -503,6 +503,29 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty } } + 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) { @@ -527,6 +550,31 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty 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 }) @@ -551,6 +599,30 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty } } + 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 }) @@ -575,6 +647,30 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty } } + 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 }) diff --git a/ICSharpCode.Decompiler/IL/Transforms/PatternMatchingTransform.cs b/ICSharpCode.Decompiler/IL/Transforms/PatternMatchingTransform.cs index 4125bdf8c..6b1ddc8dc 100644 --- a/ICSharpCode.Decompiler/IL/Transforms/PatternMatchingTransform.cs +++ b/ICSharpCode.Decompiler/IL/Transforms/PatternMatchingTransform.cs @@ -347,6 +347,10 @@ namespace ICSharpCode.Decompiler.IL.Transforms 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; @@ -357,13 +361,34 @@ namespace ICSharpCode.Decompiler.IL.Transforms } 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 (!(trueInst.MatchBranch(out var trueBlock) && trueBlock.Parent == block.Parent && trueBlock.IncomingEdgeCount == 1)) + if (varPattern.Variable.AddressCount == 1) + { + 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 (!(varPattern.Variable.StoreCount == 1 && varPattern.Variable.LoadCount == 0 && varPattern.Variable.AddressCount == 2)) + if (!(trueInst.MatchBranch(out var trueBlock) && trueBlock.Parent == block.Parent && trueBlock.IncomingEdgeCount == 1)) { return null; } @@ -463,7 +488,14 @@ namespace ICSharpCode.Decompiler.IL.Transforms return false; if (!(loadTemp.MatchLdLoc(out var tempVar) && tempVar.IsSingleDefinition && tempVar.LoadCount == 1)) return false; - return block.Instructions[0].MatchStLoc(tempVar, out condition); + 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; From 688474facdc26c890eba5484d29e56f36a0fb46d Mon Sep 17 00:00:00 2001 From: Siegfried Pammer Date: Sat, 5 Aug 2023 14:32:28 +0200 Subject: [PATCH 15/22] Add missing DecompilerSettings for new language features --- .../CSharp/ExpressionBuilder.cs | 2 +- ICSharpCode.Decompiler/DecompilerSettings.cs | 62 ++++++++++++++++++- .../IL/Instructions/MatchInstruction.cs | 18 ++++-- .../IL/Transforms/ExpressionTransforms.cs | 2 +- .../IL/Transforms/PatternMatchingTransform.cs | 29 +++++++-- ILSpy/Properties/Resources.Designer.cs | 27 ++++++++ ILSpy/Properties/Resources.resx | 9 +++ 7 files changed, 137 insertions(+), 12 deletions(-) diff --git a/ICSharpCode.Decompiler/CSharp/ExpressionBuilder.cs b/ICSharpCode.Decompiler/CSharp/ExpressionBuilder.cs index 166fcd849..00155cb2a 100644 --- a/ICSharpCode.Decompiler/CSharp/ExpressionBuilder.cs +++ b/ICSharpCode.Decompiler/CSharp/ExpressionBuilder.cs @@ -4575,7 +4575,7 @@ namespace ICSharpCode.Decompiler.CSharp } foreach (var subPattern in matchInstruction.SubPatterns) { - if (!MatchInstruction.IsPatternMatch(subPattern, out var testedOperand)) + if (!MatchInstruction.IsPatternMatch(subPattern, out var testedOperand, settings)) { Debug.Fail("Invalid sub pattern"); continue; diff --git a/ICSharpCode.Decompiler/DecompilerSettings.cs b/ICSharpCode.Decompiler/DecompilerSettings.cs index ea0a7dde9..c39539000 100644 --- a/ICSharpCode.Decompiler/DecompilerSettings.cs +++ b/ICSharpCode.Decompiler/DecompilerSettings.cs @@ -130,6 +130,7 @@ namespace ICSharpCode.Decompiler staticLocalFunctions = false; ranges = false; switchExpressions = false; + recursivePatternMatching = false; } if (languageVersion < CSharp.LanguageVersion.CSharp9_0) { @@ -141,6 +142,8 @@ namespace ICSharpCode.Decompiler withExpressions = false; usePrimaryConstructorSyntax = false; covariantReturns = false; + relationalPatterns = false; + patternCombinators = false; } if (languageVersion < CSharp.LanguageVersion.CSharp10_0) { @@ -166,10 +169,11 @@ namespace ICSharpCode.Decompiler if (fileScopedNamespaces || recordStructs) return CSharp.LanguageVersion.CSharp10_0; if (nativeIntegers || initAccessors || functionPointers || forEachWithGetEnumeratorExtension - || recordClasses || withExpressions || usePrimaryConstructorSyntax || covariantReturns) + || recordClasses || withExpressions || usePrimaryConstructorSyntax || covariantReturns + || relationalPatterns || patternCombinators) return CSharp.LanguageVersion.CSharp9_0; if (nullableReferenceTypes || readOnlyMethods || asyncEnumerator || asyncUsingAndForEachStatement - || staticLocalFunctions || ranges || switchExpressions) + || staticLocalFunctions || ranges || switchExpressions || recursivePatternMatching) return CSharp.LanguageVersion.CSharp8_0; if (introduceUnmanagedConstraint || tupleComparisons || stackAllocInitializers || patternBasedFixedStatement) @@ -1678,6 +1682,60 @@ namespace ICSharpCode.Decompiler } } + bool recursivePatternMatching = true; + + /// + /// Gets/Sets whether C# 8.0 recursive patterns should be detected. + /// + [Category("C# 8.0 / VS 2019")] + [Description("DecompilerSettings.RecursivePatternMatching")] + public bool RecursivePatternMatching { + get { return recursivePatternMatching; } + set { + if (recursivePatternMatching != value) + { + recursivePatternMatching = value; + OnPropertyChanged(); + } + } + } + + bool patternCombinators = true; + + /// + /// Gets/Sets whether C# 9.0 and, or, not patterns should be detected. + /// + [Category("C# 9.0 / VS 2019.8")] + [Description("DecompilerSettings.PatternCombinators")] + public bool PatternCombinators { + get { return patternCombinators; } + set { + if (patternCombinators != value) + { + patternCombinators = value; + OnPropertyChanged(); + } + } + } + + bool relationalPatterns = true; + + /// + /// Gets/Sets whether C# 9.0 relational patterns should be detected. + /// + [Category("C# 9.0 / VS 2019.8")] + [Description("DecompilerSettings.RelationalPatterns")] + public bool RelationalPatterns { + get { return relationalPatterns; } + set { + if (relationalPatterns != value) + { + relationalPatterns = value; + OnPropertyChanged(); + } + } + } + bool staticLocalFunctions = true; /// diff --git a/ICSharpCode.Decompiler/IL/Instructions/MatchInstruction.cs b/ICSharpCode.Decompiler/IL/Instructions/MatchInstruction.cs index 33f4a0bb5..cab918cc7 100644 --- a/ICSharpCode.Decompiler/IL/Instructions/MatchInstruction.cs +++ b/ICSharpCode.Decompiler/IL/Instructions/MatchInstruction.cs @@ -119,7 +119,7 @@ namespace ICSharpCode.Decompiler.IL /// (even if the pattern fails to match!). /// The pattern matching instruction evaluates to 1 (as I4) if the pattern matches, or 0 otherwise. /// - public static bool IsPatternMatch(ILInstruction? inst, [NotNullWhen(true)] out ILInstruction? testedOperand) + public static bool IsPatternMatch(ILInstruction? inst, [NotNullWhen(true)] out ILInstruction? testedOperand, DecompilerSettings? settings) { switch (inst) { @@ -127,13 +127,23 @@ namespace ICSharpCode.Decompiler.IL testedOperand = m.testedOperand; return true; case Comp comp: - if (comp.MatchLogicNot(out var operand) && IsPatternMatch(operand, out testedOperand)) + if (comp.MatchLogicNot(out var operand) && IsPatternMatch(operand, out testedOperand, settings)) { - return true; + return settings?.PatternCombinators ?? true; } else { testedOperand = comp.Left; + if (!(settings?.RelationalPatterns ?? true)) + { + if (comp.Kind is not (ComparisonKind.Equality or ComparisonKind.Inequality)) + return false; + } + if (!(settings?.PatternCombinators ?? true)) + { + if (comp.Kind is ComparisonKind.Inequality) + return false; + } return IsConstant(comp.Right); } case Call call when IsCallToString_op_Equality(call): @@ -201,7 +211,7 @@ namespace ICSharpCode.Decompiler.IL Debug.Assert(SubPatterns.Count >= NumPositionalPatterns); foreach (var subPattern in SubPatterns) { - if (!IsPatternMatch(subPattern, out ILInstruction? operand)) + if (!IsPatternMatch(subPattern, out ILInstruction? operand, null)) throw new InvalidOperationException("Sub-Pattern must be a valid pattern"); // the first child is TestedOperand int subPatternIndex = subPattern.ChildIndex - 1; diff --git a/ICSharpCode.Decompiler/IL/Transforms/ExpressionTransforms.cs b/ICSharpCode.Decompiler/IL/Transforms/ExpressionTransforms.cs index 8a6684c4e..1de0c6da1 100644 --- a/ICSharpCode.Decompiler/IL/Transforms/ExpressionTransforms.cs +++ b/ICSharpCode.Decompiler/IL/Transforms/ExpressionTransforms.cs @@ -557,7 +557,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms return; } } - if (MatchInstruction.IsPatternMatch(inst.Condition, out _) + if (MatchInstruction.IsPatternMatch(inst.Condition, out _, context.Settings) && inst.TrueInst.MatchLdcI4(1) && inst.FalseInst.MatchLdcI4(0)) { context.Step("match(x) ? true : false -> match(x)", inst); diff --git a/ICSharpCode.Decompiler/IL/Transforms/PatternMatchingTransform.cs b/ICSharpCode.Decompiler/IL/Transforms/PatternMatchingTransform.cs index 6b1ddc8dc..d1bf69567 100644 --- a/ICSharpCode.Decompiler/IL/Transforms/PatternMatchingTransform.cs +++ b/ICSharpCode.Decompiler/IL/Transforms/PatternMatchingTransform.cs @@ -189,6 +189,10 @@ namespace ICSharpCode.Decompiler.IL.Transforms private static ILInstruction DetectPropertySubPatterns(MatchInstruction parentPattern, ILInstruction trueInst, ILInstruction parentFalseInst, BlockContainer container, ILTransformContext context, ref ControlFlowGraph? cfg) { + if (!context.Settings.RecursivePatternMatching) + { + return trueInst; + } while (true) { Block? trueBlock = trueInst as Block; @@ -230,7 +234,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms ExtensionMethods.Swap(ref trueInst, ref falseInst); negate = true; } - if (MatchInstruction.IsPatternMatch(condition, out var operand)) + if (MatchInstruction.IsPatternMatch(condition, out var operand, context.Settings)) { if (!PropertyOrFieldAccess(operand, out var target, out _)) { @@ -240,6 +244,10 @@ namespace ICSharpCode.Decompiler.IL.Transforms { return null; } + if (negate && !context.Settings.PatternCombinators) + { + return null; + } context.Step("Move property sub pattern", condition); if (negate) { @@ -253,8 +261,13 @@ namespace ICSharpCode.Decompiler.IL.Transforms { return null; } - context.Step("Move property sub pattern", condition); - parentPattern.SubPatterns.Add(new Comp(negate ? ComparisonKind.Equality : ComparisonKind.Inequality, Sign.None, condition, new LdcI4(0))); + if (!negate && !context.Settings.PatternCombinators) + { + return null; + } + context.Step("Sub pattern: implicit != 0", condition); + parentPattern.SubPatterns.Add(new Comp(negate ? ComparisonKind.Equality : ComparisonKind.Inequality, + Sign.None, condition, new LdcI4(0))); } else { @@ -376,7 +389,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms } return null; } - if (varPattern.Variable.AddressCount == 1) + if (varPattern.Variable.AddressCount == 1 && context.Settings.PatternCombinators) { context.Step("Nullable.HasValue check -> not null pattern", block); varPattern.ReplaceWith(new Comp(ComparisonKind.Inequality, ComparisonLiftingKind.CSharp, StackType.O, Sign.None, varPattern.TestedOperand, new LdNull())); @@ -412,6 +425,10 @@ namespace ICSharpCode.Decompiler.IL.Transforms { return null; } + if (!(context.Settings.RelationalPatterns || comp.Kind is ComparisonKind.Equality or ComparisonKind.Inequality)) + { + return null; + } bool negated = false; if (!DetectExitPoints.CompatibleExitInstruction(falseInst, parentFalseInst)) { @@ -426,6 +443,10 @@ namespace ICSharpCode.Decompiler.IL.Transforms { return null; } + if (negated && !context.Settings.PatternCombinators) + { + return null; + } context.Step("Nullable.HasValue check + Nullable.GetValueOrDefault pattern", block); // varPattern: match (v = testedOperand) // comp: comp.i4(call GetValueOrDefault(ldloca v) != ldc.i4 42) diff --git a/ILSpy/Properties/Resources.Designer.cs b/ILSpy/Properties/Resources.Designer.cs index 54b1ce17b..18a93692f 100644 --- a/ILSpy/Properties/Resources.Designer.cs +++ b/ILSpy/Properties/Resources.Designer.cs @@ -1145,6 +1145,15 @@ namespace ICSharpCode.ILSpy.Properties { } } + /// + /// Looks up a localized string similar to Pattern combinators (and, or, not). + /// + public static string DecompilerSettings_PatternCombinators { + get { + return ResourceManager.GetString("DecompilerSettings.PatternCombinators", resourceCulture); + } + } + /// /// Looks up a localized string similar to Use pattern matching expressions. /// @@ -1199,6 +1208,24 @@ namespace ICSharpCode.ILSpy.Properties { } } + /// + /// Looks up a localized string similar to Recursive pattern matching. + /// + public static string DecompilerSettings_RecursivePatternMatching { + get { + return ResourceManager.GetString("DecompilerSettings.RecursivePatternMatching", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Relational patterns. + /// + public static string DecompilerSettings_RelationalPatterns { + get { + return ResourceManager.GetString("DecompilerSettings.RelationalPatterns", resourceCulture); + } + } + /// /// Looks up a localized string similar to Remove dead and side effect free code (use with caution!). /// diff --git a/ILSpy/Properties/Resources.resx b/ILSpy/Properties/Resources.resx index 518717922..f63a10682 100644 --- a/ILSpy/Properties/Resources.resx +++ b/ILSpy/Properties/Resources.resx @@ -405,6 +405,9 @@ Are you sure you want to continue? Use parameter null checking + + Pattern combinators (and, or, not) + Use pattern matching expressions @@ -423,6 +426,12 @@ Are you sure you want to continue? Record structs + + Recursive pattern matching + + + Relational patterns + Remove dead and side effect free code (use with caution!) From a0027e13b90ae7f7374497639f993a384fc3992f Mon Sep 17 00:00:00 2001 From: Christoph Wille Date: Sat, 5 Aug 2023 15:19:10 +0200 Subject: [PATCH 16/22] DefineConstants overrides defined constants in all projects; use property to amend constants. --- .github/workflows/build-ilspy.yml | 2 +- ILSpy.Installer/ILSpy.Installer.csproj | 6 +++++- 2 files changed, 6 insertions(+), 2 deletions(-) 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/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) + + From e193b838da5bc200bc8a8d791230f1120d063ebb Mon Sep 17 00:00:00 2001 From: Siegfried Pammer Date: Sat, 5 Aug 2023 15:33:55 +0200 Subject: [PATCH 17/22] Move TransformDecimalCtorToConstant to EarlyExpressionTransforms --- .../Transforms/EarlyExpressionTransforms.cs | 50 +++++++++++++++++++ .../IL/Transforms/ExpressionTransforms.cs | 43 ---------------- 2 files changed, 50 insertions(+), 43 deletions(-) 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 1de0c6da1..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)) From bf96482d56009d051dba40103ff23e9f18e30c1e Mon Sep 17 00:00:00 2001 From: Siegfried Pammer Date: Sat, 5 Aug 2023 15:35:37 +0200 Subject: [PATCH 18/22] Support decimal constants in pattern matching --- .../TestCases/Pretty/PatternMatching.cs | 58 +++++++++++++++++++ .../CSharp/ExpressionBuilder.cs | 4 +- .../IL/Instructions/MatchInstruction.cs | 9 ++- 3 files changed, 67 insertions(+), 4 deletions(-) diff --git a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/PatternMatching.cs b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/PatternMatching.cs index 4ee763441..e0038c6ba 100644 --- a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/PatternMatching.cs +++ b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/PatternMatching.cs @@ -21,6 +21,16 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty 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) @@ -682,6 +692,54 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty 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_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() { diff --git a/ICSharpCode.Decompiler/CSharp/ExpressionBuilder.cs b/ICSharpCode.Decompiler/CSharp/ExpressionBuilder.cs index 00155cb2a..b03f38b4a 100644 --- a/ICSharpCode.Decompiler/CSharp/ExpressionBuilder.cs +++ b/ICSharpCode.Decompiler/CSharp/ExpressionBuilder.cs @@ -4657,7 +4657,9 @@ namespace ICSharpCode.Decompiler.CSharp default: throw new InvalidOperationException("Unexpected comparison kind: " + comp.Kind); } - case Call call when MatchInstruction.IsCallToString_op_Equality(call): + 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/IL/Instructions/MatchInstruction.cs b/ICSharpCode.Decompiler/IL/Instructions/MatchInstruction.cs index cab918cc7..af9074f12 100644 --- a/ICSharpCode.Decompiler/IL/Instructions/MatchInstruction.cs +++ b/ICSharpCode.Decompiler/IL/Instructions/MatchInstruction.cs @@ -146,19 +146,22 @@ namespace ICSharpCode.Decompiler.IL } return IsConstant(comp.Right); } - case Call call when IsCallToString_op_Equality(call): + 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; } } - internal static bool IsCallToString_op_Equality(Call call) + internal static bool IsCallToOpEquality(Call call, KnownTypeCode knownType) { return call.Method.IsOperator && call.Method.Name == "op_Equality" - && call.Method.DeclaringType.IsKnownType(KnownTypeCode.String) + && call.Method.DeclaringType.IsKnownType(knownType) && call.Arguments.Count == 2; } From 97b6a2fe67f8747ac9ce50303e4cf3952397809b Mon Sep 17 00:00:00 2001 From: Siegfried Pammer Date: Sat, 5 Aug 2023 16:01:11 +0200 Subject: [PATCH 19/22] Add test case for empty string pattern --- .../TestCases/Pretty/PatternMatching.cs | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/PatternMatching.cs b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/PatternMatching.cs index e0038c6ba..165e4e4d6 100644 --- a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/PatternMatching.cs +++ b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/PatternMatching.cs @@ -717,6 +717,18 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty } } + 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 } }) From 4893c58ac03599aab0e26f0b79612dfd3458bb22 Mon Sep 17 00:00:00 2001 From: Siegfried Pammer Date: Sat, 5 Aug 2023 16:46:11 +0200 Subject: [PATCH 20/22] #1806: Use NormalizeTypeVisitor.TypeErasure.EquivalentTypes in DebugInfoGenerator.HandleMethodBody to fix false positives in assertion. --- ICSharpCode.Decompiler/DebugInfo/DebugInfoGenerator.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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); } From 6172d63ff3bc325bf9e6025944c13639ae089844 Mon Sep 17 00:00:00 2001 From: Siegfried Pammer Date: Sat, 5 Aug 2023 19:27:40 +0200 Subject: [PATCH 21/22] Support virtual modifier on static abstract interface members. --- .../Pretty/StaticAbstractInterfaceMembers.cs | 50 ++++++++++++++++--- .../CSharp/Syntax/TypeSystemAstBuilder.cs | 10 +++- .../Implementation/MetadataMethod.cs | 19 ++++++- 3 files changed, 69 insertions(+), 10 deletions(-) diff --git a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/StaticAbstractInterfaceMembers.cs b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/StaticAbstractInterfaceMembers.cs index 553ea0e53..4a6bc0d2b 100644 --- a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/StaticAbstractInterfaceMembers.cs +++ b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/StaticAbstractInterfaceMembers.cs @@ -2,16 +2,54 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty.StaticAbstractInterfaceMembers { - public interface I + internal interface I where T : I + { + static abstract T P { get; set; } + static abstract event Action E; + static abstract void M(); + 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 T P { get; set; } + static event Action E; + static void M() + { + } + static T operator +(IAmStatic l, T r) + { + throw new NotImplementedException(); + } + } + + internal interface IAmVirtual where T : IAmVirtual + { + static virtual T P { get; set; } + static virtual event Action E; + static virtual void M() + { + } + static virtual T operator +(T l, T r) + { + throw new NotImplementedException(); + } } - public class X : I + public class X : IAmSimple { public static int Capacity { get; } @@ -24,13 +62,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 +99,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/Syntax/TypeSystemAstBuilder.cs b/ICSharpCode.Decompiler/CSharp/Syntax/TypeSystemAstBuilder.cs index 708ff92e6..57415625b 100644 --- a/ICSharpCode.Decompiler/CSharp/Syntax/TypeSystemAstBuilder.cs +++ b/ICSharpCode.Decompiler/CSharp/Syntax/TypeSystemAstBuilder.cs @@ -2373,8 +2373,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/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; From 591ab6b75da879e71da1718517ae4059814a8c02 Mon Sep 17 00:00:00 2001 From: Siegfried Pammer Date: Sun, 6 Aug 2023 10:15:22 +0200 Subject: [PATCH 22/22] Implement support for explicit interface implementation of operators and operator uses. --- .../Pretty/StaticAbstractInterfaceMembers.cs | 105 +++++++++++++++++- .../CSharp/CSharpDecompiler.cs | 2 +- .../OutputVisitor/CSharpOutputVisitor.cs | 2 + .../Syntax/TypeMembers/OperatorDeclaration.cs | 13 ++- .../CSharp/Syntax/TypeSystemAstBuilder.cs | 9 +- .../Implementation/AbstractTypeParameter.cs | 6 +- 6 files changed, 126 insertions(+), 11 deletions(-) diff --git a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/StaticAbstractInterfaceMembers.cs b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/StaticAbstractInterfaceMembers.cs index 4a6bc0d2b..7d0b6ed5e 100644 --- a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/StaticAbstractInterfaceMembers.cs +++ b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/StaticAbstractInterfaceMembers.cs @@ -2,11 +2,59 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty.StaticAbstractInterfaceMembers { + 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(); + 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); @@ -25,28 +73,77 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty.StaticAbstractInterfaceM internal interface IAmStatic where T : IAmStatic { + static int f; static T P { get; set; } static event Action E; - static void M() + static void M(object x) { } - static T operator +(IAmStatic l, T r) + 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() + 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 : IAmSimple 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/OutputVisitor/CSharpOutputVisitor.cs b/ICSharpCode.Decompiler/CSharp/OutputVisitor/CSharpOutputVisitor.cs index 7fee876ca..66e6a2acf 100644 --- a/ICSharpCode.Decompiler/CSharp/OutputVisitor/CSharpOutputVisitor.cs +++ b/ICSharpCode.Decompiler/CSharp/OutputVisitor/CSharpOutputVisitor.cs @@ -2574,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/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 57415625b..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; } 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)