diff --git a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/TupleTests.cs b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/TupleTests.cs index 55191b152..e9cd6678d 100644 --- a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/TupleTests.cs +++ b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/TupleTests.cs @@ -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 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}"); } } diff --git a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/TupleTests.opt.roslyn.il b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/TupleTests.opt.roslyn.il index 5e46366c7..aef3e97c4 100644 --- a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/TupleTests.opt.roslyn.il +++ b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/TupleTests.opt.roslyn.il @@ -492,9 +492,6 @@ .method public hidebysig instance void Foreach(class [mscorlib]System.Collections.Generic.IEnumerable`1> 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> V_0, diff --git a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/TupleTests.roslyn.il b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/TupleTests.roslyn.il index a9bdadc98..2d3494916 100644 --- a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/TupleTests.roslyn.il +++ b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/TupleTests.roslyn.il @@ -517,9 +517,6 @@ .method public hidebysig instance void Foreach(class [mscorlib]System.Collections.Generic.IEnumerable`1> 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> V_0, diff --git a/ICSharpCode.Decompiler/CSharp/Resolver/CSharpResolver.cs b/ICSharpCode.Decompiler/CSharp/Resolver/CSharpResolver.cs index 1b0ecf469..1a91fc33d 100644 --- a/ICSharpCode.Decompiler/CSharp/Resolver/CSharpResolver.cs +++ b/ICSharpCode.Decompiler/CSharp/Resolver/CSharpResolver.cs @@ -1678,7 +1678,122 @@ namespace ICSharpCode.Decompiler.CSharp.Resolver return memberLookup.Lookup(this.CurrentObjectInitializer, identifier, EmptyList.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.Instance, NameLookupMode.InvocationTarget); + getEnumeratorInvocation = ResolveInvocation(getEnumeratorInvocation, new ResolveResult[0]); + } else { + var getEnumeratorMethodGroup = memberLookup.Lookup(expression, "GetEnumerator", EmptyList.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.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.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.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.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 /// /// Gets all extension methods that are available in the current context. diff --git a/ICSharpCode.Decompiler/CSharp/StatementBuilder.cs b/ICSharpCode.Decompiler/CSharp/StatementBuilder.cs index 3df502bd8..503c9aac8 100644 --- a/ICSharpCode.Decompiler/CSharp/StatementBuilder.cs +++ b/ICSharpCode.Decompiler/CSharp/StatementBuilder.cs @@ -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 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 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 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)) { diff --git a/ICSharpCode.Decompiler/Semantics/ForEachResolveResult.cs b/ICSharpCode.Decompiler/Semantics/ForEachResolveResult.cs index 3013a7282..579b0b0e9 100644 --- a/ICSharpCode.Decompiler/Semantics/ForEachResolveResult.cs +++ b/ICSharpCode.Decompiler/Semantics/ForEachResolveResult.cs @@ -48,11 +48,6 @@ namespace ICSharpCode.Decompiler.Semantics /// public readonly IType ElementType; - /// - /// Gets the element variable. - /// - public readonly IVariable ElementVariable; - /// /// Gets the Current property on the IEnumerator. /// Returns null if the property is not found. @@ -65,7 +60,7 @@ namespace ICSharpCode.Decompiler.Semantics /// 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 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; } diff --git a/ILSpy/Controls/CustomDialog.cs b/ILSpy/Controls/CustomDialog.cs index e3a99214c..445c5655b 100644 --- a/ILSpy/Controls/CustomDialog.cs +++ b/ILSpy/Controls/CustomDialog.cs @@ -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 this.AutoScaleMode = AutoScaleMode.Dpi; this.AutoScaleDimensions = new SizeF(96, 96); } + + private void CustomDialog_KeyDown(object sender, KeyEventArgs e) + { + throw new NotImplementedException(); + } } }