Browse Source

Add support for simple recursive patterns where the sub pattern is a simple type pattern.

pull/3049/head
Siegfried Pammer 2 years ago
parent
commit
f7343c75d0
  1. 19
      ICSharpCode.Decompiler.Tests/TestCases/Pretty/PatternMatching.cs
  2. 42
      ICSharpCode.Decompiler/CSharp/ExpressionBuilder.cs
  3. 34
      ICSharpCode.Decompiler/CSharp/OutputVisitor/CSharpOutputVisitor.cs
  4. 15
      ICSharpCode.Decompiler/CSharp/Syntax/DepthFirstAstVisitor.cs
  5. 4
      ICSharpCode.Decompiler/CSharp/Syntax/Expressions/DeclarationExpression.cs
  6. 67
      ICSharpCode.Decompiler/CSharp/Syntax/Expressions/RecursivePatternExpression.cs
  7. 3
      ICSharpCode.Decompiler/CSharp/Syntax/IAstVisitor.cs
  8. 4
      ICSharpCode.Decompiler/ICSharpCode.Decompiler.csproj
  9. 44
      ICSharpCode.Decompiler/IL/Transforms/PatternMatchingTransform.cs
  10. 166
      ICSharpCode.Decompiler/Util/Index.cs

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

@ -4,6 +4,13 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty @@ -4,6 +4,13 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty
{
public class PatternMatching
{
public class X
{
public int A { get; set; }
public string B { get; set; }
public object C { get; set; }
}
public void SimpleTypePattern(object x)
{
if (x is string value)
@ -303,6 +310,18 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty @@ -303,6 +310,18 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty
}
}
public void RecursivePattern_Type(object x)
{
if (x is X { C: string text })
{
Console.WriteLine("Test " + text);
}
else
{
Console.WriteLine("not Test");
}
}
private bool F()
{
return true;

42
ICSharpCode.Decompiler/CSharp/ExpressionBuilder.cs

@ -4564,9 +4564,45 @@ namespace ICSharpCode.Decompiler.CSharp @@ -4564,9 +4564,45 @@ namespace ICSharpCode.Decompiler.CSharp
throw new NotImplementedException();
if (matchInstruction.IsDeconstructTuple)
throw new NotImplementedException();
if (matchInstruction.SubPatterns.Any())
throw new NotImplementedException();
if (matchInstruction.HasDesignator)
if (matchInstruction.SubPatterns.Count > 0)
{
RecursivePatternExpression recursivePatternExpression = new();
recursivePatternExpression.Type = ConvertType(matchInstruction.Variable.Type);
foreach (var subPattern in matchInstruction.SubPatterns)
{
if (!MatchInstruction.IsPatternMatch(subPattern, out var testedOperand))
{
Debug.Fail("Invalid sub pattern");
continue;
}
IMember member;
if (testedOperand is CallInstruction call)
{
member = call.Method.AccessorOwner;
}
else if (testedOperand.MatchLdFld(out _, out var f))
{
member = f;
}
else
{
Debug.Fail("Invalid sub pattern");
continue;
}
recursivePatternExpression.SubPatterns.Add(
new NamedArgumentExpression { Name = member.Name, Expression = TranslatePattern(subPattern) }
.WithRR(new MemberResolveResult(null, member))
);
}
if (matchInstruction.HasDesignator)
{
SingleVariableDesignation designator = new SingleVariableDesignation { Identifier = matchInstruction.Variable.Name };
designator.AddAnnotation(new ILVariableResolveResult(matchInstruction.Variable));
recursivePatternExpression.Designation = designator;
}
return recursivePatternExpression.WithILInstruction(matchInstruction);
}
else if (matchInstruction.HasDesignator)
{
SingleVariableDesignation designator = new SingleVariableDesignation { Identifier = matchInstruction.Variable.Name };
designator.AddAnnotation(new ILVariableResolveResult(matchInstruction.Variable));

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

@ -948,6 +948,40 @@ namespace ICSharpCode.Decompiler.CSharp.OutputVisitor @@ -948,6 +948,40 @@ namespace ICSharpCode.Decompiler.CSharp.OutputVisitor
EndNode(declarationExpression);
}
public virtual void VisitRecursivePatternExpression(RecursivePatternExpression recursivePatternExpression)
{
StartNode(recursivePatternExpression);
recursivePatternExpression.Type.AcceptVisitor(this);
Space();
if (recursivePatternExpression.IsPositional)
{
WriteToken(Roles.LPar);
}
else
{
WriteToken(Roles.LBrace);
}
Space();
WriteCommaSeparatedList(recursivePatternExpression.SubPatterns);
Space();
if (recursivePatternExpression.IsPositional)
{
WriteToken(Roles.RPar);
}
else
{
WriteToken(Roles.RBrace);
}
if (!recursivePatternExpression.Designation.IsNull)
{
Space();
recursivePatternExpression.Designation.AcceptVisitor(this);
}
EndNode(recursivePatternExpression);
}
public virtual void VisitOutVarDeclarationExpression(OutVarDeclarationExpression outVarDeclarationExpression)
{
StartNode(outVarDeclarationExpression);

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

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

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

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

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

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

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

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

4
ICSharpCode.Decompiler/ICSharpCode.Decompiler.csproj

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

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

@ -22,6 +22,7 @@ using System; @@ -22,6 +22,7 @@ using System;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using System.Reflection;
using ICSharpCode.Decompiler.IL.ControlFlow;
using ICSharpCode.Decompiler.TypeSystem;
@ -39,7 +40,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms @@ -39,7 +40,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms
foreach (var container in function.Descendants.OfType<BlockContainer>())
{
ControlFlowGraph? cfg = null;
foreach (var block in container.Blocks)
foreach (var block in container.Blocks.Reverse())
{
if (PatternMatchValueTypes(block, container, context, ref cfg))
{
@ -179,9 +180,50 @@ namespace ICSharpCode.Decompiler.IL.Transforms @@ -179,9 +180,50 @@ namespace ICSharpCode.Decompiler.IL.Transforms
block.Instructions[block.Instructions.Count - 1] = falseInst;
block.Instructions.RemoveRange(pos, ifInst.ChildIndex - pos);
v.Kind = VariableKind.PatternLocal;
if (trueInst.MatchBranch(out var trueBlock) && trueBlock.IncomingEdgeCount == 1 && trueBlock.Parent == container)
{
DetectPropertySubPatterns((MatchInstruction)ifInst.Condition, trueBlock, falseInst);
}
return true;
}
private bool DetectPropertySubPatterns(MatchInstruction parentPattern, Block block, ILInstruction parentFalseInst)
{
// if (match.notnull.type[System.String] (V_0 = callvirt get_C(ldloc V_2))) br IL_0022
// br IL_0037
if (block.Instructions.Count == 2 && block.MatchIfAtEndOfBlock(out var condition, out var trueInst, out var falseInst))
{
if (MatchInstruction.IsPatternMatch(condition, out var operand))
{
if (operand is not CallInstruction {
Method: {
SymbolKind: SymbolKind.Accessor,
AccessorKind: MethodSemanticsAttributes.Getter
},
Arguments: [LdLoc ldloc]
} call)
{
return false;
}
if (ldloc.Variable != parentPattern.Variable)
{
return false;
}
if (!DetectExitPoints.CompatibleExitInstruction(parentFalseInst, falseInst))
{
return false;
}
parentPattern.SubPatterns.Add(condition);
block.Instructions.RemoveAt(0);
block.Instructions[0] = trueInst;
return true;
}
}
return false;
}
private bool CheckAllUsesDominatedBy(ILVariable v, BlockContainer container, ILInstruction trueInst,
ILInstruction storeToV, ILInstruction? loadInNullCheck, ILTransformContext context, ref ControlFlowGraph? cfg)
{

166
ICSharpCode.Decompiler/Util/Index.cs

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