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