diff --git a/ICSharpCode.Decompiler.Tests/ICSharpCode.Decompiler.Tests.csproj b/ICSharpCode.Decompiler.Tests/ICSharpCode.Decompiler.Tests.csproj
index 053e73191..abf931437 100644
--- a/ICSharpCode.Decompiler.Tests/ICSharpCode.Decompiler.Tests.csproj
+++ b/ICSharpCode.Decompiler.Tests/ICSharpCode.Decompiler.Tests.csproj
@@ -90,6 +90,7 @@
+
diff --git a/ICSharpCode.Decompiler.Tests/PrettyTestRunner.cs b/ICSharpCode.Decompiler.Tests/PrettyTestRunner.cs
index 66456641c..7a6cc9aa0 100644
--- a/ICSharpCode.Decompiler.Tests/PrettyTestRunner.cs
+++ b/ICSharpCode.Decompiler.Tests/PrettyTestRunner.cs
@@ -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)
{
diff --git a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/IndexRangeTest.cs b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/IndexRangeTest.cs
new file mode 100644
index 000000000..bb47378ba
--- /dev/null
+++ b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/IndexRangeTest.cs
@@ -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 GetList()
+ {
+ throw null;
+ }
+ public static Span 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));
+ }
+ }
+}
diff --git a/ICSharpCode.Decompiler/CSharp/CallBuilder.cs b/ICSharpCode.Decompiler/CSharp/CallBuilder.cs
index 267a757fe..ba8aaf82e 100644
--- a/ICSharpCode.Decompiler/CSharp/CallBuilder.cs
+++ b/ICSharpCode.Decompiler/CSharp/CallBuilder.cs
@@ -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
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;
+ }
}
}
\ No newline at end of file
diff --git a/ICSharpCode.Decompiler/CSharp/OutputVisitor/CSharpOutputVisitor.cs b/ICSharpCode.Decompiler/CSharp/OutputVisitor/CSharpOutputVisitor.cs
index faa67d21c..6e1e9650f 100644
--- a/ICSharpCode.Decompiler/CSharp/OutputVisitor/CSharpOutputVisitor.cs
+++ b/ICSharpCode.Decompiler/CSharp/OutputVisitor/CSharpOutputVisitor.cs
@@ -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");
}
diff --git a/ICSharpCode.Decompiler/CSharp/OutputVisitor/InsertParenthesesVisitor.cs b/ICSharpCode.Decompiler/CSharp/OutputVisitor/InsertParenthesesVisitor.cs
index 3af55614f..4791b3127 100644
--- a/ICSharpCode.Decompiler/CSharp/OutputVisitor/InsertParenthesesVisitor.cs
+++ b/ICSharpCode.Decompiler/CSharp/OutputVisitor/InsertParenthesesVisitor.cs
@@ -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
///
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
+ }
///
/// Gets the row number in the C# 4.0 spec operator precedence table.
///
- 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
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;
}
///
/// Parenthesizes the expression if it does not have the minimum required precedence.
///
- 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
// 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
{
// 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
// 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
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
{
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
{
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
// 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
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
public override void VisitNamedExpression (NamedExpression namedExpression)
{
if (InsertParenthesesForReadability) {
- ParenthesizeIfRequired(namedExpression.Expression, RelationalAndTypeTesting + 1);
+ ParenthesizeIfRequired(namedExpression.Expression, PrecedenceLevel.RelationalAndTypeTesting + 1);
}
base.VisitNamedExpression (namedExpression);
}
diff --git a/ICSharpCode.Decompiler/CSharp/Syntax/Expressions/BinaryOperatorExpression.cs b/ICSharpCode.Decompiler/CSharp/Syntax/Expressions/BinaryOperatorExpression.cs
index a7eb4d6db..b6358f56a 100644
--- a/ICSharpCode.Decompiler/CSharp/Syntax/Expressions/BinaryOperatorExpression.cs
+++ b/ICSharpCode.Decompiler/CSharp/Syntax/Expressions/BinaryOperatorExpression.cs
@@ -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 LeftRole = new Role("Left", Expression.Null);
public readonly static Role RightRole = new Role("Right", Expression.Null);
@@ -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
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
ShiftRight,
/// left ?? right
- NullCoalescing
+ NullCoalescing,
+ /// left .. right
+ /// left and right are optional = may be Expression.Null
+ Range
}
}
diff --git a/ICSharpCode.Decompiler/CSharp/Syntax/Expressions/UnaryOperatorExpression.cs b/ICSharpCode.Decompiler/CSharp/Syntax/Expressions/UnaryOperatorExpression.cs
index 31e9bd9f1..eb29f2d65 100644
--- a/ICSharpCode.Decompiler/CSharp/Syntax/Expressions/UnaryOperatorExpression.cs
+++ b/ICSharpCode.Decompiler/CSharp/Syntax/Expressions/UnaryOperatorExpression.cs
@@ -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
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
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
/// C# 8 postfix ! operator (dammit operator)
///
SuppressNullableWarning,
+ ///
+ /// C# 8 prefix ^ operator
+ ///
+ IndexFromEnd,
}
}
diff --git a/ICSharpCode.Decompiler/DecompilerSettings.cs b/ICSharpCode.Decompiler/DecompilerSettings.cs
index 95eb3fcb7..18efc74a0 100644
--- a/ICSharpCode.Decompiler/DecompilerSettings.cs
+++ b/ICSharpCode.Decompiler/DecompilerSettings.cs
@@ -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
}
}
+ bool ranges = true;
+
+ ///
+ /// Gets/Sets whether C# 8.0 static local functions should be transformed.
+ ///
+ [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;
///
diff --git a/ICSharpCode.Decompiler/TypeSystem/KnownTypeReference.cs b/ICSharpCode.Decompiler/TypeSystem/KnownTypeReference.cs
index 805f95b14..a6981f2b2 100644
--- a/ICSharpCode.Decompiler/TypeSystem/KnownTypeReference.cs
+++ b/ICSharpCode.Decompiler/TypeSystem/KnownTypeReference.cs
@@ -147,6 +147,10 @@ namespace ICSharpCode.Decompiler.TypeSystem
IAsyncEnumerableOfT,
/// System.Collections.Generic.IAsyncEnumerator{T}
IAsyncEnumeratorOfT,
+ /// System.Index
+ Index,
+ /// System.Range
+ Range
}
///
@@ -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
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),
};
///
diff --git a/ILSpy/Properties/Resources.Designer.cs b/ILSpy/Properties/Resources.Designer.cs
index 267bf70dd..2da49f2d0 100644
--- a/ILSpy/Properties/Resources.Designer.cs
+++ b/ILSpy/Properties/Resources.Designer.cs
@@ -920,6 +920,15 @@ namespace ICSharpCode.ILSpy.Properties {
}
}
+ ///
+ /// Looks up a localized string similar to Ranges.
+ ///
+ public static string DecompilerSettings_Ranges {
+ get {
+ return ResourceManager.GetString("DecompilerSettings.Ranges", resourceCulture);
+ }
+ }
+
///
/// Looks up a localized string similar to Read-only methods.
///
diff --git a/ILSpy/Properties/Resources.resx b/ILSpy/Properties/Resources.resx
index de2f7a957..426065be5 100644
--- a/ILSpy/Properties/Resources.resx
+++ b/ILSpy/Properties/Resources.resx
@@ -852,4 +852,7 @@ Do you want to continue?
Do you want to continue?
+
+ Ranges
+
\ No newline at end of file