Browse Source

foreach loops now use "var" if the element type is a tuple and can be inferred from the collection type.

Additionally, the tuple element names inferred from the collection type are now used when translating the foreach body.
pull/1198/head
Daniel Grunwald 7 years ago
parent
commit
64547de4a0
  1. 10
      ICSharpCode.Decompiler.Tests/TestCases/Pretty/TupleTests.cs
  2. 3
      ICSharpCode.Decompiler.Tests/TestCases/Pretty/TupleTests.opt.roslyn.il
  3. 3
      ICSharpCode.Decompiler.Tests/TestCases/Pretty/TupleTests.roslyn.il
  4. 117
      ICSharpCode.Decompiler/CSharp/Resolver/CSharpResolver.cs
  5. 24
      ICSharpCode.Decompiler/CSharp/StatementBuilder.cs
  6. 10
      ICSharpCode.Decompiler/Semantics/ForEachResolveResult.cs
  7. 7
      ILSpy/Controls/CustomDialog.cs

10
ICSharpCode.Decompiler.Tests/TestCases/Pretty/TupleTests.cs

@ -109,9 +109,9 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty
Console.WriteLine(valueTuple.GetType().FullName); Console.WriteLine(valueTuple.GetType().FullName);
} }
public void Foreach(IEnumerable<(int Index, string Data)> input) public void Foreach(IEnumerable<(int, string)> input)
{ {
foreach ((int, string) item3 in input) { foreach (var item3 in input) {
int item = item3.Item1; int item = item3.Item1;
string item2 = item3.Item2; string item2 = item3.Item2;
Console.WriteLine($"{item}: {item2}"); Console.WriteLine($"{item}: {item2}");
@ -120,9 +120,9 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty
public void ForeachNamedElements(IEnumerable<(int Index, string Data)> input) public void ForeachNamedElements(IEnumerable<(int Index, string Data)> input)
{ {
foreach ((int, string) item3 in input) { foreach (var item3 in input) {
int item = item3.Item1; int item = item3.Index;
string item2 = item3.Item2; string item2 = item3.Data;
Console.WriteLine($"{item}: {item2}"); Console.WriteLine($"{item}: {item2}");
} }
} }

3
ICSharpCode.Decompiler.Tests/TestCases/Pretty/TupleTests.opt.roslyn.il

@ -492,9 +492,6 @@
.method public hidebysig instance void .method public hidebysig instance void
Foreach(class [mscorlib]System.Collections.Generic.IEnumerable`1<valuetype [mscorlib]System.ValueTuple`2<int32,string>> input) cil managed Foreach(class [mscorlib]System.Collections.Generic.IEnumerable`1<valuetype [mscorlib]System.ValueTuple`2<int32,string>> input) cil managed
{ {
.param [1]
.custom instance void [mscorlib]System.Runtime.CompilerServices.TupleElementNamesAttribute::.ctor(string[]) = ( 01 00 02 00 00 00 05 49 6E 64 65 78 04 44 61 74 // .......Index.Dat
61 00 00 ) // a..
// Code size 71 (0x47) // Code size 71 (0x47)
.maxstack 3 .maxstack 3
.locals init (class [mscorlib]System.Collections.Generic.IEnumerator`1<valuetype [mscorlib]System.ValueTuple`2<int32,string>> V_0, .locals init (class [mscorlib]System.Collections.Generic.IEnumerator`1<valuetype [mscorlib]System.ValueTuple`2<int32,string>> V_0,

3
ICSharpCode.Decompiler.Tests/TestCases/Pretty/TupleTests.roslyn.il

@ -517,9 +517,6 @@
.method public hidebysig instance void .method public hidebysig instance void
Foreach(class [mscorlib]System.Collections.Generic.IEnumerable`1<valuetype [mscorlib]System.ValueTuple`2<int32,string>> input) cil managed Foreach(class [mscorlib]System.Collections.Generic.IEnumerable`1<valuetype [mscorlib]System.ValueTuple`2<int32,string>> input) cil managed
{ {
.param [1]
.custom instance void [mscorlib]System.Runtime.CompilerServices.TupleElementNamesAttribute::.ctor(string[]) = ( 01 00 02 00 00 00 05 49 6E 64 65 78 04 44 61 74 // .......Index.Dat
61 00 00 ) // a..
// Code size 79 (0x4f) // Code size 79 (0x4f)
.maxstack 3 .maxstack 3
.locals init (class [mscorlib]System.Collections.Generic.IEnumerator`1<valuetype [mscorlib]System.ValueTuple`2<int32,string>> V_0, .locals init (class [mscorlib]System.Collections.Generic.IEnumerator`1<valuetype [mscorlib]System.ValueTuple`2<int32,string>> V_0,

117
ICSharpCode.Decompiler/CSharp/Resolver/CSharpResolver.cs

@ -1678,7 +1678,122 @@ namespace ICSharpCode.Decompiler.CSharp.Resolver
return memberLookup.Lookup(this.CurrentObjectInitializer, identifier, EmptyList<IType>.Instance, false); return memberLookup.Lookup(this.CurrentObjectInitializer, identifier, EmptyList<IType>.Instance, false);
} }
#endregion #endregion
#region ResolveForeach
public ForEachResolveResult ResolveForeach(ResolveResult expression)
{
var memberLookup = CreateMemberLookup();
IType collectionType, enumeratorType, elementType;
ResolveResult getEnumeratorInvocation;
ResolveResult currentRR = null;
// C# 4.0 spec: §8.8.4 The foreach statement
if (expression.Type.Kind == TypeKind.Array || expression.Type.Kind == TypeKind.Dynamic) {
collectionType = compilation.FindType(KnownTypeCode.IEnumerable);
enumeratorType = compilation.FindType(KnownTypeCode.IEnumerator);
if (expression.Type.Kind == TypeKind.Array) {
elementType = ((ArrayType)expression.Type).ElementType;
} else {
elementType = SpecialType.Dynamic;
}
getEnumeratorInvocation = ResolveCast(collectionType, expression);
getEnumeratorInvocation = ResolveMemberAccess(getEnumeratorInvocation, "GetEnumerator", EmptyList<IType>.Instance, NameLookupMode.InvocationTarget);
getEnumeratorInvocation = ResolveInvocation(getEnumeratorInvocation, new ResolveResult[0]);
} else {
var getEnumeratorMethodGroup = memberLookup.Lookup(expression, "GetEnumerator", EmptyList<IType>.Instance, true) as MethodGroupResolveResult;
if (getEnumeratorMethodGroup != null) {
var or = getEnumeratorMethodGroup.PerformOverloadResolution(
compilation, new ResolveResult[0],
allowExtensionMethods: false, allowExpandingParams: false, allowOptionalParameters: false);
if (or.FoundApplicableCandidate && !or.IsAmbiguous && !or.BestCandidate.IsStatic && or.BestCandidate.Accessibility == Accessibility.Public) {
collectionType = expression.Type;
getEnumeratorInvocation = or.CreateResolveResult(expression);
enumeratorType = getEnumeratorInvocation.Type;
currentRR = memberLookup.Lookup(new ResolveResult(enumeratorType), "Current", EmptyList<IType>.Instance, false);
elementType = currentRR.Type;
} else {
CheckForEnumerableInterface(expression, out collectionType, out enumeratorType, out elementType, out getEnumeratorInvocation);
}
} else {
CheckForEnumerableInterface(expression, out collectionType, out enumeratorType, out elementType, out getEnumeratorInvocation);
}
}
IMethod moveNextMethod = null;
var moveNextMethodGroup = memberLookup.Lookup(new ResolveResult(enumeratorType), "MoveNext", EmptyList<IType>.Instance, false) as MethodGroupResolveResult;
if (moveNextMethodGroup != null) {
var or = moveNextMethodGroup.PerformOverloadResolution(
compilation, new ResolveResult[0],
allowExtensionMethods: false, allowExpandingParams: false, allowOptionalParameters: false);
moveNextMethod = or.GetBestCandidateWithSubstitutedTypeArguments() as IMethod;
}
if (currentRR == null)
currentRR = memberLookup.Lookup(new ResolveResult(enumeratorType), "Current", EmptyList<IType>.Instance, false);
IProperty currentProperty = null;
if (currentRR is MemberResolveResult)
currentProperty = ((MemberResolveResult)currentRR).Member as IProperty;
var voidType = compilation.FindType(KnownTypeCode.Void);
return new ForEachResolveResult(getEnumeratorInvocation, collectionType, enumeratorType, elementType,
currentProperty, moveNextMethod, voidType);
}
void CheckForEnumerableInterface(ResolveResult expression, out IType collectionType, out IType enumeratorType, out IType elementType, out ResolveResult getEnumeratorInvocation)
{
bool? isGeneric;
elementType = GetElementTypeFromIEnumerable(expression.Type, compilation, false, out isGeneric);
if (isGeneric == true) {
ITypeDefinition enumerableOfT = compilation.FindType(KnownTypeCode.IEnumerableOfT).GetDefinition();
if (enumerableOfT != null)
collectionType = new ParameterizedType(enumerableOfT, new[] { elementType });
else
collectionType = SpecialType.UnknownType;
ITypeDefinition enumeratorOfT = compilation.FindType(KnownTypeCode.IEnumeratorOfT).GetDefinition();
if (enumeratorOfT != null)
enumeratorType = new ParameterizedType(enumeratorOfT, new[] { elementType });
else
enumeratorType = SpecialType.UnknownType;
} else if (isGeneric == false) {
collectionType = compilation.FindType(KnownTypeCode.IEnumerable);
enumeratorType = compilation.FindType(KnownTypeCode.IEnumerator);
} else {
collectionType = SpecialType.UnknownType;
enumeratorType = SpecialType.UnknownType;
}
getEnumeratorInvocation = ResolveCast(collectionType, expression);
getEnumeratorInvocation = ResolveMemberAccess(getEnumeratorInvocation, "GetEnumerator", EmptyList<IType>.Instance, NameLookupMode.InvocationTarget);
getEnumeratorInvocation = ResolveInvocation(getEnumeratorInvocation, new ResolveResult[0]);
}
static IType GetElementTypeFromIEnumerable(IType collectionType, ICompilation compilation, bool allowIEnumerator, out bool? isGeneric)
{
bool foundNonGenericIEnumerable = false;
foreach (IType baseType in collectionType.GetAllBaseTypes()) {
ITypeDefinition baseTypeDef = baseType.GetDefinition();
if (baseTypeDef != null) {
KnownTypeCode typeCode = baseTypeDef.KnownTypeCode;
if (typeCode == KnownTypeCode.IEnumerableOfT || (allowIEnumerator && typeCode == KnownTypeCode.IEnumeratorOfT)) {
ParameterizedType pt = baseType as ParameterizedType;
if (pt != null) {
isGeneric = true;
return pt.GetTypeArgument(0);
}
}
if (typeCode == KnownTypeCode.IEnumerable || (allowIEnumerator && typeCode == KnownTypeCode.IEnumerator))
foundNonGenericIEnumerable = true;
}
}
// System.Collections.IEnumerable found in type hierarchy -> Object is element type.
if (foundNonGenericIEnumerable) {
isGeneric = false;
return compilation.FindType(KnownTypeCode.Object);
}
isGeneric = null;
return SpecialType.UnknownType;
}
#endregion
#region GetExtensionMethods #region GetExtensionMethods
/// <summary> /// <summary>
/// Gets all extension methods that are available in the current context. /// Gets all extension methods that are available in the current context.

24
ICSharpCode.Decompiler/CSharp/StatementBuilder.cs

@ -454,6 +454,7 @@ namespace ICSharpCode.Decompiler.CSharp
// For example: foreach (ClassA item in nonGenericEnumerable) // For example: foreach (ClassA item in nonGenericEnumerable)
var type = singleGetter.Method.ReturnType; var type = singleGetter.Method.ReturnType;
ILInstruction instToReplace = singleGetter; ILInstruction instToReplace = singleGetter;
bool useVar = false;
switch (instToReplace.Parent) { switch (instToReplace.Parent) {
case CastClass cc: case CastClass cc:
type = cc.Type; type = cc.Type;
@ -463,7 +464,18 @@ namespace ICSharpCode.Decompiler.CSharp
type = ua.Type; type = ua.Type;
instToReplace = ua; instToReplace = ua;
break; break;
default:
if (TupleType.IsTupleCompatible(type, out _)) {
// foreach with get_Current returning a tuple type, let's check which type "var" would infer:
var foreachRR = exprBuilder.resolver.ResolveForeach(collectionExpr.GetResolveResult());
if (EqualErasedType(type, foreachRR.ElementType)) {
type = foreachRR.ElementType;
useVar = true;
}
}
break;
} }
// Handle the required foreach-variable transformation: // Handle the required foreach-variable transformation:
switch (transformation) { switch (transformation) {
case RequiredGetCurrentTransformation.UseExistingVariable: case RequiredGetCurrentTransformation.UseExistingVariable:
@ -507,9 +519,12 @@ namespace ICSharpCode.Decompiler.CSharp
Debug.Assert(firstStatement is ExpressionStatement); Debug.Assert(firstStatement is ExpressionStatement);
firstStatement.Remove(); firstStatement.Remove();
if (settings.AnonymousTypes && type.ContainsAnonymousType())
useVar = true;
// Construct the foreach loop. // Construct the foreach loop.
var foreachStmt = new ForeachStatement { var foreachStmt = new ForeachStatement {
VariableType = settings.AnonymousTypes && foreachVariable.Type.ContainsAnonymousType() ? new SimpleType("var") : exprBuilder.ConvertType(foreachVariable.Type), VariableType = useVar ? new SimpleType("var") : exprBuilder.ConvertType(foreachVariable.Type),
VariableName = foreachVariable.Name, VariableName = foreachVariable.Name,
InExpression = collectionExpr.Detach(), InExpression = collectionExpr.Detach(),
EmbeddedStatement = foreachBody EmbeddedStatement = foreachBody
@ -529,6 +544,13 @@ namespace ICSharpCode.Decompiler.CSharp
return foreachStmt; return foreachStmt;
} }
static bool EqualErasedType(IType a, IType b)
{
a = a.AcceptVisitor(NormalizeTypeVisitor.TypeErasure);
b = b.AcceptVisitor(NormalizeTypeVisitor.TypeErasure);
return a.Equals(b);
}
private bool IsDynamicCastToIEnumerable(Expression expr, out Expression dynamicExpr) private bool IsDynamicCastToIEnumerable(Expression expr, out Expression dynamicExpr)
{ {
if (!(expr is CastExpression cast)) { if (!(expr is CastExpression cast)) {

10
ICSharpCode.Decompiler/Semantics/ForEachResolveResult.cs

@ -48,11 +48,6 @@ namespace ICSharpCode.Decompiler.Semantics
/// </summary> /// </summary>
public readonly IType ElementType; public readonly IType ElementType;
/// <summary>
/// Gets the element variable.
/// </summary>
public readonly IVariable ElementVariable;
/// <summary> /// <summary>
/// Gets the Current property on the IEnumerator. /// Gets the Current property on the IEnumerator.
/// Returns null if the property is not found. /// Returns null if the property is not found.
@ -65,7 +60,7 @@ namespace ICSharpCode.Decompiler.Semantics
/// </summary> /// </summary>
public readonly IMethod MoveNextMethod; public readonly IMethod MoveNextMethod;
public ForEachResolveResult(ResolveResult getEnumeratorCall, IType collectionType, IType enumeratorType, IType elementType, IVariable elementVariable, IProperty currentProperty, IMethod moveNextMethod, IType voidType) public ForEachResolveResult(ResolveResult getEnumeratorCall, IType collectionType, IType enumeratorType, IType elementType, IProperty currentProperty, IMethod moveNextMethod, IType voidType)
: base(voidType) : base(voidType)
{ {
if (getEnumeratorCall == null) if (getEnumeratorCall == null)
@ -76,13 +71,10 @@ namespace ICSharpCode.Decompiler.Semantics
throw new ArgumentNullException("enumeratorType"); throw new ArgumentNullException("enumeratorType");
if (elementType == null) if (elementType == null)
throw new ArgumentNullException("elementType"); throw new ArgumentNullException("elementType");
if (elementVariable == null)
throw new ArgumentNullException("elementVariable");
this.GetEnumeratorCall = getEnumeratorCall; this.GetEnumeratorCall = getEnumeratorCall;
this.CollectionType = collectionType; this.CollectionType = collectionType;
this.EnumeratorType = enumeratorType; this.EnumeratorType = enumeratorType;
this.ElementType = elementType; this.ElementType = elementType;
this.ElementVariable = elementVariable;
this.CurrentProperty = currentProperty; this.CurrentProperty = currentProperty;
this.MoveNextMethod = moveNextMethod; this.MoveNextMethod = moveNextMethod;
} }

7
ILSpy/Controls/CustomDialog.cs

@ -100,6 +100,8 @@ namespace ICSharpCode.ILSpy.Controls
{ {
if (cancelButton == -1 && e.KeyCode == Keys.Escape) { if (cancelButton == -1 && e.KeyCode == Keys.Escape) {
this.Close(); this.Close();
} else if (e.KeyCode == Keys.C && e.Control) {
Clipboard.SetText(this.Text + Environment.NewLine + label.Text);
} }
} }
@ -156,5 +158,10 @@ namespace ICSharpCode.ILSpy.Controls
this.AutoScaleMode = AutoScaleMode.Dpi; this.AutoScaleMode = AutoScaleMode.Dpi;
this.AutoScaleDimensions = new SizeF(96, 96); this.AutoScaleDimensions = new SizeF(96, 96);
} }
private void CustomDialog_KeyDown(object sender, KeyEventArgs e)
{
throw new NotImplementedException();
}
} }
} }

Loading…
Cancel
Save