You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
1959 lines
74 KiB
1959 lines
74 KiB
// Copyright (c) 2014 Daniel Grunwald
|
|
//
|
|
// Permission is hereby granted, free of charge, to any person obtaining a copy of this
|
|
// software and associated documentation files (the "Software"), to deal in the Software
|
|
// without restriction, including without limitation the rights to use, copy, modify, merge,
|
|
// publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons
|
|
// to whom the Software is furnished to do so, subject to the following conditions:
|
|
//
|
|
// The above copyright notice and this permission notice shall be included in all copies or
|
|
// substantial portions of the Software.
|
|
//
|
|
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
|
|
// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
|
|
// PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE
|
|
// FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
|
|
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
|
// DEALINGS IN THE SOFTWARE.
|
|
|
|
using System;
|
|
using System.Collections.Generic;
|
|
using System.Collections.Immutable;
|
|
using System.Diagnostics;
|
|
using System.Linq;
|
|
using System.Text;
|
|
|
|
using ICSharpCode.Decompiler.CSharp.Resolver;
|
|
using ICSharpCode.Decompiler.CSharp.Syntax;
|
|
using ICSharpCode.Decompiler.IL;
|
|
using ICSharpCode.Decompiler.Semantics;
|
|
using ICSharpCode.Decompiler.TypeSystem;
|
|
using ICSharpCode.Decompiler.TypeSystem.Implementation;
|
|
using ICSharpCode.Decompiler.Util;
|
|
|
|
namespace ICSharpCode.Decompiler.CSharp
|
|
{
|
|
struct CallBuilder
|
|
{
|
|
struct ExpectedTargetDetails
|
|
{
|
|
public OpCode CallOpCode;
|
|
public bool NeedsBoxingConversion;
|
|
}
|
|
|
|
struct ArgumentList
|
|
{
|
|
public TranslatedExpression[] Arguments;
|
|
public IParameter[] ExpectedParameters;
|
|
public string[] ParameterNames;
|
|
public string[] ArgumentNames;
|
|
public int FirstOptionalArgumentIndex;
|
|
public BitSet IsPrimitiveValue;
|
|
public IReadOnlyList<int> ArgumentToParameterMap;
|
|
|
|
public bool AddNamesToPrimitiveValues;
|
|
public bool UseImplicitlyTypedOut;
|
|
public bool IsExpandedForm;
|
|
public int Length => Arguments.Length;
|
|
|
|
private int GetActualArgumentCount()
|
|
{
|
|
if (FirstOptionalArgumentIndex < 0)
|
|
return Arguments.Length;
|
|
return FirstOptionalArgumentIndex;
|
|
}
|
|
|
|
public IList<ResolveResult> GetArgumentResolveResults(int skipCount = 0)
|
|
{
|
|
var expectedParameters = ExpectedParameters;
|
|
var useImplicitlyTypedOut = UseImplicitlyTypedOut;
|
|
|
|
return Arguments
|
|
.SelectWithIndex(GetResolveResult)
|
|
.Skip(skipCount)
|
|
.Take(GetActualArgumentCount())
|
|
.ToArray();
|
|
|
|
ResolveResult GetResolveResult(int index, TranslatedExpression expression)
|
|
{
|
|
var param = expectedParameters[index];
|
|
if (useImplicitlyTypedOut && param.IsOut && expression.Type is ByReferenceType brt)
|
|
return new OutVarResolveResult(brt.ElementType);
|
|
return expression.ResolveResult;
|
|
}
|
|
}
|
|
|
|
public IList<ResolveResult> GetArgumentResolveResultsDirect(int skipCount = 0)
|
|
{
|
|
return Arguments
|
|
.Skip(skipCount)
|
|
.Take(GetActualArgumentCount())
|
|
.Select(a => a.ResolveResult)
|
|
.ToArray();
|
|
}
|
|
|
|
public IEnumerable<Expression> GetArgumentExpressions(int skipCount = 0)
|
|
{
|
|
if (AddNamesToPrimitiveValues && IsPrimitiveValue.Any() && !IsExpandedForm
|
|
&& !ParameterNames.Any(p => string.IsNullOrEmpty(p)))
|
|
{
|
|
Debug.Assert(skipCount == 0);
|
|
if (ArgumentNames == null)
|
|
{
|
|
ArgumentNames = new string[Arguments.Length];
|
|
}
|
|
|
|
for (int i = 0; i < Arguments.Length; i++)
|
|
{
|
|
if (IsPrimitiveValue[i] && ArgumentNames[i] == null)
|
|
{
|
|
ArgumentNames[i] = ParameterNames[i];
|
|
}
|
|
}
|
|
}
|
|
int argumentCount = GetActualArgumentCount();
|
|
var useImplicitlyTypedOut = UseImplicitlyTypedOut;
|
|
if (ArgumentNames == null)
|
|
{
|
|
return Arguments.Skip(skipCount).Take(argumentCount).Select(arg => AddAnnotations(arg.Expression));
|
|
}
|
|
else
|
|
{
|
|
Debug.Assert(skipCount == 0);
|
|
return Arguments.Take(argumentCount).Zip(ArgumentNames.Take(argumentCount),
|
|
(arg, name) => {
|
|
if (name == null)
|
|
return AddAnnotations(arg.Expression);
|
|
else
|
|
return new NamedArgumentExpression(name, AddAnnotations(arg.Expression));
|
|
});
|
|
}
|
|
|
|
Expression AddAnnotations(Expression expression)
|
|
{
|
|
if (!useImplicitlyTypedOut)
|
|
return expression;
|
|
if (expression.GetResolveResult() is ByReferenceResolveResult brrr
|
|
&& brrr.IsOut)
|
|
{
|
|
expression.AddAnnotation(UseImplicitlyTypedOutAnnotation.Instance);
|
|
}
|
|
return expression;
|
|
}
|
|
}
|
|
|
|
public bool CanInferAnonymousTypePropertyNamesFromArguments()
|
|
{
|
|
for (int i = 0; i < Arguments.Length; i++)
|
|
{
|
|
string inferredName;
|
|
switch (Arguments[i].Expression)
|
|
{
|
|
case IdentifierExpression identifier:
|
|
inferredName = identifier.Identifier;
|
|
break;
|
|
case MemberReferenceExpression member:
|
|
inferredName = member.MemberName;
|
|
break;
|
|
default:
|
|
inferredName = null;
|
|
break;
|
|
}
|
|
|
|
if (inferredName != ExpectedParameters[i].Name)
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
[Conditional("DEBUG")]
|
|
public void CheckNoNamedOrOptionalArguments()
|
|
{
|
|
Debug.Assert(ArgumentToParameterMap == null && ArgumentNames == null && FirstOptionalArgumentIndex < 0);
|
|
}
|
|
}
|
|
|
|
readonly DecompilerSettings settings;
|
|
readonly ExpressionBuilder expressionBuilder;
|
|
readonly CSharpResolver resolver;
|
|
readonly IDecompilerTypeSystem typeSystem;
|
|
|
|
public CallBuilder(ExpressionBuilder expressionBuilder, IDecompilerTypeSystem typeSystem, DecompilerSettings settings)
|
|
{
|
|
this.expressionBuilder = expressionBuilder;
|
|
this.resolver = expressionBuilder.resolver;
|
|
this.settings = settings;
|
|
this.typeSystem = typeSystem;
|
|
}
|
|
|
|
public TranslatedExpression Build(CallInstruction inst)
|
|
{
|
|
if (inst is NewObj newobj && IL.Transforms.DelegateConstruction.MatchDelegateConstruction(newobj, out _, out _, out _))
|
|
{
|
|
return HandleDelegateConstruction(newobj);
|
|
}
|
|
if (settings.TupleTypes && TupleTransform.MatchTupleConstruction(inst as NewObj, out var tupleElements) && tupleElements.Length >= 2)
|
|
{
|
|
var elementTypes = TupleType.GetTupleElementTypes(inst.Method.DeclaringType);
|
|
Debug.Assert(!elementTypes.IsDefault, "MatchTupleConstruction should not success unless we got a valid tuple type.");
|
|
Debug.Assert(elementTypes.Length == tupleElements.Length);
|
|
var tuple = new TupleExpression();
|
|
var elementRRs = new List<ResolveResult>();
|
|
foreach (var (element, elementType) in tupleElements.Zip(elementTypes))
|
|
{
|
|
var translatedElement = expressionBuilder.Translate(element, elementType)
|
|
.ConvertTo(elementType, expressionBuilder, allowImplicitConversion: true);
|
|
tuple.Elements.Add(translatedElement.Expression);
|
|
elementRRs.Add(translatedElement.ResolveResult);
|
|
}
|
|
return tuple.WithRR(new TupleResolveResult(
|
|
expressionBuilder.compilation,
|
|
elementRRs.ToImmutableArray(),
|
|
valueTupleAssembly: inst.Method.DeclaringType.GetDefinition()?.ParentModule
|
|
)).WithILInstruction(inst);
|
|
}
|
|
return Build(inst.OpCode, inst.Method, inst.Arguments, constrainedTo: inst.ConstrainedTo)
|
|
.WithILInstruction(inst);
|
|
}
|
|
|
|
public ExpressionWithResolveResult Build(OpCode callOpCode, IMethod method,
|
|
IReadOnlyList<ILInstruction> callArguments,
|
|
IReadOnlyList<int> argumentToParameterMap = null,
|
|
IType constrainedTo = null)
|
|
{
|
|
if (method.IsExplicitInterfaceImplementation && callOpCode == OpCode.Call)
|
|
{
|
|
// Direct non-virtual call to explicit interface implementation.
|
|
// This can't really be represented in C#, but at least in the case where
|
|
// the class is sealed, we can equivalently call the interface member instead:
|
|
var interfaceMembers = method.ExplicitlyImplementedInterfaceMembers.ToList();
|
|
if (method.DeclaringTypeDefinition?.Kind == TypeKind.Class && method.DeclaringTypeDefinition.IsSealed && interfaceMembers.Count == 1)
|
|
{
|
|
method = (IMethod)interfaceMembers.Single();
|
|
callOpCode = OpCode.CallVirt;
|
|
}
|
|
}
|
|
// Used for Call, CallVirt and NewObj
|
|
var expectedTargetDetails = new ExpectedTargetDetails {
|
|
CallOpCode = callOpCode
|
|
};
|
|
ILFunction localFunction = null;
|
|
if (method.IsLocalFunction)
|
|
{
|
|
localFunction = expressionBuilder.ResolveLocalFunction(method);
|
|
Debug.Assert(localFunction != null);
|
|
}
|
|
TranslatedExpression target;
|
|
if (callOpCode == OpCode.NewObj)
|
|
{
|
|
target = default(TranslatedExpression); // no target
|
|
}
|
|
else if (localFunction != null)
|
|
{
|
|
var ide = new IdentifierExpression(localFunction.Name);
|
|
if (method.TypeArguments.Count > 0)
|
|
{
|
|
ide.TypeArguments.AddRange(method.TypeArguments.Select(expressionBuilder.ConvertType));
|
|
}
|
|
ide.AddAnnotation(localFunction);
|
|
target = ide.WithoutILInstruction()
|
|
.WithRR(ToMethodGroup(method, localFunction));
|
|
}
|
|
else
|
|
{
|
|
target = expressionBuilder.TranslateTarget(
|
|
callArguments.FirstOrDefault(),
|
|
nonVirtualInvocation: callOpCode == OpCode.Call || method.IsConstructor,
|
|
memberStatic: method.IsStatic,
|
|
memberDeclaringType: constrainedTo ?? method.DeclaringType);
|
|
if (constrainedTo == null
|
|
&& target.Expression is CastExpression cast
|
|
&& target.ResolveResult is ConversionResolveResult conversion
|
|
&& target.Type.IsKnownType(KnownTypeCode.Object)
|
|
&& conversion.Conversion.IsBoxingConversion)
|
|
{
|
|
// boxing conversion on call target?
|
|
// let's see if we can make that implicit:
|
|
target = target.UnwrapChild(cast.Expression);
|
|
// we'll need to make sure the boxing effect is preserved
|
|
expectedTargetDetails.NeedsBoxingConversion = true;
|
|
}
|
|
}
|
|
|
|
int firstParamIndex = (method.IsStatic || callOpCode == OpCode.NewObj) ? 0 : 1;
|
|
Debug.Assert(firstParamIndex == 0 || argumentToParameterMap == null
|
|
|| argumentToParameterMap[0] == -1);
|
|
|
|
var argumentList = BuildArgumentList(expectedTargetDetails, target.ResolveResult, method,
|
|
firstParamIndex, callArguments, argumentToParameterMap);
|
|
|
|
if (localFunction != null)
|
|
{
|
|
return new InvocationExpression(target, argumentList.GetArgumentExpressions())
|
|
.WithRR(new CSharpInvocationResolveResult(target.ResolveResult, method,
|
|
argumentList.GetArgumentResolveResults(), isExpandedForm: argumentList.IsExpandedForm));
|
|
}
|
|
|
|
if (method is VarArgInstanceMethod)
|
|
{
|
|
argumentList.FirstOptionalArgumentIndex = -1;
|
|
argumentList.AddNamesToPrimitiveValues = false;
|
|
argumentList.UseImplicitlyTypedOut = false;
|
|
int regularParameterCount = ((VarArgInstanceMethod)method).RegularParameterCount;
|
|
var argListArg = new UndocumentedExpression();
|
|
argListArg.UndocumentedExpressionType = UndocumentedExpressionType.ArgList;
|
|
int paramIndex = regularParameterCount;
|
|
var builder = expressionBuilder;
|
|
Debug.Assert(argumentToParameterMap == null && argumentList.ArgumentNames == null);
|
|
argListArg.Arguments.AddRange(argumentList.Arguments.Skip(regularParameterCount).Select(arg => arg.ConvertTo(argumentList.ExpectedParameters[paramIndex++].Type, builder).Expression));
|
|
var argListRR = new ResolveResult(SpecialType.ArgList);
|
|
argumentList.Arguments = argumentList.Arguments.Take(regularParameterCount)
|
|
.Concat(new[] { argListArg.WithoutILInstruction().WithRR(argListRR) }).ToArray();
|
|
method = ((VarArgInstanceMethod)method).BaseMethod;
|
|
argumentList.ExpectedParameters = method.Parameters.ToArray();
|
|
}
|
|
|
|
if (settings.Ranges)
|
|
{
|
|
if (HandleRangeConstruction(out var result, callOpCode, method, target, argumentList))
|
|
{
|
|
return result;
|
|
}
|
|
}
|
|
|
|
if (callOpCode == OpCode.NewObj)
|
|
{
|
|
return HandleConstructorCall(expectedTargetDetails, target.ResolveResult, method, argumentList);
|
|
}
|
|
|
|
if (method.Name == "Invoke" && method.DeclaringType.Kind == TypeKind.Delegate && !IsNullConditional(target))
|
|
{
|
|
return new InvocationExpression(target, argumentList.GetArgumentExpressions())
|
|
.WithRR(new CSharpInvocationResolveResult(target.ResolveResult, method,
|
|
argumentList.GetArgumentResolveResults(), isExpandedForm: argumentList.IsExpandedForm, isDelegateInvocation: true));
|
|
}
|
|
|
|
if (settings.StringInterpolation && IsInterpolatedStringCreation(method, argumentList) &&
|
|
TryGetStringInterpolationTokens(argumentList, out string format, out var tokens))
|
|
{
|
|
var arguments = argumentList.Arguments;
|
|
var content = new List<InterpolatedStringContent>();
|
|
|
|
bool unpackSingleElementArray = !argumentList.IsExpandedForm && argumentList.Length == 2
|
|
&& argumentList.Arguments[1].Expression is ArrayCreateExpression ace
|
|
&& ace.Initializer?.Elements.Count == 1;
|
|
|
|
void UnpackSingleElementArray(ref TranslatedExpression argument)
|
|
{
|
|
if (!unpackSingleElementArray)
|
|
return;
|
|
var arrayCreation = (ArrayCreateExpression)argumentList.Arguments[1].Expression;
|
|
var arrayCreationRR = (ArrayCreateResolveResult)argumentList.Arguments[1].ResolveResult;
|
|
var element = arrayCreation.Initializer.Elements.First().Detach();
|
|
argument = new TranslatedExpression(element, arrayCreationRR.InitializerElements.First());
|
|
}
|
|
|
|
if (tokens.Count > 0)
|
|
{
|
|
foreach (var (kind, index, alignment, text) in tokens)
|
|
{
|
|
TranslatedExpression argument;
|
|
switch (kind)
|
|
{
|
|
case TokenKind.String:
|
|
content.Add(new InterpolatedStringText(text));
|
|
break;
|
|
case TokenKind.Argument:
|
|
argument = arguments[index + 1];
|
|
UnpackSingleElementArray(ref argument);
|
|
content.Add(new Interpolation(argument));
|
|
break;
|
|
case TokenKind.ArgumentWithFormat:
|
|
argument = arguments[index + 1];
|
|
UnpackSingleElementArray(ref argument);
|
|
content.Add(new Interpolation(argument, suffix: text));
|
|
break;
|
|
case TokenKind.ArgumentWithAlignment:
|
|
argument = arguments[index + 1];
|
|
UnpackSingleElementArray(ref argument);
|
|
content.Add(new Interpolation(argument, alignment));
|
|
break;
|
|
case TokenKind.ArgumentWithAlignmentAndFormat:
|
|
argument = arguments[index + 1];
|
|
UnpackSingleElementArray(ref argument);
|
|
content.Add(new Interpolation(argument, alignment, text));
|
|
break;
|
|
}
|
|
}
|
|
var formattableStringType = expressionBuilder.compilation.FindType(KnownTypeCode.FormattableString);
|
|
var isrr = new InterpolatedStringResolveResult(expressionBuilder.compilation.FindType(KnownTypeCode.String),
|
|
format, argumentList.GetArgumentResolveResults(1).ToArray());
|
|
var expr = new InterpolatedStringExpression();
|
|
expr.Content.AddRange(content);
|
|
if (method.Name == "Format")
|
|
return expr.WithRR(isrr);
|
|
return new CastExpression(expressionBuilder.ConvertType(formattableStringType),
|
|
expr.WithRR(isrr))
|
|
.WithRR(new ConversionResolveResult(formattableStringType, isrr, Conversion.ImplicitInterpolatedStringConversion));
|
|
}
|
|
}
|
|
|
|
int allowedParamCount = (method.ReturnType.IsKnownType(KnownTypeCode.Void) ? 1 : 0);
|
|
if (method.IsAccessor && (method.AccessorOwner.SymbolKind == SymbolKind.Indexer || argumentList.ExpectedParameters.Length == allowedParamCount))
|
|
{
|
|
argumentList.CheckNoNamedOrOptionalArguments();
|
|
return HandleAccessorCall(expectedTargetDetails, method, target, argumentList.Arguments.ToList(), argumentList.ArgumentNames);
|
|
}
|
|
|
|
if (IsDelegateEqualityComparison(method, argumentList.Arguments))
|
|
{
|
|
argumentList.CheckNoNamedOrOptionalArguments();
|
|
return HandleDelegateEqualityComparison(method, argumentList.Arguments)
|
|
.WithRR(new CSharpInvocationResolveResult(target.ResolveResult, method,
|
|
argumentList.GetArgumentResolveResults(), isExpandedForm: argumentList.IsExpandedForm));
|
|
}
|
|
|
|
if (method.IsOperator && method.Name == "op_Implicit" && argumentList.Length == 1)
|
|
{
|
|
argumentList.CheckNoNamedOrOptionalArguments();
|
|
return HandleImplicitConversion(method, argumentList.Arguments[0]);
|
|
}
|
|
|
|
var transform = GetRequiredTransformationsForCall(expectedTargetDetails, method, ref target,
|
|
ref argumentList, CallTransformation.All, out IParameterizedMember foundMethod);
|
|
|
|
// Note: after this, 'method' and 'foundMethod' may differ,
|
|
// but as far as allowed by IsAppropriateCallTarget().
|
|
|
|
// Need to update list of parameter names, because foundMethod is different and thus might use different names.
|
|
if (!method.Equals(foundMethod) && argumentList.ParameterNames.Length >= foundMethod.Parameters.Count)
|
|
{
|
|
for (int i = 0; i < foundMethod.Parameters.Count; i++)
|
|
{
|
|
argumentList.ParameterNames[i] = foundMethod.Parameters[i].Name;
|
|
}
|
|
}
|
|
|
|
Expression targetExpr;
|
|
string methodName = method.Name;
|
|
AstNodeCollection<AstType> typeArgumentList;
|
|
if ((transform & CallTransformation.NoOptionalArgumentAllowed) != 0)
|
|
{
|
|
argumentList.FirstOptionalArgumentIndex = -1;
|
|
}
|
|
if ((transform & CallTransformation.RequireTarget) != 0)
|
|
{
|
|
targetExpr = new MemberReferenceExpression(target.Expression, methodName);
|
|
typeArgumentList = ((MemberReferenceExpression)targetExpr).TypeArguments;
|
|
|
|
// HACK : convert this.Dispose() to ((IDisposable)this).Dispose(), if Dispose is an explicitly implemented interface method.
|
|
// settings.AlwaysCastTargetsOfExplicitInterfaceImplementationCalls == true is used in Windows Forms' InitializeComponent methods.
|
|
if (method.IsExplicitInterfaceImplementation && (target.Expression is ThisReferenceExpression || settings.AlwaysCastTargetsOfExplicitInterfaceImplementationCalls))
|
|
{
|
|
var interfaceMember = method.ExplicitlyImplementedInterfaceMembers.First();
|
|
var castExpression = new CastExpression(expressionBuilder.ConvertType(interfaceMember.DeclaringType), target.Expression.Detach());
|
|
methodName = interfaceMember.Name;
|
|
targetExpr = new MemberReferenceExpression(castExpression, methodName);
|
|
typeArgumentList = ((MemberReferenceExpression)targetExpr).TypeArguments;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
targetExpr = new IdentifierExpression(methodName);
|
|
typeArgumentList = ((IdentifierExpression)targetExpr).TypeArguments;
|
|
}
|
|
|
|
if ((transform & CallTransformation.RequireTypeArguments) != 0 && (!settings.AnonymousTypes || !method.TypeArguments.Any(a => a.ContainsAnonymousType())))
|
|
typeArgumentList.AddRange(method.TypeArguments.Select(expressionBuilder.ConvertType));
|
|
return new InvocationExpression(targetExpr, argumentList.GetArgumentExpressions())
|
|
.WithRR(new CSharpInvocationResolveResult(target.ResolveResult, foundMethod,
|
|
argumentList.GetArgumentResolveResultsDirect(), isExpandedForm: argumentList.IsExpandedForm));
|
|
}
|
|
|
|
/// <summary>
|
|
/// Converts a call to an Add method to a collection initializer expression.
|
|
/// </summary>
|
|
public ExpressionWithResolveResult BuildCollectionInitializerExpression(OpCode callOpCode, IMethod method,
|
|
InitializedObjectResolveResult target, IReadOnlyList<ILInstruction> callArguments)
|
|
{
|
|
// (see ECMA-334, section 12.7.11.4):
|
|
// The collection object to which a collection initializer is applied shall be of a type that implements
|
|
// System.Collections.IEnumerable or a compile-time error occurs. For each specified element in order,
|
|
// the collection initializer invokes an Add method on the target object with the expression list of the
|
|
// element initializer as argument list, applying normal overload resolution for each invocation. Thus, the
|
|
// collection object shall contain an applicable Add method for each element initializer.
|
|
|
|
// The list of applicable methods includes all methods (as of C# 6.0 extension methods, too) named 'Add'
|
|
// that can be invoked on the target object, with the following exceptions:
|
|
// - Methods with ref or out parameters may not be used,
|
|
// - methods that have type parameters, that cannot be inferred from the parameter list may not be used,
|
|
// - vararg methods may not be used.
|
|
// - named arguments are not supported.
|
|
// However, note that params methods may be used.
|
|
|
|
// At this point, we assume that 'method' fulfills all the conditions mentioned above. We just need to make
|
|
// sure that the correct method is called by resolving any ambiguities by inserting casts, if necessary.
|
|
|
|
ExpectedTargetDetails expectedTargetDetails = new ExpectedTargetDetails { CallOpCode = callOpCode };
|
|
var unused = new IdentifierExpression("initializedObject").WithRR(target).WithoutILInstruction();
|
|
var args = callArguments.ToList();
|
|
if (method.IsExtensionMethod)
|
|
args.Insert(0, new Nop());
|
|
|
|
var argumentList = BuildArgumentList(expectedTargetDetails, target, method,
|
|
firstParamIndex: 0, args, null);
|
|
argumentList.ArgumentNames = null;
|
|
argumentList.AddNamesToPrimitiveValues = false;
|
|
argumentList.UseImplicitlyTypedOut = false;
|
|
var transform = GetRequiredTransformationsForCall(expectedTargetDetails, method, ref unused,
|
|
ref argumentList, CallTransformation.None, out _);
|
|
Debug.Assert(transform == CallTransformation.None || transform == CallTransformation.NoOptionalArgumentAllowed);
|
|
|
|
// Calls with only one argument do not need an array initializer expression to wrap them.
|
|
// Any special cases are handled by the caller (i.e., ExpressionBuilder.TranslateObjectAndCollectionInitializer)
|
|
// Note: we intentionally ignore the firstOptionalArgumentIndex in this case.
|
|
int skipCount;
|
|
if (method.IsExtensionMethod)
|
|
{
|
|
if (argumentList.Arguments.Length == 2)
|
|
return argumentList.Arguments[1];
|
|
skipCount = 1;
|
|
}
|
|
else
|
|
{
|
|
if (argumentList.Arguments.Length == 1)
|
|
return argumentList.Arguments[0];
|
|
skipCount = 0;
|
|
}
|
|
|
|
if ((transform & CallTransformation.NoOptionalArgumentAllowed) != 0)
|
|
argumentList.FirstOptionalArgumentIndex = -1;
|
|
|
|
return new ArrayInitializerExpression(argumentList.GetArgumentExpressions(skipCount))
|
|
.WithRR(new CSharpInvocationResolveResult(target, method, argumentList.GetArgumentResolveResults(skipCount).ToArray(),
|
|
isExtensionMethodInvocation: method.IsExtensionMethod, isExpandedForm: argumentList.IsExpandedForm));
|
|
}
|
|
|
|
public ExpressionWithResolveResult BuildDictionaryInitializerExpression(OpCode callOpCode, IMethod method,
|
|
InitializedObjectResolveResult target, IReadOnlyList<ILInstruction> indices, ILInstruction value = null)
|
|
{
|
|
if (method is null)
|
|
throw new ArgumentNullException(nameof(method));
|
|
ExpectedTargetDetails expectedTargetDetails = new ExpectedTargetDetails { CallOpCode = callOpCode };
|
|
|
|
var callArguments = new List<ILInstruction>();
|
|
callArguments.Add(new LdNull());
|
|
callArguments.AddRange(indices);
|
|
callArguments.Add(value ?? new Nop());
|
|
|
|
var argumentList = BuildArgumentList(expectedTargetDetails, target, method, 1, callArguments, null);
|
|
var unused = new IdentifierExpression("initializedObject").WithRR(target).WithoutILInstruction();
|
|
|
|
var assignment = HandleAccessorCall(expectedTargetDetails, method, unused,
|
|
argumentList.Arguments.ToList(), argumentList.ArgumentNames);
|
|
|
|
if (((AssignmentExpression)assignment).Left is IndexerExpression indexer && !indexer.Target.IsNull)
|
|
indexer.Target.Remove();
|
|
|
|
if (value != null)
|
|
return assignment;
|
|
|
|
return new ExpressionWithResolveResult(((AssignmentExpression)assignment).Left.Detach());
|
|
}
|
|
|
|
private static bool IsInterpolatedStringCreation(IMethod method, ArgumentList argumentList)
|
|
{
|
|
return method.IsStatic && (
|
|
(method.DeclaringType.IsKnownType(KnownTypeCode.String) && method.Name == "Format") ||
|
|
(method.Name == "Create" && method.DeclaringType.Name == "FormattableStringFactory" &&
|
|
method.DeclaringType.Namespace == "System.Runtime.CompilerServices")
|
|
)
|
|
&& argumentList.ArgumentNames == null // Argument names are not allowed
|
|
&& (
|
|
argumentList.IsExpandedForm // Must be expanded form
|
|
|| !method.Parameters.Last().IsParams // -or- not a params overload
|
|
|| (argumentList.Length == 2 && argumentList.Arguments[1].Expression is ArrayCreateExpression) // -or- an array literal
|
|
);
|
|
}
|
|
|
|
private bool TryGetStringInterpolationTokens(ArgumentList argumentList, out string format, out List<(TokenKind Kind, int Index, int Alignment, string Format)> tokens)
|
|
{
|
|
tokens = null;
|
|
format = null;
|
|
TranslatedExpression[] arguments = argumentList.Arguments;
|
|
if (arguments.Length == 0 || argumentList.ArgumentNames != null || argumentList.ArgumentToParameterMap != null)
|
|
return false;
|
|
if (!(arguments[(int)0].ResolveResult is ConstantResolveResult crr && crr.Type.IsKnownType((KnownTypeCode)KnownTypeCode.String)))
|
|
return false;
|
|
if (!arguments.Skip(1).All(a => !a.Expression.DescendantsAndSelf.OfType<PrimitiveExpression>().Any(p => p.Value is string)))
|
|
return false;
|
|
tokens = new List<(TokenKind Kind, int Index, int Alignment, string Format)>();
|
|
int i = 0;
|
|
format = (string)crr.ConstantValue;
|
|
foreach (var (kind, data) in TokenizeFormatString(format))
|
|
{
|
|
int index;
|
|
string[] arg;
|
|
switch (kind)
|
|
{
|
|
case TokenKind.Error:
|
|
return false;
|
|
case TokenKind.String:
|
|
tokens.Add((kind, -1, 0, data));
|
|
break;
|
|
case TokenKind.Argument:
|
|
if (!int.TryParse(data, out index) || index != i)
|
|
return false;
|
|
i++;
|
|
tokens.Add((kind, index, 0, null));
|
|
break;
|
|
case TokenKind.ArgumentWithFormat:
|
|
arg = data.Split(new[] { ':' }, 2);
|
|
if (arg.Length != 2 || arg[1].Length == 0)
|
|
return false;
|
|
if (!int.TryParse(arg[0], out index) || index != i)
|
|
return false;
|
|
i++;
|
|
tokens.Add((kind, index, 0, arg[1]));
|
|
break;
|
|
case TokenKind.ArgumentWithAlignment:
|
|
arg = data.Split(new[] { ',' }, 2);
|
|
if (arg.Length != 2 || arg[1].Length == 0)
|
|
return false;
|
|
if (!int.TryParse(arg[0], out index) || index != i)
|
|
return false;
|
|
if (!int.TryParse(arg[1], out int alignment))
|
|
return false;
|
|
i++;
|
|
tokens.Add((kind, index, alignment, null));
|
|
break;
|
|
case TokenKind.ArgumentWithAlignmentAndFormat:
|
|
arg = data.Split(new[] { ',', ':' }, 3);
|
|
if (arg.Length != 3 || arg[1].Length == 0 || arg[2].Length == 0)
|
|
return false;
|
|
if (!int.TryParse(arg[0], out index) || index != i)
|
|
return false;
|
|
if (!int.TryParse(arg[1], out alignment))
|
|
return false;
|
|
i++;
|
|
tokens.Add((kind, index, alignment, arg[2]));
|
|
break;
|
|
default:
|
|
return false;
|
|
}
|
|
}
|
|
return i == arguments.Length - 1;
|
|
}
|
|
|
|
private enum TokenKind
|
|
{
|
|
Error,
|
|
String,
|
|
Argument,
|
|
ArgumentWithFormat,
|
|
ArgumentWithAlignment,
|
|
ArgumentWithAlignmentAndFormat,
|
|
}
|
|
|
|
private IEnumerable<(TokenKind, string)> TokenizeFormatString(string value)
|
|
{
|
|
int pos = -1;
|
|
|
|
int Peek(int steps = 1)
|
|
{
|
|
if (pos + steps < value.Length)
|
|
return value[pos + steps];
|
|
return -1;
|
|
}
|
|
|
|
int Next()
|
|
{
|
|
int val = Peek();
|
|
pos++;
|
|
return val;
|
|
}
|
|
|
|
int next;
|
|
TokenKind kind = TokenKind.String;
|
|
StringBuilder sb = new StringBuilder();
|
|
|
|
while ((next = Next()) > -1)
|
|
{
|
|
switch ((char)next)
|
|
{
|
|
case '{':
|
|
if (Peek() == '{')
|
|
{
|
|
kind = TokenKind.String;
|
|
sb.Append("{{");
|
|
Next();
|
|
}
|
|
else
|
|
{
|
|
if (sb.Length > 0)
|
|
{
|
|
yield return (kind, sb.ToString());
|
|
}
|
|
kind = TokenKind.Argument;
|
|
sb.Clear();
|
|
}
|
|
break;
|
|
case '}':
|
|
if (kind != TokenKind.String)
|
|
{
|
|
yield return (kind, sb.ToString());
|
|
sb.Clear();
|
|
kind = TokenKind.String;
|
|
}
|
|
else if (Peek() == '}')
|
|
{
|
|
sb.Append("}}");
|
|
Next();
|
|
}
|
|
else
|
|
{
|
|
yield return (TokenKind.Error, null);
|
|
}
|
|
break;
|
|
case ':':
|
|
if (kind == TokenKind.Argument)
|
|
{
|
|
kind = TokenKind.ArgumentWithFormat;
|
|
}
|
|
else if (kind == TokenKind.ArgumentWithAlignment)
|
|
{
|
|
kind = TokenKind.ArgumentWithAlignmentAndFormat;
|
|
}
|
|
sb.Append(':');
|
|
break;
|
|
case ',':
|
|
if (kind == TokenKind.Argument)
|
|
{
|
|
kind = TokenKind.ArgumentWithAlignment;
|
|
}
|
|
sb.Append(',');
|
|
break;
|
|
default:
|
|
sb.Append((char)next);
|
|
break;
|
|
}
|
|
}
|
|
if (sb.Length > 0)
|
|
{
|
|
if (kind == TokenKind.String)
|
|
yield return (kind, sb.ToString());
|
|
else
|
|
yield return (TokenKind.Error, null);
|
|
}
|
|
}
|
|
|
|
private ArgumentList BuildArgumentList(ExpectedTargetDetails expectedTargetDetails, ResolveResult target, IMethod method,
|
|
int firstParamIndex, IReadOnlyList<ILInstruction> callArguments, IReadOnlyList<int> argumentToParameterMap)
|
|
{
|
|
ArgumentList list = new ArgumentList();
|
|
|
|
// Translate arguments to the expected parameter types
|
|
var arguments = new List<TranslatedExpression>(method.Parameters.Count);
|
|
string[] argumentNames = null;
|
|
Debug.Assert(callArguments.Count == firstParamIndex + method.Parameters.Count);
|
|
var expectedParameters = new List<IParameter>(method.Parameters.Count); // parameters, but in argument order
|
|
bool isExpandedForm = false;
|
|
BitSet isPrimitiveValue = new BitSet(method.Parameters.Count);
|
|
|
|
// Optional arguments:
|
|
// This value has the following values:
|
|
// -2 - there are no optional arguments
|
|
// -1 - optional arguments are forbidden
|
|
// >= 0 - the index of the first argument that can be removed, because it is optional
|
|
// and is the default value of the parameter.
|
|
int firstOptionalArgumentIndex = expressionBuilder.settings.OptionalArguments ? -2 : -1;
|
|
for (int i = firstParamIndex; i < callArguments.Count; i++)
|
|
{
|
|
IParameter parameter;
|
|
if (argumentToParameterMap != null)
|
|
{
|
|
if (argumentNames == null && argumentToParameterMap[i] != i - firstParamIndex)
|
|
{
|
|
// Starting at the first argument that is out-of-place,
|
|
// assign names to that argument and all following arguments:
|
|
argumentNames = new string[method.Parameters.Count];
|
|
}
|
|
parameter = method.Parameters[argumentToParameterMap[i]];
|
|
if (argumentNames != null)
|
|
{
|
|
argumentNames[arguments.Count] = parameter.Name;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
parameter = method.Parameters[i - firstParamIndex];
|
|
}
|
|
var arg = expressionBuilder.Translate(callArguments[i], parameter.Type);
|
|
if (IsPrimitiveValueThatShouldBeNamedArgument(arg, method, parameter))
|
|
{
|
|
isPrimitiveValue.Set(arguments.Count);
|
|
}
|
|
if (IsOptionalArgument(parameter, arg))
|
|
{
|
|
if (firstOptionalArgumentIndex == -2)
|
|
firstOptionalArgumentIndex = i - firstParamIndex;
|
|
}
|
|
else
|
|
{
|
|
firstOptionalArgumentIndex = -2;
|
|
}
|
|
if (parameter.IsParams && i + 1 == callArguments.Count && argumentToParameterMap == null)
|
|
{
|
|
// 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 (TransformParamsArgument(expectedTargetDetails, target, method, parameter,
|
|
arg, ref expectedParameters, ref arguments))
|
|
{
|
|
Debug.Assert(argumentNames == null);
|
|
firstOptionalArgumentIndex = -1;
|
|
isExpandedForm = true;
|
|
continue;
|
|
}
|
|
}
|
|
|
|
IType parameterType;
|
|
if (parameter.Type.Kind == TypeKind.Dynamic)
|
|
{
|
|
parameterType = expressionBuilder.compilation.FindType(KnownTypeCode.Object);
|
|
}
|
|
else
|
|
{
|
|
parameterType = parameter.Type;
|
|
}
|
|
|
|
arg = arg.ConvertTo(parameterType, expressionBuilder, allowImplicitConversion: arg.Type.Kind != TypeKind.Dynamic);
|
|
|
|
if (parameter.ReferenceKind != ReferenceKind.None)
|
|
{
|
|
arg = ExpressionBuilder.ChangeDirectionExpressionTo(arg, parameter.ReferenceKind);
|
|
}
|
|
|
|
arguments.Add(arg);
|
|
expectedParameters.Add(parameter);
|
|
}
|
|
|
|
list.ExpectedParameters = expectedParameters.ToArray();
|
|
list.Arguments = arguments.ToArray();
|
|
list.ParameterNames = expectedParameters.SelectArray(p => p.Name);
|
|
list.ArgumentNames = argumentNames;
|
|
list.ArgumentToParameterMap = argumentToParameterMap;
|
|
list.IsExpandedForm = isExpandedForm;
|
|
list.IsPrimitiveValue = isPrimitiveValue;
|
|
list.FirstOptionalArgumentIndex = firstOptionalArgumentIndex;
|
|
list.UseImplicitlyTypedOut = true;
|
|
list.AddNamesToPrimitiveValues = expressionBuilder.settings.NamedArguments && expressionBuilder.settings.NonTrailingNamedArguments;
|
|
return list;
|
|
}
|
|
|
|
private bool IsPrimitiveValueThatShouldBeNamedArgument(TranslatedExpression arg, IMethod method, IParameter p)
|
|
{
|
|
if (!arg.ResolveResult.IsCompileTimeConstant || method.DeclaringType.IsKnownType(KnownTypeCode.NullableOfT))
|
|
return false;
|
|
return p.Type.IsKnownType(KnownTypeCode.Boolean);
|
|
}
|
|
|
|
private bool TransformParamsArgument(ExpectedTargetDetails expectedTargetDetails, ResolveResult targetResolveResult,
|
|
IMethod method, IParameter parameter, TranslatedExpression arg, ref List<IParameter> expectedParameters,
|
|
ref List<TranslatedExpression> arguments)
|
|
{
|
|
if (CheckArgument(out int length, out IType elementType))
|
|
{
|
|
var expandedParameters = new List<IParameter>(expectedParameters);
|
|
var expandedArguments = new List<TranslatedExpression>(arguments);
|
|
if (length > 0)
|
|
{
|
|
var arrayElements = ((ArrayCreateExpression)arg.Expression).Initializer.Elements.ToArray();
|
|
for (int j = 0; j < length; j++)
|
|
{
|
|
expandedParameters.Add(new DefaultParameter(elementType, parameter.Name + j));
|
|
if (j < arrayElements.Length)
|
|
expandedArguments.Add(new TranslatedExpression(arrayElements[j]));
|
|
else
|
|
expandedArguments.Add(expressionBuilder.GetDefaultValueExpression(elementType).WithoutILInstruction());
|
|
}
|
|
}
|
|
if (IsUnambiguousCall(expectedTargetDetails, method, targetResolveResult, Empty<IType>.Array,
|
|
expandedArguments.SelectArray(a => a.ResolveResult), argumentNames: null,
|
|
firstOptionalArgumentIndex: -1, out _,
|
|
out var bestCandidateIsExpandedForm) == OverloadResolutionErrors.None && bestCandidateIsExpandedForm)
|
|
{
|
|
expectedParameters = expandedParameters;
|
|
arguments = expandedArguments.SelectList(a => new TranslatedExpression(a.Expression.Detach()));
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
|
|
bool CheckArgument(out int len, out IType t)
|
|
{
|
|
len = 0;
|
|
t = null;
|
|
if (arg.ResolveResult is CSharpInvocationResolveResult csirr &&
|
|
csirr.Arguments.Count == 0 && csirr.Member is IMethod emptyMethod &&
|
|
emptyMethod.IsStatic &&
|
|
"System.Array.Empty" == emptyMethod.FullName &&
|
|
emptyMethod.TypeArguments.Count == 1)
|
|
{
|
|
t = emptyMethod.TypeArguments[0];
|
|
return true;
|
|
}
|
|
|
|
if (arg.ResolveResult is ArrayCreateResolveResult acrr &&
|
|
acrr.SizeArguments.Count == 1 &&
|
|
acrr.SizeArguments[0].IsCompileTimeConstant &&
|
|
acrr.SizeArguments[0].ConstantValue is int l)
|
|
{
|
|
len = l;
|
|
t = ((ArrayType)acrr.Type).ElementType;
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
}
|
|
|
|
bool IsOptionalArgument(IParameter parameter, TranslatedExpression arg)
|
|
{
|
|
if (!parameter.IsOptional || !arg.ResolveResult.IsCompileTimeConstant)
|
|
return false;
|
|
if (parameter.GetAttributes().Any(a => a.AttributeType.IsKnownType(KnownAttribute.CallerMemberName)
|
|
|| a.AttributeType.IsKnownType(KnownAttribute.CallerFilePath)
|
|
|| a.AttributeType.IsKnownType(KnownAttribute.CallerLineNumber)))
|
|
return false;
|
|
return object.Equals(parameter.GetConstantValue(), arg.ResolveResult.ConstantValue);
|
|
}
|
|
|
|
[Flags]
|
|
enum CallTransformation
|
|
{
|
|
None = 0,
|
|
RequireTarget = 1,
|
|
RequireTypeArguments = 2,
|
|
NoOptionalArgumentAllowed = 4,
|
|
All = 7
|
|
}
|
|
|
|
private CallTransformation GetRequiredTransformationsForCall(ExpectedTargetDetails expectedTargetDetails, IMethod method,
|
|
ref TranslatedExpression target, ref ArgumentList argumentList, CallTransformation allowedTransforms, out IParameterizedMember foundMethod)
|
|
{
|
|
CallTransformation transform = CallTransformation.None;
|
|
|
|
// initialize requireTarget flag
|
|
bool requireTarget;
|
|
ResolveResult targetResolveResult;
|
|
if ((allowedTransforms & CallTransformation.RequireTarget) != 0)
|
|
{
|
|
if (settings.AlwaysQualifyMemberReferences || expressionBuilder.HidesVariableWithName(method.Name))
|
|
{
|
|
requireTarget = true;
|
|
}
|
|
else
|
|
{
|
|
if (method.IsLocalFunction)
|
|
requireTarget = false;
|
|
else if (method.IsStatic)
|
|
requireTarget = !expressionBuilder.IsCurrentOrContainingType(method.DeclaringTypeDefinition) || method.Name == ".cctor";
|
|
else if (method.Name == ".ctor")
|
|
requireTarget = true; // always use target for base/this-ctor-call, the constructor initializer pattern depends on this
|
|
else if (target.Expression is BaseReferenceExpression)
|
|
requireTarget = (expectedTargetDetails.CallOpCode != OpCode.CallVirt && method.IsVirtual);
|
|
else
|
|
requireTarget = target.Expression is not ThisReferenceExpression;
|
|
}
|
|
targetResolveResult = requireTarget ? target.ResolveResult : null;
|
|
}
|
|
else
|
|
{
|
|
// HACK: this is a special case for collection initializer calls, they do not allow a target to be
|
|
// emitted, but we still need it for overload resolution.
|
|
requireTarget = true;
|
|
targetResolveResult = target.ResolveResult;
|
|
}
|
|
|
|
// initialize requireTypeArguments flag
|
|
bool requireTypeArguments;
|
|
IType[] typeArguments;
|
|
bool appliedRequireTypeArgumentsShortcut = false;
|
|
if (method.TypeParameters.Count > 0 && (allowedTransforms & CallTransformation.RequireTypeArguments) != 0
|
|
&& !IsPossibleExtensionMethodCallOnNull(method, argumentList.Arguments))
|
|
{
|
|
// The ambiguity resolution below only adds type arguments as last resort measure, however there are
|
|
// methods, such as Enumerable.OfType<TResult>(IEnumerable input) that always require type arguments,
|
|
// as those cannot be inferred from the parameters, which leads to bloated expressions full of extra casts
|
|
// that are no longer required once we add the type arguments.
|
|
// We lend overload resolution a hand by detecting such cases beforehand and requiring type arguments,
|
|
// if necessary.
|
|
if (!CanInferTypeArgumentsFromArguments(method, argumentList, expressionBuilder.typeInference))
|
|
{
|
|
requireTypeArguments = true;
|
|
typeArguments = method.TypeArguments.ToArray();
|
|
appliedRequireTypeArgumentsShortcut = true;
|
|
}
|
|
else
|
|
{
|
|
requireTypeArguments = false;
|
|
typeArguments = Empty<IType>.Array;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
requireTypeArguments = false;
|
|
typeArguments = Empty<IType>.Array;
|
|
}
|
|
|
|
bool targetCasted = false;
|
|
bool argumentsCasted = false;
|
|
bool originalRequireTarget = requireTarget;
|
|
bool skipTargetCast = method.Accessibility <= Accessibility.Protected && expressionBuilder.IsBaseTypeOfCurrentType(method.DeclaringTypeDefinition);
|
|
OverloadResolutionErrors errors;
|
|
while ((errors = IsUnambiguousCall(expectedTargetDetails, method, targetResolveResult, typeArguments,
|
|
argumentList.GetArgumentResolveResults().ToArray(), argumentList.ArgumentNames, argumentList.FirstOptionalArgumentIndex, out foundMethod,
|
|
out var bestCandidateIsExpandedForm)) != OverloadResolutionErrors.None || bestCandidateIsExpandedForm != argumentList.IsExpandedForm)
|
|
{
|
|
switch (errors)
|
|
{
|
|
case OverloadResolutionErrors.OutVarTypeMismatch:
|
|
Debug.Assert(argumentList.UseImplicitlyTypedOut);
|
|
argumentList.UseImplicitlyTypedOut = false;
|
|
continue;
|
|
case OverloadResolutionErrors.TypeInferenceFailed:
|
|
if ((allowedTransforms & CallTransformation.RequireTypeArguments) != 0)
|
|
{
|
|
goto case OverloadResolutionErrors.WrongNumberOfTypeArguments;
|
|
}
|
|
goto default;
|
|
case OverloadResolutionErrors.WrongNumberOfTypeArguments:
|
|
Debug.Assert((allowedTransforms & CallTransformation.RequireTypeArguments) != 0);
|
|
if (requireTypeArguments)
|
|
goto default;
|
|
requireTypeArguments = true;
|
|
typeArguments = method.TypeArguments.ToArray();
|
|
continue;
|
|
case OverloadResolutionErrors.MissingArgumentForRequiredParameter:
|
|
if (argumentList.FirstOptionalArgumentIndex == -1)
|
|
goto default;
|
|
argumentList.FirstOptionalArgumentIndex = -1;
|
|
continue;
|
|
default:
|
|
// TODO : implement some more intelligent algorithm that decides which of these fixes (cast args, add target, cast target, add type args)
|
|
// is best in this case. Additionally we should not cast all arguments at once, but step-by-step try to add only a minimal number of casts.
|
|
if (argumentList.FirstOptionalArgumentIndex >= 0)
|
|
{
|
|
argumentList.FirstOptionalArgumentIndex = -1;
|
|
}
|
|
else if (!argumentsCasted)
|
|
{
|
|
// If we added type arguments beforehand, but that didn't make the code any better,
|
|
// undo that decision and add casts first.
|
|
if (appliedRequireTypeArgumentsShortcut)
|
|
{
|
|
requireTypeArguments = false;
|
|
typeArguments = Empty<IType>.Array;
|
|
appliedRequireTypeArgumentsShortcut = false;
|
|
}
|
|
argumentsCasted = true;
|
|
argumentList.UseImplicitlyTypedOut = false;
|
|
CastArguments(argumentList.Arguments, argumentList.ExpectedParameters);
|
|
}
|
|
else if ((allowedTransforms & CallTransformation.RequireTarget) != 0 && !requireTarget)
|
|
{
|
|
requireTarget = true;
|
|
targetResolveResult = target.ResolveResult;
|
|
}
|
|
else if ((allowedTransforms & CallTransformation.RequireTarget) != 0 && !targetCasted)
|
|
{
|
|
if (skipTargetCast && requireTarget != originalRequireTarget)
|
|
{
|
|
requireTarget = originalRequireTarget;
|
|
if (!originalRequireTarget)
|
|
targetResolveResult = null;
|
|
allowedTransforms &= ~CallTransformation.RequireTarget;
|
|
}
|
|
else
|
|
{
|
|
targetCasted = true;
|
|
target = target.ConvertTo(method.DeclaringType, expressionBuilder);
|
|
targetResolveResult = target.ResolveResult;
|
|
}
|
|
}
|
|
else if ((allowedTransforms & CallTransformation.RequireTypeArguments) != 0 && !requireTypeArguments)
|
|
{
|
|
requireTypeArguments = true;
|
|
typeArguments = method.TypeArguments.ToArray();
|
|
}
|
|
else
|
|
{
|
|
break;
|
|
}
|
|
continue;
|
|
}
|
|
// We've given up.
|
|
foundMethod = method;
|
|
break;
|
|
}
|
|
if ((allowedTransforms & CallTransformation.RequireTarget) != 0 && requireTarget)
|
|
transform |= CallTransformation.RequireTarget;
|
|
if ((allowedTransforms & CallTransformation.RequireTypeArguments) != 0 && requireTypeArguments)
|
|
transform |= CallTransformation.RequireTypeArguments;
|
|
if (argumentList.FirstOptionalArgumentIndex < 0)
|
|
transform |= CallTransformation.NoOptionalArgumentAllowed;
|
|
return transform;
|
|
}
|
|
|
|
private bool IsPossibleExtensionMethodCallOnNull(IMethod method, IList<TranslatedExpression> arguments)
|
|
{
|
|
return method.IsExtensionMethod && arguments.Count > 0 && arguments[0].Expression is NullReferenceExpression;
|
|
}
|
|
|
|
static bool CanInferTypeArgumentsFromArguments(IMethod method, ArgumentList argumentList, TypeInference typeInference)
|
|
{
|
|
if (method.TypeParameters.Count == 0)
|
|
return true;
|
|
// always use unspecialized member, otherwise type inference fails
|
|
method = (IMethod)method.MemberDefinition;
|
|
IReadOnlyList<IType> paramTypesInArgumentOrder;
|
|
if (argumentList.ArgumentToParameterMap == null)
|
|
paramTypesInArgumentOrder = method.Parameters.SelectReadOnlyArray(p => p.Type);
|
|
else
|
|
paramTypesInArgumentOrder = argumentList.ArgumentToParameterMap
|
|
.SelectReadOnlyArray(
|
|
index => index >= 0 ? method.Parameters[index].Type : SpecialType.UnknownType
|
|
);
|
|
typeInference.InferTypeArguments(method.TypeParameters,
|
|
argumentList.Arguments.SelectReadOnlyArray(a => a.ResolveResult), paramTypesInArgumentOrder,
|
|
out bool success);
|
|
return success;
|
|
}
|
|
|
|
private void CastArguments(IList<TranslatedExpression> arguments, IList<IParameter> expectedParameters)
|
|
{
|
|
for (int i = 0; i < arguments.Count; i++)
|
|
{
|
|
if (settings.AnonymousTypes && expectedParameters[i].Type.ContainsAnonymousType())
|
|
{
|
|
if (arguments[i].Expression is LambdaExpression lambda)
|
|
{
|
|
ModifyReturnTypeOfLambda(lambda);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
IType parameterType;
|
|
if (expectedParameters[i].Type.Kind == TypeKind.Dynamic)
|
|
{
|
|
parameterType = expressionBuilder.compilation.FindType(KnownTypeCode.Object);
|
|
}
|
|
else
|
|
{
|
|
parameterType = expectedParameters[i].Type;
|
|
}
|
|
|
|
arguments[i] = arguments[i].ConvertTo(parameterType, expressionBuilder, allowImplicitConversion: false);
|
|
}
|
|
}
|
|
}
|
|
|
|
static bool IsNullConditional(Expression expr)
|
|
{
|
|
return expr is UnaryOperatorExpression uoe && uoe.Operator == UnaryOperatorType.NullConditional;
|
|
}
|
|
|
|
private void ModifyReturnTypeOfLambda(LambdaExpression lambda)
|
|
{
|
|
var resolveResult = (DecompiledLambdaResolveResult)lambda.GetResolveResult();
|
|
if (lambda.Body is Expression exprBody)
|
|
lambda.Body = new TranslatedExpression(exprBody.Detach()).ConvertTo(resolveResult.ReturnType, expressionBuilder);
|
|
else
|
|
ModifyReturnStatementInsideLambda(resolveResult.ReturnType, lambda);
|
|
resolveResult.InferredReturnType = resolveResult.ReturnType;
|
|
}
|
|
|
|
private void ModifyReturnStatementInsideLambda(IType returnType, AstNode parent)
|
|
{
|
|
foreach (var child in parent.Children)
|
|
{
|
|
if (child is LambdaExpression || child is AnonymousMethodExpression)
|
|
continue;
|
|
if (child is ReturnStatement ret)
|
|
{
|
|
ret.Expression = new TranslatedExpression(ret.Expression.Detach()).ConvertTo(returnType, expressionBuilder);
|
|
continue;
|
|
}
|
|
ModifyReturnStatementInsideLambda(returnType, child);
|
|
}
|
|
}
|
|
|
|
private bool IsDelegateEqualityComparison(IMethod method, IList<TranslatedExpression> arguments)
|
|
{
|
|
// Comparison on a delegate type is a C# builtin operator
|
|
// that compiles down to a Delegate.op_Equality call.
|
|
// We handle this as a special case to avoid inserting a cast to System.Delegate.
|
|
return method.IsOperator
|
|
&& method.DeclaringType.IsKnownType(KnownTypeCode.Delegate)
|
|
&& (method.Name == "op_Equality" || method.Name == "op_Inequality")
|
|
&& arguments.Count == 2
|
|
&& arguments[0].Type.Kind == TypeKind.Delegate
|
|
&& arguments[1].Type.Equals(arguments[0].Type);
|
|
}
|
|
|
|
private Expression HandleDelegateEqualityComparison(IMethod method, IList<TranslatedExpression> arguments)
|
|
{
|
|
return new BinaryOperatorExpression(
|
|
arguments[0],
|
|
method.Name == "op_Equality" ? BinaryOperatorType.Equality : BinaryOperatorType.InEquality,
|
|
arguments[1]
|
|
);
|
|
}
|
|
|
|
private ExpressionWithResolveResult HandleImplicitConversion(IMethod method, TranslatedExpression argument)
|
|
{
|
|
var conversions = CSharpConversions.Get(expressionBuilder.compilation);
|
|
IType targetType = method.ReturnType;
|
|
var conv = conversions.ImplicitConversion(argument.Type, targetType);
|
|
if (!(conv.IsUserDefined && conv.IsValid && conv.Method.Equals(method)))
|
|
{
|
|
// implicit conversion to targetType isn't directly possible, so first insert a cast to the argument type
|
|
argument = argument.ConvertTo(method.Parameters[0].Type, expressionBuilder);
|
|
conv = conversions.ImplicitConversion(argument.Type, targetType);
|
|
}
|
|
if (argument.Expression is DirectionExpression { FieldDirection: FieldDirection.In, Expression: var lvalueExpr })
|
|
{
|
|
// `(TargetType)(in arg)` is invalid syntax.
|
|
// Also, `f(in arg)` is invalid when there's an implicit conversion involved.
|
|
argument = argument.UnwrapChild(lvalueExpr);
|
|
}
|
|
return new CastExpression(expressionBuilder.ConvertType(targetType), argument.Expression)
|
|
.WithRR(new ConversionResolveResult(targetType, argument.ResolveResult, conv));
|
|
}
|
|
|
|
OverloadResolutionErrors IsUnambiguousCall(ExpectedTargetDetails expectedTargetDetails, IMethod method,
|
|
ResolveResult target, IType[] typeArguments, ResolveResult[] arguments,
|
|
string[] argumentNames, int firstOptionalArgumentIndex,
|
|
out IParameterizedMember foundMember, out bool bestCandidateIsExpandedForm)
|
|
{
|
|
foundMember = null;
|
|
bestCandidateIsExpandedForm = false;
|
|
var currentTypeDefinition = resolver.CurrentTypeDefinition;
|
|
var lookup = new MemberLookup(currentTypeDefinition, currentTypeDefinition.ParentModule);
|
|
|
|
Log.WriteLine("IsUnambiguousCall: Performing overload resolution for " + method);
|
|
Log.WriteCollection(" Arguments: ", arguments);
|
|
|
|
argumentNames = firstOptionalArgumentIndex < 0 || argumentNames == null
|
|
? argumentNames
|
|
: argumentNames.Take(firstOptionalArgumentIndex).ToArray();
|
|
|
|
var or = new OverloadResolution(resolver.Compilation,
|
|
arguments, argumentNames, typeArguments,
|
|
conversions: expressionBuilder.resolver.conversions);
|
|
if (expectedTargetDetails.CallOpCode == OpCode.NewObj)
|
|
{
|
|
foreach (IMethod ctor in method.DeclaringType.GetConstructors())
|
|
{
|
|
bool allowProtectedAccess =
|
|
resolver.CurrentTypeDefinition == method.DeclaringTypeDefinition;
|
|
if (lookup.IsAccessible(ctor, allowProtectedAccess))
|
|
{
|
|
or.AddCandidate(ctor);
|
|
}
|
|
}
|
|
}
|
|
else if (method.IsOperator)
|
|
{
|
|
IEnumerable<IParameterizedMember> operatorCandidates;
|
|
if (arguments.Length == 1)
|
|
{
|
|
IType argType = NullableType.GetUnderlyingType(arguments[0].Type);
|
|
operatorCandidates = resolver.GetUserDefinedOperatorCandidates(argType, method.Name);
|
|
if (method.Name == "op_Explicit")
|
|
{
|
|
// For casts, also consider candidates from the target type we are casting to.
|
|
var hashSet = new HashSet<IParameterizedMember>(operatorCandidates);
|
|
IType targetType = NullableType.GetUnderlyingType(method.ReturnType);
|
|
hashSet.UnionWith(
|
|
resolver.GetUserDefinedOperatorCandidates(targetType, method.Name)
|
|
);
|
|
operatorCandidates = hashSet;
|
|
}
|
|
}
|
|
else if (arguments.Length == 2)
|
|
{
|
|
IType lhsType = NullableType.GetUnderlyingType(arguments[0].Type);
|
|
IType rhsType = NullableType.GetUnderlyingType(arguments[1].Type);
|
|
var hashSet = new HashSet<IParameterizedMember>();
|
|
hashSet.UnionWith(resolver.GetUserDefinedOperatorCandidates(lhsType, method.Name));
|
|
hashSet.UnionWith(resolver.GetUserDefinedOperatorCandidates(rhsType, method.Name));
|
|
operatorCandidates = hashSet;
|
|
}
|
|
else
|
|
{
|
|
operatorCandidates = EmptyList<IParameterizedMember>.Instance;
|
|
}
|
|
foreach (var m in operatorCandidates)
|
|
{
|
|
or.AddCandidate(m);
|
|
}
|
|
}
|
|
else if (target == null)
|
|
{
|
|
var result = resolver.ResolveSimpleName(method.Name, typeArguments, isInvocationTarget: true)
|
|
as MethodGroupResolveResult;
|
|
if (result == null)
|
|
return OverloadResolutionErrors.AmbiguousMatch;
|
|
or.AddMethodLists(result.MethodsGroupedByDeclaringType.ToArray());
|
|
}
|
|
else
|
|
{
|
|
var result = lookup.Lookup(target, method.Name, typeArguments, isInvocation: true) as MethodGroupResolveResult;
|
|
if (result == null)
|
|
return OverloadResolutionErrors.AmbiguousMatch;
|
|
or.AddMethodLists(result.MethodsGroupedByDeclaringType.ToArray());
|
|
}
|
|
bestCandidateIsExpandedForm = or.BestCandidateIsExpandedForm;
|
|
if (or.BestCandidateErrors != OverloadResolutionErrors.None)
|
|
return or.BestCandidateErrors;
|
|
if (or.IsAmbiguous)
|
|
return OverloadResolutionErrors.AmbiguousMatch;
|
|
foundMember = or.GetBestCandidateWithSubstitutedTypeArguments();
|
|
if (!IsAppropriateCallTarget(expectedTargetDetails, method, foundMember))
|
|
return OverloadResolutionErrors.AmbiguousMatch;
|
|
var map = or.GetArgumentToParameterMap();
|
|
for (int i = 0; i < arguments.Length; i++)
|
|
{
|
|
ResolveResult arg = arguments[i];
|
|
int parameterIndex = map[i];
|
|
if (arg is OutVarResolveResult rr && parameterIndex >= 0)
|
|
{
|
|
var param = foundMember.Parameters[parameterIndex];
|
|
var paramType = param.Type.UnwrapByRef();
|
|
if (!paramType.Equals(rr.OriginalVariableType))
|
|
return OverloadResolutionErrors.OutVarTypeMismatch;
|
|
}
|
|
}
|
|
|
|
return OverloadResolutionErrors.None;
|
|
}
|
|
|
|
bool IsUnambiguousAccess(ExpectedTargetDetails expectedTargetDetails, ResolveResult target, IMethod method,
|
|
IList<TranslatedExpression> arguments, string[] argumentNames, out IMember foundMember)
|
|
{
|
|
Log.WriteLine("IsUnambiguousAccess: Performing overload resolution for " + method);
|
|
Log.WriteCollection(" Arguments: ", arguments.Select(a => a.ResolveResult));
|
|
|
|
foundMember = null;
|
|
if (target == null)
|
|
{
|
|
var result = resolver.ResolveSimpleName(method.AccessorOwner.Name,
|
|
EmptyList<IType>.Instance,
|
|
isInvocationTarget: false) as MemberResolveResult;
|
|
if (result == null || result.IsError)
|
|
return false;
|
|
foundMember = result.Member;
|
|
}
|
|
else
|
|
{
|
|
var lookup = new MemberLookup(resolver.CurrentTypeDefinition, resolver.CurrentTypeDefinition.ParentModule);
|
|
if (method.AccessorOwner.SymbolKind == SymbolKind.Indexer)
|
|
{
|
|
var or = new OverloadResolution(resolver.Compilation,
|
|
arguments.SelectArray(a => a.ResolveResult),
|
|
argumentNames: argumentNames,
|
|
typeArguments: Empty<IType>.Array,
|
|
conversions: expressionBuilder.resolver.conversions);
|
|
or.AddMethodLists(lookup.LookupIndexers(target));
|
|
if (or.BestCandidateErrors != OverloadResolutionErrors.None)
|
|
return false;
|
|
if (or.IsAmbiguous)
|
|
return false;
|
|
foundMember = or.GetBestCandidateWithSubstitutedTypeArguments();
|
|
}
|
|
else
|
|
{
|
|
var result = lookup.Lookup(target,
|
|
method.AccessorOwner.Name,
|
|
EmptyList<IType>.Instance,
|
|
isInvocation: false) as MemberResolveResult;
|
|
if (result == null || result.IsError)
|
|
return false;
|
|
foundMember = result.Member;
|
|
}
|
|
}
|
|
return foundMember != null && IsAppropriateCallTarget(expectedTargetDetails, method.AccessorOwner, foundMember);
|
|
}
|
|
|
|
ExpressionWithResolveResult HandleAccessorCall(ExpectedTargetDetails expectedTargetDetails, IMethod method,
|
|
TranslatedExpression target, List<TranslatedExpression> arguments, string[] argumentNames)
|
|
{
|
|
bool requireTarget;
|
|
if (settings.AlwaysQualifyMemberReferences || method.AccessorOwner.SymbolKind == SymbolKind.Indexer || expressionBuilder.HidesVariableWithName(method.AccessorOwner.Name))
|
|
requireTarget = true;
|
|
else if (method.IsStatic)
|
|
requireTarget = !expressionBuilder.IsCurrentOrContainingType(method.DeclaringTypeDefinition);
|
|
else
|
|
requireTarget = !(target.Expression is ThisReferenceExpression);
|
|
bool targetCasted = false;
|
|
bool isSetter = method.ReturnType.IsKnownType(KnownTypeCode.Void);
|
|
bool argumentsCasted = (isSetter && method.Parameters.Count == 1) || (!isSetter && method.Parameters.Count == 0);
|
|
var targetResolveResult = requireTarget ? target.ResolveResult : null;
|
|
|
|
TranslatedExpression value = default(TranslatedExpression);
|
|
if (isSetter)
|
|
{
|
|
value = arguments.Last();
|
|
arguments.Remove(value);
|
|
}
|
|
|
|
IMember foundMember;
|
|
while (!IsUnambiguousAccess(expectedTargetDetails, targetResolveResult, method, arguments, argumentNames, out foundMember))
|
|
{
|
|
if (!argumentsCasted)
|
|
{
|
|
argumentsCasted = true;
|
|
CastArguments(arguments, method.Parameters.ToList());
|
|
}
|
|
else if (!requireTarget)
|
|
{
|
|
requireTarget = true;
|
|
targetResolveResult = target.ResolveResult;
|
|
}
|
|
else if (!targetCasted)
|
|
{
|
|
targetCasted = true;
|
|
target = target.ConvertTo(method.AccessorOwner.DeclaringType, expressionBuilder);
|
|
targetResolveResult = target.ResolveResult;
|
|
}
|
|
else
|
|
{
|
|
foundMember = method.AccessorOwner;
|
|
break;
|
|
}
|
|
}
|
|
|
|
var rr = new MemberResolveResult(target.ResolveResult, foundMember);
|
|
|
|
if (isSetter)
|
|
{
|
|
TranslatedExpression expr;
|
|
|
|
if (arguments.Count != 0)
|
|
{
|
|
expr = new IndexerExpression(target.ResolveResult is InitializedObjectResolveResult ? null : target.Expression, arguments.Select(a => a.Expression))
|
|
.WithoutILInstruction().WithRR(rr);
|
|
}
|
|
else if (requireTarget)
|
|
{
|
|
expr = new MemberReferenceExpression(target.Expression, method.AccessorOwner.Name)
|
|
.WithoutILInstruction().WithRR(rr);
|
|
}
|
|
else
|
|
{
|
|
expr = new IdentifierExpression(method.AccessorOwner.Name)
|
|
.WithoutILInstruction().WithRR(rr);
|
|
}
|
|
|
|
var op = AssignmentOperatorType.Assign;
|
|
if (method.AccessorOwner is IEvent parentEvent)
|
|
{
|
|
if (method.Equals(parentEvent.AddAccessor))
|
|
{
|
|
op = AssignmentOperatorType.Add;
|
|
}
|
|
if (method.Equals(parentEvent.RemoveAccessor))
|
|
{
|
|
op = AssignmentOperatorType.Subtract;
|
|
}
|
|
}
|
|
return new AssignmentExpression(expr, op, value.Expression).WithRR(new TypeResolveResult(method.AccessorOwner.ReturnType));
|
|
}
|
|
else
|
|
{
|
|
if (arguments.Count != 0)
|
|
{
|
|
return new IndexerExpression(target.Expression, arguments.Select(a => a.Expression))
|
|
.WithoutILInstruction().WithRR(rr);
|
|
}
|
|
else if (requireTarget)
|
|
{
|
|
return new MemberReferenceExpression(target.Expression, method.AccessorOwner.Name)
|
|
.WithoutILInstruction().WithRR(rr);
|
|
}
|
|
else
|
|
{
|
|
return new IdentifierExpression(method.AccessorOwner.Name)
|
|
.WithoutILInstruction().WithRR(rr);
|
|
}
|
|
}
|
|
}
|
|
|
|
bool IsAppropriateCallTarget(ExpectedTargetDetails expectedTargetDetails, IMember expectedTarget, IMember actualTarget)
|
|
{
|
|
if (expectedTarget.Equals(actualTarget, NormalizeTypeVisitor.TypeErasure))
|
|
return true;
|
|
|
|
if (expectedTargetDetails.CallOpCode == OpCode.CallVirt && actualTarget.IsOverride)
|
|
{
|
|
if (expectedTargetDetails.NeedsBoxingConversion && actualTarget.DeclaringType.IsReferenceType != true)
|
|
return false;
|
|
foreach (var possibleTarget in InheritanceHelper.GetBaseMembers(actualTarget, false))
|
|
{
|
|
if (expectedTarget.Equals(possibleTarget, NormalizeTypeVisitor.TypeErasure))
|
|
return true;
|
|
if (!possibleTarget.IsOverride)
|
|
break;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
ExpressionWithResolveResult HandleConstructorCall(ExpectedTargetDetails expectedTargetDetails, ResolveResult target, IMethod method, ArgumentList argumentList)
|
|
{
|
|
if (settings.AnonymousTypes && method.DeclaringType.IsAnonymousType())
|
|
{
|
|
Debug.Assert(argumentList.ArgumentToParameterMap == null && argumentList.ArgumentNames == null && argumentList.FirstOptionalArgumentIndex < 0);
|
|
var atce = new AnonymousTypeCreateExpression();
|
|
if (argumentList.CanInferAnonymousTypePropertyNamesFromArguments())
|
|
{
|
|
atce.Initializers.AddRange(argumentList.GetArgumentExpressions());
|
|
}
|
|
else
|
|
{
|
|
for (int i = 0; i < argumentList.Length; i++)
|
|
{
|
|
atce.Initializers.Add(
|
|
new NamedExpression {
|
|
Name = argumentList.ExpectedParameters[i].Name,
|
|
Expression = argumentList.Arguments[i].ConvertTo(argumentList.ExpectedParameters[i].Type, expressionBuilder)
|
|
});
|
|
}
|
|
}
|
|
return atce.WithRR(new CSharpInvocationResolveResult(
|
|
target, method, argumentList.GetArgumentResolveResults(),
|
|
isExpandedForm: argumentList.IsExpandedForm, argumentToParameterMap: argumentList.ArgumentToParameterMap
|
|
));
|
|
}
|
|
else
|
|
{
|
|
while (IsUnambiguousCall(expectedTargetDetails, method, null, Empty<IType>.Array,
|
|
argumentList.GetArgumentResolveResults().ToArray(),
|
|
argumentList.ArgumentNames, argumentList.FirstOptionalArgumentIndex, out _,
|
|
out var bestCandidateIsExpandedForm) != OverloadResolutionErrors.None || bestCandidateIsExpandedForm != argumentList.IsExpandedForm)
|
|
{
|
|
if (argumentList.FirstOptionalArgumentIndex >= 0)
|
|
{
|
|
argumentList.FirstOptionalArgumentIndex = -1;
|
|
continue;
|
|
}
|
|
CastArguments(argumentList.Arguments, argumentList.ExpectedParameters);
|
|
break; // make sure that we don't not end up in an infinite loop
|
|
}
|
|
return new ObjectCreateExpression(
|
|
expressionBuilder.ConvertType(method.DeclaringType),
|
|
argumentList.GetArgumentExpressions()
|
|
).WithRR(new CSharpInvocationResolveResult(
|
|
target, method, argumentList.GetArgumentResolveResults().ToArray(),
|
|
isExpandedForm: argumentList.IsExpandedForm, argumentToParameterMap: argumentList.ArgumentToParameterMap
|
|
));
|
|
}
|
|
}
|
|
|
|
TranslatedExpression HandleDelegateConstruction(CallInstruction inst)
|
|
{
|
|
ILInstruction thisArg = inst.Arguments[0];
|
|
ILInstruction func = inst.Arguments[1];
|
|
IMethod method;
|
|
ExpectedTargetDetails expectedTargetDetails = default;
|
|
switch (func.OpCode)
|
|
{
|
|
case OpCode.LdFtn:
|
|
method = ((LdFtn)func).Method;
|
|
expectedTargetDetails.CallOpCode = OpCode.Call;
|
|
break;
|
|
case OpCode.LdVirtFtn:
|
|
method = ((LdVirtFtn)func).Method;
|
|
expectedTargetDetails.CallOpCode = OpCode.CallVirt;
|
|
break;
|
|
default:
|
|
throw new ArgumentException($"Unknown instruction type: {func.OpCode}");
|
|
}
|
|
if (CanUseDelegateConstruction(method, thisArg, inst.Method.DeclaringType.GetDelegateInvokeMethod()))
|
|
{
|
|
return HandleDelegateConstruction(inst.Method.DeclaringType, method, expectedTargetDetails, thisArg, inst);
|
|
}
|
|
else
|
|
{
|
|
var argumentList = BuildArgumentList(expectedTargetDetails, null, inst.Method,
|
|
0, inst.Arguments, null);
|
|
return HandleConstructorCall(new ExpectedTargetDetails { CallOpCode = OpCode.NewObj }, null, inst.Method, argumentList).WithILInstruction(inst);
|
|
}
|
|
}
|
|
|
|
private bool CanUseDelegateConstruction(IMethod targetMethod, ILInstruction thisArg, IMethod invokeMethod)
|
|
{
|
|
// Accessors cannot be directly referenced as method group in C#
|
|
// see https://github.com/icsharpcode/ILSpy/issues/1741#issuecomment-540179101
|
|
if (targetMethod.IsAccessor)
|
|
return false;
|
|
if (targetMethod.IsStatic)
|
|
{
|
|
// If the invoke method is known, we can compare the parameter counts to figure out whether the
|
|
// delegate is static or binds the first argument
|
|
if (invokeMethod != null)
|
|
{
|
|
if (invokeMethod.Parameters.Count == targetMethod.Parameters.Count)
|
|
{
|
|
return thisArg.MatchLdNull();
|
|
}
|
|
else if (targetMethod.IsExtensionMethod && invokeMethod.Parameters.Count == targetMethod.Parameters.Count - 1)
|
|
{
|
|
return true;
|
|
}
|
|
else
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// delegate type unknown:
|
|
return thisArg.MatchLdNull() || targetMethod.IsExtensionMethod;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// targetMethod is instance method
|
|
if (invokeMethod != null && invokeMethod.Parameters.Count != targetMethod.Parameters.Count)
|
|
return false;
|
|
return true;
|
|
}
|
|
}
|
|
|
|
internal TranslatedExpression Build(LdVirtDelegate inst)
|
|
{
|
|
return HandleDelegateConstruction(inst.Type, inst.Method, new ExpectedTargetDetails { CallOpCode = OpCode.CallVirt }, inst.Argument, inst);
|
|
}
|
|
|
|
internal ExpressionWithResolveResult BuildMethodReference(IMethod method, bool isVirtual)
|
|
{
|
|
var expr = BuildDelegateReference(method, invokeMethod: null, new ExpectedTargetDetails { CallOpCode = isVirtual ? OpCode.CallVirt : OpCode.Call }, thisArg: null);
|
|
expr.Expression.RemoveAnnotations<ResolveResult>();
|
|
return expr.Expression.WithRR(new MemberResolveResult(null, method));
|
|
}
|
|
|
|
ExpressionWithResolveResult BuildDelegateReference(IMethod method, IMethod invokeMethod, ExpectedTargetDetails expectedTargetDetails, ILInstruction thisArg)
|
|
{
|
|
ExpressionBuilder expressionBuilder = this.expressionBuilder;
|
|
ExpressionWithResolveResult targetExpression;
|
|
(TranslatedExpression target, bool addTypeArguments, string methodName, ResolveResult result) = DisambiguateDelegateReference(method, invokeMethod, expectedTargetDetails, thisArg);
|
|
if (target.Expression != null)
|
|
{
|
|
var mre = new MemberReferenceExpression(target, methodName);
|
|
if (addTypeArguments)
|
|
{
|
|
mre.TypeArguments.AddRange(method.TypeArguments.Select(expressionBuilder.ConvertType));
|
|
}
|
|
targetExpression = mre.WithRR(result);
|
|
}
|
|
else
|
|
{
|
|
var ide = new IdentifierExpression(methodName);
|
|
if (addTypeArguments)
|
|
{
|
|
ide.TypeArguments.AddRange(method.TypeArguments.Select(expressionBuilder.ConvertType));
|
|
}
|
|
targetExpression = ide.WithRR(result);
|
|
}
|
|
return targetExpression;
|
|
|
|
}
|
|
|
|
(TranslatedExpression target, bool addTypeArguments, string methodName, ResolveResult result) DisambiguateDelegateReference(IMethod method, IMethod invokeMethod, ExpectedTargetDetails expectedTargetDetails, ILInstruction thisArg)
|
|
{
|
|
if (method.IsLocalFunction)
|
|
{
|
|
ILFunction localFunction = expressionBuilder.ResolveLocalFunction(method);
|
|
Debug.Assert(localFunction != null);
|
|
return (default, addTypeArguments: true, localFunction.Name, ToMethodGroup(method, localFunction));
|
|
}
|
|
if (method.IsExtensionMethod && method.Parameters.Count - 1 == invokeMethod?.Parameters.Count)
|
|
{
|
|
IType targetType = method.Parameters[0].Type;
|
|
if (targetType.Kind == TypeKind.ByReference && thisArg is Box thisArgBox)
|
|
{
|
|
targetType = ((ByReferenceType)targetType).ElementType;
|
|
thisArg = thisArgBox.Argument;
|
|
}
|
|
TranslatedExpression target = expressionBuilder.Translate(thisArg, targetType);
|
|
var currentTarget = target;
|
|
bool targetCasted = false;
|
|
bool addTypeArguments = false;
|
|
// Initial inputs for IsUnambiguousMethodReference:
|
|
ResolveResult targetResolveResult = target.ResolveResult;
|
|
IReadOnlyList<IType> typeArguments = EmptyList<IType>.Instance;
|
|
if (thisArg.MatchLdNull())
|
|
{
|
|
targetCasted = true;
|
|
currentTarget = currentTarget.ConvertTo(targetType, expressionBuilder);
|
|
targetResolveResult = currentTarget.ResolveResult;
|
|
}
|
|
// Find somewhat minimal solution:
|
|
ResolveResult result;
|
|
while (!IsUnambiguousMethodReference(expectedTargetDetails, method, targetResolveResult, typeArguments, true, out result))
|
|
{
|
|
if (!targetCasted)
|
|
{
|
|
// try casting target
|
|
targetCasted = true;
|
|
currentTarget = currentTarget.ConvertTo(targetType, expressionBuilder);
|
|
targetResolveResult = currentTarget.ResolveResult;
|
|
continue;
|
|
}
|
|
if (!addTypeArguments)
|
|
{
|
|
// try adding type arguments
|
|
addTypeArguments = true;
|
|
typeArguments = method.TypeArguments;
|
|
continue;
|
|
}
|
|
break;
|
|
}
|
|
return (currentTarget, addTypeArguments, method.Name, result);
|
|
}
|
|
else
|
|
{
|
|
// Prepare call target
|
|
IType targetType = method.DeclaringType;
|
|
if (targetType.IsReferenceType == false && thisArg is Box thisArgBox)
|
|
{
|
|
// Normal struct instance method calls (which TranslateTarget is meant for) expect a 'ref T',
|
|
// but delegate construction uses a 'box T'.
|
|
if (thisArgBox.Argument is LdObj ldobj)
|
|
{
|
|
thisArg = ldobj.Target;
|
|
}
|
|
else
|
|
{
|
|
thisArg = new AddressOf(thisArgBox.Argument, thisArgBox.Type);
|
|
}
|
|
}
|
|
TranslatedExpression target = expressionBuilder.TranslateTarget(thisArg,
|
|
nonVirtualInvocation: expectedTargetDetails.CallOpCode == OpCode.Call,
|
|
memberStatic: method.IsStatic,
|
|
memberDeclaringType: method.DeclaringType);
|
|
// check if target is required
|
|
bool requireTarget = expressionBuilder.HidesVariableWithName(method.Name)
|
|
|| (method.IsStatic ? !expressionBuilder.IsCurrentOrContainingType(method.DeclaringTypeDefinition) : !(target.Expression is ThisReferenceExpression));
|
|
// Try to find minimal expression
|
|
// If target is required, include it from the start
|
|
bool targetAdded = requireTarget;
|
|
TranslatedExpression currentTarget = targetAdded ? target : default;
|
|
// Remember other decisions:
|
|
bool targetCasted = false;
|
|
bool addTypeArguments = false;
|
|
// Initial inputs for IsUnambiguousMethodReference:
|
|
ResolveResult targetResolveResult = targetAdded ? target.ResolveResult : null;
|
|
IReadOnlyList<IType> typeArguments = EmptyList<IType>.Instance;
|
|
// Find somewhat minimal solution:
|
|
ResolveResult result;
|
|
while (!IsUnambiguousMethodReference(expectedTargetDetails, method, targetResolveResult, typeArguments, false, out result))
|
|
{
|
|
if (!addTypeArguments)
|
|
{
|
|
// try adding type arguments
|
|
addTypeArguments = true;
|
|
typeArguments = method.TypeArguments;
|
|
continue;
|
|
}
|
|
if (!targetAdded)
|
|
{
|
|
// try adding target
|
|
targetAdded = true;
|
|
currentTarget = target;
|
|
targetResolveResult = target.ResolveResult;
|
|
continue;
|
|
}
|
|
if (!targetCasted)
|
|
{
|
|
// try casting target
|
|
targetCasted = true;
|
|
currentTarget = currentTarget.ConvertTo(targetType, expressionBuilder);
|
|
targetResolveResult = currentTarget.ResolveResult;
|
|
continue;
|
|
}
|
|
break;
|
|
}
|
|
return (currentTarget, addTypeArguments, method.Name, result);
|
|
}
|
|
}
|
|
|
|
|
|
TranslatedExpression HandleDelegateConstruction(IType delegateType, IMethod method, ExpectedTargetDetails expectedTargetDetails, ILInstruction thisArg, ILInstruction inst)
|
|
{
|
|
var invokeMethod = delegateType.GetDelegateInvokeMethod();
|
|
var targetExpression = BuildDelegateReference(method, invokeMethod, expectedTargetDetails, thisArg);
|
|
var oce = new ObjectCreateExpression(expressionBuilder.ConvertType(delegateType), targetExpression)
|
|
.WithILInstruction(inst)
|
|
.WithRR(new ConversionResolveResult(
|
|
delegateType,
|
|
targetExpression.ResolveResult,
|
|
Conversion.MethodGroupConversion(method, expectedTargetDetails.CallOpCode == OpCode.CallVirt, false)));
|
|
return oce;
|
|
}
|
|
|
|
bool IsUnambiguousMethodReference(ExpectedTargetDetails expectedTargetDetails, IMethod method, ResolveResult target, IReadOnlyList<IType> typeArguments, bool isExtensionMethodReference, out ResolveResult result)
|
|
{
|
|
Log.WriteLine("IsUnambiguousMethodReference: Performing overload resolution for " + method);
|
|
|
|
var lookup = new MemberLookup(resolver.CurrentTypeDefinition, resolver.CurrentTypeDefinition.ParentModule);
|
|
OverloadResolution or;
|
|
|
|
if (isExtensionMethodReference)
|
|
{
|
|
var resolver = this.resolver.WithCurrentUsingScope(this.expressionBuilder.statementBuilder.decompileRun.UsingScope.Resolve(this.resolver.Compilation));
|
|
result = resolver.ResolveMemberAccess(target, method.Name, typeArguments, NameLookupMode.InvocationTarget) as MethodGroupResolveResult;
|
|
if (result == null)
|
|
return false;
|
|
or = ((MethodGroupResolveResult)result).PerformOverloadResolution(resolver.CurrentTypeResolveContext.Compilation,
|
|
method.Parameters.SelectReadOnlyArray(p => new TypeResolveResult(p.Type)),
|
|
argumentNames: null, allowExtensionMethods: true);
|
|
if (or == null || or.IsAmbiguous)
|
|
return false;
|
|
}
|
|
else
|
|
{
|
|
or = new OverloadResolution(resolver.Compilation,
|
|
arguments: method.Parameters.SelectReadOnlyArray(p => new TypeResolveResult(p.Type)), // there are no arguments, use parameter types
|
|
argumentNames: null, // argument names are not possible
|
|
typeArguments.ToArray(),
|
|
conversions: expressionBuilder.resolver.conversions
|
|
);
|
|
if (target == null)
|
|
{
|
|
result = resolver.ResolveSimpleName(method.Name, typeArguments, isInvocationTarget: false);
|
|
if (!(result is MethodGroupResolveResult mgrr))
|
|
return false;
|
|
or.AddMethodLists(mgrr.MethodsGroupedByDeclaringType.ToArray());
|
|
}
|
|
else
|
|
{
|
|
result = lookup.Lookup(target, method.Name, typeArguments, isInvocation: false);
|
|
if (!(result is MethodGroupResolveResult mgrr))
|
|
return false;
|
|
or.AddMethodLists(mgrr.MethodsGroupedByDeclaringType.ToArray());
|
|
}
|
|
}
|
|
|
|
var foundMethod = or.GetBestCandidateWithSubstitutedTypeArguments();
|
|
if (!IsAppropriateCallTarget(expectedTargetDetails, method, foundMethod))
|
|
return false;
|
|
return result is MethodGroupResolveResult;
|
|
}
|
|
|
|
static MethodGroupResolveResult ToMethodGroup(IMethod method, ILFunction localFunction)
|
|
{
|
|
return new MethodGroupResolveResult(
|
|
null,
|
|
localFunction.Name,
|
|
new[] {
|
|
new MethodListWithDeclaringType(
|
|
method.DeclaringType,
|
|
new IParameterizedMember[] { method }
|
|
)
|
|
}, method.TypeArguments
|
|
);
|
|
}
|
|
|
|
internal TranslatedExpression CallWithNamedArgs(Block block)
|
|
{
|
|
Debug.Assert(block.Kind == BlockKind.CallWithNamedArgs);
|
|
var call = (CallInstruction)block.FinalInstruction;
|
|
var arguments = new ILInstruction[call.Arguments.Count];
|
|
var argumentToParameterMap = new int[arguments.Length];
|
|
int firstParamIndex = call.IsInstanceCall ? 1 : 0;
|
|
// Arguments from temporary variables (VariableKind.NamedArgument):
|
|
int pos = 0;
|
|
foreach (StLoc stloc in block.Instructions)
|
|
{
|
|
Debug.Assert(stloc.Variable.LoadInstructions.Single().Parent == call);
|
|
arguments[pos] = stloc.Value;
|
|
argumentToParameterMap[pos] = stloc.Variable.LoadInstructions.Single().ChildIndex - firstParamIndex;
|
|
pos++;
|
|
}
|
|
// Remaining argument:
|
|
foreach (var arg in call.Arguments)
|
|
{
|
|
if (arg.MatchLdLoc(out var v) && v.Kind == VariableKind.NamedArgument)
|
|
{
|
|
continue; // already handled in loop above
|
|
}
|
|
arguments[pos] = arg;
|
|
argumentToParameterMap[pos] = arg.ChildIndex - firstParamIndex;
|
|
pos++;
|
|
}
|
|
Debug.Assert(pos == arguments.Length);
|
|
return Build(call.OpCode, call.Method, arguments, argumentToParameterMap, call.ConstrainedTo)
|
|
.WithILInstruction(call).WithILInstruction(block);
|
|
}
|
|
|
|
private bool HandleRangeConstruction(out ExpressionWithResolveResult result, OpCode callOpCode, IMethod method, TranslatedExpression target, ArgumentList argumentList)
|
|
{
|
|
result = default;
|
|
if (argumentList.ArgumentNames != null)
|
|
{
|
|
return false; // range syntax doesn't support named arguments
|
|
}
|
|
if (method.DeclaringType.IsKnownType(KnownTypeCode.Range))
|
|
{
|
|
if (callOpCode == OpCode.NewObj && argumentList.Length == 2)
|
|
{
|
|
result = new BinaryOperatorExpression(argumentList.Arguments[0], BinaryOperatorType.Range, argumentList.Arguments[1])
|
|
.WithRR(new MemberResolveResult(null, method));
|
|
return true;
|
|
}
|
|
else if (callOpCode == OpCode.Call && method.Name == "get_All" && argumentList.Length == 0)
|
|
{
|
|
result = new BinaryOperatorExpression(Expression.Null, BinaryOperatorType.Range, Expression.Null)
|
|
.WithRR(new MemberResolveResult(null, method.AccessorOwner ?? method));
|
|
return true;
|
|
}
|
|
else if (callOpCode == OpCode.Call && method.Name == "StartAt" && argumentList.Length == 1)
|
|
{
|
|
result = new BinaryOperatorExpression(argumentList.Arguments[0], BinaryOperatorType.Range, Expression.Null)
|
|
.WithRR(new MemberResolveResult(null, method));
|
|
return true;
|
|
}
|
|
else if (callOpCode == OpCode.Call && method.Name == "EndAt" && argumentList.Length == 1)
|
|
{
|
|
result = new BinaryOperatorExpression(Expression.Null, BinaryOperatorType.Range, argumentList.Arguments[0])
|
|
.WithRR(new MemberResolveResult(null, method));
|
|
return true;
|
|
}
|
|
}
|
|
else if (callOpCode == OpCode.NewObj && method.DeclaringType.IsKnownType(KnownTypeCode.Index))
|
|
{
|
|
if (argumentList.Length != 2)
|
|
return false;
|
|
if (!(argumentList.Arguments[1].Expression is PrimitiveExpression pe && pe.Value is true))
|
|
return false;
|
|
result = new UnaryOperatorExpression(UnaryOperatorType.IndexFromEnd, argumentList.Arguments[0])
|
|
.WithRR(new MemberResolveResult(null, method));
|
|
return true;
|
|
}
|
|
else if (method is SyntheticRangeIndexAccessor rangeIndexAccessor && rangeIndexAccessor.IsSlicing)
|
|
{
|
|
// For slicing the method is called Slice()/Substring(), but we still need to output indexer notation.
|
|
// So special-case range-based slicing here.
|
|
result = new IndexerExpression(target, argumentList.Arguments.Select(a => a.Expression))
|
|
.WithRR(new MemberResolveResult(target.ResolveResult, method));
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
}
|
|
}
|