OverloadResolver.cs :  » Script » IronRuby » Microsoft » Scripting » Actions » Calls » C# / CSharp Open Source

Home
C# / CSharp Open Source
1.2.6.4 mono .net core
2.2.6.4 mono core
3.Aspect Oriented Frameworks
4.Bloggers
5.Build Systems
6.Business Application
7.Charting Reporting Tools
8.Chat Servers
9.Code Coverage Tools
10.Content Management Systems CMS
11.CRM ERP
12.Database
13.Development
14.Email
15.Forum
16.Game
17.GIS
18.GUI
19.IDEs
20.Installers Generators
21.Inversion of Control Dependency Injection
22.Issue Tracking
23.Logging Tools
24.Message
25.Mobile
26.Network Clients
27.Network Servers
28.Office
29.PDF
30.Persistence Frameworks
31.Portals
32.Profilers
33.Project Management
34.RSS RDF
35.Rule Engines
36.Script
37.Search Engines
38.Sound Audio
39.Source Control
40.SQL Clients
41.Template Engines
42.Testing
43.UML
44.Web Frameworks
45.Web Service
46.Web Testing
47.Wiki Engines
48.Windows Presentation Foundation
49.Workflows
50.XML Parsers
C# / C Sharp
C# / C Sharp by API
C# / CSharp Tutorial
C# / CSharp Open Source » Script » IronRuby 
IronRuby » Microsoft » Scripting » Actions » Calls » OverloadResolver.cs
/* ****************************************************************************
 *
 * Copyright (c) Microsoft Corporation. 
 *
 * This source code is subject to terms and conditions of the Microsoft Public License. A 
 * copy of the license can be found in the License.html file at the root of this distribution. If 
 * you cannot locate the  Microsoft Public License, 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 Microsoft Public License.
 *
 * You must not remove this notice, or any other, from this software.
 *
 *
 * ***************************************************************************/

#if !CLR2
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.Contracts;
using Microsoft.Scripting.Generation;
using Microsoft.Scripting.Runtime;
using Microsoft.Scripting.Utils;
using AstUtilsMicrosoft.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) {

            var result = new List<ApplicableCandidate>();
            foreach (ApplicableCandidate candidate in candidates) {
                if (candidate.Method.Overload.ContainsGenericParameters) {
                    continue;
                }

                CallFailure callFailure;
                if (TryConvertArguments(candidate.Method, candidate.ArgumentBinding, level, out callFailure)) {
                    result.Add(candidate);
                } else {
                    AddFailure(ref failures, callFailure);
                }
            }

            if (result.Count == 0) {
                // 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;
                }
            }

            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 Func<object[], object> GetConvertor(int index, DynamicMetaObject metaObject, ParameterInfo info, Type toType) {
            throw new NotImplementedException();
        }

        // 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.Count) {
                                arities[visibleCount] = true;
                            }
                        }
                    }
                }

                for (int i = 0; i < arities.Count; 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;
        }

        [Confined]
        public override string ToString() {
            string res = "";
            foreach (CandidateSet set in _candidateSets.Values) {
                res += set + Environment.NewLine;
            }
            return res;
        }
    }
}
www.java2v.com | Contact Us
Copyright 2009 - 12 Demo Source and Support. All rights reserved.
All other trademarks are property of their respective owners.