/Runtime/Microsoft.Dynamic/Actions/Calls/OverloadResolver.cs
C# | 1201 lines | 852 code | 201 blank | 148 comment | 225 complexity | 2b5468fb9df2b4d53949a92e0e3462a9 MD5 | raw file
Possible License(s): CPL-1.0, BSD-3-Clause, ISC, GPL-2.0, MPL-2.0-no-copyleft-exception
- /* ****************************************************************************
- *
- * Copyright (c) Microsoft Corporation.
- *
- * This source code is subject to terms and conditions of the Apache License, Version 2.0. A
- * copy of the license can be found in the License.html file at the root of this distribution. If
- * you cannot locate the Apache License, Version 2.0, please send an email to
- * dlr@microsoft.com. By using this source code in any fashion, you are agreeing to be bound
- * by the terms of the Apache License, Version 2.0.
- *
- * You must not remove this notice, or any other, from this software.
- *
- *
- * ***************************************************************************/
- #if FEATURE_CORE_DLR
- using System.Linq.Expressions;
- #else
- using Microsoft.Scripting.Ast;
- #endif
- using System;
- using System.Collections;
- using System.Collections.Generic;
- using System.Diagnostics;
- using System.Dynamic;
- using System.Reflection;
- using System.Runtime.CompilerServices;
- using System.Text;
- using Microsoft.Scripting.Generation;
- using Microsoft.Scripting.Runtime;
- using Microsoft.Scripting.Utils;
- using AstUtils = Microsoft.Scripting.Ast.Utils;
- namespace Microsoft.Scripting.Actions.Calls {
- using Ast = Expression;
-
- /// <summary>
- /// Provides binding and overload resolution to .NET methods.
- ///
- /// MethodBinder's can be used for:
- /// generating new AST code for calling a method
- /// calling a method via reflection at runtime
- /// (not implemented) performing an abstract call
- ///
- /// MethodBinder's support default arguments, optional arguments, by-ref (in and out), and keyword arguments.
- ///
- /// Implementation Details:
- ///
- /// The MethodBinder works by building up a CandidateSet for each number of effective arguments that can be
- /// passed to a set of overloads. For example a set of overloads such as:
- /// foo(object a, object b, object c)
- /// foo(int a, int b)
- ///
- /// would have 2 target sets - one for 3 parameters and one for 2 parameters. For parameter arrays
- /// we fallback and create the appropriately sized CandidateSet on demand.
- ///
- /// Each CandidateSet consists of a set of MethodCandidate's. Each MethodCandidate knows the flattened
- /// parameters that could be received. For example for a function such as:
- /// foo(params int[] args)
- ///
- /// When this method is in a CandidateSet of size 3 the MethodCandidate takes 3 parameters - all of them
- /// ints; if it's in a CandidateSet of size 4 it takes 4 parameters. Effectively a MethodCandidate is
- /// a simplified view that allows all arguments to be treated as required positional arguments.
- ///
- /// Each MethodCandidate in turn refers to a MethodTarget. The MethodTarget is composed of a set
- /// of ArgBuilder's and a ReturnBuilder which know how to consume the positional arguments and pass
- /// them to the appropriate argument of the destination method. This includes routing keyword
- /// arguments to the correct position, providing the default values for optional arguments, etc...
- ///
- /// After binding is finished the MethodCandidates are thrown away and a BindingTarget is returned.
- /// The BindingTarget indicates whether the binding was successful and if not any additional information
- /// that should be reported to the user about the failed binding. It also exposes the MethodTarget which
- /// allows consumers to get the flattened list of required parameters for the call. MethodCandidates
- /// are not exposed and are an internal implementation detail of the MethodBinder.
- /// </summary>
- public abstract partial class OverloadResolver {
- private readonly ActionBinder _binder;
- // built as target sets are built:
- private string _methodName;
- private NarrowingLevel _minLevel, _maxLevel; // specifies the minimum and maximum narrowing levels for conversions during binding
- private IList<string> _argNames;
- private Dictionary<int, CandidateSet> _candidateSets; // the methods as they map from # of arguments -> the possible CandidateSet's.
- private List<MethodCandidate> _paramsCandidates; // the methods which are params methods which need special treatment because they don't have fixed # of args
-
- // built as arguments are processed:
- private ActualArguments _actualArguments;
- private int _maxAccessedCollapsedArg;
- private List<ParameterExpression> _temps;
- protected OverloadResolver(ActionBinder binder) {
- ContractUtils.RequiresNotNull(binder, "binder");
- _binder = binder;
- _maxAccessedCollapsedArg = -1;
- }
- public ActionBinder Binder {
- get { return _binder; }
- }
- internal List<ParameterExpression> Temps {
- get { return _temps; }
- }
- internal ParameterExpression GetTemporary(Type type, string name) {
- Assert.NotNull(type);
- if (_temps == null) {
- _temps = new List<ParameterExpression>();
- }
- ParameterExpression res = Expression.Variable(type, name);
- _temps.Add(res);
- return res;
- }
- #region ResolveOverload
- /// <summary>
- /// Resolves a method overload and returns back a BindingTarget.
- ///
- /// The BindingTarget can then be tested for the success or particular type of
- /// failure that prevents the method from being called. If successfully bound the BindingTarget
- /// contains a list of argument meta-objects with additional restrictions that ensure the selection
- /// of the particular overload.
- /// </summary>
- public BindingTarget ResolveOverload(string methodName, IList<MethodBase> methods, NarrowingLevel minLevel, NarrowingLevel maxLevel) {
- return ResolveOverload(
- methodName,
- ArrayUtils.ToArray(methods, (m) => new ReflectionOverloadInfo(m)),
- minLevel,
- maxLevel
- );
- }
- public BindingTarget ResolveOverload(string methodName, IList<OverloadInfo> methods, NarrowingLevel minLevel, NarrowingLevel maxLevel) {
- ContractUtils.RequiresNotNullItems(methods, "methods");
- ContractUtils.Requires(minLevel <= maxLevel);
- if (_candidateSets != null) {
- throw new InvalidOperationException("Overload resolver cannot be reused.");
- }
- _methodName = methodName;
- _minLevel = minLevel;
- _maxLevel = maxLevel;
-
- // step 1:
- IList<DynamicMetaObject> namedArgs;
- GetNamedArguments(out namedArgs, out _argNames);
-
- // uses arg names:
- BuildCandidateSets(methods);
- // uses target sets:
- int preSplatLimit, postSplatLimit;
- GetSplatLimits(out preSplatLimit, out postSplatLimit);
- // step 2:
- _actualArguments = CreateActualArguments(namedArgs, _argNames, preSplatLimit, postSplatLimit);
- if (_actualArguments == null) {
- return new BindingTarget(methodName, BindingResult.InvalidArguments);
- }
- // steps 3, 4:
- var candidateSet = GetCandidateSet();
- if (candidateSet != null && !candidateSet.IsParamsDictionaryOnly()) {
- return MakeBindingTarget(candidateSet);
- }
- // step 5:
- return new BindingTarget(methodName, _actualArguments.VisibleCount, GetExpectedArgCounts());
- }
- #endregion
- #region Step 1: TargetSet construction, custom special parameters handling
- /// <summary>
- /// Checks to see if the language allows named arguments to be bound to instance fields or
- /// properties and turned into setters. By default this is only allowed on contructors.
- /// </summary>
- internal protected virtual bool AllowMemberInitialization(OverloadInfo method) {
- #pragma warning disable 618 // obsolete
- return AllowKeywordArgumentSetting(method.ReflectionInfo);
- #pragma warning restore 618
- }
- [Obsolete("Use OverloadInfo.AllowMemberInitialization instead")]
- internal protected virtual bool AllowKeywordArgumentSetting(MethodBase method) {
- return CompilerHelpers.IsConstructor(method);
- }
- /// <summary>
- /// Gets an expression that evaluates to the result of GetByRefArray operation.
- /// </summary>
- internal protected virtual Expression GetByRefArrayExpression(Expression argumentArrayExpression) {
- return argumentArrayExpression;
- }
- /// <summary>
- /// Allow to bind an array/dictionary instance or a null reference to params array/dictionary parameter.
- /// </summary>
- protected virtual bool BindToUnexpandedParams(MethodCandidate candidate) {
- return true;
- }
- /// <summary>
- /// Called before arguments binding.
- /// </summary>
- /// <returns>
- /// A bitmask that indicates (set bits) the parameters that were mapped by this method.
- /// A default mapping will be constructed for the remaining parameters (cleared bits).
- /// </returns>
- [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1045:DoNotPassTypesByReference", MessageId = "3#")]
- internal protected virtual BitArray MapSpecialParameters(ParameterMapping mapping) {
- if (!mapping.Overload.IsStatic) {
- var type = mapping.Overload.DeclaringType;
- mapping.AddParameter(new ParameterWrapper(null, type, null, ParameterBindingFlags.ProhibitNull));
- mapping.AddInstanceBuilder(new InstanceBuilder(mapping.ArgIndex));
- }
- return null;
- }
- private void BuildCandidateSets(IEnumerable<OverloadInfo> methods) {
- Debug.Assert(_candidateSets == null);
- Debug.Assert(_argNames != null);
- _candidateSets = new Dictionary<int, CandidateSet>();
- foreach (OverloadInfo method in methods) {
- if (IsUnsupported(method)) continue;
- AddBasicMethodTargets(method);
- }
-
- if (_paramsCandidates != null) {
- // For all the methods that take a params array, create MethodCandidates that clash with the
- // other overloads of the method
- foreach (MethodCandidate candidate in _paramsCandidates) {
- foreach (int count in _candidateSets.Keys) {
- MethodCandidate target = candidate.MakeParamsExtended(count, _argNames);
- if (target != null) {
- AddTarget(target);
- }
- }
- }
- }
- }
- private CandidateSet GetCandidateSet() {
- Debug.Assert(_candidateSets != null && _actualArguments != null);
- CandidateSet result;
- // use precomputed set if arguments are fully expanded and we have one:
- if (_actualArguments.CollapsedCount == 0 && _candidateSets.TryGetValue(_actualArguments.Count, out result)) {
- return result;
- }
- if (_paramsCandidates != null) {
- // build a new target set specific to the number of arguments we have:
- result = BuildExpandedTargetSet(_actualArguments.Count);
- if (result.Candidates.Count > 0) {
- return result;
- }
- }
- return null;
- }
- private CandidateSet BuildExpandedTargetSet(int count) {
- var set = new CandidateSet(count);
- if (_paramsCandidates != null) {
- foreach (MethodCandidate maker in _paramsCandidates) {
- MethodCandidate target = maker.MakeParamsExtended(count, _argNames);
- if (target != null) {
- set.Add(target);
- }
- }
- }
- return set;
- }
- private void AddTarget(MethodCandidate target) {
- int count = target.ParameterCount;
- CandidateSet set;
- if (!_candidateSets.TryGetValue(count, out set)) {
- set = new CandidateSet(count);
- _candidateSets[count] = set;
- }
- set.Add(target);
- }
- private void AddSimpleTarget(MethodCandidate target) {
- if (target.HasParamsArray || target.HasParamsDictionary) {
- if (BindToUnexpandedParams(target)) {
- AddTarget(target);
- }
-
- if (_paramsCandidates == null) {
- _paramsCandidates = new List<MethodCandidate>();
- }
- _paramsCandidates.Add(target);
- } else {
- AddTarget(target);
- }
- }
- private void AddBasicMethodTargets(OverloadInfo method) {
- Assert.NotNull(method);
- var mapping = new ParameterMapping(this, method, _argNames);
- mapping.MapParameters(false);
- foreach (var defaultCandidate in mapping.CreateDefaultCandidates()) {
- AddSimpleTarget(defaultCandidate);
- }
- // TODO: We reduce out/ref parameters only for the main overload.
- // We should rather treat all out params as optional (either a StrongBox is provided or not).
- var byRefReducedCandidate = mapping.CreateByRefReducedCandidate();
- if (byRefReducedCandidate != null) {
- AddSimpleTarget(byRefReducedCandidate);
- }
- AddSimpleTarget(mapping.CreateCandidate());
- }
- private static bool IsUnsupported(OverloadInfo method) {
- return (method.CallingConvention & CallingConventions.VarArgs) != 0;
- }
- #endregion
- #region Step 2: Actual Arguments
- public ActualArguments GetActualArguments() {
- if (_actualArguments == null) {
- throw new InvalidOperationException("Actual arguments have not been built yet.");
- }
- return _actualArguments;
- }
- protected virtual void GetNamedArguments(out IList<DynamicMetaObject> namedArgs, out IList<string> argNames) {
- // language doesn't support named arguments:
- argNames = ArrayUtils.EmptyStrings;
- namedArgs = DynamicMetaObject.EmptyMetaObjects;
- }
- /// <summary>
- /// Return null if arguments cannot be constructed and overload resolution should produce an error.
- /// </summary>
- protected abstract ActualArguments CreateActualArguments(IList<DynamicMetaObject> namedArgs, IList<string> argNames, int preSplatLimit, int postSplatLimit);
- #endregion
- #region Step 3: Resolution
- internal BindingTarget MakeBindingTarget(CandidateSet targetSet) {
- List<CallFailure> failures = null;
- List<CallFailure> nameBindingFailures = null;
- // get candidates whose named arguments can be bind to the parameters:
- var potential = EnsureMatchingNamedArgs(targetSet.Candidates, ref nameBindingFailures);
- if (potential.Count == 0) {
- return MakeFailedBindingTarget(nameBindingFailures.ToArray());
- }
- // go through all available narrowing levels selecting candidates.
- for (NarrowingLevel level = _minLevel; level <= _maxLevel; level++) {
- if (failures != null) {
- failures.Clear();
- }
- // only allow candidates whose non-collapsed arguments are convertible to the parameter types:
- var applicable = SelectCandidatesWithConvertibleArgs(potential, level, ref failures);
- if (applicable.Count == 0) {
- continue;
- } else if (applicable.Count == 1) {
- return MakeSuccessfulBindingTarget(applicable[0], potential, level, targetSet);
- }
- // see if collapsed arguments be converted to the corresponding element types:
- applicable = SelectCandidatesWithConvertibleCollapsedArgs(applicable, level, ref failures);
- if (applicable.Count == 0) {
- continue;
- } else if (applicable.Count == 1) {
- return MakeSuccessfulBindingTarget(applicable[0], potential, level, targetSet);
- }
- var bestCandidate = SelectBestCandidate(applicable, level);
- if (bestCandidate != null) {
- return MakeSuccessfulBindingTarget(bestCandidate, potential, level, targetSet);
- } else {
- return MakeAmbiguousBindingTarget(applicable);
- }
- }
- if (failures == null) {
- // this can happen if there is no callable method:
- return new BindingTarget(_methodName, BindingResult.NoCallableMethod);
- }
- if (nameBindingFailures != null) {
- failures.AddRange(nameBindingFailures);
- }
- return MakeFailedBindingTarget(failures.ToArray());
- }
- private List<ApplicableCandidate> EnsureMatchingNamedArgs(List<MethodCandidate> candidates, ref List<CallFailure> failures) {
- var result = new List<ApplicableCandidate>();
- foreach (MethodCandidate candidate in candidates) {
- // skip params dictionaries - we want to only pick up the methods normalized
- // to have argument names (which we created because the MethodBinder gets
- // created w/ keyword arguments).
- if (!candidate.HasParamsDictionary) {
- CallFailure callFailure;
- ArgumentBinding namesBinding;
- if (_actualArguments.TryBindNamedArguments(candidate, out namesBinding, out callFailure)) {
- result.Add(new ApplicableCandidate(candidate, namesBinding));
- } else {
- AddFailure(ref failures, callFailure);
- }
- }
- }
- return result;
- }
- private List<ApplicableCandidate> SelectCandidatesWithConvertibleArgs(List<ApplicableCandidate> candidates, NarrowingLevel level,
- ref List<CallFailure> failures) {
- bool hasGenericCandidates = false;
- var result = new List<ApplicableCandidate>();
- foreach (ApplicableCandidate candidate in candidates) {
- if (candidate.Method.Overload.ContainsGenericParameters) {
- hasGenericCandidates = true;
- continue;
- }
- CallFailure callFailure;
- if (TryConvertArguments(candidate.Method, candidate.ArgumentBinding, level, out callFailure)) {
- result.Add(candidate);
- } else {
- AddFailure(ref failures, callFailure);
- }
- }
- if (hasGenericCandidates) {
- // attempt generic method type inference
- foreach (ApplicableCandidate candidate in candidates) {
- if (!candidate.Method.Overload.IsGenericMethodDefinition) {
- continue;
- }
- MethodCandidate newCandidate = TypeInferer.InferGenericMethod(candidate, _actualArguments);
- if (newCandidate != null) {
- CallFailure callFailure;
- if (TryConvertArguments(newCandidate, candidate.ArgumentBinding, level, out callFailure)) {
- result.Add(new ApplicableCandidate(newCandidate, candidate.ArgumentBinding));
- } else {
- AddFailure(ref failures, callFailure);
- }
- } else {
- AddFailure(ref failures, new CallFailure(candidate.Method, CallFailureReason.TypeInference));
- }
- }
- }
- return result;
- }
- private List<ApplicableCandidate> SelectCandidatesWithConvertibleCollapsedArgs(List<ApplicableCandidate> candidates,
- NarrowingLevel level, ref List<CallFailure> failures) {
- if (_actualArguments.CollapsedCount == 0) {
- return candidates;
- }
- var result = new List<ApplicableCandidate>();
- foreach (ApplicableCandidate candidate in candidates) {
- CallFailure callFailure;
- if (TryConvertCollapsedArguments(candidate.Method, level, out callFailure)) {
- result.Add(candidate);
- } else {
- AddFailure(ref failures, callFailure);
- }
- }
- return result;
- }
- private static void AddFailure(ref List<CallFailure> failures, CallFailure failure) {
- if (failures == null) {
- failures = new List<CallFailure>(1);
- }
- failures.Add(failure);
- }
- private bool TryConvertArguments(MethodCandidate candidate, ArgumentBinding namesBinding, NarrowingLevel narrowingLevel, out CallFailure failure) {
- Debug.Assert(_actualArguments.Count == candidate.ParameterCount);
- BitArray hasConversion = new BitArray(_actualArguments.Count);
- bool success = true;
- for (int i = 0; i < _actualArguments.Count; i++) {
- success &= (hasConversion[i] = CanConvertFrom(_actualArguments[i].GetLimitType(), _actualArguments[i], candidate.GetParameter(i, namesBinding), narrowingLevel));
- }
- if (!success) {
- var conversionResults = new ConversionResult[_actualArguments.Count];
- for (int i = 0; i < _actualArguments.Count; i++) {
- conversionResults[i] = new ConversionResult(_actualArguments[i].Value, _actualArguments[i].GetLimitType(), candidate.GetParameter(i, namesBinding).Type, !hasConversion[i]);
- }
- failure = new CallFailure(candidate, conversionResults);
- } else {
- failure = null;
- }
- return success;
- }
- private bool TryConvertCollapsedArguments(MethodCandidate candidate, NarrowingLevel narrowingLevel, out CallFailure failure) {
- Debug.Assert(_actualArguments.CollapsedCount > 0);
- // There must be at least one expanded parameter preceding splat index (see MethodBinder.GetSplatLimits):
- ParameterWrapper parameter = candidate.GetParameter(_actualArguments.SplatIndex - 1);
- Debug.Assert(parameter.ParameterInfo != null && candidate.Overload.IsParamArray(parameter.ParameterInfo.Position));
- for (int i = 0; i < _actualArguments.CollapsedCount; i++) {
- object value = GetCollapsedArgumentValue(i);
- Type argType = CompilerHelpers.GetType(value);
- if (!CanConvertFrom(argType, null, parameter, narrowingLevel)) {
- failure = new CallFailure(candidate, new[] { new ConversionResult(value, argType, parameter.Type, false) });
- return false;
- }
- }
- failure = null;
- return true;
- }
- private RestrictedArguments GetRestrictedArgs(ApplicableCandidate selectedCandidate, IList<ApplicableCandidate> candidates, int targetSetSize) {
- Debug.Assert(selectedCandidate.Method.ParameterCount == _actualArguments.Count);
- int argCount = _actualArguments.Count;
- var restrictedArgs = new DynamicMetaObject[argCount];
- var types = new Type[argCount];
- bool hasAdditionalRestrictions = false;
- for (int i = 0; i < argCount; i++) {
- var arg = _actualArguments[i];
- if (targetSetSize > 0 && IsOverloadedOnParameter(i, argCount, candidates) ||
- !selectedCandidate.GetParameter(i).Type.IsAssignableFrom(arg.Expression.Type)) {
- restrictedArgs[i] = RestrictArgument(arg, selectedCandidate.GetParameter(i));
- types[i] = arg.GetLimitType();
- } else {
- restrictedArgs[i] = arg;
- }
- BindingRestrictions additionalRestrictions;
- if (selectedCandidate.Method.Restrictions != null && selectedCandidate.Method.Restrictions.TryGetValue(arg, out additionalRestrictions)) {
- hasAdditionalRestrictions = true;
- restrictedArgs[i] = new DynamicMetaObject(restrictedArgs[i].Expression, restrictedArgs[i].Restrictions.Merge(additionalRestrictions));
- }
- }
- return new RestrictedArguments(restrictedArgs, types, hasAdditionalRestrictions);
- }
- private DynamicMetaObject RestrictArgument(DynamicMetaObject arg, ParameterWrapper parameter) {
- if (parameter.Type == typeof(object)) {
- // don't use Restrict as it'll box & unbox.
- return new DynamicMetaObject(arg.Expression, BindingRestrictionsHelpers.GetRuntimeTypeRestriction(arg.Expression, arg.GetLimitType()));
- } else {
- return arg.Restrict(arg.GetLimitType());
- }
- }
- /// <summary>
- /// Determines whether given overloads are overloaded on index-th parameter (the types of the index-th parameters are the same).
- /// </summary>
- private static bool IsOverloadedOnParameter(int argIndex, int argCount, IList<ApplicableCandidate> overloads) {
- Debug.Assert(argIndex >= 0);
- Type seenParametersType = null;
- foreach (var overload in overloads) {
- int parameterCount = overload.Method.ParameterCount;
- if (parameterCount == 0) {
- continue;
- }
- var lastParameter = overload.Method.GetParameter(parameterCount - 1);
- Type parameterType;
- if (argIndex < parameterCount) {
- var parameter = overload.GetParameter(argIndex);
- if (parameter.IsParamsArray) {
- if (parameterCount == argCount) {
- // We're the params array argument and a single value is being passed
- // directly to it. The params array could be in the middle for
- // a params setter. so pis.Count - readIndex is usually 1 for the
- // params at the end, and therefore types.Length - 1 is usually if we're
- // the last argument. We always have to check this type to disambiguate
- // between passing an object which is compatible with the arg array and
- // passing an object which goes into the arg array. Maybe we could do
- // better sometimes.
- return true;
- }
- parameterType = lastParameter.Type.GetElementType();
- } else if (parameter.Type.ContainsGenericParameters()) {
- return true;
- } else {
- parameterType = parameter.Type;
- }
- } else if (lastParameter.IsParamsArray) {
- parameterType = lastParameter.Type.GetElementType();
- } else {
- continue;
- }
- if (seenParametersType == null) {
- seenParametersType = parameterType;
- } else if (seenParametersType != parameterType) {
- return true;
- }
- }
- return false;
- }
- private bool IsBest(ApplicableCandidate candidate, List<ApplicableCandidate> candidates, NarrowingLevel level) {
- foreach (ApplicableCandidate other in candidates) {
- if (candidate == other) {
- continue;
- }
- if (GetPreferredCandidate(candidate, other, level) != Candidate.One) {
- return false;
- }
- }
- return true;
- }
- internal Candidate GetPreferredCandidate(ApplicableCandidate one, ApplicableCandidate two, NarrowingLevel level) {
- Candidate cmpParams = GetPreferredParameters(one, two, level);
- if (cmpParams.Chosen()) {
- return cmpParams;
- }
- return CompareEquivalentCandidates(one, two);
- }
- internal protected virtual Candidate CompareEquivalentCandidates(ApplicableCandidate one, ApplicableCandidate two) {
- Candidate ret = CompareEquivalentParameters(one.Method, two.Method);
- if (ret.Chosen()) {
- return ret;
- }
- return Candidate.Equivalent;
- }
- internal Candidate CompareEquivalentParameters(MethodCandidate one, MethodCandidate two) {
- // Prefer normal methods over explicit interface implementations
- if (two.Overload.IsPrivate && !one.Overload.IsPrivate) return Candidate.One;
- if (one.Overload.IsPrivate && !two.Overload.IsPrivate) return Candidate.Two;
- // Prefer non-generic methods over generic methods
- if (one.Overload.IsGenericMethod) {
- if (!two.Overload.IsGenericMethod) {
- return Candidate.Two;
- } else {
- //!!! Need to support selecting least generic method here
- return Candidate.Equivalent;
- }
- } else if (two.Overload.IsGenericMethod) {
- return Candidate.One;
- }
- // prefer methods without out params over those with them
- switch (Compare(one.ReturnBuilder.CountOutParams, two.ReturnBuilder.CountOutParams)) {
- case 1: return Candidate.Two;
- case -1: return Candidate.One;
- }
- // prefer methods using earlier conversions rules to later ones
- for (int i = Int32.MaxValue; i >= 0; ) {
- int maxPriorityThis = FindMaxPriority(one.ArgBuilders, i);
- int maxPriorityOther = FindMaxPriority(two.ArgBuilders, i);
- if (maxPriorityThis < maxPriorityOther) return Candidate.One;
- if (maxPriorityOther < maxPriorityThis) return Candidate.Two;
- i = maxPriorityThis - 1;
- }
- // prefer methods whose name exactly matches the call site name:
- if (one.Overload.Name != two.Overload.Name) {
- if (one.Overload.Name == _methodName) {
- return Candidate.One;
- }
- if (two.Overload.Name == _methodName) {
- return Candidate.Two;
- }
- }
- // prefer regular methods over extensions:
- if (one.Overload.IsExtension != two.Overload.IsExtension) {
- return one.Overload.IsExtension ? Candidate.Two : Candidate.One;
- }
- return Candidate.Equivalent;
- }
- private static int Compare(int x, int y) {
- if (x < y) return -1;
- else if (x > y) return +1;
- else return 0;
- }
- private static int FindMaxPriority(IList<ArgBuilder> abs, int ceiling) {
- int max = 0;
- foreach (ArgBuilder ab in abs) {
- if (ab.Priority > ceiling) continue;
- max = System.Math.Max(max, ab.Priority);
- }
- return max;
- }
- private Candidate GetPreferredParameters(ApplicableCandidate one, ApplicableCandidate two, NarrowingLevel level) {
- Debug.Assert(one.Method.ParameterCount == two.Method.ParameterCount);
- var args = GetActualArguments();
- Candidate result = Candidate.Equivalent;
- for (int i = 0; i < args.Count; i++) {
- Candidate preferred = GetPreferredParameter(one.GetParameter(i), two.GetParameter(i), args[i], level);
- switch (result) {
- case Candidate.Equivalent:
- result = preferred;
- break;
- case Candidate.One:
- if (preferred == Candidate.Two) return Candidate.Ambiguous;
- break;
- case Candidate.Two:
- if (preferred == Candidate.One) return Candidate.Ambiguous;
- break;
- case Candidate.Ambiguous:
- if (preferred != Candidate.Equivalent) {
- result = preferred;
- }
- break;
- default:
- throw new InvalidOperationException();
- }
- }
- // TODO: process collapsed arguments:
- return result;
- }
- private Candidate GetPreferredParameter(ParameterWrapper candidateOne, ParameterWrapper candidateTwo, DynamicMetaObject arg, NarrowingLevel level) {
- Assert.NotNull(candidateOne, candidateTwo);
- if (ParametersEquivalent(candidateOne, candidateTwo)) {
- return Candidate.Equivalent;
- }
- Candidate candidate = SelectBestConversionFor(arg, candidateOne, candidateTwo, level);
- if (candidate.Chosen()) {
- return candidate;
- }
- if (CanConvertFrom(candidateTwo, candidateOne)) {
- if (CanConvertFrom(candidateOne, candidateTwo)) {
- return Candidate.Ambiguous;
- } else {
- return Candidate.Two;
- }
- } else if (CanConvertFrom(candidateOne, candidateTwo)) {
- return Candidate.One;
- }
- // Special additional rules to order numeric value types
- Type t1 = candidateOne.Type;
- Type t2 = candidateTwo.Type;
- Candidate preferred = PreferConvert(t1, t2);
- if (preferred.Chosen()) {
- return preferred;
- }
- preferred = PreferConvert(t2, t1).TheOther();
- if (preferred.Chosen()) {
- return preferred;
- }
- // consider the actual argument type:
- Type argType = arg.GetLimitType();
- NarrowingLevel levelOne = NarrowingLevel.None;
- while (levelOne < level && !CanConvertFrom(argType, arg, candidateOne, levelOne)) {
- if (levelOne == NarrowingLevel.All) {
- Debug.Assert(false, "Each argument should be convertible to the corresponding parameter");
- break;
- }
- levelOne++;
- }
- NarrowingLevel levelTwo = NarrowingLevel.None;
- while (levelTwo < level && !CanConvertFrom(argType, arg, candidateTwo, levelTwo)) {
- if (levelTwo == NarrowingLevel.All) {
- Debug.Assert(false, "Each argument should be convertible to the corresponding parameter");
- break;
- }
- levelTwo++;
- }
- if (levelOne < levelTwo) {
- return Candidate.One;
- } else if (levelOne > levelTwo) {
- return Candidate.Two;
- } else {
- return Candidate.Ambiguous;
- }
- }
- private ApplicableCandidate SelectBestCandidate(List<ApplicableCandidate> candidates, NarrowingLevel level) {
- foreach (var candidate in candidates) {
- if (IsBest(candidate, candidates, level)) {
- return candidate;
- }
- }
- return null;
- }
- private BindingTarget MakeSuccessfulBindingTarget(ApplicableCandidate result, List<ApplicableCandidate> potentialCandidates,
- NarrowingLevel level, CandidateSet targetSet) {
- return new BindingTarget(
- _methodName,
- _actualArguments.VisibleCount,
- result.Method,
- level,
- GetRestrictedArgs(result, potentialCandidates, targetSet.Arity)
- );
- }
- private BindingTarget MakeFailedBindingTarget(CallFailure[] failures) {
- return new BindingTarget(_methodName, _actualArguments.VisibleCount, failures);
- }
- private BindingTarget MakeAmbiguousBindingTarget(List<ApplicableCandidate> result) {
- var methods = new MethodCandidate[result.Count];
- for (int i = 0; i < result.Count; i++) {
- methods[i] = result[i].Method;
- }
- return new BindingTarget(_methodName, _actualArguments.VisibleCount, methods);
- }
- #endregion
- #region Step 4: Argument Building, Conversions
- public virtual bool ParametersEquivalent(ParameterWrapper parameter1, ParameterWrapper parameter2) {
- return parameter1.Type == parameter2.Type && parameter1.ProhibitNull == parameter2.ProhibitNull;
- }
- public virtual bool CanConvertFrom(ParameterWrapper parameter1, ParameterWrapper parameter2) {
- return CanConvertFrom(parameter1.Type, null, parameter2, NarrowingLevel.None);
- }
- public virtual bool CanConvertFrom(Type fromType, DynamicMetaObject fromArgument, ParameterWrapper toParameter, NarrowingLevel level) {
- Assert.NotNull(fromType, toParameter);
- Type toType = toParameter.Type;
- if (fromType == typeof(DynamicNull)) {
- if (toParameter.ProhibitNull) {
- return false;
- }
- if (toType.IsGenericType() && toType.GetGenericTypeDefinition() == typeof(Nullable<>)) {
- return true;
- }
- if (!toType.IsValueType()) {
- return true;
- }
- }
- if (fromType == toType) {
- return true;
- }
- return _binder.CanConvertFrom(fromType, toType, toParameter.ProhibitNull, level);
- }
- /// <summary>
- /// Selects the best (of two) candidates for conversion from actualType
- /// </summary>
- public virtual Candidate SelectBestConversionFor(DynamicMetaObject arg, ParameterWrapper candidateOne, ParameterWrapper candidateTwo, NarrowingLevel level) {
- return Candidate.Equivalent;
- }
- /// <summary>
- /// Provides ordering for two parameter types if there is no conversion between the two parameter types.
- /// </summary>
- public virtual Candidate PreferConvert(Type t1, Type t2) {
- return _binder.PreferConvert(t1, t2);
- }
- // TODO: revisit
- public virtual Expression Convert(DynamicMetaObject metaObject, Type restrictedType, ParameterInfo info, Type toType) {
- Assert.NotNull(metaObject, toType);
- return _binder.ConvertExpression(metaObject.Expression, toType, ConversionResultKind.ExplicitCast, null);
- }
- // TODO: revisit
- public virtual Expression GetDynamicConversion(Expression value, Type type) {
- return Expression.Convert(value, type);
- }
- #endregion
- #region Step 5: Results, Errors
-
- private int[] GetExpectedArgCounts() {
- if (_candidateSets.Count == 0 && _paramsCandidates == null) {
- return new int[0];
- }
- int minParamsArray = Int32.MaxValue;
- if (_paramsCandidates != null) {
- foreach (var candidate in _paramsCandidates) {
- if (candidate.HasParamsArray) {
- minParamsArray = System.Math.Min(minParamsArray, candidate.GetVisibleParameterCount() - 1);
- }
- }
- }
- var result = new List<int>();
- if (_candidateSets.Count > 0) {
- var arities = new BitArray(System.Math.Min(_candidateSets.Keys.Max(), minParamsArray) + 1);
- foreach (var targetSet in _candidateSets.Values) {
- foreach (var candidate in targetSet.Candidates) {
- if (!candidate.HasParamsArray) {
- int visibleCount = candidate.GetVisibleParameterCount();
- if (visibleCount < arities.Length) {
- arities[visibleCount] = true;
- }
- }
- }
- }
- for (int i = 0; i < arities.Length; i++) {
- if (arities[i] || i == minParamsArray) {
- result.Add(i);
- }
- }
- } else if (minParamsArray < Int32.MaxValue) {
- result.Add(minParamsArray);
- }
- // all arities starting from minParamsArray are available:
- if (minParamsArray < Int32.MaxValue) {
- result.Add(Int32.MaxValue);
- }
- return result.ToArray();
- }
- public virtual ErrorInfo MakeInvalidParametersError(BindingTarget target) {
- switch (target.Result) {
- case BindingResult.CallFailure: return MakeCallFailureError(target);
- case BindingResult.AmbiguousMatch: return MakeAmbiguousCallError(target);
- case BindingResult.IncorrectArgumentCount: return MakeIncorrectArgumentCountError(target);
- case BindingResult.InvalidArguments: return MakeInvalidArgumentsError();
- case BindingResult.NoCallableMethod: return MakeNoCallableMethodError();
- default: throw new InvalidOperationException();
- }
- }
- private static ErrorInfo MakeIncorrectArgumentCountError(BindingTarget target) {
- int minArgs = Int32.MaxValue;
- int maxArgs = Int32.MinValue;
- foreach (int argCnt in target.ExpectedArgumentCount) {
- minArgs = System.Math.Min(minArgs, argCnt);
- maxArgs = System.Math.Max(maxArgs, argCnt);
- }
- return ErrorInfo.FromException(
- Ast.Call(
- typeof(BinderOps).GetMethod("TypeErrorForIncorrectArgumentCount", new Type[] {
- typeof(string), typeof(int), typeof(int) , typeof(int), typeof(int), typeof(bool), typeof(bool)
- }),
- AstUtils.Constant(target.Name, typeof(string)), // name
- AstUtils.Constant(minArgs), // min formal normal arg cnt
- AstUtils.Constant(maxArgs), // max formal normal arg cnt
- AstUtils.Constant(0), // default cnt
- AstUtils.Constant(target.ActualArgumentCount), // args provided
- AstUtils.Constant(false), // hasArgList
- AstUtils.Constant(false) // kwargs provided
- )
- );
- }
- private ErrorInfo MakeAmbiguousCallError(BindingTarget target) {
- StringBuilder sb = new StringBuilder("Multiple targets could match: ");
- string outerComma = "";
- foreach (MethodCandidate candidate in target.AmbiguousMatches) {
- Type[] types = candidate.GetParameterTypes();
- string innerComma = "";
- sb.Append(outerComma);
- sb.Append(target.Name);
- sb.Append('(');
- foreach (Type t in types) {
- sb.Append(innerComma);
- sb.Append(_binder.GetTypeName(t));
- innerComma = ", ";
- }
- sb.Append(')');
- outerComma = ", ";
- }
- return ErrorInfo.FromException(
- Ast.Call(
- typeof(BinderOps).GetMethod("SimpleTypeError"),
- AstUtils.Constant(sb.ToString(), typeof(string))
- )
- );
- }
- private ErrorInfo MakeCallFailureError(BindingTarget target) {
- foreach (CallFailure cf in target.CallFailures) {
- switch (cf.Reason) {
- case CallFailureReason.ConversionFailure:
- foreach (ConversionResult cr in cf.ConversionResults) {
- if (cr.Failed) {
- return ErrorInfo.FromException(
- Ast.Call(
- typeof(BinderOps).GetMethod("SimpleTypeError"),
- AstUtils.Constant(String.Format("expected {0}, got {1}", _binder.GetTypeName(cr.To), cr.GetArgumentTypeName(_binder)))
- )
- );
- }
- }
- break;
- case CallFailureReason.DuplicateKeyword:
- return ErrorInfo.FromException(
- Ast.Call(
- typeof(BinderOps).GetMethod("TypeErrorForDuplicateKeywordArgument"),
- AstUtils.Constant(target.Name, typeof(string)),
- AstUtils.Constant(cf.KeywordArguments[0], typeof(string)) // TODO: Report all bad arguments?
- )
- );
- case CallFailureReason.UnassignableKeyword:
- return ErrorInfo.FromException(
- Ast.Call(
- typeof(BinderOps).GetMethod("TypeErrorForExtraKeywordArgument"),
- AstUtils.Constant(target.Name, typeof(string)),
- AstUtils.Constant(cf.KeywordArguments[0], typeof(string)) // TODO: Report all bad arguments?
- )
- );
- case CallFailureReason.TypeInference:
- return ErrorInfo.FromException(
- Ast.Call(
- typeof(BinderOps).GetMethod("TypeErrorForNonInferrableMethod"),
- AstUtils.Constant(target.Name, typeof(string))
- )
- );
- default: throw new InvalidOperationException();
- }
- }
- throw new InvalidOperationException();
- }
- private ErrorInfo MakeInvalidArgumentsError() {
- return ErrorInfo.FromException(Ast.Call(typeof(BinderOps).GetMethod("SimpleTypeError"), AstUtils.Constant("Invalid arguments.")));
- }
- private ErrorInfo MakeNoCallableMethodError() {
- return ErrorInfo.FromException(
- Ast.New(typeof(InvalidOperationException).GetConstructor(new[] { typeof(string) }), AstUtils.Constant("No callable method."))
- );
- }
- #endregion
- #region Splatting
- // Get minimal number of arguments that must precede/follow splat mark in actual arguments.
- private void GetSplatLimits(out int preSplatLimit, out int postSplatLimit) {
- Debug.Assert(_candidateSets != null);
- if (_paramsCandidates != null) {
- int preCount = -1;
- int postCount = -1;
- // For all the methods that take a params array, create MethodCandidates that clash with the
- // other overloads of the method
- foreach (MethodCandidate candidate in _paramsCandidates) {
- preCount = System.Math.Max(preCount, candidate.ParamsArrayIndex);
- postCount = System.Math.Max(postCount, candidate.ParameterCount - candidate.ParamsArrayIndex - 1);
- }
- int maxArity = _candidateSets.Keys.Max();
- if (preCount + postCount < maxArity) {
- preCount = maxArity - postCount;
- }
- // +1 ensures that there is at least one expanded parameter before splatIndex (see MethodCandidate.TryConvertCollapsedArguments):
- preSplatLimit = preCount + 1;
- postSplatLimit = postCount;
- } else {
- // no limits, expand splatted arg fully:
- postSplatLimit = Int32.MaxValue;
- preSplatLimit = Int32.MaxValue;
- }
- }
- /// <summary>
- /// The method is called each time an item of lazily splatted argument is needed.
- /// </summary>
- internal Expression GetSplattedItemExpression(Expression indexExpression) {
- // TODO: move up?
- return Expression.Call(GetSplattedExpression(), typeof(IList).GetMethod("get_Item"), indexExpression);
- }
- protected abstract Expression GetSplattedExpression();
- protected abstract object GetSplattedItem(int index);
- internal object GetCollapsedArgumentValue(int collapsedArgIndex) {
- var result = GetSplattedItem(_actualArguments.ToSplattedItemIndex(collapsedArgIndex));
- _maxAccessedCollapsedArg = System.Math.Max(_maxAccessedCollapsedArg, collapsedArgIndex);
- return result;
- }
- public int MaxAccessedCollapsedArg {
- get { return _maxAccessedCollapsedArg; }
- }
- internal Type[] GetAccessedCollapsedArgTypes() {
- Type[] types = new Type[_maxAccessedCollapsedArg + 1];
- for (int i = 0; i < types.Length; i++) {
- var arg = GetSplattedItem(_actualArguments.ToSplattedItemIndex(i));
- types[i] = (arg != null) ? arg.GetType() : null;
- }
- return types;
- }
- // TODO: move up?
- public Expression GetCollapsedArgsCondition() {
- // collapsed args:
- if (_maxAccessedCollapsedArg >= 0) {
- Type[] collapsedTypes = GetAccessedCollapsedArgTypes();
- return Ast.Call(null, typeof(CompilerHelpers).GetMethod("TypesEqual"),
- GetSplattedExpression(),
- AstUtils.Constant(_actualArguments.ToSplattedItemIndex(0)),
- Ast.Constant(collapsedTypes)
- );
- } else {
- return null;
- }
- }
- #endregion
- public virtual Type GetGenericInferenceType(DynamicMetaObject dynamicObject) {
- return dynamicObject.LimitType;
- }
- public override string ToString() {
- string res = "";
- foreach (CandidateSet set in _candidateSets.Values) {
- res += set + Environment.NewLine;
- }
- return res;
- }
- }
- }