Browse Source

Add support for C# 6 dictionary initializer.

pull/844/head
Siegfried Pammer 8 years ago
parent
commit
7ea511ef32
  1. 30
      ICSharpCode.Decompiler.Tests/TestCases/Correctness/InitializerTests.cs
  2. 25
      ICSharpCode.Decompiler/CSharp/ExpressionBuilder.cs
  3. 88
      ICSharpCode.Decompiler/IL/Transforms/TransformCollectionAndObjectInitializers.cs

30
ICSharpCode.Decompiler.Tests/TestCases/Correctness/InitializerTests.cs

@ -84,6 +84,13 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Correctness
} }
set { } set { }
} }
public InitializerTests.Data this[int i, string j] {
get {
return null;
}
set { }
}
} }
private struct StructData private struct StructData
@ -531,12 +538,28 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Correctness
}); });
} }
#if false
static int GetInt() static int GetInt()
{ {
return 1; return 1;
} }
static string GetString()
{
return "Test";
}
#if !LEGACY_CSC
public static void SimpleDictInitializer()
{
InitializerTests.X(InitializerTests.Y(), new InitializerTests.Data {
MoreData =
{
a = InitializerTests.MyEnum.a,
[2] = (Data)null
}
});
}
public static void MixedObjectAndDictInitializer() public static void MixedObjectAndDictInitializer()
{ {
InitializerTests.X(InitializerTests.Y(), new InitializerTests.Data { InitializerTests.X(InitializerTests.Y(), new InitializerTests.Data {
@ -546,8 +569,9 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Correctness
[GetInt()] = { [GetInt()] = {
a = InitializerTests.MyEnum.b, a = InitializerTests.MyEnum.b,
FieldList = { MyEnum2.c }, FieldList = { MyEnum2.c },
[2] = null [GetInt(), GetString()] = new Data(),
}, [2] = (Data)null
}
} }
}); });
} }

25
ICSharpCode.Decompiler/CSharp/ExpressionBuilder.cs

@ -1448,7 +1448,12 @@ namespace ICSharpCode.Decompiler.CSharp
var elements = new List<Expression>(block.Instructions.Count); var elements = new List<Expression>(block.Instructions.Count);
elementsStack.Push(elements); elementsStack.Push(elements);
List<AccessPathElement> currentPath = null; List<AccessPathElement> currentPath = null;
var indexVariables = new Dictionary<ILVariable, ILInstruction>();
foreach (var inst in block.Instructions.Skip(1)) { foreach (var inst in block.Instructions.Skip(1)) {
if (inst is StLoc indexStore) {
indexVariables.Add(indexStore.Variable, indexStore.Value);
continue;
}
var info = AccessPathElement.GetAccessPath(inst, initObjRR.Type); var info = AccessPathElement.GetAccessPath(inst, initObjRR.Type);
if (info.Kind == AccessPathKind.Invalid) continue; if (info.Kind == AccessPathKind.Invalid) continue;
if (currentPath == null) { if (currentPath == null) {
@ -1463,7 +1468,7 @@ namespace ICSharpCode.Decompiler.CSharp
var methodElement = currentPath[elementsStack.Count - 1]; var methodElement = currentPath[elementsStack.Count - 1];
var pathElement = currentPath[elementsStack.Count - 2]; var pathElement = currentPath[elementsStack.Count - 2];
var values = elementsStack.Pop(); var values = elementsStack.Pop();
elementsStack.Peek().Add(MakeInitializerAssignment(methodElement.Member, pathElement.Member, values)); elementsStack.Peek().Add(MakeInitializerAssignment(methodElement.Member, pathElement, values, indexVariables));
} }
currentPath = info.Path; currentPath = info.Path;
} }
@ -1476,9 +1481,15 @@ namespace ICSharpCode.Decompiler.CSharp
elementsStack.Peek().Add(MakeInitializerElements(info.Values, ((IMethod)lastElement.Member).Parameters)); elementsStack.Peek().Add(MakeInitializerElements(info.Values, ((IMethod)lastElement.Member).Parameters));
break; break;
case AccessPathKind.Setter: case AccessPathKind.Setter:
var target = new IdentifierExpression(lastElement.Member.Name) if (lastElement.Indices?.Length > 0) {
.WithILInstruction(inst).WithRR(memberRR); var indexer = new IndexerExpression(null, lastElement.Indices.SelectArray(i => Translate(i is LdLoc ld ? indexVariables[ld.Variable] : i).Expression))
elementsStack.Peek().Add(Assignment(target, Translate(info.Values.Single()))); .WithILInstruction(inst).WithRR(memberRR);
elementsStack.Peek().Add(Assignment(indexer, Translate(info.Values.Single())));
} else {
var target = new IdentifierExpression(lastElement.Member.Name)
.WithILInstruction(inst).WithRR(memberRR);
elementsStack.Peek().Add(Assignment(target, Translate(info.Values.Single())));
}
break; break;
} }
} }
@ -1486,16 +1497,16 @@ namespace ICSharpCode.Decompiler.CSharp
var methodElement = currentPath[elementsStack.Count - 1]; var methodElement = currentPath[elementsStack.Count - 1];
var pathElement = currentPath[elementsStack.Count - 2]; var pathElement = currentPath[elementsStack.Count - 2];
var values = elementsStack.Pop(); var values = elementsStack.Pop();
elementsStack.Peek().Add(MakeInitializerAssignment(methodElement.Member, pathElement.Member, values)); elementsStack.Peek().Add(MakeInitializerAssignment(methodElement.Member, pathElement, values, indexVariables));
} }
var oce = (ObjectCreateExpression)expr.Expression; var oce = (ObjectCreateExpression)expr.Expression;
oce.Initializer = new ArrayInitializerExpression(elements); oce.Initializer = new ArrayInitializerExpression(elements);
return expr.WithILInstruction(block); return expr.WithILInstruction(block);
} }
Expression MakeInitializerAssignment(IMember method, IMember member, List<Expression> values) Expression MakeInitializerAssignment(IMember method, AccessPathElement member, List<Expression> values, Dictionary<ILVariable, ILInstruction> indexVariables)
{ {
var target = new IdentifierExpression(member.Name); var target = member.Indices?.Length > 0 ? (Expression)new IndexerExpression(null, member.Indices.SelectArray(i => Translate(i is LdLoc ld ? indexVariables[ld.Variable] : i).Expression)) : new IdentifierExpression(member.Member.Name);
Expression value; Expression value;
if (values.Count == 1 && !(values[0] is AssignmentExpression) && !(method.SymbolKind == SymbolKind.Method && method.Name == "Add")) if (values.Count == 1 && !(values[0] is AssignmentExpression) && !(method.SymbolKind == SymbolKind.Method && method.Name == "Add"))
value = values[0]; value = values[0];

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

@ -83,14 +83,20 @@ namespace ICSharpCode.Decompiler.IL.Transforms
context.Step("CollectionOrObjectInitializer", inst); context.Step("CollectionOrObjectInitializer", inst);
int initializerItemsCount = 0; int initializerItemsCount = 0;
var blockType = initializerBlock?.Type ?? BlockType.CollectionInitializer; var blockType = initializerBlock?.Type ?? BlockType.CollectionInitializer;
var possibleIndexVariables = new Dictionary<ILVariable, (int Index, ILInstruction Value)>();
// Detect initializer type by scanning the following statements // Detect initializer type by scanning the following statements
// each must be a callvirt with ldloc v as first argument // each must be a callvirt with ldloc v as first argument
// if the method is a setter we're dealing with an object initializer // if the method is a setter we're dealing with an object initializer
// if the method is named Add and has at least 2 arguments we're dealing with a collection/dictionary initializer // if the method is named Add and has at least 2 arguments we're dealing with a collection/dictionary initializer
while (pos + initializerItemsCount + 1 < body.Instructions.Count while (pos + initializerItemsCount + 1 < body.Instructions.Count
&& IsPartOfInitializer(body.Instructions, pos + initializerItemsCount + 1, v, instType, ref blockType)) && IsPartOfInitializer(body.Instructions, pos + initializerItemsCount + 1, v, instType, ref blockType, possibleIndexVariables)) {
initializerItemsCount++; initializerItemsCount++;
if (initializerItemsCount == 0) }
var index = possibleIndexVariables.Where(info => info.Value.Index > -1).Min(info => (int?)info.Value.Index);
if (index != null) {
initializerItemsCount = index.Value - pos - 1;
}
if (initializerItemsCount <= 0)
return false; return false;
ILVariable finalSlot; ILVariable finalSlot;
if (initializerBlock == null) { if (initializerBlock == null) {
@ -108,17 +114,21 @@ namespace ICSharpCode.Decompiler.IL.Transforms
var newCall = (CallInstruction)call.Clone(); var newCall = (CallInstruction)call.Clone();
var newTarget = newCall.Arguments[0]; var newTarget = newCall.Arguments[0];
foreach (var load in newTarget.Descendants.OfType<IInstructionWithVariableOperand>()) foreach (var load in newTarget.Descendants.OfType<IInstructionWithVariableOperand>())
if (load is LdLoc || load is LdLoca) if ((load is LdLoc || load is LdLoca) && load.Variable == v)
load.Variable = finalSlot; load.Variable = finalSlot;
initializerBlock.Instructions.Add(newCall); initializerBlock.Instructions.Add(newCall);
break; break;
case StObj stObj: case StObj stObj:
var newStObj = (StObj)stObj.Clone(); var newStObj = (StObj)stObj.Clone();
foreach (var load in newStObj.Target.Descendants.OfType<IInstructionWithVariableOperand>()) foreach (var load in newStObj.Target.Descendants.OfType<IInstructionWithVariableOperand>())
if (load is LdLoc || load is LdLoca) if ((load is LdLoc || load is LdLoca) && load.Variable == v)
load.Variable = finalSlot; load.Variable = finalSlot;
initializerBlock.Instructions.Add(newStObj); initializerBlock.Instructions.Add(newStObj);
break; break;
case StLoc stLoc:
var newStLoc = (StLoc)stLoc.Clone();
initializerBlock.Instructions.Add(newStLoc);
break;
} }
} }
initInst.ReplaceWith(initializerBlock); initInst.ReplaceWith(initializerBlock);
@ -129,9 +139,15 @@ namespace ICSharpCode.Decompiler.IL.Transforms
return true; return true;
} }
bool IsPartOfInitializer(InstructionCollection<ILInstruction> instructions, int pos, ILVariable target, IType rootType, ref BlockType blockType) bool IsPartOfInitializer(InstructionCollection<ILInstruction> instructions, int pos, ILVariable target, IType rootType, ref BlockType blockType, Dictionary<ILVariable, (int Index, ILInstruction Value)> possibleIndexVariables)
{ {
(var kind, var path, var values, var targetVariable) = AccessPathElement.GetAccessPath(instructions[pos], rootType); if (instructions[pos] is StLoc stloc && stloc.Variable.Kind == VariableKind.Local && stloc.Variable.IsSingleDefinition) {
if (stloc.Value.Descendants.OfType<IInstructionWithVariableOperand>().Any(ld => ld.Variable == target && (ld is LdLoc || ld is LdLoca)))
return false;
possibleIndexVariables.Add(stloc.Variable, (stloc.ChildIndex, stloc.Value));
return true;
}
(var kind, var path, var values, var targetVariable) = AccessPathElement.GetAccessPath(instructions[pos], rootType, possibleIndexVariables);
switch (kind) { switch (kind) {
case AccessPathKind.Adder: case AccessPathKind.Adder:
return target == targetVariable; return target == targetVariable;
@ -156,24 +172,25 @@ namespace ICSharpCode.Decompiler.IL.Transforms
public struct AccessPathElement : IEquatable<AccessPathElement> public struct AccessPathElement : IEquatable<AccessPathElement>
{ {
public AccessPathElement(IMember member, ILVariable index = null) public AccessPathElement(IMember member, ILInstruction[] indices = null)
{ {
this.Member = member; this.Member = member;
this.Index = index; this.Indices = indices;
} }
public readonly IMember Member; public readonly IMember Member;
public readonly ILVariable Index; public readonly ILInstruction[] Indices;
public override string ToString() => $"[{Member}, {Index}]"; public override string ToString() => $"[{Member}, {Indices}]";
public static (AccessPathKind Kind, List<AccessPathElement> Path, List<ILInstruction> Values, ILVariable Target) GetAccessPath(ILInstruction instruction, IType rootType) 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)
{ {
List<AccessPathElement> path = new List<AccessPathElement>(); List<AccessPathElement> path = new List<AccessPathElement>();
ILVariable target = null; ILVariable target = null;
AccessPathKind kind = AccessPathKind.Invalid; AccessPathKind kind = AccessPathKind.Invalid;
List<ILInstruction> values = null; List<ILInstruction> values = null;
IMethod method; IMethod method;
var inst = instruction;
while (instruction != null) { while (instruction != null) {
switch (instruction) { switch (instruction) {
case CallInstruction call: case CallInstruction call:
@ -181,21 +198,31 @@ namespace ICSharpCode.Decompiler.IL.Transforms
method = call.Method; method = call.Method;
if (!IsMethodApplicable(method, call.Arguments, rootType)) goto default; if (!IsMethodApplicable(method, call.Arguments, rootType)) goto default;
instruction = call.Arguments[0]; instruction = call.Arguments[0];
if (method.IsAccessor) {
var property = (IProperty)method.AccessorOwner;
var isGetter = property.Getter == method;
var indices = call.Arguments.Skip(1).Take(call.Arguments.Count - (isGetter ? 1 : 2)).ToArray();
if (possibleIndexVariables != null) {
foreach (var index in indices.OfType<IInstructionWithVariableOperand>()) {
if (possibleIndexVariables.TryGetValue(index.Variable, out var info))
possibleIndexVariables[index.Variable] = (-1, info.Value);
}
}
path.Insert(0, new AccessPathElement(method.AccessorOwner, indices));
} else {
path.Insert(0, new AccessPathElement(method));
}
if (values == null) { if (values == null) {
values = new List<ILInstruction>(call.Arguments.Skip(1));
if (values.Count == 0)
goto default;
if (method.IsAccessor) { if (method.IsAccessor) {
kind = AccessPathKind.Setter; kind = AccessPathKind.Setter;
values = new List<ILInstruction> { call.Arguments.Last() };
} else { } else {
kind = AccessPathKind.Adder; kind = AccessPathKind.Adder;
values = new List<ILInstruction>(call.Arguments.Skip(1));
if (values.Count == 0)
goto default;
} }
} }
if (method.IsAccessor) {
path.Insert(0, new AccessPathElement(method.AccessorOwner));
} else {
path.Insert(0, new AccessPathElement(method));
}
break; break;
case LdObj ldobj: case LdObj ldobj:
if (ldobj.Target is LdFlda ldflda) { if (ldobj.Target is LdFlda ldflda) {
@ -280,15 +307,13 @@ namespace ICSharpCode.Decompiler.IL.Transforms
unchecked { unchecked {
if (Member != null) if (Member != null)
hashCode += 1000000007 * Member.GetHashCode(); hashCode += 1000000007 * Member.GetHashCode();
if (Index != null)
hashCode += 1000000009 * Index.GetHashCode();
} }
return hashCode; return hashCode;
} }
public bool Equals(AccessPathElement other) public bool Equals(AccessPathElement other)
{ {
return other.Member.Equals(this.Member) && other.Index == this.Index; return other.Member.Equals(this.Member) && (other.Indices == this.Indices || other.Indices.SequenceEqual(this.Indices, ILInstructionMatchComparer.Instance));
} }
public static bool operator ==(AccessPathElement lhs, AccessPathElement rhs) public static bool operator ==(AccessPathElement lhs, AccessPathElement rhs)
@ -301,4 +326,23 @@ namespace ICSharpCode.Decompiler.IL.Transforms
return !(lhs == rhs); return !(lhs == rhs);
} }
} }
class ILInstructionMatchComparer : IEqualityComparer<ILInstruction>
{
public static readonly ILInstructionMatchComparer Instance = new ILInstructionMatchComparer();
public bool Equals(ILInstruction x, ILInstruction y)
{
if (x == y)
return true;
if (x == null || y == null)
return false;
return x.Match(y).Success;
}
public int GetHashCode(ILInstruction obj)
{
return obj.GetHashCode();
}
}
} }

Loading…
Cancel
Save