Browse Source

Add support for C# 8 range syntax.

This initial commit only handles the trivial case where an Index or Range object is constructed.
The TODO portions of the test case show there are plenty of cases where where the C# compiler emits more complex code patterns that will require ILAst transforms.
pull/1986/head
Daniel Grunwald 5 years ago
parent
commit
4846feb640
  1. 1
      ICSharpCode.Decompiler.Tests/ICSharpCode.Decompiler.Tests.csproj
  2. 8
      ICSharpCode.Decompiler.Tests/PrettyTestRunner.cs
  3. 199
      ICSharpCode.Decompiler.Tests/TestCases/Pretty/IndexRangeTest.cs
  4. 42
      ICSharpCode.Decompiler/CSharp/CallBuilder.cs
  5. 3
      ICSharpCode.Decompiler/CSharp/OutputVisitor/CSharpOutputVisitor.cs
  6. 142
      ICSharpCode.Decompiler/CSharp/OutputVisitor/InsertParenthesesVisitor.cs
  7. 10
      ICSharpCode.Decompiler/CSharp/Syntax/Expressions/BinaryOperatorExpression.cs
  8. 8
      ICSharpCode.Decompiler/CSharp/Syntax/Expressions/UnaryOperatorExpression.cs
  9. 20
      ICSharpCode.Decompiler/DecompilerSettings.cs
  10. 8
      ICSharpCode.Decompiler/TypeSystem/KnownTypeReference.cs
  11. 9
      ILSpy/Properties/Resources.Designer.cs
  12. 3
      ILSpy/Properties/Resources.resx

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

@ -90,6 +90,7 @@ @@ -90,6 +90,7 @@
<Compile Include="DisassemblerPrettyTestRunner.cs" />
<Compile Include="TestCases\Correctness\StringConcat.cs" />
<Compile Include="TestCases\ILPretty\Issue1681.cs" />
<None Include="TestCases\Pretty\IndexRangeTest.cs" />
<None Include="TestCases\ILPretty\Issue1918.cs" />
<Compile Include="TestCases\ILPretty\Issue1922.cs" />
<Compile Include="TestCases\Ugly\NoLocalFunctions.cs" />

8
ICSharpCode.Decompiler.Tests/PrettyTestRunner.cs

@ -89,7 +89,13 @@ namespace ICSharpCode.Decompiler.Tests @@ -89,7 +89,13 @@ namespace ICSharpCode.Decompiler.Tests
RunForLibrary();
RunForLibrary(asmOptions: AssemblerOptions.UseDebug);
}
[Test]
public void IndexRangeTest([ValueSource(nameof(dotnetCoreOnlyOptions))] CompilerOptions cscOptions)
{
RunForLibrary(cscOptions: cscOptions);
}
[Test]
public void InlineAssignmentTest([ValueSource(nameof(defaultOptions))] CompilerOptions cscOptions)
{

199
ICSharpCode.Decompiler.Tests/TestCases/Pretty/IndexRangeTest.cs

@ -0,0 +1,199 @@ @@ -0,0 +1,199 @@
using System;
using System.Collections.Generic;
namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty
{
internal class CustomList
{
public int Count => 0;
public int this[int index] => 0;
public CustomList Slice(int start, int length)
{
return this;
}
}
internal class CustomList2
{
public int Count => 0;
public int this[int index] => 0;
public int this[Index index] => 0;
public CustomList2 this[Range range] => this;
public CustomList2 Slice(int start, int length)
{
return this;
}
}
internal class IndexRangeTest
{
public static string[] GetArray()
{
throw null;
}
public static List<string> GetList()
{
throw null;
}
public static Span<int> GetSpan()
{
throw null;
}
public static string GetString()
{
throw null;
}
public static Index GetIndex(int i = 0)
{
return i;
}
public static Range GetRange(int i = 0)
{
return i..^i;
}
public static int GetInt(int i = 0)
{
return i;
}
public static void UseIndex()
{
#if TODO
Console.WriteLine(GetArray()[GetIndex()]);
Console.WriteLine(GetList()[GetIndex()]);
Console.WriteLine(GetSpan()[GetIndex()]);
Console.WriteLine(GetString()[GetIndex()]);
Console.WriteLine(new CustomList()[GetIndex()]);
#endif
Console.WriteLine(new CustomList2()[GetIndex()]);
}
public static void UseRange()
{
#if TODO
Console.WriteLine(GetArray()[GetRange()]);
//Console.WriteLine(GetList()[GetRange()]); // fails to compile
Console.WriteLine(GetSpan()[GetRange()].ToString());
Console.WriteLine(GetString()[GetRange()]);
Console.WriteLine(new CustomList()[GetRange()]);
#endif
Console.WriteLine(new CustomList2()[GetRange()]);
}
public static void UseNewRangeFromIndex()
{
#if TODO
Console.WriteLine(GetArray()[GetIndex()..GetIndex()]);
//Console.WriteLine(GetList()[GetIndex()..GetIndex()]); // fails to compile
Console.WriteLine(GetSpan()[GetIndex()..GetIndex()].ToString());
Console.WriteLine(GetString()[GetIndex()..GetIndex()]);
Console.WriteLine(new CustomList()[GetIndex()..GetIndex()]);
#endif
Console.WriteLine(new CustomList2()[GetIndex()..GetIndex()]);
}
public static void UseNewRangeFromIntegers_BothFromStart()
{
#if TODO
Console.WriteLine(GetArray()[GetInt(1)..GetInt(2)]);
//Console.WriteLine(GetList()[GetInt()..GetInt()]); // fails to compile
Console.WriteLine(GetSpan()[GetInt(1)..GetInt(2)].ToString());
Console.WriteLine(GetString()[GetInt(1)..GetInt(2)]);
Console.WriteLine(new CustomList()[GetInt(1)..GetInt(2)]);
#endif
Console.WriteLine(new CustomList2()[GetInt(1)..GetInt(2)]);
}
public static void UseNewRangeFromIntegers_BothFromEnd()
{
#if TODO
Console.WriteLine(GetArray()[^GetInt(1)..^GetInt(2)]);
//Console.WriteLine(GetList()[^GetInt()..^GetInt()]); // fails to compile
Console.WriteLine(GetSpan()[^GetInt(1)..^GetInt(2)].ToString());
Console.WriteLine(GetString()[^GetInt(1)..^GetInt(2)]);
Console.WriteLine(new CustomList()[^GetInt(1)..^GetInt(2)]);
#endif
Console.WriteLine(new CustomList2()[^GetInt(1)..^GetInt(2)]);
}
public static void UseNewRangeFromIntegers_FromStartAndEnd()
{
#if TODO
Console.WriteLine(GetArray()[GetInt(1)..^GetInt(2)]);
//Console.WriteLine(GetList()[GetInt()..^GetInt()]); // fails to compile
Console.WriteLine(GetSpan()[GetInt(1)..^GetInt(2)].ToString());
Console.WriteLine(GetString()[GetInt(1)..^GetInt(2)]);
Console.WriteLine(new CustomList()[GetInt(1)..^GetInt(2)]);
#endif
Console.WriteLine(new CustomList2()[GetInt(1)..^GetInt(2)]);
}
public static void UseNewRangeFromIntegers_FromEndAndStart()
{
#if TODO
Console.WriteLine(GetArray()[^GetInt(1)..GetInt(2)]);
//Console.WriteLine(GetList()[^GetInt()..GetInt()]); // fails to compile
Console.WriteLine(GetSpan()[^GetInt(1)..GetInt(2)].ToString());
Console.WriteLine(GetString()[^GetInt(1)..GetInt(2)]);
Console.WriteLine(new CustomList()[^GetInt(1)..GetInt(2)]);
#endif
Console.WriteLine(new CustomList2()[^GetInt(1)..GetInt(2)]);
}
public static void UseNewRangeFromIntegers_OnlyEndPoint()
{
#if TODO
Console.WriteLine(GetArray()[..GetInt(2)]);
//Console.WriteLine(GetList()[..GetInt()]); // fails to compile
Console.WriteLine(GetSpan()[..GetInt(2)].ToString());
Console.WriteLine(GetString()[..GetInt(2)]);
Console.WriteLine(new CustomList()[..GetInt(2)]);
#endif
Console.WriteLine(new CustomList2()[..GetInt(2)]);
}
public static void UseNewRangeFromIntegers_OnlyStartPoint()
{
#if TODO
Console.WriteLine(GetArray()[GetInt(1)..]);
//Console.WriteLine(GetList()[GetInt()..]); // fails to compile
Console.WriteLine(GetSpan()[GetInt(1)..].ToString());
Console.WriteLine(GetString()[GetInt(1)..]);
Console.WriteLine(new CustomList()[GetInt(1)..]);
#endif
Console.WriteLine(new CustomList2()[GetInt(1)..]);
}
public static void UseWholeRange()
{
#if TODO
Console.WriteLine(GetArray()[..]);
//Console.WriteLine(GetList()[..]); // fails to compile
Console.WriteLine(GetSpan()[..].ToString());
Console.WriteLine(GetString()[..]);
Console.WriteLine(new CustomList()[..]);
#endif
Console.WriteLine(new CustomList2()[..]);
}
public static void UseIndexForIntIndexerWhenIndexIndexerIsAvailable()
{
// Same code as the compiler emits for CustomList,
// but here we can't translate it back to `customList[GetIndex()]`
// because that would call a different overload.
CustomList2 customList = new CustomList2();
int count = customList.Count;
int offset = GetIndex().GetOffset(count);
Console.WriteLine(customList[offset]);
}
public static void UseSliceWhenRangeIndexerIsAvailable()
{
// Same code as the compiler emits for CustomList,
// but here we can't translate it back to `customList[GetIndex()]`
// because that would call a different overload.
CustomList2 customList = new CustomList2();
int count = customList.Count;
Range range = GetRange();
int offset = range.Start.GetOffset(count);
int length = range.End.GetOffset(count) - offset;
Console.WriteLine(customList.Slice(offset, length));
}
}
}

42
ICSharpCode.Decompiler/CSharp/CallBuilder.cs

@ -252,6 +252,12 @@ namespace ICSharpCode.Decompiler.CSharp @@ -252,6 +252,12 @@ namespace ICSharpCode.Decompiler.CSharp
argumentList.ExpectedParameters = method.Parameters.ToArray();
}
if (settings.Ranges) {
if (HandleRangeConstruction(out var result, callOpCode, method, argumentList)) {
return result;
}
}
if (callOpCode == OpCode.NewObj) {
return HandleConstructorCall(expectedTargetDetails, target.ResolveResult, method, argumentList);
}
@ -1494,5 +1500,41 @@ namespace ICSharpCode.Decompiler.CSharp @@ -1494,5 +1500,41 @@ namespace ICSharpCode.Decompiler.CSharp
return Build(call.OpCode, call.Method, arguments, argumentToParameterMap, call.ConstrainedTo)
.WithILInstruction(call).WithILInstruction(block);
}
private bool HandleRangeConstruction(out ExpressionWithResolveResult result, OpCode callOpCode, IMethod method, ArgumentList argumentList)
{
result = default;
if (argumentList.ArgumentNames != null) {
return false; // range syntax doesn't support named arguments
}
if (method.DeclaringType.IsKnownType(KnownTypeCode.Range)) {
if (callOpCode == OpCode.NewObj && argumentList.Length == 2) {
result = new BinaryOperatorExpression(argumentList.Arguments[0], BinaryOperatorType.Range, argumentList.Arguments[1])
.WithRR(new ResolveResult(method.DeclaringType));
return true;
} else if (callOpCode == OpCode.Call && method.Name == "get_All" && argumentList.Length == 0) {
result = new BinaryOperatorExpression(Expression.Null, BinaryOperatorType.Range, Expression.Null)
.WithRR(new ResolveResult(method.DeclaringType));
return true;
} else if (callOpCode == OpCode.Call && method.Name == "StartAt" && argumentList.Length == 1) {
result = new BinaryOperatorExpression(argumentList.Arguments[0], BinaryOperatorType.Range, Expression.Null)
.WithRR(new ResolveResult(method.DeclaringType));
return true;
} else if (callOpCode == OpCode.Call && method.Name == "EndAt" && argumentList.Length == 1) {
result = new BinaryOperatorExpression(Expression.Null, BinaryOperatorType.Range, argumentList.Arguments[0])
.WithRR(new ResolveResult(method.DeclaringType));
return true;
}
} else if (callOpCode == OpCode.NewObj && method.DeclaringType.IsKnownType(KnownTypeCode.Index)) {
if (argumentList.Length != 2)
return false;
if (!(argumentList.Arguments[1].Expression is PrimitiveExpression pe && pe.Value is true))
return false;
result = new UnaryOperatorExpression(UnaryOperatorType.IndexFromEnd, argumentList.Arguments[0])
.WithRR(new ResolveResult(method.DeclaringType));
return true;
}
return false;
}
}
}

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

@ -696,6 +696,9 @@ namespace ICSharpCode.Decompiler.CSharp.OutputVisitor @@ -696,6 +696,9 @@ namespace ICSharpCode.Decompiler.CSharp.OutputVisitor
case BinaryOperatorType.NullCoalescing:
spacePolicy = true;
break;
case BinaryOperatorType.Range:
spacePolicy = false;
break;
default:
throw new NotSupportedException("Invalid value for BinaryOperatorType");
}

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

@ -1,4 +1,4 @@ @@ -1,4 +1,4 @@
// Copyright (c) 2010-2013 AlphaSierraPapa for the SharpDevelop Team
// Copyright (c) 2010-2020 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
@ -36,25 +36,39 @@ namespace ICSharpCode.Decompiler.CSharp.OutputVisitor @@ -36,25 +36,39 @@ namespace ICSharpCode.Decompiler.CSharp.OutputVisitor
/// </summary>
public bool InsertParenthesesForReadability { get; set; }
const int Primary = 17;
const int NullableRewrap = 16;
const int QueryOrLambda = 15;
const int Unary = 14;
const int RelationalAndTypeTesting = 10;
const int Equality = 9;
const int Conditional = 2;
const int Assignment = 1;
enum PrecedenceLevel
{
// Higher integer value = higher precedence.
Assignment,
Conditional, // ?:
NullCoalescing, // ??
ConditionalOr, // ||
ConditionalAnd, // &&
BitwiseOr, // |
ExclusiveOr, // binary ^
BitwiseAnd, // binary &
Equality, // == !=
RelationalAndTypeTesting, // < <= > >= is
Shift, // << >>
Additive, // binary + -
Multiplicative, // * / %
Range, // ..
Unary,
QueryOrLambda,
NullableRewrap,
Primary
}
/// <summary>
/// Gets the row number in the C# 4.0 spec operator precedence table.
/// </summary>
static int GetPrecedence(Expression expr)
static PrecedenceLevel GetPrecedence(Expression expr)
{
// Note: the operator precedence table on MSDN is incorrect
if (expr is QueryExpression) {
// Not part of the table in the C# spec, but we need to ensure that queries within
// primary expressions get parenthesized.
return QueryOrLambda;
return PrecedenceLevel.QueryOrLambda;
}
if (expr is UnaryOperatorExpression uoe) {
switch (uoe.Operator) {
@ -62,81 +76,83 @@ namespace ICSharpCode.Decompiler.CSharp.OutputVisitor @@ -62,81 +76,83 @@ namespace ICSharpCode.Decompiler.CSharp.OutputVisitor
case UnaryOperatorType.PostIncrement:
case UnaryOperatorType.NullConditional:
case UnaryOperatorType.SuppressNullableWarning:
return Primary;
return PrecedenceLevel.Primary;
case UnaryOperatorType.NullConditionalRewrap:
return NullableRewrap;
return PrecedenceLevel.NullableRewrap;
case UnaryOperatorType.IsTrue:
return Conditional;
return PrecedenceLevel.Conditional;
default:
return Unary;
return PrecedenceLevel.Unary;
}
}
if (expr is CastExpression)
return Unary;
return PrecedenceLevel.Unary;
if (expr is PrimitiveExpression primitive) {
var value = primitive.Value;
if (value is int i && i < 0)
return Unary;
return PrecedenceLevel.Unary;
if (value is long l && l < 0)
return Unary;
return PrecedenceLevel.Unary;
if (value is float f && f < 0)
return Unary;
return PrecedenceLevel.Unary;
if (value is double d && d < 0)
return Unary;
return PrecedenceLevel.Unary;
if (value is decimal de && de < 0)
return Unary;
return PrecedenceLevel.Unary;
return PrecedenceLevel.Primary;
}
BinaryOperatorExpression boe = expr as BinaryOperatorExpression;
if (boe != null) {
if (expr is BinaryOperatorExpression boe) {
switch (boe.Operator) {
case BinaryOperatorType.Range:
return PrecedenceLevel.Range;
case BinaryOperatorType.Multiply:
case BinaryOperatorType.Divide:
case BinaryOperatorType.Modulus:
return 13; // multiplicative
return PrecedenceLevel.Multiplicative;
case BinaryOperatorType.Add:
case BinaryOperatorType.Subtract:
return 12; // additive
return PrecedenceLevel.Additive;
case BinaryOperatorType.ShiftLeft:
case BinaryOperatorType.ShiftRight:
return 11;
return PrecedenceLevel.Shift;
case BinaryOperatorType.GreaterThan:
case BinaryOperatorType.GreaterThanOrEqual:
case BinaryOperatorType.LessThan:
case BinaryOperatorType.LessThanOrEqual:
return RelationalAndTypeTesting;
return PrecedenceLevel.RelationalAndTypeTesting;
case BinaryOperatorType.Equality:
case BinaryOperatorType.InEquality:
return Equality;
return PrecedenceLevel.Equality;
case BinaryOperatorType.BitwiseAnd:
return 8;
return PrecedenceLevel.BitwiseAnd;
case BinaryOperatorType.ExclusiveOr:
return 7;
return PrecedenceLevel.ExclusiveOr;
case BinaryOperatorType.BitwiseOr:
return 6;
return PrecedenceLevel.BitwiseOr;
case BinaryOperatorType.ConditionalAnd:
return 5;
return PrecedenceLevel.ConditionalAnd;
case BinaryOperatorType.ConditionalOr:
return 4;
return PrecedenceLevel.ConditionalOr;
case BinaryOperatorType.NullCoalescing:
return 3;
return PrecedenceLevel.NullCoalescing;
default:
throw new NotSupportedException("Invalid value for BinaryOperatorType");
}
}
if (expr is IsExpression || expr is AsExpression)
return RelationalAndTypeTesting;
return PrecedenceLevel.RelationalAndTypeTesting;
if (expr is ConditionalExpression || expr is DirectionExpression)
return Conditional;
return PrecedenceLevel.Conditional;
if (expr is AssignmentExpression || expr is LambdaExpression)
return Assignment;
return PrecedenceLevel.Assignment;
// anything else: primary expression
return Primary;
return PrecedenceLevel.Primary;
}
/// <summary>
/// Parenthesizes the expression if it does not have the minimum required precedence.
/// </summary>
static void ParenthesizeIfRequired(Expression expr, int minimumPrecedence)
static void ParenthesizeIfRequired(Expression expr, PrecedenceLevel minimumPrecedence)
{
if (GetPrecedence(expr) < minimumPrecedence) {
Parenthesize(expr);
@ -151,25 +167,25 @@ namespace ICSharpCode.Decompiler.CSharp.OutputVisitor @@ -151,25 +167,25 @@ namespace ICSharpCode.Decompiler.CSharp.OutputVisitor
// Primary expressions
public override void VisitMemberReferenceExpression(MemberReferenceExpression memberReferenceExpression)
{
ParenthesizeIfRequired(memberReferenceExpression.Target, Primary);
ParenthesizeIfRequired(memberReferenceExpression.Target, PrecedenceLevel.Primary);
base.VisitMemberReferenceExpression(memberReferenceExpression);
}
public override void VisitPointerReferenceExpression(PointerReferenceExpression pointerReferenceExpression)
{
ParenthesizeIfRequired(pointerReferenceExpression.Target, Primary);
ParenthesizeIfRequired(pointerReferenceExpression.Target, PrecedenceLevel.Primary);
base.VisitPointerReferenceExpression(pointerReferenceExpression);
}
public override void VisitInvocationExpression(InvocationExpression invocationExpression)
{
ParenthesizeIfRequired(invocationExpression.Target, Primary);
ParenthesizeIfRequired(invocationExpression.Target, PrecedenceLevel.Primary);
base.VisitInvocationExpression(invocationExpression);
}
public override void VisitIndexerExpression(IndexerExpression indexerExpression)
{
ParenthesizeIfRequired(indexerExpression.Target, Primary);
ParenthesizeIfRequired(indexerExpression.Target, PrecedenceLevel.Primary);
ArrayCreateExpression ace = indexerExpression.Target as ArrayCreateExpression;
if (ace != null && (InsertParenthesesForReadability || ace.Initializer.IsNull)) {
// require parentheses for "(new int[1])[0]"
@ -192,7 +208,7 @@ namespace ICSharpCode.Decompiler.CSharp.OutputVisitor @@ -192,7 +208,7 @@ namespace ICSharpCode.Decompiler.CSharp.OutputVisitor
{
// Even in readability mode, don't parenthesize casts of casts.
if (!(castExpression.Expression is CastExpression)) {
ParenthesizeIfRequired(castExpression.Expression, InsertParenthesesForReadability ? NullableRewrap : Unary);
ParenthesizeIfRequired(castExpression.Expression, InsertParenthesesForReadability ? PrecedenceLevel.NullableRewrap : PrecedenceLevel.Unary);
}
// There's a nasty issue in the C# grammar: cast expressions including certain operators are ambiguous in some cases
// "(int)-1" is fine, but "(A)-b" is not a cast.
@ -255,14 +271,14 @@ namespace ICSharpCode.Decompiler.CSharp.OutputVisitor @@ -255,14 +271,14 @@ namespace ICSharpCode.Decompiler.CSharp.OutputVisitor
// Binary Operators
public override void VisitBinaryOperatorExpression(BinaryOperatorExpression binaryOperatorExpression)
{
int precedence = GetPrecedence(binaryOperatorExpression);
PrecedenceLevel precedence = GetPrecedence(binaryOperatorExpression);
if (binaryOperatorExpression.Operator == BinaryOperatorType.NullCoalescing) {
if (InsertParenthesesForReadability) {
ParenthesizeIfRequired(binaryOperatorExpression.Left, NullableRewrap);
ParenthesizeIfRequired(binaryOperatorExpression.Left, PrecedenceLevel.NullableRewrap);
if (GetBinaryOperatorType(binaryOperatorExpression.Right) == BinaryOperatorType.NullCoalescing) {
ParenthesizeIfRequired(binaryOperatorExpression.Right, precedence);
} else {
ParenthesizeIfRequired(binaryOperatorExpression.Right, NullableRewrap);
ParenthesizeIfRequired(binaryOperatorExpression.Right, PrecedenceLevel.NullableRewrap);
}
} else {
// ?? is right-associative
@ -270,10 +286,10 @@ namespace ICSharpCode.Decompiler.CSharp.OutputVisitor @@ -270,10 +286,10 @@ namespace ICSharpCode.Decompiler.CSharp.OutputVisitor
ParenthesizeIfRequired(binaryOperatorExpression.Right, precedence);
}
} else {
if (InsertParenthesesForReadability && precedence < Equality) {
if (InsertParenthesesForReadability && precedence < PrecedenceLevel.Equality) {
// In readable mode, boost the priority of the left-hand side if the operator
// there isn't the same as the operator on this expression.
int boostTo = IsBitwise(binaryOperatorExpression.Operator) ? Unary : Equality;
PrecedenceLevel boostTo = IsBitwise(binaryOperatorExpression.Operator) ? PrecedenceLevel.Unary : PrecedenceLevel.Equality;
if (GetBinaryOperatorType(binaryOperatorExpression.Left) == binaryOperatorExpression.Operator) {
ParenthesizeIfRequired(binaryOperatorExpression.Left, precedence);
} else {
@ -309,9 +325,9 @@ namespace ICSharpCode.Decompiler.CSharp.OutputVisitor @@ -309,9 +325,9 @@ namespace ICSharpCode.Decompiler.CSharp.OutputVisitor
{
if (InsertParenthesesForReadability) {
// few people know the precedence of 'is', so always put parentheses in nice-looking mode.
ParenthesizeIfRequired(isExpression.Expression, NullableRewrap);
ParenthesizeIfRequired(isExpression.Expression, PrecedenceLevel.NullableRewrap);
} else {
ParenthesizeIfRequired(isExpression.Expression, RelationalAndTypeTesting);
ParenthesizeIfRequired(isExpression.Expression, PrecedenceLevel.RelationalAndTypeTesting);
}
base.VisitIsExpression(isExpression);
}
@ -320,9 +336,9 @@ namespace ICSharpCode.Decompiler.CSharp.OutputVisitor @@ -320,9 +336,9 @@ namespace ICSharpCode.Decompiler.CSharp.OutputVisitor
{
if (InsertParenthesesForReadability) {
// few people know the precedence of 'as', so always put parentheses in nice-looking mode.
ParenthesizeIfRequired(asExpression.Expression, NullableRewrap);
ParenthesizeIfRequired(asExpression.Expression, PrecedenceLevel.NullableRewrap);
} else {
ParenthesizeIfRequired(asExpression.Expression, RelationalAndTypeTesting);
ParenthesizeIfRequired(asExpression.Expression, PrecedenceLevel.RelationalAndTypeTesting);
}
base.VisitAsExpression(asExpression);
}
@ -341,13 +357,13 @@ namespace ICSharpCode.Decompiler.CSharp.OutputVisitor @@ -341,13 +357,13 @@ namespace ICSharpCode.Decompiler.CSharp.OutputVisitor
// Only ((a ? b : c) ? d : e) strictly needs the additional parentheses
if (InsertParenthesesForReadability && !IsConditionalRefExpression(conditionalExpression)) {
// Precedence of ?: can be confusing; so always put parentheses in nice-looking mode.
ParenthesizeIfRequired(conditionalExpression.Condition, NullableRewrap);
ParenthesizeIfRequired(conditionalExpression.TrueExpression, NullableRewrap);
ParenthesizeIfRequired(conditionalExpression.FalseExpression, NullableRewrap);
ParenthesizeIfRequired(conditionalExpression.Condition, PrecedenceLevel.NullableRewrap);
ParenthesizeIfRequired(conditionalExpression.TrueExpression, PrecedenceLevel.NullableRewrap);
ParenthesizeIfRequired(conditionalExpression.FalseExpression, PrecedenceLevel.NullableRewrap);
} else {
ParenthesizeIfRequired(conditionalExpression.Condition, Conditional + 1);
ParenthesizeIfRequired(conditionalExpression.TrueExpression, Conditional);
ParenthesizeIfRequired(conditionalExpression.FalseExpression, Conditional);
ParenthesizeIfRequired(conditionalExpression.Condition, PrecedenceLevel.Conditional + 1);
ParenthesizeIfRequired(conditionalExpression.TrueExpression, PrecedenceLevel.Conditional);
ParenthesizeIfRequired(conditionalExpression.FalseExpression, PrecedenceLevel.Conditional);
}
base.VisitConditionalExpression(conditionalExpression);
}
@ -361,11 +377,11 @@ namespace ICSharpCode.Decompiler.CSharp.OutputVisitor @@ -361,11 +377,11 @@ namespace ICSharpCode.Decompiler.CSharp.OutputVisitor
public override void VisitAssignmentExpression(AssignmentExpression assignmentExpression)
{
// assignment is right-associative
ParenthesizeIfRequired(assignmentExpression.Left, Assignment + 1);
ParenthesizeIfRequired(assignmentExpression.Left, PrecedenceLevel.Assignment + 1);
if (InsertParenthesesForReadability && !(assignmentExpression.Right is DirectionExpression)) {
ParenthesizeIfRequired(assignmentExpression.Right, RelationalAndTypeTesting + 1);
ParenthesizeIfRequired(assignmentExpression.Right, PrecedenceLevel.RelationalAndTypeTesting + 1);
} else {
ParenthesizeIfRequired(assignmentExpression.Right, Assignment);
ParenthesizeIfRequired(assignmentExpression.Right, PrecedenceLevel.Assignment);
}
base.VisitAssignmentExpression(assignmentExpression);
}
@ -394,7 +410,7 @@ namespace ICSharpCode.Decompiler.CSharp.OutputVisitor @@ -394,7 +410,7 @@ namespace ICSharpCode.Decompiler.CSharp.OutputVisitor
public override void VisitNamedExpression (NamedExpression namedExpression)
{
if (InsertParenthesesForReadability) {
ParenthesizeIfRequired(namedExpression.Expression, RelationalAndTypeTesting + 1);
ParenthesizeIfRequired(namedExpression.Expression, PrecedenceLevel.RelationalAndTypeTesting + 1);
}
base.VisitNamedExpression (namedExpression);
}

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

@ -54,6 +54,7 @@ namespace ICSharpCode.Decompiler.CSharp.Syntax @@ -54,6 +54,7 @@ namespace ICSharpCode.Decompiler.CSharp.Syntax
public readonly static TokenRole ShiftLeftRole = new TokenRole ("<<");
public readonly static TokenRole ShiftRightRole = new TokenRole (">>");
public readonly static TokenRole NullCoalescingRole = new TokenRole ("??");
public readonly static TokenRole RangeRole = new TokenRole ("..");
public readonly static Role<Expression> LeftRole = new Role<Expression>("Left", Expression.Null);
public readonly static Role<Expression> RightRole = new Role<Expression>("Right", Expression.Null);
@ -151,6 +152,8 @@ namespace ICSharpCode.Decompiler.CSharp.Syntax @@ -151,6 +152,8 @@ namespace ICSharpCode.Decompiler.CSharp.Syntax
return ShiftRightRole;
case BinaryOperatorType.NullCoalescing:
return NullCoalescingRole;
case BinaryOperatorType.Range:
return RangeRole;
default:
throw new NotSupportedException("Invalid value for BinaryOperatorType");
}
@ -197,6 +200,8 @@ namespace ICSharpCode.Decompiler.CSharp.Syntax @@ -197,6 +200,8 @@ namespace ICSharpCode.Decompiler.CSharp.Syntax
return ExpressionType.RightShift;
case BinaryOperatorType.NullCoalescing:
return ExpressionType.Coalesce;
case BinaryOperatorType.Range:
return ExpressionType.Extension;
default:
throw new NotSupportedException("Invalid value for BinaryOperatorType");
}
@ -255,6 +260,9 @@ namespace ICSharpCode.Decompiler.CSharp.Syntax @@ -255,6 +260,9 @@ namespace ICSharpCode.Decompiler.CSharp.Syntax
ShiftRight,
/// <summary>left ?? right</summary>
NullCoalescing
NullCoalescing,
/// <summary>left .. right</summary>
/// <remarks>left and right are optional = may be Expression.Null</remarks>
Range
}
}

8
ICSharpCode.Decompiler/CSharp/Syntax/Expressions/UnaryOperatorExpression.cs

@ -45,6 +45,7 @@ namespace ICSharpCode.Decompiler.CSharp.Syntax @@ -45,6 +45,7 @@ namespace ICSharpCode.Decompiler.CSharp.Syntax
public readonly static TokenRole AwaitRole = new TokenRole ("await");
public readonly static TokenRole NullConditionalRole = new TokenRole ("?");
public readonly static TokenRole SuppressNullableWarningRole = new TokenRole ("!");
public readonly static TokenRole IndexFromEndRole = new TokenRole ("^");
public UnaryOperatorExpression()
{
@ -122,6 +123,8 @@ namespace ICSharpCode.Decompiler.CSharp.Syntax @@ -122,6 +123,8 @@ namespace ICSharpCode.Decompiler.CSharp.Syntax
return null; // no syntax
case UnaryOperatorType.SuppressNullableWarning:
return SuppressNullableWarningRole;
case UnaryOperatorType.IndexFromEnd:
return IndexFromEndRole;
default:
throw new NotSupportedException("Invalid value for UnaryOperatorType");
}
@ -150,6 +153,7 @@ namespace ICSharpCode.Decompiler.CSharp.Syntax @@ -150,6 +153,7 @@ namespace ICSharpCode.Decompiler.CSharp.Syntax
case UnaryOperatorType.AddressOf:
case UnaryOperatorType.Await:
case UnaryOperatorType.SuppressNullableWarning:
case UnaryOperatorType.IndexFromEnd:
return ExpressionType.Extension;
default:
throw new NotSupportedException("Invalid value for UnaryOperatorType");
@ -206,5 +210,9 @@ namespace ICSharpCode.Decompiler.CSharp.Syntax @@ -206,5 +210,9 @@ namespace ICSharpCode.Decompiler.CSharp.Syntax
/// C# 8 postfix ! operator (dammit operator)
/// </summary>
SuppressNullableWarning,
/// <summary>
/// C# 8 prefix ^ operator
/// </summary>
IndexFromEnd,
}
}

20
ICSharpCode.Decompiler/DecompilerSettings.cs

@ -112,12 +112,13 @@ namespace ICSharpCode.Decompiler @@ -112,12 +112,13 @@ namespace ICSharpCode.Decompiler
asyncUsingAndForEachStatement = false;
asyncEnumerator = false;
staticLocalFunctions = false;
ranges = false;
}
}
public CSharp.LanguageVersion GetMinimumRequiredVersion()
{
if (nullableReferenceTypes || readOnlyMethods || asyncEnumerator || asyncUsingAndForEachStatement || staticLocalFunctions)
if (nullableReferenceTypes || readOnlyMethods || asyncEnumerator || asyncUsingAndForEachStatement || staticLocalFunctions || ranges)
return CSharp.LanguageVersion.CSharp8_0;
if (introduceUnmanagedConstraint || tupleComparisons || stackAllocInitializers || patternBasedFixedStatement)
return CSharp.LanguageVersion.CSharp7_3;
@ -1100,6 +1101,23 @@ namespace ICSharpCode.Decompiler @@ -1100,6 +1101,23 @@ namespace ICSharpCode.Decompiler
}
}
bool ranges = true;
/// <summary>
/// Gets/Sets whether C# 8.0 static local functions should be transformed.
/// </summary>
[Category("C# 8.0 / VS 2019")]
[Description("DecompilerSettings.Ranges")]
public bool Ranges {
get { return ranges; }
set {
if (ranges != value) {
ranges = value;
OnPropertyChanged();
}
}
}
bool nullableReferenceTypes = true;
/// <summary>

8
ICSharpCode.Decompiler/TypeSystem/KnownTypeReference.cs

@ -147,6 +147,10 @@ namespace ICSharpCode.Decompiler.TypeSystem @@ -147,6 +147,10 @@ namespace ICSharpCode.Decompiler.TypeSystem
IAsyncEnumerableOfT,
/// <summary><c>System.Collections.Generic.IAsyncEnumerator{T}</c></summary>
IAsyncEnumeratorOfT,
/// <summary><c>System.Index</c></summary>
Index,
/// <summary><c>System.Range</c></summary>
Range
}
/// <summary>
@ -155,7 +159,7 @@ namespace ICSharpCode.Decompiler.TypeSystem @@ -155,7 +159,7 @@ namespace ICSharpCode.Decompiler.TypeSystem
[Serializable]
public sealed class KnownTypeReference : ITypeReference
{
internal const int KnownTypeCodeCount = (int)KnownTypeCode.IAsyncEnumeratorOfT + 1;
internal const int KnownTypeCodeCount = (int)KnownTypeCode.Range + 1;
static readonly KnownTypeReference[] knownTypeReferences = new KnownTypeReference[KnownTypeCodeCount] {
null, // None
@ -218,6 +222,8 @@ namespace ICSharpCode.Decompiler.TypeSystem @@ -218,6 +222,8 @@ namespace ICSharpCode.Decompiler.TypeSystem
new KnownTypeReference(KnownTypeCode.Unsafe, TypeKind.Class, "System.Runtime.CompilerServices", "Unsafe", 0),
new KnownTypeReference(KnownTypeCode.IAsyncEnumerableOfT, TypeKind.Interface, "System.Collections.Generic", "IAsyncEnumerable", 1),
new KnownTypeReference(KnownTypeCode.IAsyncEnumeratorOfT, TypeKind.Interface, "System.Collections.Generic", "IAsyncEnumerator", 1),
new KnownTypeReference(KnownTypeCode.Index, TypeKind.Struct, "System", "Index", 0),
new KnownTypeReference(KnownTypeCode.Range, TypeKind.Struct, "System", "Range", 0),
};
/// <summary>

9
ILSpy/Properties/Resources.Designer.cs generated

@ -920,6 +920,15 @@ namespace ICSharpCode.ILSpy.Properties { @@ -920,6 +920,15 @@ namespace ICSharpCode.ILSpy.Properties {
}
}
/// <summary>
/// Looks up a localized string similar to Ranges.
/// </summary>
public static string DecompilerSettings_Ranges {
get {
return ResourceManager.GetString("DecompilerSettings.Ranges", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Read-only methods.
/// </summary>

3
ILSpy/Properties/Resources.resx

@ -852,4 +852,7 @@ Do you want to continue?</value> @@ -852,4 +852,7 @@ Do you want to continue?</value>
Do you want to continue?</value>
</data>
<data name="DecompilerSettings.Ranges" xml:space="preserve">
<value>Ranges</value>
</data>
</root>
Loading…
Cancel
Save