diff --git a/ICSharpCode.Decompiler.Tests/ICSharpCode.Decompiler.Tests.csproj b/ICSharpCode.Decompiler.Tests/ICSharpCode.Decompiler.Tests.csproj
index f5f4337e8..b409376e0 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..c4c7f6a6c
--- /dev/null
+++ b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/IndexRangeTest.cs
@@ -0,0 +1,281 @@
+// Copyright (c) 2020 Daniel Grunwald
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy of this
+// software and associated documentation files (the "Software"), to deal in the Software
+// without restriction, including without limitation the rights to use, copy, modify, merge,
+// publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons
+// to whom the Software is furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in all copies or
+// substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
+// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
+// PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE
+// FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
+// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+// DEALINGS IN THE SOFTWARE.
+
+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 int[] 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 Range[] SeveralRanges()
+ {
+ // Some of these are semantically identical, but we can still distinguish them in the IL code:
+ return new Range[14] {
+ ..,
+ 0..,
+ ^0..,
+ GetInt(1)..,
+ ^GetInt(2)..,
+ ..0,
+ ..^0,
+ ..GetInt(3),
+ ..^GetInt(4),
+ 0..^0,
+ ^0..0,
+ 0..0,
+ GetInt(5)..GetInt(6),
+ 0..(GetInt(7) + GetInt(8))
+ };
+ }
+
+ public static void UseIndex()
+ {
+ Console.WriteLine(GetArray()[GetIndex()]);
+ Console.WriteLine(GetList()[GetIndex()]);
+ Console.WriteLine(GetSpan()[GetIndex()]);
+ Console.WriteLine(GetString()[GetIndex()]);
+ Console.WriteLine(new CustomList()[GetIndex()]);
+ Console.WriteLine(new CustomList2()[GetIndex()]);
+ }
+
+ public static void UseIndexFromEnd()
+ {
+ Console.WriteLine(GetArray()[^GetInt()]);
+ Console.WriteLine(GetList()[^GetInt()]);
+ Console.WriteLine(GetSpan()[^GetInt()]);
+ Console.WriteLine(GetString()[^GetInt()]);
+ Console.WriteLine(new CustomList()[^GetInt()]);
+ Console.WriteLine(new CustomList2()[^GetInt()]);
+ }
+
+ public static void UseIndexForWrite()
+ {
+ GetArray()[GetIndex()] = GetInt();
+ GetList()[GetIndex()] = GetInt();
+ GetSpan()[GetIndex()] = GetInt();
+ }
+
+ private static void UseRef(ref int i)
+ {
+ }
+
+ public static void UseIndexForRef()
+ {
+ UseRef(ref GetArray()[GetIndex()]);
+ UseRef(ref GetArray()[^GetInt()]);
+ UseRef(ref GetSpan()[GetIndex()]);
+ UseRef(ref GetSpan()[^GetInt()]);
+ }
+
+ public static void UseRange()
+ {
+ 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()]);
+ Console.WriteLine(new CustomList2()[GetRange()]);
+ }
+ public static void UseNewRangeFromIndex()
+ {
+ Console.WriteLine(GetArray()[GetIndex(1)..GetIndex(2)]);
+ //Console.WriteLine(GetList()[GetIndex(1)..GetIndex(2)]); // fails to compile
+ Console.WriteLine(GetSpan()[GetIndex(1)..GetIndex(2)].ToString());
+ Console.WriteLine(GetString()[GetIndex(1)..GetIndex(2)]);
+ Console.WriteLine(new CustomList()[GetIndex(1)..GetIndex(2)]);
+ Console.WriteLine(new CustomList2()[GetIndex(1)..GetIndex(2)]);
+ }
+ public static void UseNewRangeFromIntegers_BothFromStart()
+ {
+ 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)]);
+ Console.WriteLine(new CustomList2()[GetInt(1)..GetInt(2)]);
+ }
+ public static void UseNewRangeFromIntegers_BothFromEnd()
+ {
+ 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)]);
+ Console.WriteLine(new CustomList2()[^GetInt(1)..^GetInt(2)]);
+ }
+ public static void UseNewRangeFromIntegers_FromStartAndEnd()
+ {
+ 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)]);
+ Console.WriteLine(new CustomList2()[GetInt(1)..^GetInt(2)]);
+ }
+ public static void UseNewRangeFromIntegers_FromEndAndStart()
+ {
+ 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)]);
+ Console.WriteLine(new CustomList2()[^GetInt(1)..GetInt(2)]);
+ }
+
+ public static void UseNewRangeFromIntegers_OnlyEndPoint()
+ {
+ 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)]);
+ Console.WriteLine(new CustomList2()[..GetInt(2)]);
+ }
+
+ public static void UseNewRangeFromIntegers_OnlyEndPoint_FromEnd()
+ {
+ 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)]);
+ Console.WriteLine(new CustomList2()[..^GetInt(2)]);
+ }
+
+ public static void UseNewRangeFromIntegers_OnlyStartPoint()
+ {
+ 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)..]);
+ Console.WriteLine(new CustomList2()[GetInt(1)..]);
+ }
+
+ public static void UseNewRangeFromIntegers_OnlyStartPoint_FromEnd()
+ {
+ 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)..]);
+ Console.WriteLine(new CustomList2()[^GetInt(1)..]);
+ }
+
+ public static void UseConstantRange()
+ {
+ // Fortunately the C# compiler doesn't optimize
+ // "str.Length - 2 - 1" here, so the normal pattern applies.
+ Console.WriteLine(GetString()[1..2]);
+ Console.WriteLine(GetString()[1..^1]);
+ Console.WriteLine(GetString()[^2..^1]);
+
+ Console.WriteLine(GetString()[..1]);
+ Console.WriteLine(GetString()[..^1]);
+ Console.WriteLine(GetString()[1..]);
+ Console.WriteLine(GetString()[^1..]);
+ }
+
+ public static void UseWholeRange()
+ {
+ Console.WriteLine(GetArray()[..]);
+ //Console.WriteLine(GetList()[..]); // fails to compile
+ Console.WriteLine(GetSpan()[..].ToString());
+ Console.WriteLine(GetString()[..]);
+ Console.WriteLine(new CustomList()[..]);
+ 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/CSharpDecompiler.cs b/ICSharpCode.Decompiler/CSharp/CSharpDecompiler.cs
index d5d9ce9d9..3fdf9f0ad 100644
--- a/ICSharpCode.Decompiler/CSharp/CSharpDecompiler.cs
+++ b/ICSharpCode.Decompiler/CSharp/CSharpDecompiler.cs
@@ -150,7 +150,8 @@ namespace ICSharpCode.Decompiler.CSharp
new TransformCollectionAndObjectInitializers(),
new TransformExpressionTrees(),
new NamedArgumentTransform(),
- new UserDefinedLogicTransform()
+ new UserDefinedLogicTransform(),
+ new IndexRangeTransform()
),
}
},
diff --git a/ICSharpCode.Decompiler/CSharp/CallBuilder.cs b/ICSharpCode.Decompiler/CSharp/CallBuilder.cs
index 267a757fe..68a3daf8e 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, target, argumentList)) {
+ return result;
+ }
+ }
+
if (callOpCode == OpCode.NewObj) {
return HandleConstructorCall(expectedTargetDetails, target.ResolveResult, method, argumentList);
}
@@ -1494,5 +1500,47 @@ 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, TranslatedExpression target, 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 MemberResolveResult(null, method));
+ 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 MemberResolveResult(null, method.AccessorOwner ?? method));
+ 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 MemberResolveResult(null, method));
+ 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 MemberResolveResult(null, method));
+ 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 MemberResolveResult(null, method));
+ return true;
+ } else if (method is SyntheticRangeIndexAccessor rangeIndexAccessor && rangeIndexAccessor.IsSlicing) {
+ // For slicing the method is called Slice()/Substring(), but we still need to output indexer notation.
+ // So special-case range-based slicing here.
+ result = new IndexerExpression(target, argumentList.Arguments.Select(a => a.Expression))
+ .WithRR(new MemberResolveResult(target.ResolveResult, method));
+ return true;
+ }
+ return false;
+ }
}
}
\ No newline at end of file
diff --git a/ICSharpCode.Decompiler/CSharp/ExpressionBuilder.cs b/ICSharpCode.Decompiler/CSharp/ExpressionBuilder.cs
index 31f9fa0f1..7c6660e50 100644
--- a/ICSharpCode.Decompiler/CSharp/ExpressionBuilder.cs
+++ b/ICSharpCode.Decompiler/CSharp/ExpressionBuilder.cs
@@ -2244,14 +2244,23 @@ namespace ICSharpCode.Decompiler.CSharp
arrayType = new ArrayType(compilation, inst.Type, inst.Indices.Count);
arrayExpr = arrayExpr.ConvertTo(arrayType, this);
}
- TranslatedExpression expr = new IndexerExpression(
- arrayExpr, inst.Indices.Select(i => TranslateArrayIndex(i).Expression)
- ).WithILInstruction(inst).WithRR(new ResolveResult(arrayType.ElementType));
+ IndexerExpression indexerExpr;
+ if (inst.WithSystemIndex) {
+ var systemIndex = compilation.FindType(KnownTypeCode.Index);
+ indexerExpr = new IndexerExpression(
+ arrayExpr, inst.Indices.Select(i => Translate(i, typeHint: systemIndex).ConvertTo(systemIndex, this).Expression)
+ );
+ } else {
+ indexerExpr = new IndexerExpression(
+ arrayExpr, inst.Indices.Select(i => TranslateArrayIndex(i).Expression)
+ );
+ }
+ TranslatedExpression expr = indexerExpr.WithILInstruction(inst).WithRR(new ResolveResult(arrayType.ElementType));
return new DirectionExpression(FieldDirection.Ref, expr)
.WithoutILInstruction().WithRR(new ByReferenceResolveResult(expr.Type, ReferenceKind.Ref));
}
- TranslatedExpression TranslateArrayIndex(ILInstruction i)
+ TranslatedExpression TranslateArrayIndex(ILInstruction i, bool expectSystemIndex = false)
{
var input = Translate(i);
KnownTypeCode targetType;
diff --git a/ICSharpCode.Decompiler/CSharp/OutputVisitor/CSharpOutputVisitor.cs b/ICSharpCode.Decompiler/CSharp/OutputVisitor/CSharpOutputVisitor.cs
index 4eea34baa..1d3f99617 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/CSharp/Transforms/ReplaceMethodCallsWithOperators.cs b/ICSharpCode.Decompiler/CSharp/Transforms/ReplaceMethodCallsWithOperators.cs
index f031778fd..e50eb7134 100644
--- a/ICSharpCode.Decompiler/CSharp/Transforms/ReplaceMethodCallsWithOperators.cs
+++ b/ICSharpCode.Decompiler/CSharp/Transforms/ReplaceMethodCallsWithOperators.cs
@@ -125,6 +125,13 @@ namespace ICSharpCode.Decompiler.CSharp.Transforms
invocationExpression.ReplaceWith(new ObjectCreateExpression(context.TypeSystemAstBuilder.ConvertType(method.TypeArguments.First())));
}
break;
+ case "System.Runtime.CompilerServices.RuntimeHelpers.GetSubArray":
+ if (arguments.Length == 2 && context.Settings.Ranges) {
+ var slicing = new IndexerExpression(arguments[0].Detach(), arguments[1].Detach());
+ slicing.CopyAnnotationsFrom(invocationExpression);
+ invocationExpression.ReplaceWith(slicing);
+ }
+ break;
}
BinaryOperatorType? bop = GetBinaryOperatorTypeFromMetadataName(method.Name);
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/ICSharpCode.Decompiler.csproj b/ICSharpCode.Decompiler/ICSharpCode.Decompiler.csproj
index d21930dbb..3d735ec42 100644
--- a/ICSharpCode.Decompiler/ICSharpCode.Decompiler.csproj
+++ b/ICSharpCode.Decompiler/ICSharpCode.Decompiler.csproj
@@ -64,6 +64,7 @@
+
@@ -384,6 +385,7 @@
+
diff --git a/ICSharpCode.Decompiler/IL/Instructions.cs b/ICSharpCode.Decompiler/IL/Instructions.cs
index 1db516ca1..7fd43ab93 100644
--- a/ICSharpCode.Decompiler/IL/Instructions.cs
+++ b/ICSharpCode.Decompiler/IL/Instructions.cs
@@ -4713,6 +4713,7 @@ namespace ICSharpCode.Decompiler.IL
clone.Indices.AddRange(this.Indices.Select(arg => (ILInstruction)arg.Clone()));
return clone;
}
+ public bool WithSystemIndex;
public bool DelayExceptions; // NullReferenceException/IndexOutOfBoundsException only occurs when the reference is dereferenced
public override StackType ResultType { get { return StackType.Ref; } }
/// Gets whether the 'readonly' prefix was applied to this instruction.
@@ -4729,6 +4730,8 @@ namespace ICSharpCode.Decompiler.IL
public override void WriteTo(ITextOutput output, ILAstWritingOptions options)
{
WriteILRange(output, options);
+ if (WithSystemIndex)
+ output.Write("withsystemindex.");
if (DelayExceptions)
output.Write("delayex.");
if (IsReadOnly)
@@ -4759,7 +4762,7 @@ namespace ICSharpCode.Decompiler.IL
protected internal override bool PerformMatch(ILInstruction other, ref Patterns.Match match)
{
var o = other as LdElema;
- return o != null && type.Equals(o.type) && this.array.PerformMatch(o.array, ref match) && Patterns.ListMatch.DoMatch(this.Indices, o.Indices, ref match) && DelayExceptions == o.DelayExceptions && IsReadOnly == o.IsReadOnly;
+ return o != null && type.Equals(o.type) && this.array.PerformMatch(o.array, ref match) && Patterns.ListMatch.DoMatch(this.Indices, o.Indices, ref match) && this.WithSystemIndex == o.WithSystemIndex && DelayExceptions == o.DelayExceptions && IsReadOnly == o.IsReadOnly;
}
}
}
diff --git a/ICSharpCode.Decompiler/IL/Instructions.tt b/ICSharpCode.Decompiler/IL/Instructions.tt
index e18ce8d8e..69c2640eb 100644
--- a/ICSharpCode.Decompiler/IL/Instructions.tt
+++ b/ICSharpCode.Decompiler/IL/Instructions.tt
@@ -278,6 +278,7 @@
CustomClassName("LdLen"), CustomArguments(("array", new[] { "O" })), CustomConstructor, CustomWriteTo, MayThrow),
new OpCode("ldelema", "Load address of array element.",
CustomClassName("LdElema"), HasTypeOperand, CustomChildren(new [] { new ArgumentInfo("array"), new ArgumentInfo("indices") { IsCollection = true } }, true),
+ BoolFlag("WithSystemIndex"),
MayThrowIfNotDelayed, ResultType("Ref"), SupportsReadonlyPrefix),
new OpCode("get.pinnable.reference", "Retrieves a pinnable reference for the input object." + Environment.NewLine
+ "The input must be an object reference (O)." + Environment.NewLine
@@ -1105,6 +1106,17 @@ protected override void Disconnected()
opCode.WriteOperand.Add("member.WriteTo(output);");
};
+ // Adds a member of type bool to the instruction.
+ static Action BoolFlag(string flagName)
+ {
+ return opCode => {
+ opCode.PerformMatchConditions.Add($"this.{flagName} == o.{flagName}");
+ opCode.Members.Add($"public bool {flagName};");
+ opCode.GenerateWriteTo = true;
+ opCode.WriteOpCodePrefix.Add($"if ({flagName}){Environment.NewLine}\toutput.Write(\"{flagName.ToLowerInvariant()}.\");");
+ };
+ }
+
// LoadConstant trait: the instruction loads a compile-time constant. Implies NoArguments.
static Action LoadConstant(string operandType)
{
diff --git a/ICSharpCode.Decompiler/IL/Transforms/ExpressionTransforms.cs b/ICSharpCode.Decompiler/IL/Transforms/ExpressionTransforms.cs
index a011b0051..97c17d1a8 100644
--- a/ICSharpCode.Decompiler/IL/Transforms/ExpressionTransforms.cs
+++ b/ICSharpCode.Decompiler/IL/Transforms/ExpressionTransforms.cs
@@ -208,6 +208,8 @@ namespace ICSharpCode.Decompiler.IL.Transforms
{
base.VisitLdElema(inst);
CleanUpArrayIndices(inst.Indices);
+ if (IndexRangeTransform.HandleLdElema(inst, context))
+ return;
}
protected internal override void VisitNewArr(NewArr inst)
diff --git a/ICSharpCode.Decompiler/IL/Transforms/ILInlining.cs b/ICSharpCode.Decompiler/IL/Transforms/ILInlining.cs
index 95fd133ba..ca20dbbb7 100644
--- a/ICSharpCode.Decompiler/IL/Transforms/ILInlining.cs
+++ b/ICSharpCode.Decompiler/IL/Transforms/ILInlining.cs
@@ -21,6 +21,7 @@ using System.Diagnostics;
using System.Linq;
using System.Reflection;
using ICSharpCode.Decompiler.TypeSystem;
+using ICSharpCode.Decompiler.TypeSystem.Implementation;
namespace ICSharpCode.Decompiler.IL.Transforms
{
@@ -469,6 +470,14 @@ namespace ICSharpCode.Decompiler.IL.Transforms
if (parent.SlotInfo == CompoundAssignmentInstruction.TargetSlot) {
return true;
}
+ if (((CallInstruction)parent).Method is SyntheticRangeIndexAccessor) {
+ return true;
+ }
+ break;
+ case OpCode.LdElema:
+ if (((LdElema)parent).WithSystemIndex) {
+ return true;
+ }
break;
}
// decide based on the top-level target instruction into which we are inlining:
diff --git a/ICSharpCode.Decompiler/IL/Transforms/IndexRangeTransform.cs b/ICSharpCode.Decompiler/IL/Transforms/IndexRangeTransform.cs
new file mode 100644
index 000000000..2140c5416
--- /dev/null
+++ b/ICSharpCode.Decompiler/IL/Transforms/IndexRangeTransform.cs
@@ -0,0 +1,596 @@
+// Copyright (c) 2020 Daniel Grunwald
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy of this
+// software and associated documentation files (the "Software"), to deal in the Software
+// without restriction, including without limitation the rights to use, copy, modify, merge,
+// publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons
+// to whom the Software is furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in all copies or
+// substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
+// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
+// PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE
+// FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
+// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+// DEALINGS IN THE SOFTWARE.
+
+using System;
+using System.Diagnostics;
+using System.Linq;
+using ICSharpCode.Decompiler.TypeSystem;
+using ICSharpCode.Decompiler.TypeSystem.Implementation;
+
+namespace ICSharpCode.Decompiler.IL.Transforms
+{
+ ///
+ /// Transform for the C# 8 System.Index / System.Range feature
+ ///
+ class IndexRangeTransform : IStatementTransform
+ {
+ ///
+ /// Called by expression transforms.
+ /// Handles the `array[System.Index]` cases.
+ ///
+ public static bool HandleLdElema(LdElema ldelema, ILTransformContext context)
+ {
+ if (!context.Settings.Ranges)
+ return false;
+ if (!ldelema.Array.MatchLdLoc(out ILVariable array))
+ return false;
+ if (ldelema.Indices.Count != 1)
+ return false; // the index/range feature doesn't support multi-dimensional arrays
+ var index = ldelema.Indices[0];
+ if (index is CallInstruction call && call.Method.Name == "GetOffset" && call.Method.DeclaringType.IsKnownType(KnownTypeCode.Index)) {
+ // ldelema T(ldloc array, call GetOffset(..., ldlen.i4(ldloc array)))
+ // -> withsystemindex.ldelema T(ldloc array, ...)
+ if (call.Arguments.Count != 2)
+ return false;
+ if (!(call.Arguments[1].MatchLdLen(StackType.I4, out var arrayLoad) && arrayLoad.MatchLdLoc(array)))
+ return false;
+ context.Step("ldelema with System.Index", ldelema);
+ foreach (var node in call.Arguments[1].Descendants)
+ ldelema.AddILRange(node);
+ ldelema.AddILRange(call);
+ ldelema.WithSystemIndex = true;
+ // The method call had a `ref System.Index` argument for the this pointer, but we want a `System.Index` by-value.
+ ldelema.Indices[0] = new LdObj(call.Arguments[0], call.Method.DeclaringType);
+ return true;
+ } else if (index is BinaryNumericInstruction bni && bni.Operator == BinaryNumericOperator.Sub && !bni.IsLifted && !bni.CheckForOverflow) {
+ // ldelema T(ldloc array, binary.sub.i4(ldlen.i4(ldloc array), ...))
+ // -> withsystemindex.ldelema T(ldloc array, newobj System.Index(..., fromEnd: true))
+ if (!(bni.Left.MatchLdLen(StackType.I4, out var arrayLoad) && arrayLoad.MatchLdLoc(array)))
+ return false;
+ var indexMethods = new IndexMethods(context.TypeSystem);
+ if (!indexMethods.IsValid)
+ return false; // don't use System.Index if not supported by the target framework
+ context.Step("ldelema indexed from end", ldelema);
+ foreach (var node in bni.Left.Descendants)
+ ldelema.AddILRange(node);
+ ldelema.AddILRange(bni);
+ ldelema.WithSystemIndex = true;
+ ldelema.Indices[0] = MakeIndex(IndexKind.FromEnd, bni.Right, indexMethods);
+ return true;
+ }
+
+ return false;
+ }
+
+ class IndexMethods
+ {
+ public readonly IMethod IndexCtor;
+ public readonly IMethod IndexImplicitConv;
+ public readonly IMethod RangeCtor;
+ public IType IndexType => IndexCtor?.DeclaringType;
+ public IType RangeType => RangeCtor?.DeclaringType;
+ public bool IsValid => IndexCtor != null && IndexImplicitConv != null && RangeCtor != null;
+
+ public readonly IMethod RangeStartAt;
+ public readonly IMethod RangeEndAt;
+ public readonly IMethod RangeGetAll;
+
+ public IndexMethods(ICompilation compilation)
+ {
+ var indexType = compilation.FindType(KnownTypeCode.Index);
+ foreach (var ctor in indexType.GetConstructors(m => m.Parameters.Count == 2)) {
+ if (ctor.Parameters[0].Type.IsKnownType(KnownTypeCode.Int32)
+ && ctor.Parameters[1].Type.IsKnownType(KnownTypeCode.Boolean)) {
+ this.IndexCtor = ctor;
+ }
+ }
+ foreach (var op in indexType.GetMethods(m => m.IsOperator && m.Name == "op_Implicit")) {
+ if (op.Parameters.Count == 1 && op.Parameters[0].Type.IsKnownType(KnownTypeCode.Int32)) {
+ this.IndexImplicitConv = op;
+ }
+ }
+ var rangeType = compilation.FindType(KnownTypeCode.Range);
+ foreach (var ctor in rangeType.GetConstructors(m => m.Parameters.Count == 2)) {
+ if (ctor.Parameters[0].Type.IsKnownType(KnownTypeCode.Index)
+ && ctor.Parameters[1].Type.IsKnownType(KnownTypeCode.Index)) {
+ this.RangeCtor = ctor;
+ }
+ }
+ foreach (var m in rangeType.GetMethods(m => m.Parameters.Count == 1)) {
+ if (m.Parameters.Count == 1 && m.Parameters[0].Type.IsKnownType(KnownTypeCode.Index)) {
+ if (m.Name == "StartAt")
+ this.RangeStartAt = m;
+ else if (m.Name == "EndAt")
+ this.RangeEndAt = m;
+ }
+ }
+ foreach (var p in rangeType.GetProperties(p => p.IsStatic && p.Name == "All")) {
+ this.RangeGetAll = p.Getter;
+ }
+ }
+ }
+
+ void IStatementTransform.Run(Block block, int pos, StatementTransformContext context)
+ {
+ if (!context.Settings.Ranges)
+ return;
+ int startPos = pos;
+ ILVariable containerVar = null;
+ // The container length access may be a separate instruction, or it may be inline with the variable's use
+ if (MatchContainerLengthStore(block.Instructions[pos], out ILVariable containerLengthVar, ref containerVar)) {
+ // stloc containerLengthVar(call get_Length/get_Count(ldloc container))
+ pos++;
+ } else {
+ // Reset if MatchContainerLengthStore only had a partial match. MatchGetOffset() will then set `containerVar`.
+ containerLengthVar = null;
+ containerVar = null;
+ }
+ if (block.Instructions[pos].MatchStLoc(out var rangeVar, out var rangeVarInit) && rangeVar.Type.IsKnownType(KnownTypeCode.Range)) {
+ // stloc rangeVar(rangeVarInit)
+ pos++;
+ } else {
+ rangeVar = null;
+ rangeVarInit = null;
+ }
+ // stloc startOffsetVar(call GetOffset(startIndexLoad, ldloc length))
+ if (!block.Instructions[pos].MatchStLoc(out ILVariable startOffsetVar, out ILInstruction startOffsetVarInit))
+ return;
+ if (!(startOffsetVar.IsSingleDefinition && startOffsetVar.StackType == StackType.I4))
+ return;
+ var startIndexKind = MatchGetOffset(startOffsetVarInit, out ILInstruction startIndexLoad, containerLengthVar, ref containerVar);
+ pos++;
+ if (startOffsetVar.LoadCount == 1) {
+ TransformIndexing();
+ } else if (startOffsetVar.LoadCount == 2) {
+ // might be slicing: startOffset is used once for the slice length calculation, and once for the Slice() method call
+ TransformSlicing();
+ }
+
+ void TransformIndexing()
+ {
+ // complex_expr(call get_Item(ldloc container, ldloc startOffsetVar))
+
+ if (rangeVar != null)
+ return;
+ if (!(startOffsetVar.LoadInstructions.Single().Parent is CallInstruction call))
+ return;
+ if (call.Method.AccessorKind == System.Reflection.MethodSemanticsAttributes.Getter && call.Arguments.Count == 2) {
+ if (call.Method.AccessorOwner?.SymbolKind != SymbolKind.Indexer)
+ return;
+ if (call.Method.Parameters.Count != 1)
+ return;
+ } else if (call.Method.AccessorKind == System.Reflection.MethodSemanticsAttributes.Setter && call.Arguments.Count == 3) {
+ if (call.Method.AccessorOwner?.SymbolKind != SymbolKind.Indexer)
+ return;
+ if (call.Method.Parameters.Count != 2)
+ return;
+ } else if (IsSlicingMethod(call.Method)) {
+ TransformSlicing(sliceLengthWasMisdetectedAsStartOffset: true);
+ return;
+ } else {
+ return;
+ }
+ if (startIndexKind == IndexKind.FromStart) {
+ // FromStart is only relevant for slicing; indexing from the start does not involve System.Index at all.
+ return;
+ }
+ if (!CheckContainerLengthVariableUseCount(containerLengthVar, startIndexKind)) {
+ return;
+ }
+ // startOffsetVar might be used deep inside a complex statement, ensure we can inline up to that point:
+ for (int i = startPos; i < pos; i++) {
+ if (!ILInlining.CanInlineInto(block.Instructions[pos], startOffsetVar, block.Instructions[i]))
+ return;
+ }
+ if (!call.Method.Parameters[0].Type.IsKnownType(KnownTypeCode.Int32))
+ return;
+ if (!MatchContainerVar(call.Arguments[0], ref containerVar))
+ return;
+ if (!call.Arguments[1].MatchLdLoc(startOffsetVar))
+ return;
+ var specialMethods = new IndexMethods(context.TypeSystem);
+ if (!specialMethods.IsValid)
+ return;
+ if (!CSharpWillGenerateIndexer(call.Method.DeclaringType, slicing: false))
+ return;
+
+ context.Step($"{call.Method.Name} indexed with {startIndexKind}", call);
+ var newMethod = new SyntheticRangeIndexAccessor(call.Method, specialMethods.IndexType, slicing: false);
+ var newCall = CallInstruction.Create(call.OpCode, newMethod);
+ newCall.ConstrainedTo = call.ConstrainedTo;
+ newCall.ILStackWasEmpty = call.ILStackWasEmpty;
+ newCall.Arguments.Add(call.Arguments[0]);
+ newCall.Arguments.Add(MakeIndex(startIndexKind, startIndexLoad, specialMethods));
+ newCall.Arguments.AddRange(call.Arguments.Skip(2));
+ newCall.AddILRange(call);
+ for (int i = startPos; i < pos; i++) {
+ newCall.AddILRange(block.Instructions[i]);
+ }
+ call.ReplaceWith(newCall);
+ block.Instructions.RemoveRange(startPos, pos - startPos);
+ }
+
+ void TransformSlicing(bool sliceLengthWasMisdetectedAsStartOffset = false)
+ {
+ ILVariable sliceLengthVar;
+ ILInstruction sliceLengthVarInit;
+ if (sliceLengthWasMisdetectedAsStartOffset) {
+ // Special case: when slicing without a start point, the slice length calculation is mis-detected as the start offset,
+ // and since it only has a single use, we end in TransformIndexing(), which then calls TransformSlicing
+ // on this code path.
+ sliceLengthVar = startOffsetVar;
+ sliceLengthVarInit = ((StLoc)sliceLengthVar.StoreInstructions.Single()).Value;
+ startOffsetVar = null;
+ startIndexLoad = new LdcI4(0);
+ startIndexKind = IndexKind.TheStart;
+ } else {
+ // stloc containerLengthVar(call get_Length(ldloc containerVar))
+ // stloc startOffset(call GetOffset(startIndexLoad, ldloc length))
+ // -- we are here --
+ // stloc sliceLengthVar(binary.sub.i4(call GetOffset(endIndexLoad, ldloc length), ldloc startOffset))
+ // complex_expr(call Slice(ldloc containerVar, ldloc startOffset, ldloc sliceLength))
+
+ if (!block.Instructions[pos].MatchStLoc(out sliceLengthVar, out sliceLengthVarInit))
+ return;
+ pos++;
+ }
+
+ if (!(sliceLengthVar.IsSingleDefinition && sliceLengthVar.LoadCount == 1))
+ return;
+ if (!MatchSliceLength(sliceLengthVarInit, out IndexKind endIndexKind, out ILInstruction endIndexLoad, containerLengthVar, ref containerVar, startOffsetVar))
+ return;
+ if (!CheckContainerLengthVariableUseCount(containerLengthVar, startIndexKind, endIndexKind)) {
+ return;
+ }
+ if (rangeVar != null) {
+ if (!MatchIndexFromRange(startIndexKind, startIndexLoad, rangeVar, "get_Start"))
+ return;
+ if (!MatchIndexFromRange(endIndexKind, endIndexLoad, rangeVar, "get_End"))
+ return;
+ }
+ if (!(sliceLengthVar.LoadInstructions.Single().Parent is CallInstruction call))
+ return;
+ if (!IsSlicingMethod(call.Method))
+ return;
+ if (call.Arguments.Count != 3)
+ return;
+ if (!MatchContainerVar(call.Arguments[0], ref containerVar))
+ return;
+ if (startOffsetVar == null) {
+ Debug.Assert(startIndexKind == IndexKind.TheStart);
+ if (!call.Arguments[1].MatchLdcI4(0))
+ return;
+ } else {
+ if (!call.Arguments[1].MatchLdLoc(startOffsetVar))
+ return;
+ }
+ if (!call.Arguments[2].MatchLdLoc(sliceLengthVar))
+ return;
+ if (!CSharpWillGenerateIndexer(call.Method.DeclaringType, slicing: true))
+ return;
+ if (startIndexKind == IndexKind.FromStart && endIndexKind == IndexKind.FromStart) {
+ // It's possible we actually have a startIndex/endIndex that involves the container length,
+ // but we couldn't detect it yet because the statement initializing the containerLengthVar is
+ // not yet part of the region to be transformed.
+ // If we transform now, we'd end up with:
+ // int length = span.Length;
+ // Console.WriteLine(span[(length - GetInt(1))..(length - GetInt(2))].ToString());
+ // which is correct but unnecessarily complex.
+ // So we peek ahead at the next instruction to be transformed:
+ if (startPos > 0 && MatchContainerLengthStore(block.Instructions[startPos - 1], out _, ref containerVar)) {
+ // Looks like the transform would be able do to a better job including that previous instruction,
+ // so let's avoid transforming just yet.
+ return;
+ }
+ // Something similar happens with the rangeVar:
+ if (startPos > 0 && block.Instructions[startPos - 1] is StLoc stloc && stloc.Variable.Type.IsKnownType(KnownTypeCode.Range)) {
+ return;
+ }
+ }
+ var specialMethods = new IndexMethods(context.TypeSystem);
+ if (!specialMethods.IsValid)
+ return;
+
+ context.Step($"{call.Method.Name} sliced with {startIndexKind}..{endIndexKind}", call);
+ var newMethod = new SyntheticRangeIndexAccessor(call.Method, specialMethods.RangeType, slicing: true);
+ var newCall = CallInstruction.Create(call.OpCode, newMethod);
+ newCall.ConstrainedTo = call.ConstrainedTo;
+ newCall.ILStackWasEmpty = call.ILStackWasEmpty;
+ newCall.Arguments.Add(call.Arguments[0]);
+ if (rangeVar != null) {
+ newCall.Arguments.Add(rangeVarInit);
+ } else if (startIndexKind == IndexKind.TheStart && endIndexKind == IndexKind.TheEnd && specialMethods.RangeGetAll != null) {
+ newCall.Arguments.Add(new Call(specialMethods.RangeGetAll));
+ } else if (startIndexKind == IndexKind.TheStart && specialMethods.RangeEndAt != null) {
+ var rangeCtorCall = new Call(specialMethods.RangeEndAt);
+ rangeCtorCall.Arguments.Add(MakeIndex(endIndexKind, endIndexLoad, specialMethods));
+ newCall.Arguments.Add(rangeCtorCall);
+ } else if (endIndexKind == IndexKind.TheEnd && specialMethods.RangeStartAt != null) {
+ var rangeCtorCall = new Call(specialMethods.RangeStartAt);
+ rangeCtorCall.Arguments.Add(MakeIndex(startIndexKind, startIndexLoad, specialMethods));
+ newCall.Arguments.Add(rangeCtorCall);
+ } else {
+ var rangeCtorCall = new NewObj(specialMethods.RangeCtor);
+ rangeCtorCall.Arguments.Add(MakeIndex(startIndexKind, startIndexLoad, specialMethods));
+ rangeCtorCall.Arguments.Add(MakeIndex(endIndexKind, endIndexLoad, specialMethods));
+ newCall.Arguments.Add(rangeCtorCall);
+ }
+ newCall.AddILRange(call);
+ for (int i = startPos; i < pos; i++) {
+ newCall.AddILRange(block.Instructions[i]);
+ }
+ call.ReplaceWith(newCall);
+ block.Instructions.RemoveRange(startPos, pos - startPos);
+ }
+ }
+
+ static bool IsSlicingMethod(IMethod method)
+ {
+ if (method.IsExtensionMethod)
+ return false;
+ if (method.Parameters.Count != 2)
+ return false;
+ if (!method.Parameters.All(p => p.Type.IsKnownType(KnownTypeCode.Int32)))
+ return false;
+ return method.Name == "Slice"
+ || (method.Name == "Substring" && method.DeclaringType.IsKnownType(KnownTypeCode.String));
+ }
+
+ ///
+ /// Check that the number of uses of the containerLengthVar variable matches those expected in the pattern.
+ ///
+ private bool CheckContainerLengthVariableUseCount(ILVariable containerLengthVar, IndexKind startIndexKind, IndexKind endIndexKind = IndexKind.FromStart)
+ {
+ int expectedUses = 0;
+ if (startIndexKind != IndexKind.FromStart && startIndexKind != IndexKind.TheStart)
+ expectedUses += 1;
+ if (endIndexKind != IndexKind.FromStart && endIndexKind != IndexKind.TheStart)
+ expectedUses += 1;
+ if (containerLengthVar != null) {
+ return containerLengthVar.LoadCount == expectedUses;
+ } else {
+ return expectedUses <= 1; // can have one inline use
+ }
+ }
+
+ ///
+ /// Matches 'addressof System.Index(call get_Start/get_End(ldloca rangeVar))'
+ ///
+ static bool MatchIndexFromRange(IndexKind indexKind, ILInstruction indexLoad, ILVariable rangeVar, string accessorName)
+ {
+ if (indexKind != IndexKind.RefSystemIndex)
+ return false;
+ if (!(indexLoad is AddressOf addressOf))
+ return false;
+ if (!addressOf.Type.IsKnownType(KnownTypeCode.Index))
+ return false;
+ if (!(addressOf.Value is Call call))
+ return false;
+ if (call.Method.Name != accessorName)
+ return false;
+ if (!call.Method.DeclaringType.IsKnownType(KnownTypeCode.Range))
+ return false;
+ if (call.Arguments.Count != 1)
+ return false;
+ return call.Arguments[0].MatchLdLoca(rangeVar);
+ }
+
+ static ILInstruction MakeIndex(IndexKind indexKind, ILInstruction indexLoad, IndexMethods specialMethods)
+ {
+ if (indexKind == IndexKind.RefSystemIndex) {
+ // stloc containerLengthVar(call get_Length/get_Count(ldloc container))
+ // stloc startOffsetVar(call GetOffset(startIndexLoad, ldloc length))
+ // complex_expr(call get_Item(ldloc container, ldloc startOffsetVar))
+ // -->
+ // complex_expr(call get_Item(ldloc container, ldobj startIndexLoad))
+ return new LdObj(indexLoad, specialMethods.IndexType);
+ } else if (indexKind == IndexKind.FromEnd || indexKind == IndexKind.TheEnd) {
+ // stloc offsetVar(binary.sub.i4(call get_Length/get_Count(ldloc container), startIndexLoad))
+ // complex_expr(call get_Item(ldloc container, ldloc startOffsetVar))
+ // -->
+ // complex_expr(call get_Item(ldloc container, newobj System.Index(startIndexLoad, fromEnd: true)))
+ return new NewObj(specialMethods.IndexCtor) { Arguments = { indexLoad, new LdcI4(1) } };
+ } else {
+ Debug.Assert(indexKind == IndexKind.FromStart || indexKind == IndexKind.TheStart);
+ return new Call(specialMethods.IndexImplicitConv) { Arguments = { indexLoad } };
+ }
+ }
+
+ ///
+ /// Gets whether the C# compiler will call `container[int]` when using `container[Index]`.
+ ///
+ private bool CSharpWillGenerateIndexer(IType declaringType, bool slicing)
+ {
+ bool foundInt32Overload = false;
+ bool foundIndexOverload = false;
+ bool foundRangeOverload = false;
+ bool foundCountProperty = false;
+ foreach (var prop in declaringType.GetProperties(p => p.IsIndexer || (p.Name == "Length" || p.Name == "Count"))) {
+ if (prop.IsIndexer && prop.Parameters.Count == 1) {
+ var p = prop.Parameters[0];
+ if (p.Type.IsKnownType(KnownTypeCode.Int32)) {
+ foundInt32Overload = true;
+ } else if (p.Type.IsKnownType(KnownTypeCode.Index)) {
+ foundIndexOverload = true;
+ } else if (p.Type.IsKnownType(KnownTypeCode.Range)) {
+ foundRangeOverload = true;
+ }
+ } else if (prop.Name == "Length" || prop.Name == "Count") {
+ foundCountProperty = true;
+ }
+ }
+ if (slicing) {
+ return /* foundSlicingMethod && */ foundCountProperty && !foundRangeOverload;
+ } else {
+ return foundInt32Overload && foundCountProperty && !foundIndexOverload;
+ }
+ }
+
+ ///
+ /// Matches the instruction:
+ /// stloc containerLengthVar(call get_Length/get_Count(ldloc containerVar))
+ ///
+ static bool MatchContainerLengthStore(ILInstruction inst, out ILVariable lengthVar, ref ILVariable containerVar)
+ {
+ if (!inst.MatchStLoc(out lengthVar, out var init))
+ return false;
+ if (!(lengthVar.IsSingleDefinition && lengthVar.StackType == StackType.I4))
+ return false;
+ return MatchContainerLength(init, null, ref containerVar);
+ }
+
+ ///
+ /// If lengthVar is non-null, matches 'ldloc lengthVar'.
+ ///
+ /// Otherwise, matches the instruction:
+ /// call get_Length/get_Count(ldloc containerVar)
+ ///
+ static bool MatchContainerLength(ILInstruction init, ILVariable lengthVar, ref ILVariable containerVar)
+ {
+ if (lengthVar != null) {
+ Debug.Assert(containerVar != null);
+ return init.MatchLdLoc(lengthVar);
+ }
+ if (!(init is CallInstruction call))
+ return false;
+ if (call.ResultType != StackType.I4)
+ return false;
+ if (!(call.Method.IsAccessor && call.Method.AccessorKind == System.Reflection.MethodSemanticsAttributes.Getter))
+ return false;
+ if (!(call.Method.AccessorOwner is IProperty lengthProp))
+ return false;
+ if (lengthProp.Name == "Length") {
+ // OK, Length is preferred
+ } else if (lengthProp.Name == "Count") {
+ // Also works, but only if the type doesn't have "Length"
+ if (lengthProp.DeclaringType.GetProperties(p => p.Name == "Length").Any())
+ return false;
+ }
+ if (!lengthProp.ReturnType.IsKnownType(KnownTypeCode.Int32))
+ return false;
+ if (lengthProp.IsVirtual && call.OpCode != OpCode.CallVirt)
+ return false;
+ if (call.Arguments.Count != 1)
+ return false;
+ return MatchContainerVar(call.Arguments[0], ref containerVar);
+ }
+
+ static bool MatchContainerVar(ILInstruction inst, ref ILVariable containerVar)
+ {
+ if (containerVar != null) {
+ return inst.MatchLdLoc(containerVar) || inst.MatchLdLoca(containerVar);
+ } else {
+ return inst.MatchLdLoc(out containerVar) || inst.MatchLdLoca(out containerVar);
+ }
+ }
+
+ enum IndexKind
+ {
+ ///
+ /// indexLoad is an integer, from the start of the container
+ ///
+ FromStart,
+ ///
+ /// indexLoad is loading the address of a System.Index struct
+ ///
+ RefSystemIndex,
+ ///
+ /// indexLoad is an integer, from the end of the container
+ ///
+ FromEnd,
+ ///
+ /// Always equivalent to `0`, used for the start-index when slicing without a startpoint `a[..end]`
+ ///
+ TheStart,
+ ///
+ /// Always equivalent to `^0`, used for the end-index when slicing without an endpoint `a[start..]`
+ ///
+ TheEnd,
+ }
+
+ ///
+ /// Matches an instruction computing an offset:
+ /// call System.Index.GetOffset(indexLoad, ldloc containerLengthVar)
+ /// or
+ /// binary.sub.i4(ldloc containerLengthVar, indexLoad)
+ ///
+ /// Anything else not matching these patterns is interpreted as an `int` expression from the start of the container.
+ ///
+ static IndexKind MatchGetOffset(ILInstruction inst, out ILInstruction indexLoad,
+ ILVariable containerLengthVar, ref ILVariable containerVar)
+ {
+ indexLoad = inst;
+ if (MatchContainerLength(inst, containerLengthVar, ref containerVar)) {
+ indexLoad = new LdcI4(0);
+ return IndexKind.TheEnd;
+ } else if (inst is CallInstruction call) {
+ // call System.Index.GetOffset(indexLoad, ldloc containerLengthVar)
+ if (call.Method.Name != "GetOffset")
+ return IndexKind.FromStart;
+ if (!call.Method.DeclaringType.IsKnownType(KnownTypeCode.Index))
+ return IndexKind.FromStart;
+ if (call.Arguments.Count != 2)
+ return IndexKind.FromStart;
+ if (!MatchContainerLength(call.Arguments[1], containerLengthVar, ref containerVar))
+ return IndexKind.FromStart;
+ indexLoad = call.Arguments[0];
+ return IndexKind.RefSystemIndex;
+ } else if (inst is BinaryNumericInstruction bni && bni.Operator == BinaryNumericOperator.Sub) {
+ if (bni.CheckForOverflow || bni.ResultType != StackType.I4 || bni.IsLifted)
+ return IndexKind.FromStart;
+ // binary.sub.i4(ldloc containerLengthVar, indexLoad)
+ if (!MatchContainerLength(bni.Left, containerLengthVar, ref containerVar))
+ return IndexKind.FromStart;
+ indexLoad = bni.Right;
+ return IndexKind.FromEnd;
+ } else {
+ return IndexKind.FromStart;
+ }
+ }
+
+ ///
+ /// Matches an instruction computing a slice length:
+ /// binary.sub.i4(call GetOffset(endIndexLoad, ldloc length), ldloc startOffset))
+ ///
+ static bool MatchSliceLength(ILInstruction inst, out IndexKind endIndexKind, out ILInstruction endIndexLoad, ILVariable containerLengthVar, ref ILVariable containerVar, ILVariable startOffsetVar)
+ {
+ endIndexKind = default;
+ endIndexLoad = default;
+ if (inst is BinaryNumericInstruction bni && bni.Operator == BinaryNumericOperator.Sub) {
+ if (bni.CheckForOverflow || bni.ResultType != StackType.I4 || bni.IsLifted)
+ return false;
+ if (startOffsetVar == null) {
+ // When slicing without explicit start point: `a[..endIndex]`
+ if (!bni.Right.MatchLdcI4(0))
+ return false;
+ } else {
+ if (!bni.Right.MatchLdLoc(startOffsetVar))
+ return false;
+ }
+ endIndexKind = MatchGetOffset(bni.Left, out endIndexLoad, containerLengthVar, ref containerVar);
+ return true;
+ } else if (startOffsetVar == null) {
+ // When slicing without explicit start point: `a[..endIndex]`, the compiler doesn't always emit the "- 0".
+ endIndexKind = MatchGetOffset(inst, out endIndexLoad, containerLengthVar, ref containerVar);
+ return true;
+ } else {
+ return false;
+ }
+ }
+ }
+}
diff --git a/ICSharpCode.Decompiler/IL/Transforms/SplitVariables.cs b/ICSharpCode.Decompiler/IL/Transforms/SplitVariables.cs
index 850f50fc1..5f7473fb1 100644
--- a/ICSharpCode.Decompiler/IL/Transforms/SplitVariables.cs
+++ b/ICSharpCode.Decompiler/IL/Transforms/SplitVariables.cs
@@ -114,7 +114,8 @@ namespace ICSharpCode.Decompiler.IL.Transforms
value = ldFlda.Target;
}
if (value.OpCode != OpCode.LdLoca) {
- // GroupStores.HandleLoad() only detects ref-locals when they are directly initialized with ldloca
+ // GroupStores only handles ref-locals correctly when they are supported by GetAddressLoadForRefLocalUse(),
+ // which only works for ldflda*(ldloca)
return AddressUse.Unknown;
}
foreach (var load in stloc.Variable.LoadInstructions) {
@@ -132,14 +133,12 @@ namespace ICSharpCode.Decompiler.IL.Transforms
// Address is passed to method.
// We'll assume the method only uses the address locally,
// unless we can see an address being returned from the method:
- if (call is NewObj) {
- if (call.Method.DeclaringType.IsByRefLike) {
+ IType returnType = (call is NewObj) ? call.Method.DeclaringType : call.Method.ReturnType;
+ if (returnType.IsByRefLike) {
+ // If the address is returned from the method, it check whether it's consumed immediately.
+ // This can still be fine, as long as we also check the consumer's other arguments for 'stloc targetVar'.
+ if (DetermineAddressUse(call, targetVar) != AddressUse.Immediate)
return AddressUse.Unknown;
- }
- } else {
- if (call.Method.ReturnType.IsByRefLike) {
- return AddressUse.Unknown;
- }
}
foreach (var p in call.Method.Parameters) {
// catch "out Span" and similar
diff --git a/ICSharpCode.Decompiler/TypeSystem/Implementation/SyntheticRangeIndexer.cs b/ICSharpCode.Decompiler/TypeSystem/Implementation/SyntheticRangeIndexer.cs
new file mode 100644
index 000000000..9d79e1b79
--- /dev/null
+++ b/ICSharpCode.Decompiler/TypeSystem/Implementation/SyntheticRangeIndexer.cs
@@ -0,0 +1,137 @@
+// Copyright (c) 2020 Daniel Grunwald
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy of this
+// software and associated documentation files (the "Software"), to deal in the Software
+// without restriction, including without limitation the rights to use, copy, modify, merge,
+// publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons
+// to whom the Software is furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in all copies or
+// substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
+// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
+// PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE
+// FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
+// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+// DEALINGS IN THE SOFTWARE.
+
+using System.Collections.Generic;
+using ICSharpCode.Decompiler.Util;
+using System.Reflection;
+using System.Reflection.Metadata;
+using System.Diagnostics;
+using System.Linq;
+
+namespace ICSharpCode.Decompiler.TypeSystem.Implementation
+{
+ ///
+ /// Synthetic method representing a compiler-generated indexer
+ /// with the signature 'get_Item(System.Index)' or 'get_Item(System.Range)'.
+ /// Can also be a setter.
+ /// Used for the "Implicit Index support"/"Implicit Range support" for the C# 8 ranges feature.
+ ///
+ class SyntheticRangeIndexAccessor : IMethod
+ {
+ ///
+ /// The underlying method: `get_Item(int)`, `set_Item(int, T)` or `Slice(int, int)`.
+ ///
+ readonly IMethod underlyingMethod;
+ readonly IType indexOrRangeType;
+ readonly IReadOnlyList parameters;
+ readonly bool slicing;
+
+ public SyntheticRangeIndexAccessor(IMethod underlyingMethod, IType indexOrRangeType, bool slicing)
+ {
+ Debug.Assert(underlyingMethod != null);
+ Debug.Assert(indexOrRangeType != null);
+ this.underlyingMethod = underlyingMethod;
+ this.indexOrRangeType = indexOrRangeType;
+ this.slicing = slicing;
+ var parameters = new List();
+ parameters.Add(new DefaultParameter(indexOrRangeType, ""));
+ if (slicing) {
+ Debug.Assert(underlyingMethod.Parameters.Count == 2);
+ } else {
+ parameters.AddRange(underlyingMethod.Parameters.Skip(1));
+ }
+ this.parameters = parameters;
+ }
+
+ public bool IsSlicing => slicing;
+
+ bool IMethod.ReturnTypeIsRefReadOnly => underlyingMethod.ReturnTypeIsRefReadOnly;
+ bool IMethod.ThisIsRefReadOnly => underlyingMethod.ThisIsRefReadOnly;
+
+ IReadOnlyList IMethod.TypeParameters => EmptyList.Instance;
+ IReadOnlyList IMethod.TypeArguments => EmptyList.Instance;
+
+ bool IMethod.IsExtensionMethod => false;
+ bool IMethod.IsLocalFunction => false;
+ bool IMethod.IsConstructor => false;
+ bool IMethod.IsDestructor => false;
+ bool IMethod.IsOperator => false;
+ bool IMethod.HasBody => underlyingMethod.HasBody;
+ bool IMethod.IsAccessor => underlyingMethod.IsAccessor;
+ IMember IMethod.AccessorOwner => underlyingMethod.AccessorOwner;
+ MethodSemanticsAttributes IMethod.AccessorKind => underlyingMethod.AccessorKind;
+ IMethod IMethod.ReducedFrom => underlyingMethod.ReducedFrom;
+ IReadOnlyList IParameterizedMember.Parameters => parameters;
+ IMember IMember.MemberDefinition => underlyingMethod.MemberDefinition;
+ IType IMember.ReturnType => underlyingMethod.ReturnType;
+ IEnumerable IMember.ExplicitlyImplementedInterfaceMembers => EmptyList.Instance;
+ bool IMember.IsExplicitInterfaceImplementation => false;
+ bool IMember.IsVirtual => underlyingMethod.IsVirtual;
+ bool IMember.IsOverride => underlyingMethod.IsOverride;
+ bool IMember.IsOverridable => underlyingMethod.IsOverridable;
+ TypeParameterSubstitution IMember.Substitution => underlyingMethod.Substitution;
+ EntityHandle IEntity.MetadataToken => underlyingMethod.MetadataToken;
+ public string Name => underlyingMethod.Name;
+ IType IEntity.DeclaringType => underlyingMethod.DeclaringType;
+ ITypeDefinition IEntity.DeclaringTypeDefinition => underlyingMethod.DeclaringTypeDefinition;
+ IModule IEntity.ParentModule => underlyingMethod.ParentModule;
+ Accessibility IEntity.Accessibility => underlyingMethod.Accessibility;
+ bool IEntity.IsStatic => underlyingMethod.IsStatic;
+ bool IEntity.IsAbstract => underlyingMethod.IsAbstract;
+ bool IEntity.IsSealed => underlyingMethod.IsSealed;
+ SymbolKind ISymbol.SymbolKind => SymbolKind.Method;
+ ICompilation ICompilationProvider.Compilation => underlyingMethod.Compilation;
+ string INamedElement.FullName => underlyingMethod.FullName;
+ string INamedElement.ReflectionName => underlyingMethod.ReflectionName;
+ string INamedElement.Namespace => underlyingMethod.Namespace;
+
+ public override bool Equals(object obj)
+ {
+ return obj is SyntheticRangeIndexAccessor g
+ && this.underlyingMethod.Equals(g.underlyingMethod)
+ && this.indexOrRangeType.Equals(g.indexOrRangeType)
+ && this.slicing == g.slicing;
+ }
+
+ public override int GetHashCode()
+ {
+ return underlyingMethod.GetHashCode() ^ indexOrRangeType.GetHashCode();
+ }
+
+ bool IMember.Equals(IMember obj, TypeVisitor typeNormalization)
+ {
+ return obj is SyntheticRangeIndexAccessor g
+ && this.underlyingMethod.Equals(g.underlyingMethod, typeNormalization)
+ && this.indexOrRangeType.AcceptVisitor(typeNormalization).Equals(g.indexOrRangeType.AcceptVisitor(typeNormalization));
+ }
+
+ IEnumerable IEntity.GetAttributes() => underlyingMethod.GetAttributes();
+
+ IEnumerable IMethod.GetReturnTypeAttributes() => underlyingMethod.GetReturnTypeAttributes();
+
+ IMethod IMethod.Specialize(TypeParameterSubstitution substitution)
+ {
+ return new SyntheticRangeIndexAccessor(underlyingMethod.Specialize(substitution), indexOrRangeType, slicing);
+ }
+
+ IMember IMember.Specialize(TypeParameterSubstitution substitution)
+ {
+ return new SyntheticRangeIndexAccessor(underlyingMethod.Specialize(substitution), indexOrRangeType, slicing);
+ }
+ }
+}
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 00e102b8d..10b87ca5c 100644
--- a/ILSpy/Properties/Resources.Designer.cs
+++ b/ILSpy/Properties/Resources.Designer.cs
@@ -938,6 +938,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 45e1d4246..d89524790 100644
--- a/ILSpy/Properties/Resources.resx
+++ b/ILSpy/Properties/Resources.resx
@@ -852,6 +852,9 @@ Do you want to continue?
Do you want to continue?
+
+ Ranges
+
Add preconfigured list...