From 706419544fcb0f1f2f20a01e636a680d5a6eeafa Mon Sep 17 00:00:00 2001 From: Siegfried Pammer Date: Wed, 20 Sep 2017 13:10:43 +0200 Subject: [PATCH] Implement support for unwrapping params arrays. --- .../CSharp/ExpressionBuilder.cs | 114 ++++++++++++------ .../Util/CollectionExtensions.cs | 14 +++ 2 files changed, 90 insertions(+), 38 deletions(-) diff --git a/ICSharpCode.Decompiler/CSharp/ExpressionBuilder.cs b/ICSharpCode.Decompiler/CSharp/ExpressionBuilder.cs index f2cb414e1..f10ddb7f1 100644 --- a/ICSharpCode.Decompiler/CSharp/ExpressionBuilder.cs +++ b/ICSharpCode.Decompiler/CSharp/ExpressionBuilder.cs @@ -313,16 +313,21 @@ namespace ICSharpCode.Decompiler.CSharp protected internal override TranslatedExpression VisitLdNull(LdNull inst, TranslationContext context) { - return new NullReferenceExpression() - .WithILInstruction(inst) - .WithRR(new ConstantResolveResult(SpecialType.NullType, null)); + return GetDefaultValueExpression(SpecialType.NullType, inst); } protected internal override TranslatedExpression VisitDefaultValue(DefaultValue inst, TranslationContext context) { - return new DefaultValueExpression(ConvertType(inst.Type)) - .WithILInstruction(inst) - .WithRR(new ConstantResolveResult(inst.Type, null)); + return GetDefaultValueExpression(inst.Type, inst); + } + + TranslatedExpression GetDefaultValueExpression(IType type, ILInstruction inst = null) + { + var expr = type.IsReferenceType == true ? (Expression)new NullReferenceExpression() : new DefaultValueExpression(ConvertType(type)); + var constantType = type.IsReferenceType == true ? SpecialType.NullType : type; + if (inst == null) + return expr.WithoutILInstruction().WithRR(new ConstantResolveResult(constantType, null)); + return expr.WithILInstruction(inst).WithRR(new ConstantResolveResult(constantType, null)); } protected internal override TranslatedExpression VisitSizeOf(SizeOf inst, TranslationContext context) @@ -1125,13 +1130,43 @@ namespace ICSharpCode.Decompiler.CSharp int firstParamIndex = (method.IsStatic || inst.OpCode == OpCode.NewObj) ? 0 : 1; // Translate arguments to the expected parameter types - TranslatedExpression[] arguments = new TranslatedExpression[inst.Method.Parameters.Count]; - Debug.Assert(inst.Arguments.Count == firstParamIndex + inst.Method.Parameters.Count); - for (int i = 0; i < arguments.Length; i++) { - var parameter = method.Parameters[i]; - arguments[i] = Translate(inst.Arguments[firstParamIndex + i]) - .ConvertTo(parameter.Type, this, allowImplicitConversion: true); - + var arguments = new List(method.Parameters.Count); + Debug.Assert(inst.Arguments.Count == firstParamIndex + method.Parameters.Count); + var expectedParameters = method.Parameters.ToList(); + for (int i = 0; i < method.Parameters.Count; i++) { + var parameter = expectedParameters[i]; + var arg = Translate(inst.Arguments[firstParamIndex + i]); + if (parameter.IsParams) { + // Parameter is marked params + // If the argument is an array creation, inline all elements into the call and add missing default values. + // Otherwise handle it normally. + if (arg.ResolveResult is ArrayCreateResolveResult acrr && + acrr.SizeArguments.Count == 1 && + acrr.SizeArguments[0].IsCompileTimeConstant && + acrr.SizeArguments[0].ConstantValue is int length) { + var expectedParametersCopy = expectedParameters.Take(expectedParameters.Count - 1).ToList(); + var argumentsCopy = new List(arguments); + if (length > 0) { + var arrayElements = ((ArrayCreateExpression)arg.Expression).Initializer.Elements.ToArray(); + var elementType = ((ArrayType)acrr.Type).ElementType; + for (int j = 0; j < length; j++) { + expectedParametersCopy.Add(new DefaultParameter(elementType, parameter.Name + j)); + if (j < arrayElements.Length) + argumentsCopy.Add(new TranslatedExpression(arrayElements[j])); + else + argumentsCopy.Add(GetDefaultValueExpression(elementType)); + } + } + if (IsUnambiguousCall(inst, target, method, Array.Empty(), argumentsCopy) == OverloadResolutionErrors.None) { + expectedParameters = expectedParametersCopy; + arguments = argumentsCopy.SelectList(a => new TranslatedExpression(a.Expression.Detach())); + continue; + } + } + } + + arguments.Add(arg.ConvertTo(parameter.Type, this, allowImplicitConversion: true)); + if (parameter.IsOut && arguments[i].Expression is DirectionExpression dirExpr) { dirExpr.FieldDirection = FieldDirection.Out; } @@ -1142,11 +1177,12 @@ namespace ICSharpCode.Decompiler.CSharp var argListArg = new UndocumentedExpression(); argListArg.UndocumentedExpressionType = UndocumentedExpressionType.ArgList; int paramIndex = regularParameterCount; - argListArg.Arguments.AddRange(arguments.Skip(regularParameterCount).Select(arg => arg.ConvertTo(method.Parameters[paramIndex++].Type, this).Expression)); + argListArg.Arguments.AddRange(arguments.Skip(regularParameterCount).Select(arg => arg.ConvertTo(expectedParameters[paramIndex++].Type, this).Expression)); var argListRR = new ResolveResult(SpecialType.ArgList); arguments = arguments.Take(regularParameterCount) - .Concat(new[] { argListArg.WithoutILInstruction().WithRR(argListRR) }).ToArray(); + .Concat(new[] { argListArg.WithoutILInstruction().WithRR(argListRR) }).ToList(); method = (IMethod)method.MemberDefinition; + expectedParameters = method.Parameters.ToList(); } var argumentResolveResults = arguments.Select(arg => arg.ResolveResult).ToList(); @@ -1157,14 +1193,13 @@ namespace ICSharpCode.Decompiler.CSharp var argumentExpressions = arguments.SelectArray(arg => arg.Expression); if (settings.AnonymousTypes && method.DeclaringType.IsAnonymousType()) { AnonymousTypeCreateExpression atce = new AnonymousTypeCreateExpression(); - var parameters = inst.Method.Parameters; - if (CanInferAnonymousTypePropertyNamesFromArguments(argumentExpressions, parameters)) { + if (CanInferAnonymousTypePropertyNamesFromArguments(argumentExpressions, expectedParameters)) { atce.Initializers.AddRange(argumentExpressions); } else { for (int i = 0; i < argumentExpressions.Length; i++) { atce.Initializers.Add( new NamedExpression { - Name = parameters[i].Name, + Name = expectedParameters[i].Name, Expression = argumentExpressions[i] }); } @@ -1178,7 +1213,7 @@ namespace ICSharpCode.Decompiler.CSharp } } else { int allowedParamCount = (method.ReturnType.IsKnownType(KnownTypeCode.Void) ? 1 : 0); - if (method.IsAccessor && (method.AccessorOwner.SymbolKind == SymbolKind.Indexer || method.Parameters.Count == allowedParamCount)) { + if (method.IsAccessor && (method.AccessorOwner.SymbolKind == SymbolKind.Indexer || expectedParameters.Count == allowedParamCount)) { return HandleAccessorCall(inst, target, method, arguments.ToList()); } else { bool requireTypeArguments = false; @@ -1186,23 +1221,8 @@ namespace ICSharpCode.Decompiler.CSharp bool argumentsCasted = false; IType[] typeArguments = Array.Empty(); - OverloadResolutionErrors IsUnambiguousCall() - { - var lookup = new MemberLookup(resolver.CurrentTypeDefinition, resolver.CurrentTypeDefinition.ParentAssembly); - var result = lookup.Lookup(target.ResolveResult, method.Name, EmptyList.Instance, true) as MethodGroupResolveResult; - if (result == null) - return OverloadResolutionErrors.AmbiguousMatch; - var or = new OverloadResolution(resolver.Compilation, arguments.SelectArray(a => a.ResolveResult), typeArguments: typeArguments); - or.AddMethodLists(result.MethodsGroupedByDeclaringType.ToArray()); - if (or.BestCandidateErrors != OverloadResolutionErrors.None) - return or.BestCandidateErrors; - if (!IsAppropriateCallTarget(method, or.GetBestCandidateWithSubstitutedTypeArguments(), inst.OpCode == OpCode.CallVirt)) - return OverloadResolutionErrors.AmbiguousMatch; - return OverloadResolutionErrors.None; - } - OverloadResolutionErrors errors; - while ((errors = IsUnambiguousCall()) != OverloadResolutionErrors.None) { + while ((errors = IsUnambiguousCall(inst, target, method, typeArguments, arguments)) != OverloadResolutionErrors.None) { switch (errors) { case OverloadResolutionErrors.TypeInferenceFailed: case OverloadResolutionErrors.WrongNumberOfTypeArguments: @@ -1213,9 +1233,9 @@ namespace ICSharpCode.Decompiler.CSharp default: if (!argumentsCasted) { argumentsCasted = true; - for (int i = 0; i < arguments.Length; i++) { - if (!settings.AnonymousTypes || !method.Parameters[i].Type.ContainsAnonymousType()) - arguments[i] = arguments[i].ConvertTo(method.Parameters[i].Type, this); + for (int i = 0; i < arguments.Count; i++) { + if (!settings.AnonymousTypes || !expectedParameters[i].Type.ContainsAnonymousType()) + arguments[i] = arguments[i].ConvertTo(expectedParameters[i].Type, this); } } else if (!targetCasted) { targetCasted = true; @@ -1247,6 +1267,24 @@ namespace ICSharpCode.Decompiler.CSharp } } + OverloadResolutionErrors IsUnambiguousCall(ILInstruction inst, TranslatedExpression target, IMethod method, IType[] typeArguments, IList arguments) + { + // TODO : MemberLookup does not support ctors. (target is null in that case! -> NullArgumentException is thrown) + if (inst.OpCode == OpCode.NewObj) + return OverloadResolutionErrors.None; + var lookup = new MemberLookup(resolver.CurrentTypeDefinition, resolver.CurrentTypeDefinition.ParentAssembly); + var result = lookup.Lookup(target.ResolveResult, method.Name, EmptyList.Instance, true) as MethodGroupResolveResult; + if (result == null) + return OverloadResolutionErrors.AmbiguousMatch; + var or = new OverloadResolution(resolver.Compilation, arguments.SelectArray(a => a.ResolveResult), typeArguments: typeArguments); + or.AddMethodLists(result.MethodsGroupedByDeclaringType.ToArray()); + if (or.BestCandidateErrors != OverloadResolutionErrors.None) + return or.BestCandidateErrors; + if (!IsAppropriateCallTarget(method, or.GetBestCandidateWithSubstitutedTypeArguments(), inst.OpCode == OpCode.CallVirt)) + return OverloadResolutionErrors.AmbiguousMatch; + return OverloadResolutionErrors.None; + } + static bool CanInferAnonymousTypePropertyNamesFromArguments(IList args, IList parameters) { for (int i = 0; i < args.Count; i++) { diff --git a/ICSharpCode.Decompiler/Util/CollectionExtensions.cs b/ICSharpCode.Decompiler/Util/CollectionExtensions.cs index 92c243718..3263bb735 100644 --- a/ICSharpCode.Decompiler/Util/CollectionExtensions.cs +++ b/ICSharpCode.Decompiler/Util/CollectionExtensions.cs @@ -67,6 +67,20 @@ namespace ICSharpCode.Decompiler.Util return result; } + /// + /// Equivalent to collection.Select(func).ToList(), but more efficient as it makes + /// use of the input collection's known size. + /// + public static List SelectList(this ICollection collection, Func func) + { + List result = new List(collection.Count); + int index = 0; + foreach (var element in collection) { + result.Add(func(element)); + } + return result; + } + public static IEnumerable SelectWithIndex(this IEnumerable source, Func func) { int index = 0;