Browse Source

Add support for indexing a container with a System.Index instance.

pull/1986/head
Daniel Grunwald 5 years ago
parent
commit
12226c5f90
  1. 8
      ICSharpCode.Decompiler.Tests/TestCases/Pretty/IndexRangeTest.cs
  2. 3
      ICSharpCode.Decompiler/CSharp/CSharpDecompiler.cs
  3. 1
      ICSharpCode.Decompiler/ICSharpCode.Decompiler.csproj
  4. 4
      ICSharpCode.Decompiler/IL/Transforms/ILInlining.cs
  5. 149
      ICSharpCode.Decompiler/IL/Transforms/IndexRangeTransform.cs
  6. 128
      ICSharpCode.Decompiler/TypeSystem/Implementation/SyntheticRangeIndexer.cs

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

@ -61,12 +61,10 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty @@ -61,12 +61,10 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty
public static void UseIndex()
{
Console.WriteLine(GetArray()[GetIndex()]);
#if TODO
Console.WriteLine(GetList()[GetIndex()]);
Console.WriteLine(GetSpan()[GetIndex()]);
Console.WriteLine(GetString()[GetIndex()]);
Console.WriteLine(new CustomList()[GetIndex()]);
#endif
Console.WriteLine(new CustomList2()[GetIndex()]);
}
@ -85,10 +83,8 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty @@ -85,10 +83,8 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty
public static void UseIndexForWrite()
{
GetArray()[GetIndex()] = GetInt();
#if TODO
GetList()[GetIndex()] = GetInt();
GetSpan()[GetIndex()] = GetInt();
#endif
}
private static void UseRef(ref int i)
@ -99,6 +95,10 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty @@ -99,6 +95,10 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty
{
UseRef(ref GetArray()[GetIndex()]);
UseRef(ref GetArray()[^GetInt()]);
UseRef(ref GetSpan()[GetIndex()]);
#if TODO
UseRef(ref GetSpan()[^GetInt()]);
#endif
}
public static void UseRange()

3
ICSharpCode.Decompiler/CSharp/CSharpDecompiler.cs

@ -150,7 +150,8 @@ namespace ICSharpCode.Decompiler.CSharp @@ -150,7 +150,8 @@ namespace ICSharpCode.Decompiler.CSharp
new TransformCollectionAndObjectInitializers(),
new TransformExpressionTrees(),
new NamedArgumentTransform(),
new UserDefinedLogicTransform()
new UserDefinedLogicTransform(),
new IndexRangeTransform()
),
}
},

1
ICSharpCode.Decompiler/ICSharpCode.Decompiler.csproj

@ -385,6 +385,7 @@ @@ -385,6 +385,7 @@
<Compile Include="TypeSystem\Implementation\MetadataTypeDefinition.cs" />
<Compile Include="TypeSystem\Implementation\MetadataTypeParameter.cs" />
<Compile Include="TypeSystem\Implementation\SpecializedParameter.cs" />
<Compile Include="TypeSystem\Implementation\SyntheticRangeIndexer.cs" />
<Compile Include="TypeSystem\Implementation\ThreeState.cs" />
<Compile Include="TypeSystem\MetadataModule.cs" />
<Compile Include="TypeSystem\ModifiedType.cs" />

4
ICSharpCode.Decompiler/IL/Transforms/ILInlining.cs

@ -21,6 +21,7 @@ using System.Diagnostics; @@ -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,9 @@ namespace ICSharpCode.Decompiler.IL.Transforms @@ -469,6 +470,9 @@ 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) {

149
ICSharpCode.Decompiler/IL/Transforms/IndexRangeTransform.cs

@ -16,6 +16,10 @@ @@ -16,6 +16,10 @@
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
// DEALINGS IN THE SOFTWARE.
using System;
using System.Linq;
using ICSharpCode.Decompiler.CSharp.Resolver;
using ICSharpCode.Decompiler.Semantics;
using ICSharpCode.Decompiler.TypeSystem;
using ICSharpCode.Decompiler.TypeSystem.Implementation;
@ -24,7 +28,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms @@ -24,7 +28,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms
/// <summary>
/// Transform for the C# 8 System.Index / System.Range feature
/// </summary>
class IndexRangeTransform
class IndexRangeTransform : IStatementTransform
{
/// <summary>
/// Called by expression transforms.
@ -85,5 +89,148 @@ namespace ICSharpCode.Decompiler.IL.Transforms @@ -85,5 +89,148 @@ namespace ICSharpCode.Decompiler.IL.Transforms
}
return null;
}
void IStatementTransform.Run(Block block, int pos, StatementTransformContext context)
{
int startPos = pos;
if (!MatchContainerLengthStore(block.Instructions[pos], out ILVariable containerLengthVar, out ILVariable containerVar))
return;
pos++;
if (!MatchGetOffsetFromIndex(block.Instructions[pos], out ILVariable startOffsetVar, out ILInstruction startIndexLdloca, containerLengthVar))
return;
pos++;
if (startOffsetVar.LoadCount == 1) {
// complex_expr(call get_Item(ldloc container, ldloc offset))
// 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 (!(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 {
return;
}
if (!call.Method.Parameters[0].Type.IsKnownType(KnownTypeCode.Int32))
return;
if (!call.Arguments[0].MatchLdLoc(containerVar) && !call.Arguments[0].MatchLdLoca(containerVar))
return;
if (!call.Arguments[1].MatchLdLoc(startOffsetVar))
return;
var indexType = context.TypeSystem.FindType(KnownTypeCode.Index);
if (!CSharpWillGenerateIndexer(call.Method.DeclaringType, indexType, context))
return;
// stloc length(call get_Length/ get_Count(ldloc container))
// stloc offset(call GetOffset(..., ldloc length))
// complex_expr(call get_Item(ldloc container, ldloc offset))
// -->
// complex_expr(call get_Item(ldloc container, ...))
context.Step($"{call.Method.Name} indexed with System.Index", call);
var newMethod = new SyntheticRangeIndexAccessor(call.Method, indexType);
var newCall = CallInstruction.Create(call.OpCode, newMethod);
newCall.ConstrainedTo = call.ConstrainedTo;
newCall.ILStackWasEmpty = call.ILStackWasEmpty;
newCall.Arguments.Add(call.Arguments[0]);
newCall.Arguments.Add(new LdObj(startIndexLdloca, indexType));
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);
}
}
/// <summary>
/// Gets whether the C# compiler will call `container[int]` when using `container[Index]`.
/// </summary>
private bool CSharpWillGenerateIndexer(IType declaringType, IType indexType, ILTransformContext context)
{
bool foundInt32Overload = false;
bool foundIndexOverload = 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 (prop.Name == "Length" || prop.Name=="Count") {
foundCountProperty = true;
}
}
return foundInt32Overload && foundCountProperty && !foundIndexOverload;
}
/// <summary>
/// Matches the instruction:
/// stloc containerLengthVar(call get_Length/get_Count(ldloc containerVar))
/// </summary>
static bool MatchContainerLengthStore(ILInstruction inst, out ILVariable lengthVar, out ILVariable containerVar)
{
containerVar = null;
if (!inst.MatchStLoc(out lengthVar, out var init))
return false;
if (!(lengthVar.IsSingleDefinition && lengthVar.StackType == StackType.I4))
return false;
if (!(init is CallInstruction call))
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 call.Arguments[0].MatchLdLoc(out containerVar) || call.Arguments[0].MatchLdLoca(out containerVar);
}
/// <summary>
/// Matches the instruction:
/// stloc offsetVar(call System.Index.GetOffset(indexLdloca, ldloc containerLengthVar))
/// </summary>
static bool MatchGetOffsetFromIndex(ILInstruction inst, out ILVariable offsetVar, out ILInstruction indexLdloca, ILVariable containerLengthVar)
{
indexLdloca = null;
if (!inst.MatchStLoc(out offsetVar, out var offsetValue))
return false;
if (!(offsetVar.IsSingleDefinition && offsetVar.StackType == StackType.I4))
return false;
if (!(offsetValue is CallInstruction call))
return false;
if (call.Method.Name != "GetOffset")
return false;
if (!call.Method.DeclaringType.IsKnownType(KnownTypeCode.Index))
return false;
if (call.Arguments.Count != 2)
return false;
indexLdloca = call.Arguments[0];
return call.Arguments[1].MatchLdLoc(containerLengthVar);
}
}
}

128
ICSharpCode.Decompiler/TypeSystem/Implementation/SyntheticRangeIndexer.cs

@ -0,0 +1,128 @@ @@ -0,0 +1,128 @@
// 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
{
/// <summary>
/// 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.
/// </summary>
class SyntheticRangeIndexAccessor : IMethod
{
/// <summary>
/// The underlying method: `get_Item(int)`, `set_Item(int, T)` or `Slice(int, int)`.
/// </summary>
readonly IMethod underlyingMethod;
readonly IType indexOrRangeType;
readonly IReadOnlyList<IParameter> parameters;
public SyntheticRangeIndexAccessor(IMethod underlyingMethod, IType indexOrRangeType)
{
Debug.Assert(underlyingMethod != null);
Debug.Assert(indexOrRangeType != null);
this.underlyingMethod = underlyingMethod;
this.indexOrRangeType = indexOrRangeType;
var parameters = new List<IParameter>();
parameters.Add(new DefaultParameter(indexOrRangeType, ""));
parameters.AddRange(underlyingMethod.Parameters.Skip(1));
this.parameters = parameters;
}
bool IMethod.ReturnTypeIsRefReadOnly => underlyingMethod.ReturnTypeIsRefReadOnly;
bool IMethod.ThisIsRefReadOnly => underlyingMethod.ThisIsRefReadOnly;
IReadOnlyList<ITypeParameter> IMethod.TypeParameters => EmptyList<ITypeParameter>.Instance;
IReadOnlyList<IType> IMethod.TypeArguments => EmptyList<IType>.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<IParameter> IParameterizedMember.Parameters => parameters;
IMember IMember.MemberDefinition => underlyingMethod.MemberDefinition;
IType IMember.ReturnType => underlyingMethod.ReturnType;
IEnumerable<IMember> IMember.ExplicitlyImplementedInterfaceMembers => EmptyList<IMember>.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);
}
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<IAttribute> IEntity.GetAttributes() => underlyingMethod.GetAttributes();
IEnumerable<IAttribute> IMethod.GetReturnTypeAttributes() => underlyingMethod.GetReturnTypeAttributes();
IMethod IMethod.Specialize(TypeParameterSubstitution substitution)
{
return new SyntheticRangeIndexAccessor(underlyingMethod.Specialize(substitution), indexOrRangeType);
}
IMember IMember.Specialize(TypeParameterSubstitution substitution)
{
return new SyntheticRangeIndexAccessor(underlyingMethod.Specialize(substitution), indexOrRangeType);
}
}
}
Loading…
Cancel
Save