From f7343c75d09cbd9f73b88bd50b451bb7ce3d4664 Mon Sep 17 00:00:00 2001 From: Siegfried Pammer Date: Fri, 4 Aug 2023 15:17:54 +0200 Subject: [PATCH 01/17] 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 5b9f38787b..cbc56794b9 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 @@ public void GenericValueTypePatternStringRequiresCastToObject(T x) where T : } } + 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 707377b47d..c730843704 100644 --- a/ICSharpCode.Decompiler/CSharp/ExpressionBuilder.cs +++ b/ICSharpCode.Decompiler/CSharp/ExpressionBuilder.cs @@ -4564,9 +4564,45 @@ ExpressionWithILInstruction TranslatePattern(ILInstruction pattern) 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 3e428e5efb..152f9fdd87 100644 --- a/ICSharpCode.Decompiler/CSharp/OutputVisitor/CSharpOutputVisitor.cs +++ b/ICSharpCode.Decompiler/CSharp/OutputVisitor/CSharpOutputVisitor.cs @@ -948,6 +948,40 @@ public virtual void VisitDeclarationExpression(DeclarationExpression declaration 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 41870ba48b..2016b429c1 100644 --- a/ICSharpCode.Decompiler/CSharp/Syntax/DepthFirstAstVisitor.cs +++ b/ICSharpCode.Decompiler/CSharp/Syntax/DepthFirstAstVisitor.cs @@ -512,6 +512,11 @@ public virtual void VisitDeclarationExpression(DeclarationExpression declaration VisitChildren(declarationExpression); } + public virtual void VisitRecursivePatternExpression(RecursivePatternExpression recursivePatternExpression) + { + VisitChildren(recursivePatternExpression); + } + public virtual void VisitOutVarDeclarationExpression(OutVarDeclarationExpression outVarDeclarationExpression) { VisitChildren(outVarDeclarationExpression); @@ -1190,6 +1195,11 @@ public virtual T VisitDeclarationExpression(DeclarationExpression declarationExp 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 @@ public virtual S VisitDeclarationExpression(DeclarationExpression declarationExp 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 0e85852332..5584565249 100644 --- a/ICSharpCode.Decompiler/CSharp/Syntax/Expressions/DeclarationExpression.cs +++ b/ICSharpCode.Decompiler/CSharp/Syntax/Expressions/DeclarationExpression.cs @@ -52,7 +52,9 @@ public override S AcceptVisitor(IAstVisitor visitor, T data) 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 0000000000..3a26d735ba --- /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 25fa54d02e..37dfd40a33 100644 --- a/ICSharpCode.Decompiler/CSharp/Syntax/IAstVisitor.cs +++ b/ICSharpCode.Decompiler/CSharp/Syntax/IAstVisitor.cs @@ -36,6 +36,7 @@ public interface IAstVisitor 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 @@ public interface IAstVisitor 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 @@ public interface IAstVisitor 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 5ee433d8a1..3f8c724926 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 9a02d9c7e3..7cdc090dba 100644 --- a/ICSharpCode.Decompiler/IL/Transforms/PatternMatchingTransform.cs +++ b/ICSharpCode.Decompiler/IL/Transforms/PatternMatchingTransform.cs @@ -22,6 +22,7 @@ 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 @@ void IILTransform.Run(ILFunction function, ILTransformContext context) 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 @@ private bool PatternMatchRefTypes(Block block, BlockContainer container, ILTrans 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 0000000000..6da2439e74 --- /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 02/17] 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 cbc56794b9..81ac1c17ae 100644 --- a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/PatternMatching.cs +++ b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/PatternMatching.cs @@ -310,6 +310,7 @@ public void GenericValueTypePatternStringRequiresCastToObject(T x) where T : } } +#if CS80 public void RecursivePattern_Type(object x) { if (x is X { C: string text }) @@ -322,6 +323,19 @@ public void RecursivePattern_Type(object x) } } + 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 c730843704..feafd71998 100644 --- a/ICSharpCode.Decompiler/CSharp/ExpressionBuilder.cs +++ b/ICSharpCode.Decompiler/CSharp/ExpressionBuilder.cs @@ -4546,14 +4546,14 @@ protected internal override TranslatedExpression VisitMatchInstruction(MatchInst { 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 @@ ExpressionWithILInstruction TranslatePattern(ILInstruction pattern) 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 @@ ExpressionWithILInstruction TranslatePattern(ILInstruction pattern) 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 af925fc141..460f9a4217 100644 --- a/ICSharpCode.Decompiler/CSharp/Syntax/Expressions/UnaryOperatorExpression.cs +++ b/ICSharpCode.Decompiler/CSharp/Syntax/Expressions/UnaryOperatorExpression.cs @@ -46,6 +46,7 @@ public class UnaryOperatorExpression : Expression 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 @@ public static TokenRole GetOperatorRole(UnaryOperatorType op) 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 @@ public static ExpressionType GetLinqNodeType(UnaryOperatorType op, bool checkFor 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 @@ public enum UnaryOperatorType /// 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 7cdc090dba..c99a429a84 100644 --- a/ICSharpCode.Decompiler/IL/Transforms/PatternMatchingTransform.cs +++ b/ICSharpCode.Decompiler/IL/Transforms/PatternMatchingTransform.cs @@ -183,17 +183,17 @@ private bool PatternMatchRefTypes(Block block, BlockContainer container, ILTrans 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 @@ private bool DetectPropertySubPatterns(MatchInstruction parentPattern, Block blo } 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 03/17] 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 81ac1c17ae..038c50395e 100644 --- a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/PatternMatching.cs +++ b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/PatternMatching.cs @@ -335,6 +335,17 @@ public void RecursivePattern_Constant(object obj) } } + 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 feafd71998..2729a27ad1 100644 --- a/ICSharpCode.Decompiler/CSharp/ExpressionBuilder.cs +++ b/ICSharpCode.Decompiler/CSharp/ExpressionBuilder.cs @@ -4641,6 +4641,8 @@ ExpressionWithILInstruction TranslatePattern(ILInstruction pattern, IType leftHa 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 f12f06cf63..84a6250969 100644 --- a/ICSharpCode.Decompiler/IL/Instructions/MatchInstruction.cs +++ b/ICSharpCode.Decompiler/IL/Instructions/MatchInstruction.cs @@ -135,12 +135,22 @@ public static bool IsPatternMatch(ILInstruction? inst, [NotNullWhen(true)] out I 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 @@ private static bool IsConstant(ILInstruction inst) 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 04/17] 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 038c50395e..d125a98ef6 100644 --- a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/PatternMatching.cs +++ b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/PatternMatching.cs @@ -11,6 +11,11 @@ public class X 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 @@ public void RecursivePattern_StringConstant(object obj) 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 84a6250969..d30c30280b 100644 --- a/ICSharpCode.Decompiler/IL/Instructions/MatchInstruction.cs +++ b/ICSharpCode.Decompiler/IL/Instructions/MatchInstruction.cs @@ -22,6 +22,7 @@ using System.Diagnostics.CodeAnalysis; using ICSharpCode.Decompiler.TypeSystem; + namespace ICSharpCode.Decompiler.IL { partial class MatchInstruction : ILInstruction @@ -211,12 +212,12 @@ void AdditionalInvariants() } 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 c99a429a84..0b867b8e64 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 @@ private bool PatternMatchRefTypes(Block block, BlockContainer container, ILTrans 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 @@ private bool DetectPropertySubPatterns(MatchInstruction parentPattern, Block blo { 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 @@ private bool DetectPropertySubPatterns(MatchInstruction parentPattern, Block blo 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 @@ private bool CheckAllUsesDominatedBy(ILVariable v, BlockContainer container, ILI 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 @@ private bool PatternMatchValueTypes(Block block, BlockContainer container, ILTra 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 @@ private bool PatternMatchValueTypes(Block block, BlockContainer container, ILTra // 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 @@ private bool MatchIsInstBlock(Block block, [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 @@ private bool MatchIsInstBlock(Block block, { 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 05/17] 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 d125a98ef6..ed0168a5da 100644 --- a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/PatternMatching.cs +++ b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/PatternMatching.cs @@ -318,9 +318,9 @@ public void GenericValueTypePatternStringRequiresCastToObject(T x) where T : #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 @@ public void RecursivePattern_ValueTypeWithField(object obj) 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 2729a27ad1..f3a789cbf1 100644 --- a/ICSharpCode.Decompiler/CSharp/ExpressionBuilder.cs +++ b/ICSharpCode.Decompiler/CSharp/ExpressionBuilder.cs @@ -4558,16 +4558,21 @@ ExpressionWithILInstruction TranslatePattern(ILInstruction pattern, IType leftHa 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 @@ ExpressionWithILInstruction TranslatePattern(ILInstruction pattern, IType leftHa } 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 e4331c74be..2fd3f7782d 100644 --- a/ICSharpCode.Decompiler/IL/Transforms/AssignVariableNames.cs +++ b/ICSharpCode.Decompiler/IL/Transforms/AssignVariableNames.cs @@ -432,12 +432,25 @@ string GenerateNameForVariable(ILVariable variable) } 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 0b867b8e64..d5ac6e0a68 100644 --- a/ICSharpCode.Decompiler/IL/Transforms/PatternMatchingTransform.cs +++ b/ICSharpCode.Decompiler/IL/Transforms/PatternMatchingTransform.cs @@ -182,13 +182,13 @@ private bool PatternMatchRefTypes(Block block, BlockContainer container, ILTrans 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 @@ private void DetectPropertySubPatterns(MatchInstruction parentPattern, Block blo { 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 @@ private void DetectPropertySubPatterns(MatchInstruction parentPattern, Block blo 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 @@ private bool PatternMatchValueTypes(Block block, BlockContainer container, ILTra // 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 06/17] 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 ed0168a5da..8471db9ca3 100644 --- a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/PatternMatching.cs +++ b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/PatternMatching.cs @@ -387,6 +387,30 @@ public void RecursivePattern_MultipleConstantsMixedWithVar(object x) 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 d30c30280b..cb61f6613a 100644 --- a/ICSharpCode.Decompiler/IL/Instructions/MatchInstruction.cs +++ b/ICSharpCode.Decompiler/IL/Instructions/MatchInstruction.cs @@ -127,9 +127,9 @@ public static bool IsPatternMatch(ILInstruction? inst, [NotNullWhen(true)] out I 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 d5ac6e0a68..8b0737c9cb 100644 --- a/ICSharpCode.Decompiler/IL/Transforms/PatternMatchingTransform.cs +++ b/ICSharpCode.Decompiler/IL/Transforms/PatternMatchingTransform.cs @@ -188,12 +188,22 @@ private bool PatternMatchRefTypes(Block block, BlockContainer container, ILTrans 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 @@ private void DetectPropertySubPatterns(MatchInstruction parentPattern, Block blo { 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 @@ private void DetectPropertySubPatterns(MatchInstruction parentPattern, Block blo 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 @@ private static bool MatchBlockContainingOneCondition(Block block, [NotNullWhen(t } } - 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 07/17] 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 8471db9ca3..00d6f2a827 100644 --- a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/PatternMatching.cs +++ b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/PatternMatching.cs @@ -6,14 +6,17 @@ public class PatternMatching { 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 @@ public void GenericValueTypePatternStringRequiresCastToObject(T x) where T : #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 @@ public void RecursivePattern_Type(object x) 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 @@ public void RecursivePattern_Constant(object obj) 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 @@ public void RecursivePattern_StringConstant(object obj) 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 @@ public void RecursivePattern_ValueTypeWithField(object obj) 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 @@ public void RecursivePattern_MultipleConstantsMixedWithVar(object x) 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 @@ public void RecursivePattern_NonTypePattern(object obj) } } + 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 f3a789cbf1..166fcd849f 100644 --- a/ICSharpCode.Decompiler/CSharp/ExpressionBuilder.cs +++ b/ICSharpCode.Decompiler/CSharp/ExpressionBuilder.cs @@ -4571,7 +4571,7 @@ ExpressionWithILInstruction TranslatePattern(ILInstruction pattern, IType leftHa } 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 152f9fdd87..7fee876ca0 100644 --- a/ICSharpCode.Decompiler/CSharp/OutputVisitor/CSharpOutputVisitor.cs +++ b/ICSharpCode.Decompiler/CSharp/OutputVisitor/CSharpOutputVisitor.cs @@ -1302,7 +1302,7 @@ public virtual void VisitUnaryOperatorExpression(UnaryOperatorExpression unaryOp 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 8b0737c9cb..6d9ebaf50e 100644 --- a/ICSharpCode.Decompiler/IL/Transforms/PatternMatchingTransform.cs +++ b/ICSharpCode.Decompiler/IL/Transforms/PatternMatchingTransform.cs @@ -268,6 +268,10 @@ private static void DetectPropertySubPatterns(MatchInstruction parentPattern, Bl 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 08/17] 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 00d6f2a827..73dac3b283 100644 --- a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/PatternMatching.cs +++ b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/PatternMatching.cs @@ -331,6 +331,18 @@ public void RecursivePattern_Type(object x) } } + 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 @@ public void RecursivePatternValueType_VarLengthPattern(object obj) 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 6d9ebaf50e..4c5dfcbddc 100644 --- a/ICSharpCode.Decompiler/IL/Transforms/PatternMatchingTransform.cs +++ b/ICSharpCode.Decompiler/IL/Transforms/PatternMatchingTransform.cs @@ -180,15 +180,37 @@ private bool PatternMatchRefTypes(Block block, BlockContainer container, ILTrans 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 @@ private static void DetectPropertySubPatterns(MatchInstruction parentPattern, Bl { if (!DetectExitPoints.CompatibleExitInstruction(parentFalseInst, trueInst)) { - return; + return null; } ExtensionMethods.Swap(ref trueInst, ref falseInst); negate = true; @@ -208,11 +230,11 @@ private static void DetectPropertySubPatterns(MatchInstruction parentPattern, Bl { 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 @@ private static void DetectPropertySubPatterns(MatchInstruction parentPattern, Bl { 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 @@ private static void DetectPropertySubPatterns(MatchInstruction parentPattern, Bl 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 @@ private static bool MatchNullCheckPattern(Block block, MatchInstruction varPatte } 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 @@ private bool PatternMatchValueTypes(Block block, BlockContainer container, ILTra // 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 09/17] 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 73dac3b283..2cb188567d 100644 --- a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/PatternMatching.cs +++ b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/PatternMatching.cs @@ -6,10 +6,14 @@ public class PatternMatching { public class X { + public int? NullableIntField; + public S? NullableCustomStructField; public int I { get; set; } public string Text { get; set; } public object Obj { get; set; } public S CustomStruct { get; set; } + public int? NullableIntProp { get; set; } + public S? NullableCustomStructProp { get; set; } } public struct S @@ -486,6 +490,18 @@ public void RecursivePattern_VarLengthPattern_SwappedProps(object obj) 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 4c5dfcbddc..70e9200722 100644 --- a/ICSharpCode.Decompiler/IL/Transforms/PatternMatchingTransform.cs +++ b/ICSharpCode.Decompiler/IL/Transforms/PatternMatchingTransform.cs @@ -285,7 +285,13 @@ private bool PatternMatchRefTypes(Block block, BlockContainer container, ILTrans 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 @@ private bool PatternMatchRefTypes(Block block, BlockContainer container, ILTrans } 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 @@ private bool PatternMatchRefTypes(Block block, BlockContainer container, ILTrans 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 10/17] 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 2cb188567d..a2815b736b 100644 --- a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/PatternMatching.cs +++ b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/PatternMatching.cs @@ -491,6 +491,18 @@ public void RecursivePattern_VarLengthPattern_SwappedProps(object obj) } } + 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 227eef95ce..406d3847e1 100644 --- a/ICSharpCode.Decompiler/IL/Instructions/Comp.cs +++ b/ICSharpCode.Decompiler/IL/Instructions/Comp.cs @@ -130,7 +130,7 @@ public ComparisonKind Kind { } } - 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 cb61f6613a..33f4a0bb5d 100644 --- a/ICSharpCode.Decompiler/IL/Instructions/MatchInstruction.cs +++ b/ICSharpCode.Decompiler/IL/Instructions/MatchInstruction.cs @@ -152,7 +152,7 @@ internal static bool IsCallToString_op_Equality(Call call) && 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 70e9200722..4771a13ae7 100644 --- a/ICSharpCode.Decompiler/IL/Transforms/PatternMatchingTransform.cs +++ b/ICSharpCode.Decompiler/IL/Transforms/PatternMatchingTransform.cs @@ -50,6 +50,7 @@ void IILTransform.Run(ILFunction function, ILTransformContext context) continue; } } + container.Blocks.RemoveAll(b => b.Instructions.Count == 0); } } @@ -287,8 +288,7 @@ private bool PatternMatchRefTypes(Block block, BlockContainer container, ILTrans 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 @@ private bool PatternMatchRefTypes(Block block, BlockContainer container, ILTrans 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 @@ private bool PatternMatchRefTypes(Block block, BlockContainer container, ILTrans { 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 11/17] 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 a2815b736b..777066a1b9 100644 --- a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/PatternMatching.cs +++ b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/PatternMatching.cs @@ -503,6 +503,66 @@ public void RecursivePattern_NullableIntField_Const(object obj) } } + + 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 @@ public void RecursivePattern_NullableCustomStructProp_Const(object obj) 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 4771a13ae7..4125bdf8c4 100644 --- a/ICSharpCode.Decompiler/IL/Transforms/PatternMatchingTransform.cs +++ b/ICSharpCode.Decompiler/IL/Transforms/PatternMatchingTransform.cs @@ -186,11 +186,10 @@ private bool PatternMatchRefTypes(Block block, BlockContainer container, ILTrans 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 @@ private bool PatternMatchRefTypes(Block block, BlockContainer container, ILTrans { 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 @@ private bool PatternMatchRefTypes(Block block, BlockContainer container, ILTrans 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 @@ private bool PatternMatchRefTypes(Block block, BlockContainer container, ILTrans 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 12/17] 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 777066a1b9..4ee7634417 100644 --- a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/PatternMatching.cs +++ b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/PatternMatching.cs @@ -503,6 +503,29 @@ public void RecursivePattern_NullableIntField_Const(object obj) } } + 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 @@ public void RecursivePattern_NullableIntProp_Const(object obj) 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 @@ public void RecursivePattern_NullableCustomStructField_Const(object obj) } } + 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 @@ public void RecursivePattern_NullableCustomStructProp_Const(object obj) } } + 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 4125bdf8c4..6b1ddc8dc0 100644 --- a/ICSharpCode.Decompiler/IL/Transforms/PatternMatchingTransform.cs +++ b/ICSharpCode.Decompiler/IL/Transforms/PatternMatchingTransform.cs @@ -347,6 +347,10 @@ private static ILInstruction DetectPropertySubPatterns(MatchInstruction parentPa 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 @@ private static ILInstruction DetectPropertySubPatterns(MatchInstruction parentPa } 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 @@ private static bool MatchBlockContainingOneCondition(Block block, [NotNullWhen(t 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 13/17] 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 166fcd849f..00155cb2a8 100644 --- a/ICSharpCode.Decompiler/CSharp/ExpressionBuilder.cs +++ b/ICSharpCode.Decompiler/CSharp/ExpressionBuilder.cs @@ -4575,7 +4575,7 @@ ExpressionWithILInstruction TranslatePattern(ILInstruction pattern, IType leftHa } 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 ea0a7dde9b..c395390001 100644 --- a/ICSharpCode.Decompiler/DecompilerSettings.cs +++ b/ICSharpCode.Decompiler/DecompilerSettings.cs @@ -130,6 +130,7 @@ public void SetLanguageVersion(CSharp.LanguageVersion languageVersion) staticLocalFunctions = false; ranges = false; switchExpressions = false; + recursivePatternMatching = false; } if (languageVersion < CSharp.LanguageVersion.CSharp9_0) { @@ -141,6 +142,8 @@ public void SetLanguageVersion(CSharp.LanguageVersion languageVersion) withExpressions = false; usePrimaryConstructorSyntax = false; covariantReturns = false; + relationalPatterns = false; + patternCombinators = false; } if (languageVersion < CSharp.LanguageVersion.CSharp10_0) { @@ -166,10 +169,11 @@ public CSharp.LanguageVersion GetMinimumRequiredVersion() 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 @@ public bool PatternMatching { } } + 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 33f4a0bb5d..cab918cc7c 100644 --- a/ICSharpCode.Decompiler/IL/Instructions/MatchInstruction.cs +++ b/ICSharpCode.Decompiler/IL/Instructions/MatchInstruction.cs @@ -119,7 +119,7 @@ public MatchInstruction(ILVariable variable, ILInstruction testedOperand) /// (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 @@ public static bool IsPatternMatch(ILInstruction? inst, [NotNullWhen(true)] out I 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 @@ void AdditionalInvariants() 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 8a6684c4e0..1de0c6da14 100644 --- a/ICSharpCode.Decompiler/IL/Transforms/ExpressionTransforms.cs +++ b/ICSharpCode.Decompiler/IL/Transforms/ExpressionTransforms.cs @@ -557,7 +557,7 @@ protected internal override void VisitIfInstruction(IfInstruction inst) 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 6b1ddc8dc0..d1bf695671 100644 --- a/ICSharpCode.Decompiler/IL/Transforms/PatternMatchingTransform.cs +++ b/ICSharpCode.Decompiler/IL/Transforms/PatternMatchingTransform.cs @@ -189,6 +189,10 @@ private bool PatternMatchRefTypes(Block block, BlockContainer container, ILTrans 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 @@ private static ILInstruction DetectPropertySubPatterns(MatchInstruction parentPa 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 @@ private static ILInstruction DetectPropertySubPatterns(MatchInstruction parentPa { return null; } + if (negate && !context.Settings.PatternCombinators) + { + return null; + } context.Step("Move property sub pattern", condition); if (negate) { @@ -253,8 +261,13 @@ private static ILInstruction DetectPropertySubPatterns(MatchInstruction parentPa { 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 @@ private static ILInstruction DetectPropertySubPatterns(MatchInstruction parentPa } 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 @@ private static ILInstruction DetectPropertySubPatterns(MatchInstruction parentPa { 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 @@ private static ILInstruction DetectPropertySubPatterns(MatchInstruction parentPa { 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 54b1ce17b6..18a93692ff 100644 --- a/ILSpy/Properties/Resources.Designer.cs +++ b/ILSpy/Properties/Resources.Designer.cs @@ -1145,6 +1145,15 @@ public static string DecompilerSettings_ParameterNullCheck { } } + /// + /// 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 @@ public static string DecompilerSettings_RecordStructs { } } + /// + /// 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 5187179228..f63a10682d 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 14/17] 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 2b6b3ed2cc..89291c79b8 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 6567b67ae2..0697d38728 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 15/17] 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 12daf764c4..3d99d04009 100644 --- a/ICSharpCode.Decompiler/IL/Transforms/EarlyExpressionTransforms.cs +++ b/ICSharpCode.Decompiler/IL/Transforms/EarlyExpressionTransforms.cs @@ -172,5 +172,55 @@ internal static void AddressOfLdLocToLdLoca(LdObj inst, ILTransformContext conte 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 1de0c6da14..7f494cad6b 100644 --- a/ICSharpCode.Decompiler/IL/Transforms/ExpressionTransforms.cs +++ b/ICSharpCode.Decompiler/IL/Transforms/ExpressionTransforms.cs @@ -286,12 +286,6 @@ protected internal override void VisitCallVirt(CallVirt inst) 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 @@ bool MatchesElementCount(ILInstruction sizeInBytesInstr, IType elementType, ILIn 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 16/17] 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 4ee7634417..e0038c6baa 100644 --- a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/PatternMatching.cs +++ b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/PatternMatching.cs @@ -21,6 +21,16 @@ public struct S public int I; public string Text { get; set; } public object Obj { get; set; } + public S2 S2 { get; set; } + } + + public struct S2 + { + public int I; + public float F; + public decimal D; + public string Text { get; set; } + public object Obj { get; set; } } public void SimpleTypePattern(object x) @@ -682,6 +692,54 @@ public void RecursivePattern_NullableCustomStructProp_Var(object obj) 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 00155cb2a8..b03f38b4ae 100644 --- a/ICSharpCode.Decompiler/CSharp/ExpressionBuilder.cs +++ b/ICSharpCode.Decompiler/CSharp/ExpressionBuilder.cs @@ -4657,7 +4657,9 @@ ExpressionWithILInstruction TranslatePattern(ILInstruction pattern, IType leftHa 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 cab918cc7c..af9074f123 100644 --- a/ICSharpCode.Decompiler/IL/Instructions/MatchInstruction.cs +++ b/ICSharpCode.Decompiler/IL/Instructions/MatchInstruction.cs @@ -146,19 +146,22 @@ public static bool IsPatternMatch(ILInstruction? inst, [NotNullWhen(true)] out I } 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 17/17] 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 e0038c6baa..165e4e4d6f 100644 --- a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/PatternMatching.cs +++ b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/PatternMatching.cs @@ -717,6 +717,18 @@ public void RecursivePattern_CustomStructNested_TextLengthZero(object obj) } } + 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 } })