diff --git a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/PatternMatching.cs b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/PatternMatching.cs index abf06dc61..44e05776b 100644 --- a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/PatternMatching.cs +++ b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/PatternMatching.cs @@ -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; diff --git a/ICSharpCode.Decompiler/CSharp/CSharpDecompiler.cs b/ICSharpCode.Decompiler/CSharp/CSharpDecompiler.cs index b33e95dc2..2480f76a0 100644 --- a/ICSharpCode.Decompiler/CSharp/CSharpDecompiler.cs +++ b/ICSharpCode.Decompiler/CSharp/CSharpDecompiler.cs @@ -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 diff --git a/ICSharpCode.Decompiler/CSharp/ExpressionBuilder.cs b/ICSharpCode.Decompiler/CSharp/ExpressionBuilder.cs index 61869dc1d..2fbdc8bf0 100644 --- a/ICSharpCode.Decompiler/CSharp/ExpressionBuilder.cs +++ b/ICSharpCode.Decompiler/CSharp/ExpressionBuilder.cs @@ -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) diff --git a/ICSharpCode.Decompiler/CSharp/OutputVisitor/CSharpOutputVisitor.cs b/ICSharpCode.Decompiler/CSharp/OutputVisitor/CSharpOutputVisitor.cs index 987677f7a..c7766b1e7 100644 --- a/ICSharpCode.Decompiler/CSharp/OutputVisitor/CSharpOutputVisitor.cs +++ b/ICSharpCode.Decompiler/CSharp/OutputVisitor/CSharpOutputVisitor.cs @@ -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 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 public virtual void VisitSingleVariableDesignation(SingleVariableDesignation singleVariableDesignation) { StartNode(singleVariableDesignation); - writer.WriteIdentifier(singleVariableDesignation.IdentifierToken); + WriteIdentifier(singleVariableDesignation.IdentifierToken); EndNode(singleVariableDesignation); } diff --git a/ICSharpCode.Decompiler/CSharp/OutputVisitor/InsertParenthesesVisitor.cs b/ICSharpCode.Decompiler/CSharp/OutputVisitor/InsertParenthesesVisitor.cs index 4b706e127..e279cf021 100644 --- a/ICSharpCode.Decompiler/CSharp/OutputVisitor/InsertParenthesesVisitor.cs +++ b/ICSharpCode.Decompiler/CSharp/OutputVisitor/InsertParenthesesVisitor.cs @@ -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"); } diff --git a/ICSharpCode.Decompiler/CSharp/Syntax/Expressions/BinaryOperatorExpression.cs b/ICSharpCode.Decompiler/CSharp/Syntax/Expressions/BinaryOperatorExpression.cs index 579d92e53..f64752e06 100644 --- a/ICSharpCode.Decompiler/CSharp/Syntax/Expressions/BinaryOperatorExpression.cs +++ b/ICSharpCode.Decompiler/CSharp/Syntax/Expressions/BinaryOperatorExpression.cs @@ -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 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 LeftRole = new Role("Left", Expression.Null); public readonly static Role RightRole = new Role("Right", Expression.Null); @@ -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 NullCoalescing, /// left .. right /// left and right are optional = may be Expression.Null - Range + Range, + + /// left is right + /// right must be a pattern + IsPattern, } } diff --git a/ICSharpCode.Decompiler/CSharp/Transforms/DeclareVariables.cs b/ICSharpCode.Decompiler/CSharp/Transforms/DeclareVariables.cs index 5964caf42..510821104 100644 --- a/ICSharpCode.Decompiler/CSharp/Transforms/DeclareVariables.cs +++ b/ICSharpCode.Decompiler/CSharp/Transforms/DeclareVariables.cs @@ -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; diff --git a/ICSharpCode.Decompiler/DecompilerSettings.cs b/ICSharpCode.Decompiler/DecompilerSettings.cs index b7c467223..182b280d4 100644 --- a/ICSharpCode.Decompiler/DecompilerSettings.cs +++ b/ICSharpCode.Decompiler/DecompilerSettings.cs @@ -1446,6 +1446,24 @@ namespace ICSharpCode.Decompiler } } + bool patternMatching = true; + + /// + /// Gets/Sets whether C# 7.0 pattern matching should be detected. + /// + [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; /// diff --git a/ICSharpCode.Decompiler/ICSharpCode.Decompiler.csproj b/ICSharpCode.Decompiler/ICSharpCode.Decompiler.csproj index a1c53a689..962f6795a 100644 --- a/ICSharpCode.Decompiler/ICSharpCode.Decompiler.csproj +++ b/ICSharpCode.Decompiler/ICSharpCode.Decompiler.csproj @@ -94,6 +94,7 @@ + diff --git a/ICSharpCode.Decompiler/IL/Transforms/PatternMatchingTransform.cs b/ICSharpCode.Decompiler/IL/Transforms/PatternMatchingTransform.cs index ad9407c1f..497439a37 100644 --- a/ICSharpCode.Decompiler/IL/Transforms/PatternMatchingTransform.cs +++ b/ICSharpCode.Decompiler/IL/Transforms/PatternMatchingTransform.cs @@ -18,7 +18,6 @@ #nullable enable - namespace ICSharpCode.Decompiler.IL.Transforms { @@ -32,6 +31,8 @@ namespace ICSharpCode.Decompiler.IL.Transforms /// 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 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)) + // 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; diff --git a/ICSharpCode.Decompiler/IL/Transforms/RemoveInfeasiblePathTransform.cs b/ICSharpCode.Decompiler/IL/Transforms/RemoveInfeasiblePathTransform.cs new file mode 100644 index 000000000..7cfa2abd4 --- /dev/null +++ b/ICSharpCode.Decompiler/IL/Transforms/RemoveInfeasiblePathTransform.cs @@ -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 +{ + /// + /// 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 + /// + class RemoveInfeasiblePathTransform : IILTransform + { + void IILTransform.Run(ILFunction function, ILTransformContext context) + { + foreach (var container in function.Descendants.OfType()) + { + 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); + } + } +}