Browse Source

Translate MatchInstruction to BinaryOperatorExpression with BinaryOperatorType.IsPattern.

pull/2461/head
Siegfried Pammer 4 years ago
parent
commit
c641072685
  1. 36
      ICSharpCode.Decompiler.Tests/TestCases/Pretty/PatternMatching.cs
  2. 1
      ICSharpCode.Decompiler/CSharp/CSharpDecompiler.cs
  3. 39
      ICSharpCode.Decompiler/CSharp/ExpressionBuilder.cs
  4. 13
      ICSharpCode.Decompiler/CSharp/OutputVisitor/CSharpOutputVisitor.cs
  5. 2
      ICSharpCode.Decompiler/CSharp/OutputVisitor/InsertParenthesesVisitor.cs
  6. 10
      ICSharpCode.Decompiler/CSharp/Syntax/Expressions/BinaryOperatorExpression.cs
  7. 1
      ICSharpCode.Decompiler/CSharp/Transforms/DeclareVariables.cs
  8. 18
      ICSharpCode.Decompiler/DecompilerSettings.cs
  9. 1
      ICSharpCode.Decompiler/ICSharpCode.Decompiler.csproj
  10. 23
      ICSharpCode.Decompiler/IL/Transforms/PatternMatchingTransform.cs
  11. 116
      ICSharpCode.Decompiler/IL/Transforms/RemoveInfeasiblePathTransform.cs

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

@ -24,6 +24,42 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty @@ -24,6 +24,42 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty
return F() && x is string w && w.Contains("a");
}
public void SimpleTypePatternWithShortcircuitAnd(object x)
{
if (x is string z && z.Contains("a"))
{
Console.WriteLine(z);
}
else
{
Console.WriteLine();
}
}
public void SimpleTypePatternWithShortcircuitOr(object x)
{
if (!(x is string z) || z.Contains("a"))
{
Console.WriteLine();
}
else
{
Console.WriteLine(z);
}
}
public void SimpleTypePatternWithShortcircuitOr2(object x)
{
if (F() || !(x is string z))
{
Console.WriteLine();
}
else
{
Console.WriteLine(z);
}
}
private bool F()
{
return true;

1
ICSharpCode.Decompiler/CSharp/CSharpDecompiler.cs

@ -89,6 +89,7 @@ namespace ICSharpCode.Decompiler.CSharp @@ -89,6 +89,7 @@ namespace ICSharpCode.Decompiler.CSharp
new SplitVariables(),
new ILInlining(),
new InlineReturnTransform(), // must run before DetectPinnedRegions
new RemoveInfeasiblePathTransform(),
new DetectPinnedRegions(), // must run after inlining but before non-critical control flow transforms
new YieldReturnDecompiler(), // must run after inlining but before loop detection
new AsyncAwaitDecompiler(), // must run after inlining but before loop detection

39
ICSharpCode.Decompiler/CSharp/ExpressionBuilder.cs

@ -4340,7 +4340,44 @@ namespace ICSharpCode.Decompiler.CSharp @@ -4340,7 +4340,44 @@ namespace ICSharpCode.Decompiler.CSharp
protected internal override TranslatedExpression VisitMatchInstruction(MatchInstruction inst, TranslationContext context)
{
throw new NotImplementedException();
var left = Translate(inst.TestedOperand);
var right = TranslatePattern(inst);
return new BinaryOperatorExpression(left, BinaryOperatorType.IsPattern, right)
.WithRR(new ResolveResult(compilation.FindType(KnownTypeCode.Boolean)))
.WithILInstruction(inst);
}
ExpressionWithILInstruction TranslatePattern(ILInstruction pattern)
{
switch (pattern)
{
case MatchInstruction matchInstruction:
if (!matchInstruction.CheckType)
throw new NotImplementedException();
if (matchInstruction.IsDeconstructCall)
throw new NotImplementedException();
if (matchInstruction.IsDeconstructTuple)
throw new NotImplementedException();
if (matchInstruction.SubPatterns.Any())
throw new NotImplementedException();
if (matchInstruction.HasDesignator)
{
SingleVariableDesignation designator = new SingleVariableDesignation { Identifier = matchInstruction.Variable.Name };
designator.AddAnnotation(new ILVariableResolveResult(matchInstruction.Variable));
return new DeclarationExpression {
Type = ConvertType(matchInstruction.Variable.Type),
Designation = designator
}.WithILInstruction(matchInstruction);
}
else
{
return new TypeReferenceExpression(ConvertType(matchInstruction.Variable.Type))
.WithILInstruction(matchInstruction);
}
default:
throw new NotImplementedException();
}
}
protected internal override TranslatedExpression VisitInvalidBranch(InvalidBranch inst, TranslationContext context)

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

@ -822,6 +822,7 @@ namespace ICSharpCode.Decompiler.CSharp.OutputVisitor @@ -822,6 +822,7 @@ namespace ICSharpCode.Decompiler.CSharp.OutputVisitor
spacePolicy = policy.SpaceAroundShiftOperator;
break;
case BinaryOperatorType.NullCoalescing:
case BinaryOperatorType.IsPattern:
spacePolicy = true;
break;
case BinaryOperatorType.Range:
@ -831,7 +832,15 @@ namespace ICSharpCode.Decompiler.CSharp.OutputVisitor @@ -831,7 +832,15 @@ namespace ICSharpCode.Decompiler.CSharp.OutputVisitor
throw new NotSupportedException("Invalid value for BinaryOperatorType");
}
Space(spacePolicy);
WriteToken(BinaryOperatorExpression.GetOperatorRole(binaryOperatorExpression.Operator));
TokenRole tokenRole = BinaryOperatorExpression.GetOperatorRole(binaryOperatorExpression.Operator);
if (tokenRole == BinaryOperatorExpression.IsKeywordRole)
{
WriteKeyword(tokenRole);
}
else
{
WriteToken(tokenRole);
}
Space(spacePolicy);
binaryOperatorExpression.Right.AcceptVisitor(this);
EndNode(binaryOperatorExpression);
@ -2788,7 +2797,7 @@ namespace ICSharpCode.Decompiler.CSharp.OutputVisitor @@ -2788,7 +2797,7 @@ namespace ICSharpCode.Decompiler.CSharp.OutputVisitor
public virtual void VisitSingleVariableDesignation(SingleVariableDesignation singleVariableDesignation)
{
StartNode(singleVariableDesignation);
writer.WriteIdentifier(singleVariableDesignation.IdentifierToken);
WriteIdentifier(singleVariableDesignation.IdentifierToken);
EndNode(singleVariableDesignation);
}

2
ICSharpCode.Decompiler/CSharp/OutputVisitor/InsertParenthesesVisitor.cs

@ -143,6 +143,8 @@ namespace ICSharpCode.Decompiler.CSharp.OutputVisitor @@ -143,6 +143,8 @@ namespace ICSharpCode.Decompiler.CSharp.OutputVisitor
return PrecedenceLevel.ConditionalOr;
case BinaryOperatorType.NullCoalescing:
return PrecedenceLevel.NullCoalescing;
case BinaryOperatorType.IsPattern:
return PrecedenceLevel.RelationalAndTypeTesting;
default:
throw new NotSupportedException("Invalid value for BinaryOperatorType");
}

10
ICSharpCode.Decompiler/CSharp/Syntax/Expressions/BinaryOperatorExpression.cs

@ -25,7 +25,6 @@ @@ -25,7 +25,6 @@
// THE SOFTWARE.
using System;
using System.Collections.Generic;
using System.Linq.Expressions;
namespace ICSharpCode.Decompiler.CSharp.Syntax
@ -55,6 +54,7 @@ namespace ICSharpCode.Decompiler.CSharp.Syntax @@ -55,6 +54,7 @@ namespace ICSharpCode.Decompiler.CSharp.Syntax
public readonly static TokenRole ShiftRightRole = new TokenRole(">>");
public readonly static TokenRole NullCoalescingRole = new TokenRole("??");
public readonly static TokenRole RangeRole = new TokenRole("..");
public readonly static TokenRole IsKeywordRole = IsExpression.IsKeywordRole;
public readonly static Role<Expression> LeftRole = new Role<Expression>("Left", Expression.Null);
public readonly static Role<Expression> RightRole = new Role<Expression>("Right", Expression.Null);
@ -155,6 +155,8 @@ namespace ICSharpCode.Decompiler.CSharp.Syntax @@ -155,6 +155,8 @@ namespace ICSharpCode.Decompiler.CSharp.Syntax
return NullCoalescingRole;
case BinaryOperatorType.Range:
return RangeRole;
case BinaryOperatorType.IsPattern:
return IsKeywordRole;
default:
throw new NotSupportedException("Invalid value for BinaryOperatorType");
}
@ -265,6 +267,10 @@ namespace ICSharpCode.Decompiler.CSharp.Syntax @@ -265,6 +267,10 @@ namespace ICSharpCode.Decompiler.CSharp.Syntax
NullCoalescing,
/// <summary>left .. right</summary>
/// <remarks>left and right are optional = may be Expression.Null</remarks>
Range
Range,
/// <summary>left is right</summary>
/// <remarks>right must be a pattern</remarks>
IsPattern,
}
}

1
ICSharpCode.Decompiler/CSharp/Transforms/DeclareVariables.cs

@ -355,6 +355,7 @@ namespace ICSharpCode.Decompiler.CSharp.Transforms @@ -355,6 +355,7 @@ namespace ICSharpCode.Decompiler.CSharp.Transforms
case VariableKind.ExceptionStackSlot:
case VariableKind.UsingLocal:
case VariableKind.ForeachLocal:
case VariableKind.PatternLocal:
return false;
default:
return true;

18
ICSharpCode.Decompiler/DecompilerSettings.cs

@ -1446,6 +1446,24 @@ namespace ICSharpCode.Decompiler @@ -1446,6 +1446,24 @@ namespace ICSharpCode.Decompiler
}
}
bool patternMatching = true;
/// <summary>
/// Gets/Sets whether C# 7.0 pattern matching should be detected.
/// </summary>
[Category("C# 7.0 / VS 2017")]
[Description("DecompilerSettings.PatternMatching")]
public bool PatternMatching {
get { return patternMatching; }
set {
if (patternMatching != value)
{
patternMatching = value;
OnPropertyChanged();
}
}
}
bool staticLocalFunctions = true;
/// <summary>

1
ICSharpCode.Decompiler/ICSharpCode.Decompiler.csproj

@ -94,6 +94,7 @@ @@ -94,6 +94,7 @@
<Compile Include="IL\Transforms\IntroduceNativeIntTypeOnLocals.cs" />
<Compile Include="IL\Transforms\LdLocaDupInitObjTransform.cs" />
<Compile Include="IL\Transforms\PatternMatchingTransform.cs" />
<Compile Include="IL\Transforms\RemoveInfeasiblePathTransform.cs" />
<Compile Include="Metadata\ReferenceLoadInfo.cs" />
<Compile Include="Semantics\OutVarResolveResult.cs" />
<Compile Include="SingleFileBundle.cs" />

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

@ -18,7 +18,6 @@ @@ -18,7 +18,6 @@
#nullable enable
namespace ICSharpCode.Decompiler.IL.Transforms
{
@ -32,6 +31,8 @@ namespace ICSharpCode.Decompiler.IL.Transforms @@ -32,6 +31,8 @@ namespace ICSharpCode.Decompiler.IL.Transforms
/// </summary>
void IStatementTransform.Run(Block block, int pos, StatementTransformContext context)
{
if (!context.Settings.PatternMatching)
return;
if (pos + 1 >= block.Instructions.Count)
return;
if (block.Instructions[pos] is not StLoc
@ -54,17 +55,33 @@ namespace ICSharpCode.Decompiler.IL.Transforms @@ -54,17 +55,33 @@ namespace ICSharpCode.Decompiler.IL.Transforms
return;
if (result.LoadInst is not LdLoc)
return;
if (!result.LoadInst.Parent!.MatchCompNotEqualsNull(out _))
bool invertCondition;
if (result.LoadInst.Parent!.MatchCompNotEqualsNull(out _))
{
invertCondition = false;
}
else if (result.LoadInst.Parent!.MatchCompEqualsNull(out _))
{
invertCondition = true;
}
else
{
return;
}
context.Step($"Type pattern matching {v.Name}", block.Instructions[pos]);
// call Use(..., match.type[T](V = testedOperand))
var target = result.LoadInst.Parent;
var matchInstruction = new MatchInstruction(v, testedOperand) {
ILInstruction matchInstruction = new MatchInstruction(v, testedOperand) {
CheckNotNull = true,
CheckType = true
};
if (invertCondition)
{
matchInstruction = Comp.LogicNot(matchInstruction);
}
target.ReplaceWith(matchInstruction.WithILRange(target));
block.Instructions.RemoveAt(pos);
v.Kind = VariableKind.PatternLocal;

116
ICSharpCode.Decompiler/IL/Transforms/RemoveInfeasiblePathTransform.cs

@ -0,0 +1,116 @@ @@ -0,0 +1,116 @@
// Copyright (c) 2021 Daniel Grunwald, Siegfried Pammer
//
// 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.
#nullable enable
using System.Diagnostics.CodeAnalysis;
using System.Linq;
namespace ICSharpCode.Decompiler.IL.Transforms
{
/// <summary>
/// Block IL_0018 (incoming: *) {
/// stloc s(ldc.i4 1)
/// br IL_0019
/// }
///
/// Block IL_0019 (incoming: > 1) {
/// if (logic.not(ldloc s)) br IL_0027
/// br IL_001d
/// }
///
/// replace br IL_0019 with br IL_0027
/// </summary>
class RemoveInfeasiblePathTransform : IILTransform
{
void IILTransform.Run(ILFunction function, ILTransformContext context)
{
foreach (var container in function.Descendants.OfType<BlockContainer>())
{
bool changed = false;
foreach (var block in container.Blocks)
{
changed |= DoTransform(block, context);
}
if (changed)
{
container.SortBlocks(deleteUnreachableBlocks: true);
}
}
}
private bool DoTransform(Block block, ILTransformContext context)
{
if (!MatchBlock1(block, out var s, out int value, out var br))
return false;
if (!MatchBlock2(br.TargetBlock, s, value, out var targetBlock))
return false;
context.Step("RemoveInfeasiblePath", br);
br.TargetBlock = targetBlock;
return true;
}
// Block IL_0018 (incoming: *) {
// stloc s(ldc.i4 1)
// br IL_0019
// }
private bool MatchBlock1(Block block, [NotNullWhen(true)] out ILVariable? variable, out int constantValue, [NotNullWhen(true)] out Branch? branch)
{
variable = null;
constantValue = 0;
branch = null;
if (block.Instructions.Count != 2)
return false;
if (block.Instructions[0] is not StLoc
{
Variable: { Kind: VariableKind.StackSlot } s,
Value: LdcI4 { Value: 0 or 1 } valueInst
})
{
return false;
}
if (block.Instructions[1] is not Branch br)
return false;
variable = s;
constantValue = valueInst.Value;
branch = br;
return true;
}
// Block IL_0019 (incoming: > 1) {
// if (logic.not(ldloc s)) br IL_0027
// br IL_001d
// }
bool MatchBlock2(Block block, ILVariable s, int constantValue, [NotNullWhen(true)] out Block? targetBlock)
{
targetBlock = null;
if (block.Instructions.Count != 2)
return false;
if (block.IncomingEdgeCount <= 1)
return false;
if (!block.MatchIfAtEndOfBlock(out var load, out var trueInst, out var falseInst))
return false;
if (!load.MatchLdLoc(s))
return false;
ILInstruction target = constantValue != 0 ? trueInst : falseInst;
return target.MatchBranch(out targetBlock);
}
}
}
Loading…
Cancel
Save