Browse Source

Fix translation of generic and extension 'Add' methods in collection initializers.

pull/1213/head
Siegfried Pammer 8 years ago
parent
commit
cf8bee2c01
  1. 55
      ICSharpCode.Decompiler.Tests/TestCases/Pretty/InitializerTests.cs
  2. 891
      ICSharpCode.Decompiler.Tests/TestCases/Pretty/InitializerTests.il
  3. 846
      ICSharpCode.Decompiler.Tests/TestCases/Pretty/InitializerTests.opt.il
  4. 890
      ICSharpCode.Decompiler.Tests/TestCases/Pretty/InitializerTests.opt.roslyn.il
  5. 935
      ICSharpCode.Decompiler.Tests/TestCases/Pretty/InitializerTests.roslyn.il
  6. 17
      ICSharpCode.Decompiler/CSharp/ExpressionBuilder.cs
  7. 20
      ICSharpCode.Decompiler/CSharp/Transforms/IntroduceExtensionMethods.cs
  8. 17
      ICSharpCode.Decompiler/DecompilerSettings.cs
  9. 22
      ICSharpCode.Decompiler/IL/Transforms/NullPropagationTransform.cs
  10. 39
      ICSharpCode.Decompiler/IL/Transforms/TransformCollectionAndObjectInitializers.cs

55
ICSharpCode.Decompiler.Tests/TestCases/Pretty/InitializerTests.cs

@ -17,16 +17,42 @@ @@ -17,16 +17,42 @@
// DEALINGS IN THE SOFTWARE.
using System;
using System.Collections;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Threading;
namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty
namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty.InitializerTests
{
public class InitializerTests
public static class Extensions
{
public static void Add(this TestCases.CustomList<int> inst, int a, int b)
{
}
}
public class TestCases
{
#region Types and helpers
public class CustomList<T> : IEnumerable<T>, IEnumerable
{
public IEnumerator<T> GetEnumerator()
{
throw new NotImplementedException();
}
IEnumerator IEnumerable.GetEnumerator()
{
throw new NotImplementedException();
}
public void Add<T2>(string name)
{
new Dictionary<string, Type>().Add(name, typeof(T2));
}
}
public class C
{
public int Z;
@ -206,9 +232,11 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty @@ -206,9 +232,11 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty
public static void InvalidIndices2(int a)
{
#pragma warning disable 251
int[] array = new int[1];
array[-1] = a;
X(Y(), array);
#pragma warning restore
}
public static void IndicesInWrongOrder(int a, int b)
@ -219,6 +247,29 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty @@ -219,6 +247,29 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty
X(Y(), array);
}
public static CustomList<int> ExtensionMethodInCollectionInitializer()
{
#if CS60
return new CustomList<int> {
{
1,
2
}
};
#else
CustomList<int> customList = new CustomList<int>();
customList.Add(1, 2);
return customList;
#endif
}
public static void NoCollectionInitializerBecauseOfTypeArguments()
{
CustomList<int> customList = new CustomList<int>();
customList.Add<int>("int");
Console.WriteLine(customList);
}
public static void CollectionInitializerList()
{
X(Y(), new List<int> {

891
ICSharpCode.Decompiler.Tests/TestCases/Pretty/InitializerTests.il

File diff suppressed because it is too large Load Diff

846
ICSharpCode.Decompiler.Tests/TestCases/Pretty/InitializerTests.opt.il

File diff suppressed because it is too large Load Diff

890
ICSharpCode.Decompiler.Tests/TestCases/Pretty/InitializerTests.opt.roslyn.il

File diff suppressed because it is too large Load Diff

935
ICSharpCode.Decompiler.Tests/TestCases/Pretty/InitializerTests.roslyn.il

File diff suppressed because it is too large Load Diff

17
ICSharpCode.Decompiler/CSharp/ExpressionBuilder.cs

@ -1989,7 +1989,7 @@ namespace ICSharpCode.Decompiler.CSharp @@ -1989,7 +1989,7 @@ namespace ICSharpCode.Decompiler.CSharp
indexVariables.Add(indexStore.Variable, indexStore.Value);
continue;
}
var info = IL.Transforms.AccessPathElement.GetAccessPath(inst, initObjRR.Type, allowDictionaryInitializer: settings.DictionaryInitializers);
var info = IL.Transforms.AccessPathElement.GetAccessPath(inst, initObjRR.Type, settings: settings);
if (info.Kind == IL.Transforms.AccessPathKind.Invalid) continue;
if (currentPath == null) {
currentPath = info.Path;
@ -2012,7 +2012,7 @@ namespace ICSharpCode.Decompiler.CSharp @@ -2012,7 +2012,7 @@ namespace ICSharpCode.Decompiler.CSharp
var memberRR = new MemberResolveResult(initObjRR, lastElement.Member);
switch (info.Kind) {
case IL.Transforms.AccessPathKind.Adder:
elementsStack.Peek().Add(MakeInitializerElements(info.Values, ((IMethod)lastElement.Member).Parameters));
elementsStack.Peek().Add(MakeInitializerElements(lastElement.OpCode, (IMethod)lastElement.Member, initObjRR, info.Values));
break;
case IL.Transforms.AccessPathKind.Setter:
if (lastElement.Indices?.Length > 0) {
@ -2062,20 +2062,19 @@ namespace ICSharpCode.Decompiler.CSharp @@ -2062,20 +2062,19 @@ namespace ICSharpCode.Decompiler.CSharp
}
}
Expression MakeInitializerElements(List<ILInstruction> values, IReadOnlyList<IParameter> parameters)
Expression MakeInitializerElements(OpCode opCode, IMethod method, ResolveResult targetResolveResult, List<ILInstruction> values)
{
int firstParameterOffset = method.IsExtensionMethod ? 1 : 0;
if (values.Count == 1) {
return Translate(values[0], typeHint: parameters[0].Type).ConvertTo(parameters[0].Type, this);
return Translate(values[0], typeHint: method.Parameters[firstParameterOffset].Type).ConvertTo(method.Parameters[0].Type, this);
}
var expressions = new Expression[values.Count];
for (int i = 0; i < values.Count; i++) {
expressions[i] = Translate(values[i], typeHint: parameters[i].Type).ConvertTo(parameters[i].Type, this);
expressions[i] = Translate(values[i], typeHint: method.Parameters[i + firstParameterOffset].Type).ConvertTo(method.Parameters[i + firstParameterOffset].Type, this);
}
return new ArrayInitializerExpression(expressions);
}
readonly static ArraySpecifier[] NoSpecifiers = new ArraySpecifier[0];
TranslatedExpression TranslateArrayInitializer(Block block)
{
var stloc = block.Instructions.FirstOrDefault() as StLoc;
@ -2129,7 +2128,7 @@ namespace ICSharpCode.Decompiler.CSharp @@ -2129,7 +2128,7 @@ namespace ICSharpCode.Decompiler.CSharp
additionalSpecifiers = compType.ArraySpecifiers.Select(a => (ArraySpecifier)a.Clone()).ToArray();
compType.ArraySpecifiers.Clear();
} else {
additionalSpecifiers = NoSpecifiers;
additionalSpecifiers = Empty<ArraySpecifier>.Array;
}
}
var expr = new ArrayCreateExpression {
@ -2137,7 +2136,7 @@ namespace ICSharpCode.Decompiler.CSharp @@ -2137,7 +2136,7 @@ namespace ICSharpCode.Decompiler.CSharp
Initializer = root
};
expr.AdditionalArraySpecifiers.AddRange(additionalSpecifiers);
if (!(bool)type.ContainsAnonymousType())
if (!type.ContainsAnonymousType())
expr.Arguments.AddRange(newArr.Indices.Select(i => Translate(i).Expression));
return expr.WithILInstruction(block)
.WithRR(new ArrayCreateResolveResult(new ArrayType(compilation, type, dimensions), newArr.Indices.Select(i => Translate(i).ResolveResult).ToArray(), elementResolveResults));

20
ICSharpCode.Decompiler/CSharp/Transforms/IntroduceExtensionMethods.cs

@ -158,5 +158,25 @@ namespace ICSharpCode.Decompiler.CSharp.Transforms @@ -158,5 +158,25 @@ namespace ICSharpCode.Decompiler.CSharp.Transforms
return false;
return method.Equals(or.GetBestCandidateWithSubstitutedTypeArguments());
}
public static bool CanTransformToExtensionMethodCall(IMethod method, CSharpTypeResolveContext resolveContext, bool ignoreTypeArguments = false, bool ignoreArgumentNames = true)
{
if (method.Parameters.Count == 0) return false;
var targetType = method.Parameters.Select(p => new ResolveResult(p.Type)).First();
var paramTypes = method.Parameters.Skip(1).Select(p => new ResolveResult(p.Type)).ToArray();
var paramNames = ignoreArgumentNames ? null : method.Parameters.SelectArray(p => p.Name);
var typeArgs = ignoreTypeArguments ? Empty<IType>.Array : method.TypeArguments.ToArray();
var resolver = new CSharpResolver(resolveContext);
return CanTransformToExtensionMethodCall(resolver, method, typeArgs, targetType, paramTypes, argumentNames: paramNames);
}
public static bool CanInferTypeArgumentsFromParameters(IMethod method, IReadOnlyList<ResolveResult> arguments, ITypeResolveContext resolveContext)
{
if (method.TypeParameters.Count == 0)
return true;
var inference = new TypeInference(resolveContext.Compilation);
inference.InferTypeArguments(method.TypeParameters, arguments, method.Parameters.SelectArray(p => p.Type), out bool success);
return success;
}
}
}

17
ICSharpCode.Decompiler/DecompilerSettings.cs

@ -73,6 +73,7 @@ namespace ICSharpCode.Decompiler @@ -73,6 +73,7 @@ namespace ICSharpCode.Decompiler
nullPropagation = false;
stringInterpolation = false;
dictionaryInitializers = false;
extensionMethodsInCollectionInitializers = false;
}
if (languageVersion < CSharp.LanguageVersion.CSharp7) {
outVariables = false;
@ -478,6 +479,22 @@ namespace ICSharpCode.Decompiler @@ -478,6 +479,22 @@ namespace ICSharpCode.Decompiler
}
}
bool extensionMethodsInCollectionInitializers = true;
/// <summary>
/// Gets/Sets whether to use C# 6.0 Extension Add methods in collection initializers.
/// Only has an effect if ObjectOrCollectionInitializers is enabled.
/// </summary>
public bool ExtensionMethodsInCollectionInitializers {
get { return extensionMethodsInCollectionInitializers; }
set {
if (extensionMethodsInCollectionInitializers != value) {
extensionMethodsInCollectionInitializers = value;
OnPropertyChanged();
}
}
}
bool stringInterpolation = true;
/// <summary>

22
ICSharpCode.Decompiler/IL/Transforms/NullPropagationTransform.cs

@ -197,7 +197,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms @@ -197,7 +197,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms
if (call.Arguments.Count == 0) {
return false;
}
if (call.Method.IsStatic && (!call.Method.IsExtensionMethod || !CanTransformToExtensionMethodCall(call))) {
if (call.Method.IsStatic && (!call.Method.IsExtensionMethod || !CanTransformToExtensionMethodCall(call, context))) {
return false; // only instance or extension methods can be called with ?. syntax
}
if (call.Method.IsAccessor && !IsGetter(call.Method)) {
@ -255,19 +255,15 @@ namespace ICSharpCode.Decompiler.IL.Transforms @@ -255,19 +255,15 @@ namespace ICSharpCode.Decompiler.IL.Transforms
throw new ArgumentOutOfRangeException("mode");
}
}
}
bool CanTransformToExtensionMethodCall(CallInstruction call)
{
if (call.Method.Parameters.Count == 0) return false;
var targetType = call.Method.Parameters.Select(p => new ResolveResult(p.Type)).First();
var paramTypes = call.Method.Parameters.Skip(1).Select(p => new ResolveResult(p.Type)).ToArray();
var paramNames = call.Method.Parameters.SelectArray(p => p.Name);
var typeArgs = call.Method.TypeArguments.ToArray();
var resolveContext = new CSharp.TypeSystem.CSharpTypeResolveContext(context.TypeSystem.Compilation.MainAssembly, context.UsingScope);
var resolver = new CSharp.Resolver.CSharpResolver(resolveContext);
return CSharp.Transforms.IntroduceExtensionMethods.CanTransformToExtensionMethodCall(
resolver, call.Method, typeArgs, targetType, paramTypes, argumentNames: null);
bool CanTransformToExtensionMethodCall(CallInstruction call, ILTransformContext context)
{
return CSharp.Transforms.IntroduceExtensionMethods.CanTransformToExtensionMethodCall(
call.Method, new CSharp.TypeSystem.CSharpTypeResolveContext(
context.TypeSystem.Compilation.MainAssembly, context.UsingScope
)
);
}
}
static bool IsGetter(IMethod method)

39
ICSharpCode.Decompiler/IL/Transforms/TransformCollectionAndObjectInitializers.cs

@ -19,6 +19,9 @@ @@ -19,6 +19,9 @@
using System;
using System.Collections.Generic;
using System.Linq;
using ICSharpCode.Decompiler.CSharp.Resolver;
using ICSharpCode.Decompiler.CSharp.TypeSystem;
using ICSharpCode.Decompiler.Semantics;
using ICSharpCode.Decompiler.TypeSystem;
using ICSharpCode.Decompiler.Util;
@ -175,7 +178,8 @@ namespace ICSharpCode.Decompiler.IL.Transforms @@ -175,7 +178,8 @@ namespace ICSharpCode.Decompiler.IL.Transforms
possibleIndexVariables.Add(stloc.Variable, (stloc.ChildIndex, stloc.Value));
return true;
}
(var kind, var newPath, var values, var targetVariable) = AccessPathElement.GetAccessPath(instructions[pos], rootType, possibleIndexVariables, allowDictionaryInitializer: context.Settings.DictionaryInitializers);
var resolveContext = new CSharpTypeResolveContext(context.TypeSystem.Compilation.MainAssembly, context.UsingScope);
(var kind, var newPath, var values, var targetVariable) = AccessPathElement.GetAccessPath(instructions[pos], rootType, context.Settings, resolveContext, possibleIndexVariables);
if (kind == AccessPathKind.Invalid || target != targetVariable)
return false;
// Treat last element separately:
@ -228,19 +232,23 @@ namespace ICSharpCode.Decompiler.IL.Transforms @@ -228,19 +232,23 @@ namespace ICSharpCode.Decompiler.IL.Transforms
public struct AccessPathElement : IEquatable<AccessPathElement>
{
public AccessPathElement(IMember member, ILInstruction[] indices = null)
public AccessPathElement(OpCode opCode, IMember member, ILInstruction[] indices = null)
{
this.OpCode = opCode;
this.Member = member;
this.Indices = indices;
}
public readonly OpCode OpCode;
public readonly IMember Member;
public readonly ILInstruction[] Indices;
public override string ToString() => $"[{Member}, {Indices}]";
public static (AccessPathKind Kind, List<AccessPathElement> Path, List<ILInstruction> Values, ILVariable Target) GetAccessPath(
ILInstruction instruction, IType rootType, Dictionary<ILVariable, (int Index, ILInstruction Value)> possibleIndexVariables = null, bool allowDictionaryInitializer = false)
ILInstruction instruction, IType rootType, DecompilerSettings settings,
CSharpTypeResolveContext resolveContext = null,
Dictionary<ILVariable, (int Index, ILInstruction Value)> possibleIndexVariables = null)
{
List<AccessPathElement> path = new List<AccessPathElement>();
ILVariable target = null;
@ -253,13 +261,13 @@ namespace ICSharpCode.Decompiler.IL.Transforms @@ -253,13 +261,13 @@ namespace ICSharpCode.Decompiler.IL.Transforms
case CallInstruction call:
if (!(call is CallVirt || call is Call)) goto default;
method = call.Method;
if (!IsMethodApplicable(method, call.Arguments, rootType)) goto default;
if (resolveContext != null && !IsMethodApplicable(method, call.Arguments, rootType, resolveContext, settings)) goto default;
instruction = call.Arguments[0];
if (method.IsAccessor) {
var property = method.AccessorOwner as IProperty;
var isGetter = method.Equals(property?.Getter);
var indices = call.Arguments.Skip(1).Take(call.Arguments.Count - (isGetter ? 1 : 2)).ToArray();
if (indices.Length > 0 && !allowDictionaryInitializer) goto default;
if (indices.Length > 0 && !settings.DictionaryInitializers) goto default;
if (possibleIndexVariables != null) {
// Mark all index variables as used
foreach (var index in indices.OfType<IInstructionWithVariableOperand>()) {
@ -267,9 +275,9 @@ namespace ICSharpCode.Decompiler.IL.Transforms @@ -267,9 +275,9 @@ namespace ICSharpCode.Decompiler.IL.Transforms
possibleIndexVariables[index.Variable] = (-1, info.Value);
}
}
path.Insert(0, new AccessPathElement(method.AccessorOwner, indices));
path.Insert(0, new AccessPathElement(call.OpCode, method.AccessorOwner, indices));
} else {
path.Insert(0, new AccessPathElement(method));
path.Insert(0, new AccessPathElement(call.OpCode, method));
}
if (values == null) {
if (method.IsAccessor) {
@ -285,7 +293,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms @@ -285,7 +293,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms
break;
case LdObj ldobj: {
if (ldobj.Target is LdFlda ldflda) {
path.Insert(0, new AccessPathElement(ldflda.Field));
path.Insert(0, new AccessPathElement(ldobj.OpCode, ldflda.Field));
instruction = ldflda.Target;
break;
}
@ -293,7 +301,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms @@ -293,7 +301,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms
}
case StObj stobj: {
if (stobj.Target is LdFlda ldflda) {
path.Insert(0, new AccessPathElement(ldflda.Field));
path.Insert(0, new AccessPathElement(stobj.OpCode, ldflda.Field));
instruction = ldflda.Target;
if (values == null) {
values = new List<ILInstruction>(new[] { stobj.Value });
@ -312,7 +320,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms @@ -312,7 +320,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms
instruction = null;
break;
case LdFlda ldflda:
path.Insert(0, new AccessPathElement(ldflda.Field));
path.Insert(0, new AccessPathElement(ldflda.OpCode, ldflda.Field));
instruction = ldflda.Target;
break;
default:
@ -326,18 +334,23 @@ namespace ICSharpCode.Decompiler.IL.Transforms @@ -326,18 +334,23 @@ namespace ICSharpCode.Decompiler.IL.Transforms
return (kind, path, values, target);
}
static bool IsMethodApplicable(IMethod method, IList<ILInstruction> arguments, IType rootType)
static bool IsMethodApplicable(IMethod method, IReadOnlyList<ILInstruction> arguments, IType rootType, CSharpTypeResolveContext resolveContext, DecompilerSettings settings)
{
if (!method.IsExtensionMethod && method.IsStatic)
if (method.IsStatic && !method.IsExtensionMethod)
return false;
if (method.IsAccessor)
return true;
if (!"Add".Equals(method.Name, StringComparison.Ordinal) || arguments.Count == 0)
return false;
if (method.IsExtensionMethod)
return settings.ExtensionMethodsInCollectionInitializers
&& CSharp.Transforms.IntroduceExtensionMethods.CanTransformToExtensionMethodCall(method, resolveContext, ignoreTypeArguments: true);
var targetType = GetReturnTypeFromInstruction(arguments[0]) ?? rootType;
if (targetType == null)
return false;
return targetType.GetAllBaseTypes().Any(i => i.IsKnownType(KnownTypeCode.IEnumerable) || i.IsKnownType(KnownTypeCode.IEnumerableOfT));
if (!targetType.GetAllBaseTypes().Any(i => i.IsKnownType(KnownTypeCode.IEnumerable) || i.IsKnownType(KnownTypeCode.IEnumerableOfT)))
return false;
return CSharp.Transforms.IntroduceExtensionMethods.CanInferTypeArgumentsFromParameters(method, method.Parameters.SelectArray(p => new ResolveResult(p.Type)), resolveContext);
}
static IType GetReturnTypeFromInstruction(ILInstruction instruction)

Loading…
Cancel
Save