Browse Source

Add support for C# 8 switch expressions.

pull/2087/head
Daniel Grunwald 5 years ago
parent
commit
abb9d49a0f
  1. 1
      ICSharpCode.Decompiler.Tests/ICSharpCode.Decompiler.Tests.csproj
  2. 4
      ICSharpCode.Decompiler.Tests/ILPrettyTestRunner.cs
  3. 9
      ICSharpCode.Decompiler.Tests/PrettyTestRunner.cs
  4. 143
      ICSharpCode.Decompiler.Tests/TestCases/Pretty/SwitchExpressions.cs
  5. 50
      ICSharpCode.Decompiler/CSharp/ExpressionBuilder.cs
  6. 27
      ICSharpCode.Decompiler/CSharp/OutputVisitor/CSharpOutputVisitor.cs
  7. 9
      ICSharpCode.Decompiler/CSharp/OutputVisitor/InsertParenthesesVisitor.cs
  8. 14
      ICSharpCode.Decompiler/CSharp/StatementBuilder.cs
  9. 36
      ICSharpCode.Decompiler/CSharp/Syntax/DepthFirstAstVisitor.cs
  10. 118
      ICSharpCode.Decompiler/CSharp/Syntax/Expressions/SwitchExpression.cs
  11. 6
      ICSharpCode.Decompiler/CSharp/Syntax/IAstVisitor.cs
  12. 20
      ICSharpCode.Decompiler/DecompilerSettings.cs
  13. 1
      ICSharpCode.Decompiler/ICSharpCode.Decompiler.csproj
  14. 2
      ICSharpCode.Decompiler/IL/Instructions.cs
  15. 2
      ICSharpCode.Decompiler/IL/Instructions.tt
  16. 24
      ICSharpCode.Decompiler/IL/Instructions/SwitchInstruction.cs
  17. 167
      ICSharpCode.Decompiler/IL/Transforms/ExpressionTransforms.cs

1
ICSharpCode.Decompiler.Tests/ICSharpCode.Decompiler.Tests.csproj

@ -92,6 +92,7 @@ @@ -92,6 +92,7 @@
<Compile Include="ProjectDecompiler\TargetFrameworkTests.cs" />
<Compile Include="TestAssemblyResolver.cs" />
<Compile Include="TestCases\Correctness\StringConcat.cs" />
<Compile Include="TestCases\Pretty\SwitchExpressions.cs" />
<None Include="TestCases\Pretty\NativeInts.cs" />
<None Include="TestCases\ILPretty\CallIndirect.cs" />
<Compile Include="TestCases\ILPretty\Issue1681.cs" />

4
ICSharpCode.Decompiler.Tests/ILPrettyTestRunner.cs

@ -127,13 +127,13 @@ namespace ICSharpCode.Decompiler.Tests @@ -127,13 +127,13 @@ namespace ICSharpCode.Decompiler.Tests
[Test]
public void CS1xSwitch_Debug()
{
Run();
Run(settings: new DecompilerSettings { SwitchExpressions = false });
}
[Test]
public void CS1xSwitch_Release()
{
Run();
Run(settings: new DecompilerSettings { SwitchExpressions = false });
}
[Test]

9
ICSharpCode.Decompiler.Tests/PrettyTestRunner.cs

@ -135,10 +135,17 @@ namespace ICSharpCode.Decompiler.Tests @@ -135,10 +135,17 @@ namespace ICSharpCode.Decompiler.Tests
{
RunForLibrary(cscOptions: cscOptions, decompilerSettings: new DecompilerSettings {
// legacy csc generates a dead store in debug builds
RemoveDeadStores = (cscOptions == CompilerOptions.None)
RemoveDeadStores = (cscOptions == CompilerOptions.None),
SwitchExpressions = false,
});
}
[Test]
public void SwitchExpressions([ValueSource(nameof(roslynOnlyOptions))] CompilerOptions cscOptions)
{
RunForLibrary(cscOptions: cscOptions);
}
[Test]
public void ReduceNesting([ValueSource(nameof(defaultOptions))] CompilerOptions cscOptions)
{

143
ICSharpCode.Decompiler.Tests/TestCases/Pretty/SwitchExpressions.cs

@ -0,0 +1,143 @@ @@ -0,0 +1,143 @@
// Copyright (c) AlphaSierraPapa for the SharpDevelop Team
//
// 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 System;
namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty
{
public static class SwitchExpressions
{
public enum State
{
False,
True,
Null
}
public static bool? SwitchOverNullableEnum(State? state)
{
return state switch {
State.False => false,
State.True => true,
State.Null => null,
_ => throw new InvalidOperationException(),
};
}
public static string SparseIntegerSwitch(int i)
{
Console.WriteLine("SparseIntegerSwitch: " + i);
return i switch {
-10000000 => "-10 mln",
-100 => "-hundred",
-1 => "-1",
0 => "0",
1 => "1",
2 => "2",
4 => "4",
100 => "hundred",
10000 => "ten thousand",
10001 => "ten thousand and one",
int.MaxValue => "int.MaxValue",
_ => "something else",
};
}
public static bool SparseIntegerSwitch3(int i)
{
// not using a switch expression because we'd have to duplicate the 'true' branch
switch (i) {
case 0:
case 10:
case 11:
case 12:
case 100:
case 101:
case 200:
return true;
default:
return false;
}
}
public static string SwitchOverNullableInt(int? i)
{
return i switch {
null => "null",
0 => "zero",
5 => "five",
10 => "ten",
_ => "large",
};
}
public static void SwitchOverInt(int i)
{
Console.WriteLine(i switch {
0 => "zero",
5 => "five",
10 => "ten",
15 => "fifteen",
20 => "twenty",
25 => "twenty-five",
30 => "thirty",
_ => throw new NotImplementedException(),
});
}
public static string SwitchOverString1(string text)
{
Console.WriteLine("SwitchOverString1: " + text);
return text switch {
"First case" => "Text1",
"Second case" => "Text2",
"Third case" => "Text3",
"Fourth case" => "Text4",
"Fifth case" => "Text5",
"Sixth case" => "Text6",
null => null,
_ => "Default",
};
}
public static string SwitchOverString2(string text)
{
Console.WriteLine("SwitchOverString1: " + text);
// Cannot use switch expression, because "return Text2;" would need to be duplicated
switch (text) {
case "First case":
return "Text1";
case "Second case":
case "2nd case":
return "Text2";
case "Third case":
return "Text3";
case "Fourth case":
return "Text4";
case "Fifth case":
return "Text5";
case "Sixth case":
return "Text6";
case null:
return null;
default:
return "Default";
}
}
}
}

50
ICSharpCode.Decompiler/CSharp/ExpressionBuilder.cs

@ -2920,6 +2920,56 @@ namespace ICSharpCode.Decompiler.CSharp @@ -2920,6 +2920,56 @@ namespace ICSharpCode.Decompiler.CSharp
}
}
protected internal override TranslatedExpression VisitSwitchInstruction(SwitchInstruction inst, TranslationContext context)
{
TranslatedExpression value;
if (inst.Value is StringToInt strToInt) {
value = Translate(strToInt.Argument);
} else {
strToInt = null;
value = Translate(inst.Value);
}
IL.SwitchSection defaultSection = inst.GetDefaultSection();
SwitchExpression switchExpr = new SwitchExpression();
switchExpr.Expression = value;
IType resultType;
if (context.TypeHint.Kind != TypeKind.Unknown && context.TypeHint.GetStackType() == inst.ResultType) {
resultType = context.TypeHint;
} else {
resultType = compilation.FindType(inst.ResultType.ToKnownTypeCode());
}
foreach (var section in inst.Sections) {
if (section == defaultSection)
continue;
var ses = new SwitchExpressionSection();
if (section.HasNullLabel) {
Debug.Assert(section.Labels.IsEmpty);
ses.Pattern = new NullReferenceExpression();
} else {
long val = section.Labels.Values.Single();
var rr = statementBuilder.CreateTypedCaseLabel(val, value.Type, strToInt?.Map).Single();
ses.Pattern = astBuilder.ConvertConstantValue(rr);
}
ses.Body = TranslateSectionBody(section);
switchExpr.SwitchSections.Add(ses);
}
var defaultSES = new SwitchExpressionSection();
defaultSES.Pattern = new IdentifierExpression("_");
defaultSES.Body = TranslateSectionBody(defaultSection);
switchExpr.SwitchSections.Add(defaultSES);
return switchExpr.WithILInstruction(inst).WithRR(new ResolveResult(resultType));
Expression TranslateSectionBody(IL.SwitchSection section)
{
var body = Translate(section.Body, resultType);
return body.ConvertTo(resultType, this, allowImplicitConversion: true);
}
}
protected internal override TranslatedExpression VisitAddressOf(AddressOf inst, TranslationContext context)
{
// HACK: this is only correct if the argument is an R-value; otherwise we're missing the copy to the temporary

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

@ -1759,6 +1759,33 @@ namespace ICSharpCode.Decompiler.CSharp.OutputVisitor @@ -1759,6 +1759,33 @@ namespace ICSharpCode.Decompiler.CSharp.OutputVisitor
EndNode(caseLabel);
}
public virtual void VisitSwitchExpression(SwitchExpression switchExpression)
{
StartNode(switchExpression);
switchExpression.Expression.AcceptVisitor(this);
Space();
WriteKeyword(SwitchExpression.SwitchKeywordRole);
OpenBrace(BraceStyle.EndOfLine);
foreach (AstNode node in switchExpression.SwitchSections) {
node.AcceptVisitor(this);
Comma(node);
NewLine();
}
CloseBrace(BraceStyle.EndOfLine);
EndNode(switchExpression);
}
public virtual void VisitSwitchExpressionSection(SwitchExpressionSection switchExpressionSection)
{
StartNode(switchExpressionSection);
switchExpressionSection.Pattern.AcceptVisitor(this);
Space();
WriteToken(Roles.Arrow);
Space();
switchExpressionSection.Body.AcceptVisitor(this);
EndNode(switchExpressionSection);
}
public virtual void VisitThrowStatement(ThrowStatement throwStatement)
{
StartNode(throwStatement);

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

@ -52,6 +52,7 @@ namespace ICSharpCode.Decompiler.CSharp.OutputVisitor @@ -52,6 +52,7 @@ namespace ICSharpCode.Decompiler.CSharp.OutputVisitor
Shift, // << >>
Additive, // binary + -
Multiplicative, // * / %
Switch, // C# 8 switch expression
Range, // ..
Unary,
QueryOrLambda,
@ -145,6 +146,8 @@ namespace ICSharpCode.Decompiler.CSharp.OutputVisitor @@ -145,6 +146,8 @@ namespace ICSharpCode.Decompiler.CSharp.OutputVisitor
return PrecedenceLevel.Conditional;
if (expr is AssignmentExpression || expr is LambdaExpression)
return PrecedenceLevel.Assignment;
if (expr is SwitchExpression)
return PrecedenceLevel.Switch;
// anything else: primary expression
return PrecedenceLevel.Primary;
}
@ -414,5 +417,11 @@ namespace ICSharpCode.Decompiler.CSharp.OutputVisitor @@ -414,5 +417,11 @@ namespace ICSharpCode.Decompiler.CSharp.OutputVisitor
}
base.VisitNamedExpression (namedExpression);
}
public override void VisitSwitchExpression(SwitchExpression switchExpression)
{
ParenthesizeIfRequired(switchExpression.Expression, PrecedenceLevel.Switch + 1);
base.VisitSwitchExpression(switchExpression);
}
}
}

14
ICSharpCode.Decompiler/CSharp/StatementBuilder.cs

@ -120,7 +120,7 @@ namespace ICSharpCode.Decompiler.CSharp @@ -120,7 +120,7 @@ namespace ICSharpCode.Decompiler.CSharp
return new IfElseStatement(condition, trueStatement, falseStatement).WithILInstruction(inst);
}
IEnumerable<ConstantResolveResult> CreateTypedCaseLabel(long i, IType type, List<(string Key, int Value)> map = null)
internal IEnumerable<ConstantResolveResult> CreateTypedCaseLabel(long i, IType type, List<(string Key, int Value)> map = null)
{
object value;
// unpack nullable type, if necessary:
@ -165,20 +165,14 @@ namespace ICSharpCode.Decompiler.CSharp @@ -165,20 +165,14 @@ namespace ICSharpCode.Decompiler.CSharp
caseLabelMapping = new Dictionary<Block, ConstantResolveResult>();
TranslatedExpression value;
var strToInt = inst.Value as StringToInt;
if (strToInt != null) {
if (inst.Value is StringToInt strToInt) {
value = exprBuilder.Translate(strToInt.Argument);
} else {
strToInt = null;
value = exprBuilder.Translate(inst.Value);
}
// Pick the section with the most labels as default section.
IL.SwitchSection defaultSection = inst.Sections.First();
foreach (var section in inst.Sections) {
if (section.Labels.Count() > defaultSection.Labels.Count()) {
defaultSection = section;
}
}
IL.SwitchSection defaultSection = inst.GetDefaultSection();
var stmt = new SwitchStatement() { Expression = value };
Dictionary<IL.SwitchSection, Syntax.SwitchSection> translationDictionary = new Dictionary<IL.SwitchSection, Syntax.SwitchSection>();

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

@ -345,7 +345,17 @@ namespace ICSharpCode.Decompiler.CSharp.Syntax @@ -345,7 +345,17 @@ namespace ICSharpCode.Decompiler.CSharp.Syntax
{
VisitChildren (caseLabel);
}
public virtual void VisitSwitchExpression(SwitchExpression switchExpression)
{
VisitChildren(switchExpression);
}
public virtual void VisitSwitchExpressionSection(SwitchExpressionSection switchExpressionSection)
{
VisitChildren(switchExpressionSection);
}
public virtual void VisitThrowStatement (ThrowStatement throwStatement)
{
VisitChildren (throwStatement);
@ -987,7 +997,17 @@ namespace ICSharpCode.Decompiler.CSharp.Syntax @@ -987,7 +997,17 @@ namespace ICSharpCode.Decompiler.CSharp.Syntax
{
return VisitChildren (caseLabel);
}
public virtual T VisitSwitchExpression(SwitchExpression switchExpression)
{
return VisitChildren(switchExpression);
}
public virtual T VisitSwitchExpressionSection(SwitchExpressionSection switchExpressionSection)
{
return VisitChildren(switchExpressionSection);
}
public virtual T VisitThrowStatement (ThrowStatement throwStatement)
{
return VisitChildren (throwStatement);
@ -1629,7 +1649,17 @@ namespace ICSharpCode.Decompiler.CSharp.Syntax @@ -1629,7 +1649,17 @@ namespace ICSharpCode.Decompiler.CSharp.Syntax
{
return VisitChildren (caseLabel, data);
}
public virtual S VisitSwitchExpression(SwitchExpression switchExpression, T data)
{
return VisitChildren(switchExpression, data);
}
public virtual S VisitSwitchExpressionSection(SwitchExpressionSection switchExpressionSection, T data)
{
return VisitChildren(switchExpressionSection, data);
}
public virtual S VisitThrowStatement (ThrowStatement throwStatement, T data)
{
return VisitChildren (throwStatement, data);

118
ICSharpCode.Decompiler/CSharp/Syntax/Expressions/SwitchExpression.cs

@ -0,0 +1,118 @@ @@ -0,0 +1,118 @@
// Copyright (c) 2020 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.
namespace ICSharpCode.Decompiler.CSharp.Syntax
{
/// <summary>
/// Expression switch { SwitchSections }
/// </summary>
public class SwitchExpression : Expression
{
public static readonly TokenRole SwitchKeywordRole = new TokenRole("switch");
public static readonly Role<SwitchExpressionSection> SwitchSectionRole = new Role<SwitchExpressionSection>("SwitchSection");
public Expression Expression {
get { return GetChildByRole(Roles.Expression); }
set { SetChildByRole(Roles.Expression, value); }
}
public CSharpTokenNode SwitchToken {
get { return GetChildByRole(SwitchKeywordRole); }
}
public CSharpTokenNode LBraceToken {
get { return GetChildByRole(Roles.LBrace); }
}
public AstNodeCollection<SwitchExpressionSection> SwitchSections {
get { return GetChildrenByRole(SwitchSectionRole); }
}
public CSharpTokenNode RBraceToken {
get { return GetChildByRole(Roles.RBrace); }
}
public override void AcceptVisitor(IAstVisitor visitor)
{
visitor.VisitSwitchExpression(this);
}
public override T AcceptVisitor<T>(IAstVisitor<T> visitor)
{
return visitor.VisitSwitchExpression(this);
}
public override S AcceptVisitor<T, S>(IAstVisitor<T, S> visitor, T data)
{
return visitor.VisitSwitchExpression(this, data);
}
protected internal override bool DoMatch(AstNode other, PatternMatching.Match match)
{
SwitchExpression o = other as SwitchExpression;
return o != null && this.Expression.DoMatch(o.Expression, match) && this.SwitchSections.DoMatch(o.SwitchSections, match);
}
}
/// <summary>
/// Pattern => Expression
/// </summary>
public class SwitchExpressionSection : AstNode
{
public static readonly Role<Expression> PatternRole = new Role<Expression>("Pattern", Expression.Null);
public static readonly Role<Expression> BodyRole = new Role<Expression>("Body", Expression.Null);
public Expression Pattern {
get { return GetChildByRole(PatternRole); }
set { SetChildByRole(PatternRole, value); }
}
public CSharpTokenNode ArrowToken {
get { return GetChildByRole(Roles.Arrow); }
}
public Expression Body {
get { return GetChildByRole(BodyRole); }
set { SetChildByRole(BodyRole, value); }
}
public override NodeType NodeType => NodeType.Unknown;
public override void AcceptVisitor(IAstVisitor visitor)
{
visitor.VisitSwitchExpressionSection(this);
}
public override T AcceptVisitor<T>(IAstVisitor<T> visitor)
{
return visitor.VisitSwitchExpressionSection(this);
}
public override S AcceptVisitor<T, S>(IAstVisitor<T, S> visitor, T data)
{
return visitor.VisitSwitchExpressionSection(this, data);
}
protected internal override bool DoMatch(AstNode other, PatternMatching.Match match)
{
SwitchExpressionSection o = other as SwitchExpressionSection;
return o != null && this.Pattern.DoMatch(o.Pattern, match) && this.Body.DoMatch(o.Body, match);
}
}
}

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

@ -103,6 +103,8 @@ namespace ICSharpCode.Decompiler.CSharp.Syntax @@ -103,6 +103,8 @@ namespace ICSharpCode.Decompiler.CSharp.Syntax
void VisitSwitchStatement(SwitchStatement switchStatement);
void VisitSwitchSection(SwitchSection switchSection);
void VisitCaseLabel(CaseLabel caseLabel);
void VisitSwitchExpression(SwitchExpression switchExpression);
void VisitSwitchExpressionSection(SwitchExpressionSection switchExpressionSection);
void VisitThrowStatement(ThrowStatement throwStatement);
void VisitTryCatchStatement(TryCatchStatement tryCatchStatement);
void VisitCatchClause(CatchClause catchClause);
@ -243,6 +245,8 @@ namespace ICSharpCode.Decompiler.CSharp.Syntax @@ -243,6 +245,8 @@ namespace ICSharpCode.Decompiler.CSharp.Syntax
S VisitSwitchStatement(SwitchStatement switchStatement);
S VisitSwitchSection(SwitchSection switchSection);
S VisitCaseLabel(CaseLabel caseLabel);
S VisitSwitchExpression(SwitchExpression switchExpression);
S VisitSwitchExpressionSection(SwitchExpressionSection switchExpressionSection);
S VisitThrowStatement(ThrowStatement throwStatement);
S VisitTryCatchStatement(TryCatchStatement tryCatchStatement);
S VisitCatchClause(CatchClause catchClause);
@ -383,6 +387,8 @@ namespace ICSharpCode.Decompiler.CSharp.Syntax @@ -383,6 +387,8 @@ namespace ICSharpCode.Decompiler.CSharp.Syntax
S VisitSwitchStatement(SwitchStatement switchStatement, T data);
S VisitSwitchSection(SwitchSection switchSection, T data);
S VisitCaseLabel(CaseLabel caseLabel, T data);
S VisitSwitchExpression(SwitchExpression switchExpression, T data);
S VisitSwitchExpressionSection(SwitchExpressionSection switchExpressionSection, T data);
S VisitThrowStatement(ThrowStatement throwStatement, T data);
S VisitTryCatchStatement(TryCatchStatement tryCatchStatement, T data);
S VisitCatchClause(CatchClause catchClause, T data);

20
ICSharpCode.Decompiler/DecompilerSettings.cs

@ -113,6 +113,7 @@ namespace ICSharpCode.Decompiler @@ -113,6 +113,7 @@ namespace ICSharpCode.Decompiler
asyncEnumerator = false;
staticLocalFunctions = false;
ranges = false;
switchExpressions = false;
}
if (languageVersion < CSharp.LanguageVersion.Preview) {
nativeIntegers = false;
@ -124,7 +125,7 @@ namespace ICSharpCode.Decompiler @@ -124,7 +125,7 @@ namespace ICSharpCode.Decompiler
{
if (nativeIntegers || initAccessors)
return CSharp.LanguageVersion.Preview;
if (nullableReferenceTypes || readOnlyMethods || asyncEnumerator || asyncUsingAndForEachStatement || staticLocalFunctions || ranges)
if (nullableReferenceTypes || readOnlyMethods || asyncEnumerator || asyncUsingAndForEachStatement || staticLocalFunctions || ranges || switchExpressions)
return CSharp.LanguageVersion.CSharp8_0;
if (introduceUnmanagedConstraint || tupleComparisons || stackAllocInitializers || patternBasedFixedStatement)
return CSharp.LanguageVersion.CSharp7_3;
@ -181,6 +182,23 @@ namespace ICSharpCode.Decompiler @@ -181,6 +182,23 @@ namespace ICSharpCode.Decompiler
}
}
bool switchExpressions = true;
/// <summary>
/// Use C# 8 switch expressions.
/// </summary>
[Category("C# 8.0 / VS 2019")]
[Description("DecompilerSettings.SwitchExpressions")]
public bool SwitchExpressions {
get { return switchExpressions; }
set {
if (switchExpressions != value) {
switchExpressions = value;
OnPropertyChanged();
}
}
}
bool anonymousMethods = true;
/// <summary>

1
ICSharpCode.Decompiler/ICSharpCode.Decompiler.csproj

@ -70,6 +70,7 @@ @@ -70,6 +70,7 @@
<Compile Include="CSharp\SequencePointBuilder.cs" />
<Compile Include="CSharp\ProjectDecompiler\TargetFramework.cs" />
<Compile Include="CSharp\ProjectDecompiler\TargetServices.cs" />
<Compile Include="CSharp\Syntax\Expressions\SwitchExpression.cs" />
<Compile Include="CSharp\Syntax\FunctionPointerType.cs" />
<Compile Include="IL\Transforms\FixLoneIsInst.cs" />
<Compile Include="IL\Transforms\IndexRangeTransform.cs" />

2
ICSharpCode.Decompiler/IL/Instructions.cs

@ -1497,7 +1497,7 @@ namespace ICSharpCode.Decompiler.IL @@ -1497,7 +1497,7 @@ namespace ICSharpCode.Decompiler.IL
/// <summary>Switch statement</summary>
public sealed partial class SwitchInstruction : ILInstruction
{
public override StackType ResultType { get { return StackType.Void; } }
public override void AcceptVisitor(ILVisitor visitor)
{
visitor.VisitSwitchInstruction(this);

2
ICSharpCode.Decompiler/IL/Instructions.tt

@ -116,7 +116,7 @@ @@ -116,7 +116,7 @@
new ChildInfo("fallbackInst"),
}), CustomConstructor, CustomComputeFlags, CustomWriteTo),
new OpCode("switch", "Switch statement",
CustomClassName("SwitchInstruction"), CustomConstructor, CustomComputeFlags, CustomWriteTo, ResultType("Void"),
CustomClassName("SwitchInstruction"), CustomConstructor, CustomComputeFlags, CustomWriteTo,
MatchCondition("IsLifted == o.IsLifted && Value.PerformMatch(o.Value, ref match) && Patterns.ListMatch.DoMatch(this.Sections, o.Sections, ref match)")),
new OpCode("switch.section", "Switch section within a switch statement",
CustomClassName("SwitchSection"), CustomChildren(new [] { new ChildInfo("body") }),

24
ICSharpCode.Decompiler/IL/Instructions/SwitchInstruction.cs

@ -130,7 +130,16 @@ namespace ICSharpCode.Decompiler.IL @@ -130,7 +130,16 @@ namespace ICSharpCode.Decompiler.IL
clone.Sections.AddRange(this.Sections.Select(h => (SwitchSection)h.Clone()));
return clone;
}
StackType resultType = StackType.Void;
public override StackType ResultType => resultType;
public void SetResultType(StackType resultType)
{
this.resultType = resultType;
}
internal override void CheckInvariant(ILPhase phase)
{
base.CheckInvariant(phase);
@ -143,12 +152,25 @@ namespace ICSharpCode.Decompiler.IL @@ -143,12 +152,25 @@ namespace ICSharpCode.Decompiler.IL
}
Debug.Assert(!section.Labels.IsEmpty || section.HasNullLabel);
Debug.Assert(!section.Labels.Overlaps(sets));
Debug.Assert(section.Body.ResultType == this.ResultType);
sets = sets.UnionWith(section.Labels);
}
Debug.Assert(sets.SetEquals(LongSet.Universe), "switch does not handle all possible cases");
Debug.Assert(!expectNullSection, "Lifted switch is missing 'case null'");
Debug.Assert(this.IsLifted ? (value.ResultType == StackType.O) : (value.ResultType == StackType.I4 || value.ResultType == StackType.I8));
}
public SwitchSection GetDefaultSection()
{
// Pick the section with the most labels as default section.
IL.SwitchSection defaultSection = Sections.First();
foreach (var section in Sections) {
if (section.Labels.Count() > defaultSection.Labels.Count()) {
defaultSection = section;
}
}
return defaultSection;
}
}
partial class SwitchSection

167
ICSharpCode.Decompiler/IL/Transforms/ExpressionTransforms.cs

@ -65,8 +65,10 @@ namespace ICSharpCode.Decompiler.IL.Transforms @@ -65,8 +65,10 @@ namespace ICSharpCode.Decompiler.IL.Transforms
{
if (container.Kind == ContainerKind.Switch) {
// Special case for switch: Only visit the switch condition block.
var switchInst = (SwitchInstruction)container.EntryPoint.Instructions[0];
var switchInst = (SwitchInstruction)container.EntryPoint.Instructions[0];
switchInst.Value.AcceptVisitor(this);
HandleSwitchExpression(container, switchInst);
}
// No need to call base.VisitBlockContainer, see comment in VisitBlock.
}
@ -121,12 +123,11 @@ namespace ICSharpCode.Decompiler.IL.Transforms @@ -121,12 +123,11 @@ namespace ICSharpCode.Decompiler.IL.Transforms
inst.Kind = ComparisonKind.Equality;
}
}
var rightWithoutConv = inst.Right.UnwrapConv(ConversionKind.SignExtend).UnwrapConv(ConversionKind.ZeroExtend);
if (rightWithoutConv.MatchLdcI4(0)
&& inst.Sign == Sign.Unsigned
&& (inst.Kind == ComparisonKind.GreaterThan || inst.Kind == ComparisonKind.LessThanOrEqual))
{
&& inst.Sign == Sign.Unsigned
&& (inst.Kind == ComparisonKind.GreaterThan || inst.Kind == ComparisonKind.LessThanOrEqual)) {
if (inst.Kind == ComparisonKind.GreaterThan) {
context.Step("comp.unsigned(left > ldc.i4 0) => comp(left != ldc.i4 0)", inst);
inst.Kind = ComparisonKind.Inequality;
@ -169,21 +170,19 @@ namespace ICSharpCode.Decompiler.IL.Transforms @@ -169,21 +170,19 @@ namespace ICSharpCode.Decompiler.IL.Transforms
}
}
}
protected internal override void VisitConv(Conv inst)
{
inst.Argument.AcceptVisitor(this);
if (inst.Argument.MatchLdLen(StackType.I, out ILInstruction array) && inst.TargetType.IsIntegerType()
&& (!inst.CheckForOverflow || context.Settings.AssumeArrayLengthFitsIntoInt32))
{
&& (!inst.CheckForOverflow || context.Settings.AssumeArrayLengthFitsIntoInt32)) {
context.Step("conv.i4(ldlen array) => ldlen.i4(array)", inst);
inst.AddILRange(inst.Argument);
inst.ReplaceWith(new LdLen(inst.TargetType.GetStackType(), array).WithILRange(inst));
return;
}
if (inst.TargetType.IsFloatType() && inst.Argument is Conv conv
&& conv.Kind == ConversionKind.IntToFloat && conv.TargetType == PrimitiveType.R)
{
if (inst.TargetType.IsFloatType() && inst.Argument is Conv conv
&& conv.Kind == ConversionKind.IntToFloat && conv.TargetType == PrimitiveType.R) {
// IL conv.r.un does not indicate whether to convert the target type to R4 or R8,
// so the C# compiler usually follows it with an explicit conv.r4 or conv.r8.
// To avoid emitting '(float)(double)val', we combine these two conversions:
@ -270,7 +269,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms @@ -270,7 +269,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms
arg.AcceptVisitor(this);
}
}
protected internal override void VisitCall(Call inst)
{
var expr = EarlyExpressionTransforms.HandleCall(inst, context);
@ -430,9 +429,8 @@ namespace ICSharpCode.Decompiler.IL.Transforms @@ -430,9 +429,8 @@ namespace ICSharpCode.Decompiler.IL.Transforms
} 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))
{
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;
}
@ -501,8 +499,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms @@ -501,8 +499,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms
// Be careful: when both LHS and RHS are the constant 1, we must not
// swap the arguments as it would lead to an infinite transform loop.
if (inst.TrueInst.MatchLdcI4(0) && !inst.FalseInst.MatchLdcI4(0)
|| inst.FalseInst.MatchLdcI4(1) && !inst.TrueInst.MatchLdcI4(1))
{
|| inst.FalseInst.MatchLdcI4(1) && !inst.TrueInst.MatchLdcI4(1)) {
context.Step("canonicalize logic and/or", inst);
var t = inst.TrueInst;
inst.TrueInst = inst.FalseInst;
@ -531,6 +528,112 @@ namespace ICSharpCode.Decompiler.IL.Transforms @@ -531,6 +528,112 @@ namespace ICSharpCode.Decompiler.IL.Transforms
}
}
IfInstruction HandleConditionalOperator(IfInstruction inst)
{
// if (cond) stloc (A, V1) else stloc (A, V2) --> stloc (A, if (cond) V1 else V2)
Block trueInst = inst.TrueInst as Block;
if (trueInst == null || trueInst.Instructions.Count != 1)
return inst;
Block falseInst = inst.FalseInst as Block;
if (falseInst == null || falseInst.Instructions.Count != 1)
return inst;
ILVariable v;
ILInstruction value1, value2;
if (trueInst.Instructions[0].MatchStLoc(out v, out value1) && falseInst.Instructions[0].MatchStLoc(v, out value2)) {
context.Step("conditional operator", inst);
var newIf = new IfInstruction(Comp.LogicNot(inst.Condition), value2, value1);
newIf.AddILRange(inst);
inst.ReplaceWith(new StLoc(v, newIf));
context.RequestRerun(); // trigger potential inlining of the newly created StLoc
return newIf;
}
return inst;
}
private void HandleSwitchExpression(BlockContainer container, SwitchInstruction switchInst)
{
if (!context.Settings.SwitchExpressions)
return;
Debug.Assert(container.Kind == ContainerKind.Switch);
Debug.Assert(container.ResultType == StackType.Void);
var defaultSection = switchInst.GetDefaultSection();
StackType resultType = StackType.Void;
BlockContainer leaveTarget = null;
ILVariable resultVariable = null;
foreach (var section in switchInst.Sections) {
if (section != defaultSection) {
// every section except for the default must have exactly 1 label
if (section.Labels.Count() != (section.HasNullLabel ? 0u : 1u))
return;
}
if (!section.Body.MatchBranch(out var sectionBlock))
return;
if (sectionBlock.IncomingEdgeCount != 1)
return;
if (sectionBlock.Parent != container)
return;
if (sectionBlock.Instructions.Count == 1) {
if (sectionBlock.Instructions[0] is Throw) {
// OK
} else if (sectionBlock.Instructions[0] is Leave leave) {
if (!leave.IsLeavingFunction)
return;
leaveTarget ??= leave.TargetContainer;
Debug.Assert(leaveTarget == leave.TargetContainer);
resultType = leave.Value.ResultType;
} else {
return;
}
} else if (sectionBlock.Instructions.Count == 2) {
if (!sectionBlock.Instructions[0].MatchStLoc(out var v, out _))
return;
if (!sectionBlock.Instructions[1].MatchLeave(container))
return;
resultVariable ??= v;
if (resultVariable != v)
return;
resultType = resultVariable.StackType;
} else {
return;
}
}
// Exactly one of resultVariable/leaveTarget must be null
if ((resultVariable == null) == (leaveTarget == null))
return;
if (switchInst.Value is StringToInt str2int) {
// validate that each integer is used for exactly one value
var integersUsed = new HashSet<int>();
foreach ((string key, int val) in str2int.Map) {
if (!integersUsed.Add(val))
return;
}
}
context.Step("Switch Expression", switchInst);
switchInst.SetResultType(resultType);
foreach (var section in switchInst.Sections) {
var block = ((Branch)section.Body).TargetBlock;
if (block.Instructions.Count == 1) {
if (block.Instructions[0] is Throw t) {
t.resultType = resultType;
section.Body = t;
} else if (block.Instructions[0] is Leave leave) {
section.Body = leave.Value;
} else {
throw new InvalidOperationException();
}
} else {
section.Body = ((StLoc)block.Instructions[0]).Value;
}
}
if (resultVariable != null) {
container.ReplaceWith(new StLoc(resultVariable, switchInst));
} else {
container.ReplaceWith(new Leave(leaveTarget, switchInst));
}
context.RequestRerun(); // new StLoc might trigger inlining
}
/// <summary>
/// op is either add or remove/subtract:
/// if (dynamic.isevent (target)) {
@ -641,28 +744,6 @@ namespace ICSharpCode.Decompiler.IL.Transforms @@ -641,28 +744,6 @@ namespace ICSharpCode.Decompiler.IL.Transforms
inst.ReplaceWith(new DynamicCompoundAssign(binaryOp.Operation, binaryOp.BinderFlags, binaryOp.Left, binaryOp.LeftArgumentInfo, binaryOp.Right, binaryOp.RightArgumentInfo));
}
IfInstruction HandleConditionalOperator(IfInstruction inst)
{
// if (cond) stloc (A, V1) else stloc (A, V2) --> stloc (A, if (cond) V1 else V2)
Block trueInst = inst.TrueInst as Block;
if (trueInst == null || trueInst.Instructions.Count != 1)
return inst;
Block falseInst = inst.FalseInst as Block;
if (falseInst == null || falseInst.Instructions.Count != 1)
return inst;
ILVariable v;
ILInstruction value1, value2;
if (trueInst.Instructions[0].MatchStLoc(out v, out value1) && falseInst.Instructions[0].MatchStLoc(v, out value2)) {
context.Step("conditional operator", inst);
var newIf = new IfInstruction(Comp.LogicNot(inst.Condition), value2, value1);
newIf.AddILRange(inst);
inst.ReplaceWith(new StLoc(v, newIf));
context.RequestRerun(); // trigger potential inlining of the newly created StLoc
return newIf;
}
return inst;
}
protected internal override void VisitBinaryNumericInstruction(BinaryNumericInstruction inst)
{
base.VisitBinaryNumericInstruction(inst);
@ -670,8 +751,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms @@ -670,8 +751,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms
case BinaryNumericOperator.ShiftLeft:
case BinaryNumericOperator.ShiftRight:
if (inst.Right.MatchBinaryNumericInstruction(BinaryNumericOperator.BitAnd, out var lhs, out var rhs)
&& rhs.MatchLdcI4(inst.ResultType == StackType.I8 ? 63 : 31))
{
&& rhs.MatchLdcI4(inst.ResultType == StackType.I8 ? 63 : 31)) {
// a << (b & 31) => a << b
context.Step("Combine bit.and into shift", inst);
inst.Right = lhs;
@ -679,8 +759,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms @@ -679,8 +759,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms
break;
case BinaryNumericOperator.BitAnd:
if (inst.Left.InferType(context.TypeSystem).IsKnownType(KnownTypeCode.Boolean)
&& inst.Right.InferType(context.TypeSystem).IsKnownType(KnownTypeCode.Boolean))
{
&& inst.Right.InferType(context.TypeSystem).IsKnownType(KnownTypeCode.Boolean)) {
if (new NullableLiftingTransform(context).Run(inst)) {
// e.g. "(a.GetValueOrDefault() == b.GetValueOrDefault()) & (a.HasValue & b.HasValue)"
}
@ -688,7 +767,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms @@ -688,7 +767,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms
break;
}
}
protected internal override void VisitTryCatchHandler(TryCatchHandler inst)
{
base.VisitTryCatchHandler(inst);

Loading…
Cancel
Save