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

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

@ -492,9 +492,6 @@ @@ -492,9 +492,6 @@
.method public hidebysig instance void
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)
.maxstack 3
.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 @@ @@ -517,9 +517,6 @@
.method public hidebysig instance void
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)
.maxstack 3
.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 @@ -1678,7 +1678,122 @@ namespace ICSharpCode.Decompiler.CSharp.Resolver
return memberLookup.Lookup(this.CurrentObjectInitializer, identifier, EmptyList<IType>.Instance, false);
}
#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
/// <summary>
/// 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 @@ -454,6 +454,7 @@ namespace ICSharpCode.Decompiler.CSharp
// For example: foreach (ClassA item in nonGenericEnumerable)
var type = singleGetter.Method.ReturnType;
ILInstruction instToReplace = singleGetter;
bool useVar = false;
switch (instToReplace.Parent) {
case CastClass cc:
type = cc.Type;
@ -463,7 +464,18 @@ namespace ICSharpCode.Decompiler.CSharp @@ -463,7 +464,18 @@ namespace ICSharpCode.Decompiler.CSharp
type = ua.Type;
instToReplace = ua;
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:
switch (transformation) {
case RequiredGetCurrentTransformation.UseExistingVariable:
@ -507,9 +519,12 @@ namespace ICSharpCode.Decompiler.CSharp @@ -507,9 +519,12 @@ namespace ICSharpCode.Decompiler.CSharp
Debug.Assert(firstStatement is ExpressionStatement);
firstStatement.Remove();
if (settings.AnonymousTypes && type.ContainsAnonymousType())
useVar = true;
// Construct the foreach loop.
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,
InExpression = collectionExpr.Detach(),
EmbeddedStatement = foreachBody
@ -529,6 +544,13 @@ namespace ICSharpCode.Decompiler.CSharp @@ -529,6 +544,13 @@ namespace ICSharpCode.Decompiler.CSharp
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)
{
if (!(expr is CastExpression cast)) {

10
ICSharpCode.Decompiler/Semantics/ForEachResolveResult.cs

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

7
ILSpy/Controls/CustomDialog.cs

@ -100,6 +100,8 @@ namespace ICSharpCode.ILSpy.Controls @@ -100,6 +100,8 @@ namespace ICSharpCode.ILSpy.Controls
{
if (cancelButton == -1 && e.KeyCode == Keys.Escape) {
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 @@ -156,5 +158,10 @@ namespace ICSharpCode.ILSpy.Controls
this.AutoScaleMode = AutoScaleMode.Dpi;
this.AutoScaleDimensions = new SizeF(96, 96);
}
private void CustomDialog_KeyDown(object sender, KeyEventArgs e)
{
throw new NotImplementedException();
}
}
}

Loading…
Cancel
Save