PageRenderTime 51ms CodeModel.GetById 10ms app.highlight 30ms RepoModel.GetById 1ms app.codeStats 1ms

/Runtime/Microsoft.Dynamic/Actions/Calls/OverloadResolver.cs

http://github.com/IronLanguages/main
C# | 1201 lines | 852 code | 201 blank | 148 comment | 225 complexity | 2b5468fb9df2b4d53949a92e0e3462a9 MD5 | raw file

Large files files are truncated, but you can click here to view the full file

   1/* ****************************************************************************
   2 *
   3 * Copyright (c) Microsoft Corporation. 
   4 *
   5 * This source code is subject to terms and conditions of the Apache License, Version 2.0. A 
   6 * copy of the license can be found in the License.html file at the root of this distribution. If 
   7 * you cannot locate the  Apache License, Version 2.0, please send an email to 
   8 * dlr@microsoft.com. By using this source code in any fashion, you are agreeing to be bound 
   9 * by the terms of the Apache License, Version 2.0.
  10 *
  11 * You must not remove this notice, or any other, from this software.
  12 *
  13 *
  14 * ***************************************************************************/
  15
  16#if FEATURE_CORE_DLR
  17using System.Linq.Expressions;
  18#else
  19using Microsoft.Scripting.Ast;
  20#endif
  21
  22using System;
  23using System.Collections;
  24using System.Collections.Generic;
  25using System.Diagnostics;
  26using System.Dynamic;
  27using System.Reflection;
  28using System.Runtime.CompilerServices;
  29using System.Text;
  30using Microsoft.Scripting.Generation;
  31using Microsoft.Scripting.Runtime;
  32using Microsoft.Scripting.Utils;
  33using AstUtils = Microsoft.Scripting.Ast.Utils;
  34
  35namespace Microsoft.Scripting.Actions.Calls {
  36    using Ast = Expression;
  37    
  38    /// <summary>
  39    /// Provides binding and overload resolution to .NET methods.
  40    /// 
  41    /// MethodBinder's can be used for:
  42    ///     generating new AST code for calling a method 
  43    ///     calling a method via reflection at runtime
  44    ///     (not implemented) performing an abstract call
  45    ///     
  46    /// MethodBinder's support default arguments, optional arguments, by-ref (in and out), and keyword arguments.
  47    /// 
  48    /// Implementation Details:
  49    /// 
  50    /// The MethodBinder works by building up a CandidateSet for each number of effective arguments that can be
  51    /// passed to a set of overloads.  For example a set of overloads such as:
  52    ///     foo(object a, object b, object c)
  53    ///     foo(int a, int b)
  54    ///     
  55    /// would have 2 target sets - one for 3 parameters and one for 2 parameters.  For parameter arrays
  56    /// we fallback and create the appropriately sized CandidateSet on demand.
  57    /// 
  58    /// Each CandidateSet consists of a set of MethodCandidate's.  Each MethodCandidate knows the flattened
  59    /// parameters that could be received.  For example for a function such as:
  60    ///     foo(params int[] args)
  61    ///     
  62    /// When this method is in a CandidateSet of size 3 the MethodCandidate takes 3 parameters - all of them
  63    /// ints; if it's in a CandidateSet of size 4 it takes 4 parameters.  Effectively a MethodCandidate is 
  64    /// a simplified view that allows all arguments to be treated as required positional arguments.
  65    /// 
  66    /// Each MethodCandidate in turn refers to a MethodTarget.  The MethodTarget is composed of a set
  67    /// of ArgBuilder's and a ReturnBuilder which know how to consume the positional arguments and pass
  68    /// them to the appropriate argument of the destination method.  This includes routing keyword
  69    /// arguments to the correct position, providing the default values for optional arguments, etc...
  70    /// 
  71    /// After binding is finished the MethodCandidates are thrown away and a BindingTarget is returned. 
  72    /// The BindingTarget indicates whether the binding was successful and if not any additional information
  73    /// that should be reported to the user about the failed binding.  It also exposes the MethodTarget which
  74    /// allows consumers to get the flattened list of required parameters for the call.  MethodCandidates
  75    /// are not exposed and are an internal implementation detail of the MethodBinder.
  76    /// </summary>
  77    public abstract partial class OverloadResolver {
  78        private readonly ActionBinder _binder;               
  79
  80        // built as target sets are built:
  81        private string _methodName;
  82        private NarrowingLevel _minLevel, _maxLevel;             // specifies the minimum and maximum narrowing levels for conversions during binding
  83        private IList<string> _argNames;
  84        private Dictionary<int, CandidateSet> _candidateSets;    // the methods as they map from # of arguments -> the possible CandidateSet's.
  85        private List<MethodCandidate> _paramsCandidates;         // the methods which are params methods which need special treatment because they don't have fixed # of args
  86        
  87        // built as arguments are processed:
  88        private ActualArguments _actualArguments;
  89        private int _maxAccessedCollapsedArg;
  90        private List<ParameterExpression> _temps;
  91
  92        protected OverloadResolver(ActionBinder binder) {
  93            ContractUtils.RequiresNotNull(binder, "binder");
  94
  95            _binder = binder;
  96            _maxAccessedCollapsedArg = -1;
  97        }
  98
  99        public ActionBinder Binder {
 100            get { return _binder; }
 101        }
 102
 103        internal List<ParameterExpression> Temps {
 104            get { return _temps; }
 105        }
 106
 107        internal ParameterExpression GetTemporary(Type type, string name) {
 108            Assert.NotNull(type);
 109
 110            if (_temps == null) {
 111                _temps = new List<ParameterExpression>();
 112            }
 113
 114            ParameterExpression res = Expression.Variable(type, name);
 115            _temps.Add(res);
 116            return res;
 117        }
 118
 119        #region ResolveOverload
 120
 121        /// <summary>
 122        /// Resolves a method overload and returns back a BindingTarget.
 123        /// 
 124        /// The BindingTarget can then be tested for the success or particular type of
 125        /// failure that prevents the method from being called. If successfully bound the BindingTarget
 126        /// contains a list of argument meta-objects with additional restrictions that ensure the selection
 127        /// of the particular overload.
 128        /// </summary>
 129        public BindingTarget ResolveOverload(string methodName, IList<MethodBase> methods, NarrowingLevel minLevel, NarrowingLevel maxLevel) {
 130            return ResolveOverload(
 131                methodName,
 132                ArrayUtils.ToArray(methods, (m) => new ReflectionOverloadInfo(m)),
 133                minLevel,
 134                maxLevel
 135            );
 136        }
 137
 138        public BindingTarget ResolveOverload(string methodName, IList<OverloadInfo> methods, NarrowingLevel minLevel, NarrowingLevel maxLevel) {
 139            ContractUtils.RequiresNotNullItems(methods, "methods");
 140            ContractUtils.Requires(minLevel <= maxLevel);
 141
 142            if (_candidateSets != null) {
 143                throw new InvalidOperationException("Overload resolver cannot be reused.");
 144            }
 145
 146            _methodName = methodName;
 147            _minLevel = minLevel;
 148            _maxLevel = maxLevel;
 149            
 150            // step 1:
 151            IList<DynamicMetaObject> namedArgs;
 152            GetNamedArguments(out namedArgs, out _argNames);
 153            
 154            // uses arg names:
 155            BuildCandidateSets(methods);
 156
 157            // uses target sets:
 158            int preSplatLimit, postSplatLimit;
 159            GetSplatLimits(out preSplatLimit, out postSplatLimit);
 160
 161            // step 2:
 162            _actualArguments = CreateActualArguments(namedArgs, _argNames, preSplatLimit, postSplatLimit);
 163            if (_actualArguments == null) {
 164                return new BindingTarget(methodName, BindingResult.InvalidArguments);
 165            }
 166
 167            // steps 3, 4:
 168            var candidateSet = GetCandidateSet();
 169            if (candidateSet != null && !candidateSet.IsParamsDictionaryOnly()) {
 170                return MakeBindingTarget(candidateSet);
 171            }
 172
 173            // step 5:
 174            return new BindingTarget(methodName, _actualArguments.VisibleCount, GetExpectedArgCounts());
 175        }
 176
 177        #endregion
 178
 179        #region Step 1: TargetSet construction, custom special parameters handling
 180
 181        /// <summary>
 182        /// Checks to see if the language allows named arguments to be bound to instance fields or
 183        /// properties and turned into setters. By default this is only allowed on contructors.
 184        /// </summary>
 185        internal protected virtual bool AllowMemberInitialization(OverloadInfo method) {
 186#pragma warning disable 618 // obsolete
 187            return AllowKeywordArgumentSetting(method.ReflectionInfo);
 188#pragma warning restore 618
 189        }
 190
 191        [Obsolete("Use OverloadInfo.AllowMemberInitialization instead")]
 192        internal protected virtual bool AllowKeywordArgumentSetting(MethodBase method) {
 193            return CompilerHelpers.IsConstructor(method);
 194        }
 195
 196        /// <summary>
 197        /// Gets an expression that evaluates to the result of GetByRefArray operation.
 198        /// </summary>
 199        internal protected virtual Expression GetByRefArrayExpression(Expression argumentArrayExpression) {
 200            return argumentArrayExpression;
 201        }
 202
 203        /// <summary>
 204        /// Allow to bind an array/dictionary instance or a null reference to params array/dictionary parameter.
 205        /// </summary>
 206        protected virtual bool BindToUnexpandedParams(MethodCandidate candidate) {
 207            return true;
 208        }
 209
 210        /// <summary>
 211        /// Called before arguments binding.
 212        /// </summary>
 213        /// <returns>
 214        /// A bitmask that indicates (set bits) the parameters that were mapped by this method.
 215        /// A default mapping will be constructed for the remaining parameters (cleared bits).
 216        /// </returns>
 217        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1045:DoNotPassTypesByReference", MessageId = "3#")]
 218        internal protected virtual BitArray MapSpecialParameters(ParameterMapping mapping) {
 219            if (!mapping.Overload.IsStatic) {
 220                var type = mapping.Overload.DeclaringType;
 221                mapping.AddParameter(new ParameterWrapper(null, type, null, ParameterBindingFlags.ProhibitNull));
 222                mapping.AddInstanceBuilder(new InstanceBuilder(mapping.ArgIndex));
 223            }
 224
 225            return null;
 226        }
 227
 228        private void BuildCandidateSets(IEnumerable<OverloadInfo> methods) {
 229            Debug.Assert(_candidateSets == null);
 230            Debug.Assert(_argNames != null);
 231
 232            _candidateSets = new Dictionary<int, CandidateSet>();
 233
 234            foreach (OverloadInfo method in methods) {
 235                if (IsUnsupported(method)) continue;
 236
 237                AddBasicMethodTargets(method);
 238            }
 239            
 240            if (_paramsCandidates != null) {
 241                // For all the methods that take a params array, create MethodCandidates that clash with the 
 242                // other overloads of the method
 243                foreach (MethodCandidate candidate in _paramsCandidates) {
 244                    foreach (int count in _candidateSets.Keys) {
 245                        MethodCandidate target = candidate.MakeParamsExtended(count, _argNames);
 246                        if (target != null) {
 247                            AddTarget(target);
 248                        }
 249                    }
 250                }
 251            }
 252        }
 253
 254        private CandidateSet GetCandidateSet() {
 255            Debug.Assert(_candidateSets != null && _actualArguments != null);
 256
 257            CandidateSet result;
 258
 259            // use precomputed set if arguments are fully expanded and we have one:
 260            if (_actualArguments.CollapsedCount == 0 && _candidateSets.TryGetValue(_actualArguments.Count, out result)) {
 261                return result;
 262            }
 263
 264            if (_paramsCandidates != null) {
 265                // build a new target set specific to the number of arguments we have:
 266                result = BuildExpandedTargetSet(_actualArguments.Count);
 267                if (result.Candidates.Count > 0) {
 268                    return result;
 269                }
 270            }
 271
 272            return null;
 273        }
 274
 275        private CandidateSet BuildExpandedTargetSet(int count) {
 276            var set = new CandidateSet(count);
 277            if (_paramsCandidates != null) {
 278                foreach (MethodCandidate maker in _paramsCandidates) {
 279                    MethodCandidate target = maker.MakeParamsExtended(count, _argNames);
 280                    if (target != null) {
 281                        set.Add(target);
 282                    }
 283                }
 284            }
 285
 286            return set;
 287        }
 288
 289        private void AddTarget(MethodCandidate target) {
 290            int count = target.ParameterCount;
 291            CandidateSet set;
 292            if (!_candidateSets.TryGetValue(count, out set)) {
 293                set = new CandidateSet(count);
 294                _candidateSets[count] = set;
 295            }
 296            set.Add(target);
 297        }
 298
 299        private void AddSimpleTarget(MethodCandidate target) {
 300            if (target.HasParamsArray || target.HasParamsDictionary) {
 301                if (BindToUnexpandedParams(target)) {
 302                    AddTarget(target);
 303                }
 304                
 305                if (_paramsCandidates == null) {
 306                    _paramsCandidates = new List<MethodCandidate>();
 307                }
 308                _paramsCandidates.Add(target);
 309            } else {
 310                AddTarget(target);
 311            }
 312        }
 313
 314        private void AddBasicMethodTargets(OverloadInfo method) {
 315            Assert.NotNull(method);
 316
 317            var mapping = new ParameterMapping(this, method, _argNames);
 318
 319            mapping.MapParameters(false);
 320
 321            foreach (var defaultCandidate in mapping.CreateDefaultCandidates()) {
 322                AddSimpleTarget(defaultCandidate);
 323            }
 324
 325            // TODO: We reduce out/ref parameters only for the main overload.
 326            // We should rather treat all out params as optional (either a StrongBox is provided or not).
 327            var byRefReducedCandidate = mapping.CreateByRefReducedCandidate();
 328            if (byRefReducedCandidate != null) {
 329                AddSimpleTarget(byRefReducedCandidate);
 330            }
 331
 332            AddSimpleTarget(mapping.CreateCandidate());
 333        }
 334
 335        private static bool IsUnsupported(OverloadInfo method) {
 336            return (method.CallingConvention & CallingConventions.VarArgs) != 0;
 337        }
 338
 339        #endregion
 340
 341        #region Step 2: Actual Arguments
 342
 343        public ActualArguments GetActualArguments() {
 344            if (_actualArguments == null) {
 345                throw new InvalidOperationException("Actual arguments have not been built yet.");
 346            }
 347            return _actualArguments; 
 348        }
 349
 350        protected virtual void GetNamedArguments(out IList<DynamicMetaObject> namedArgs, out IList<string> argNames) {
 351            // language doesn't support named arguments:
 352            argNames = ArrayUtils.EmptyStrings;
 353            namedArgs = DynamicMetaObject.EmptyMetaObjects;
 354        }
 355
 356        /// <summary>
 357        /// Return null if arguments cannot be constructed and overload resolution should produce an error.
 358        /// </summary>
 359        protected abstract ActualArguments CreateActualArguments(IList<DynamicMetaObject> namedArgs, IList<string> argNames, int preSplatLimit, int postSplatLimit);
 360
 361        #endregion
 362
 363        #region Step 3: Resolution
 364
 365        internal BindingTarget MakeBindingTarget(CandidateSet targetSet) {
 366            List<CallFailure> failures = null;
 367            List<CallFailure> nameBindingFailures = null;
 368
 369            // get candidates whose named arguments can be bind to the parameters:
 370            var potential = EnsureMatchingNamedArgs(targetSet.Candidates, ref nameBindingFailures);
 371
 372            if (potential.Count == 0) {
 373                return MakeFailedBindingTarget(nameBindingFailures.ToArray());
 374            }
 375
 376            // go through all available narrowing levels selecting candidates.  
 377            for (NarrowingLevel level = _minLevel; level <= _maxLevel; level++) {
 378                if (failures != null) {
 379                    failures.Clear();
 380                }
 381
 382                // only allow candidates whose non-collapsed arguments are convertible to the parameter types:
 383                var applicable = SelectCandidatesWithConvertibleArgs(potential, level, ref failures);
 384
 385                if (applicable.Count == 0) {
 386                    continue;
 387                } else if (applicable.Count == 1) {
 388                    return MakeSuccessfulBindingTarget(applicable[0], potential, level, targetSet);
 389                }
 390
 391                // see if collapsed arguments be converted to the corresponding element types:
 392                applicable = SelectCandidatesWithConvertibleCollapsedArgs(applicable, level, ref failures);
 393
 394                if (applicable.Count == 0) {
 395                    continue;
 396                } else if (applicable.Count == 1) {
 397                    return MakeSuccessfulBindingTarget(applicable[0], potential, level, targetSet);
 398                }
 399
 400                var bestCandidate = SelectBestCandidate(applicable, level);
 401                if (bestCandidate != null) {
 402                    return MakeSuccessfulBindingTarget(bestCandidate, potential, level, targetSet);
 403                } else {
 404                    return MakeAmbiguousBindingTarget(applicable);
 405                }
 406            }
 407
 408            if (failures == null) {
 409                // this can happen if there is no callable method:
 410                return new BindingTarget(_methodName, BindingResult.NoCallableMethod);
 411            }
 412
 413            if (nameBindingFailures != null) {
 414                failures.AddRange(nameBindingFailures);
 415            }
 416            return MakeFailedBindingTarget(failures.ToArray());
 417        }
 418
 419        private List<ApplicableCandidate> EnsureMatchingNamedArgs(List<MethodCandidate> candidates, ref List<CallFailure> failures) {
 420            var result = new List<ApplicableCandidate>();
 421            foreach (MethodCandidate candidate in candidates) {
 422                // skip params dictionaries - we want to only pick up the methods normalized
 423                // to have argument names (which we created because the MethodBinder gets 
 424                // created w/ keyword arguments).
 425                if (!candidate.HasParamsDictionary) {
 426                    CallFailure callFailure;
 427                    ArgumentBinding namesBinding;
 428
 429                    if (_actualArguments.TryBindNamedArguments(candidate, out namesBinding, out callFailure)) {
 430                        result.Add(new ApplicableCandidate(candidate, namesBinding));
 431                    } else {
 432                        AddFailure(ref failures, callFailure);
 433                    }
 434                }
 435            }
 436            return result;
 437        }
 438
 439        private List<ApplicableCandidate> SelectCandidatesWithConvertibleArgs(List<ApplicableCandidate> candidates, NarrowingLevel level, 
 440            ref List<CallFailure> failures) {
 441
 442            bool hasGenericCandidates = false;
 443            var result = new List<ApplicableCandidate>();
 444            foreach (ApplicableCandidate candidate in candidates) {
 445                if (candidate.Method.Overload.ContainsGenericParameters) {
 446                    hasGenericCandidates = true;
 447                    continue;
 448                }
 449
 450                CallFailure callFailure;
 451                if (TryConvertArguments(candidate.Method, candidate.ArgumentBinding, level, out callFailure)) {
 452                    result.Add(candidate);
 453                } else {
 454                    AddFailure(ref failures, callFailure);
 455                }
 456            }
 457
 458            if (hasGenericCandidates) {
 459                // attempt generic method type inference
 460                foreach (ApplicableCandidate candidate in candidates) {
 461                    if (!candidate.Method.Overload.IsGenericMethodDefinition) {
 462                        continue;
 463                    }
 464
 465                    MethodCandidate newCandidate = TypeInferer.InferGenericMethod(candidate, _actualArguments);
 466                    if (newCandidate != null) {
 467                        CallFailure callFailure;
 468                        if (TryConvertArguments(newCandidate, candidate.ArgumentBinding, level, out callFailure)) {
 469                            result.Add(new ApplicableCandidate(newCandidate, candidate.ArgumentBinding));
 470                        } else {
 471                            AddFailure(ref failures, callFailure);
 472                        }
 473                    } else {
 474                        AddFailure(ref failures, new CallFailure(candidate.Method, CallFailureReason.TypeInference));
 475                    }
 476                }
 477            }
 478
 479            return result;
 480        }
 481
 482        private List<ApplicableCandidate> SelectCandidatesWithConvertibleCollapsedArgs(List<ApplicableCandidate> candidates,
 483            NarrowingLevel level, ref List<CallFailure> failures) {
 484
 485            if (_actualArguments.CollapsedCount == 0) {
 486                return candidates;
 487            }
 488
 489            var result = new List<ApplicableCandidate>();
 490            foreach (ApplicableCandidate candidate in candidates) {
 491                CallFailure callFailure;
 492                if (TryConvertCollapsedArguments(candidate.Method, level, out callFailure)) {
 493                    result.Add(candidate);
 494                } else {
 495                    AddFailure(ref failures, callFailure);
 496                }
 497            }
 498            return result;
 499        }
 500
 501        private static void AddFailure(ref List<CallFailure> failures, CallFailure failure) {
 502            if (failures == null) {
 503                failures = new List<CallFailure>(1);
 504            }
 505            failures.Add(failure);
 506        }
 507
 508        private bool TryConvertArguments(MethodCandidate candidate, ArgumentBinding namesBinding, NarrowingLevel narrowingLevel, out CallFailure failure) {
 509            Debug.Assert(_actualArguments.Count == candidate.ParameterCount);
 510
 511            BitArray hasConversion = new BitArray(_actualArguments.Count);
 512
 513            bool success = true;
 514            for (int i = 0; i < _actualArguments.Count; i++) {
 515                success &= (hasConversion[i] = CanConvertFrom(_actualArguments[i].GetLimitType(), _actualArguments[i], candidate.GetParameter(i, namesBinding), narrowingLevel));
 516            }
 517
 518            if (!success) {
 519                var conversionResults = new ConversionResult[_actualArguments.Count];
 520                for (int i = 0; i < _actualArguments.Count; i++) {
 521                    conversionResults[i] = new ConversionResult(_actualArguments[i].Value, _actualArguments[i].GetLimitType(), candidate.GetParameter(i, namesBinding).Type, !hasConversion[i]);
 522                }
 523                failure = new CallFailure(candidate, conversionResults);
 524            } else {
 525                failure = null;
 526            }
 527
 528            return success;
 529        }
 530
 531        private bool TryConvertCollapsedArguments(MethodCandidate candidate, NarrowingLevel narrowingLevel, out CallFailure failure) {
 532            Debug.Assert(_actualArguments.CollapsedCount > 0);
 533
 534            // There must be at least one expanded parameter preceding splat index (see MethodBinder.GetSplatLimits):
 535            ParameterWrapper parameter = candidate.GetParameter(_actualArguments.SplatIndex - 1);
 536            Debug.Assert(parameter.ParameterInfo != null && candidate.Overload.IsParamArray(parameter.ParameterInfo.Position));
 537
 538            for (int i = 0; i < _actualArguments.CollapsedCount; i++) {
 539                object value = GetCollapsedArgumentValue(i);
 540                Type argType = CompilerHelpers.GetType(value);
 541
 542                if (!CanConvertFrom(argType, null, parameter, narrowingLevel)) {
 543                    failure = new CallFailure(candidate, new[] { new ConversionResult(value, argType, parameter.Type, false) });
 544                    return false;
 545                }
 546            }
 547
 548            failure = null;
 549            return true;
 550        }
 551
 552        private RestrictedArguments GetRestrictedArgs(ApplicableCandidate selectedCandidate, IList<ApplicableCandidate> candidates, int targetSetSize) {
 553            Debug.Assert(selectedCandidate.Method.ParameterCount == _actualArguments.Count);
 554
 555            int argCount = _actualArguments.Count;
 556            var restrictedArgs = new DynamicMetaObject[argCount];
 557            var types = new Type[argCount];
 558            bool hasAdditionalRestrictions = false;
 559            for (int i = 0; i < argCount; i++) {
 560                var arg = _actualArguments[i];
 561
 562                if (targetSetSize > 0 && IsOverloadedOnParameter(i, argCount, candidates) ||
 563                    !selectedCandidate.GetParameter(i).Type.IsAssignableFrom(arg.Expression.Type)) {
 564
 565                    restrictedArgs[i] = RestrictArgument(arg, selectedCandidate.GetParameter(i));
 566                    types[i] = arg.GetLimitType();
 567                } else {
 568                    restrictedArgs[i] = arg;
 569                }
 570
 571                BindingRestrictions additionalRestrictions;
 572                if (selectedCandidate.Method.Restrictions != null && selectedCandidate.Method.Restrictions.TryGetValue(arg, out additionalRestrictions)) {
 573                    hasAdditionalRestrictions = true;
 574                    restrictedArgs[i] = new DynamicMetaObject(restrictedArgs[i].Expression, restrictedArgs[i].Restrictions.Merge(additionalRestrictions));
 575                }
 576            }
 577
 578            return new RestrictedArguments(restrictedArgs, types, hasAdditionalRestrictions);
 579        }
 580
 581        private DynamicMetaObject RestrictArgument(DynamicMetaObject arg, ParameterWrapper parameter) {
 582            if (parameter.Type == typeof(object)) {
 583                // don't use Restrict as it'll box & unbox.
 584                return new DynamicMetaObject(arg.Expression, BindingRestrictionsHelpers.GetRuntimeTypeRestriction(arg.Expression, arg.GetLimitType()));
 585            } else {
 586                return arg.Restrict(arg.GetLimitType());
 587            }
 588        }
 589
 590        /// <summary>
 591        /// Determines whether given overloads are overloaded on index-th parameter (the types of the index-th parameters are the same).
 592        /// </summary>
 593        private static bool IsOverloadedOnParameter(int argIndex, int argCount, IList<ApplicableCandidate> overloads) {
 594            Debug.Assert(argIndex >= 0);
 595
 596            Type seenParametersType = null;
 597            foreach (var overload in overloads) {
 598                int parameterCount = overload.Method.ParameterCount;
 599                if (parameterCount == 0) {
 600                    continue;
 601                }
 602
 603                var lastParameter = overload.Method.GetParameter(parameterCount - 1);
 604
 605                Type parameterType;
 606                if (argIndex < parameterCount) {
 607                    var parameter = overload.GetParameter(argIndex);
 608                    if (parameter.IsParamsArray) {
 609                        if (parameterCount == argCount) {
 610                            // We're the params array argument and a single value is being passed
 611                            // directly to it.  The params array could be in the middle for
 612                            // a params setter.  so pis.Count - readIndex is usually 1 for the
 613                            // params at the end, and therefore types.Length - 1 is usually if we're
 614                            // the last argument.  We always have to check this type to disambiguate
 615                            // between passing an object which is compatible with the arg array and
 616                            // passing an object which goes into the arg array.  Maybe we could do 
 617                            // better sometimes.
 618                            return true;
 619                        }
 620                        parameterType = lastParameter.Type.GetElementType();
 621                    } else if (parameter.Type.ContainsGenericParameters()) {
 622                        return true;
 623                    } else {
 624                        parameterType = parameter.Type;
 625                    }
 626                } else if (lastParameter.IsParamsArray) {
 627                    parameterType = lastParameter.Type.GetElementType();
 628                } else {
 629                    continue;
 630                }
 631
 632                if (seenParametersType == null) {
 633                    seenParametersType = parameterType;
 634                } else if (seenParametersType != parameterType) {
 635                    return true;
 636                }
 637            }
 638            return false;
 639        }
 640
 641        private bool IsBest(ApplicableCandidate candidate, List<ApplicableCandidate> candidates, NarrowingLevel level) {
 642            foreach (ApplicableCandidate other in candidates) {
 643                if (candidate == other) {
 644                    continue;
 645                }
 646
 647                if (GetPreferredCandidate(candidate, other, level) != Candidate.One) {
 648                    return false;
 649                }
 650            }
 651            return true;
 652        }
 653
 654        internal Candidate GetPreferredCandidate(ApplicableCandidate one, ApplicableCandidate two, NarrowingLevel level) {
 655            Candidate cmpParams = GetPreferredParameters(one, two, level);
 656            if (cmpParams.Chosen()) {
 657                return cmpParams;
 658            }
 659
 660            return CompareEquivalentCandidates(one, two);
 661        }
 662
 663        internal protected virtual Candidate CompareEquivalentCandidates(ApplicableCandidate one, ApplicableCandidate two) {
 664            Candidate ret = CompareEquivalentParameters(one.Method, two.Method);
 665            if (ret.Chosen()) {
 666                return ret;
 667            }
 668
 669            return Candidate.Equivalent;
 670        }
 671
 672        internal Candidate CompareEquivalentParameters(MethodCandidate one, MethodCandidate two) {
 673            // Prefer normal methods over explicit interface implementations
 674            if (two.Overload.IsPrivate && !one.Overload.IsPrivate) return Candidate.One;
 675            if (one.Overload.IsPrivate && !two.Overload.IsPrivate) return Candidate.Two;
 676
 677            // Prefer non-generic methods over generic methods
 678            if (one.Overload.IsGenericMethod) {
 679                if (!two.Overload.IsGenericMethod) {
 680                    return Candidate.Two;
 681                } else {
 682                    //!!! Need to support selecting least generic method here
 683                    return Candidate.Equivalent;
 684                }
 685            } else if (two.Overload.IsGenericMethod) {
 686                return Candidate.One;
 687            }
 688
 689            // prefer methods without out params over those with them
 690            switch (Compare(one.ReturnBuilder.CountOutParams, two.ReturnBuilder.CountOutParams)) {
 691                case 1: return Candidate.Two;
 692                case -1: return Candidate.One;
 693            }
 694
 695            // prefer methods using earlier conversions rules to later ones            
 696            for (int i = Int32.MaxValue; i >= 0; ) {
 697                int maxPriorityThis = FindMaxPriority(one.ArgBuilders, i);
 698                int maxPriorityOther = FindMaxPriority(two.ArgBuilders, i);
 699
 700                if (maxPriorityThis < maxPriorityOther) return Candidate.One;
 701                if (maxPriorityOther < maxPriorityThis) return Candidate.Two;
 702
 703                i = maxPriorityThis - 1;
 704            }
 705
 706            // prefer methods whose name exactly matches the call site name:
 707            if (one.Overload.Name != two.Overload.Name) {
 708                if (one.Overload.Name == _methodName) {
 709                    return Candidate.One;
 710                }
 711                if (two.Overload.Name == _methodName) {
 712                    return Candidate.Two;
 713                }
 714            }
 715
 716            // prefer regular methods over extensions:
 717            if (one.Overload.IsExtension != two.Overload.IsExtension) {
 718                return one.Overload.IsExtension ? Candidate.Two : Candidate.One;
 719            }
 720
 721            return Candidate.Equivalent;
 722        }
 723
 724        private static int Compare(int x, int y) {
 725            if (x < y) return -1;
 726            else if (x > y) return +1;
 727            else return 0;
 728        }
 729
 730        private static int FindMaxPriority(IList<ArgBuilder> abs, int ceiling) {
 731            int max = 0;
 732            foreach (ArgBuilder ab in abs) {
 733                if (ab.Priority > ceiling) continue;
 734
 735                max = System.Math.Max(max, ab.Priority);
 736            }
 737            return max;
 738        }
 739
 740        private Candidate GetPreferredParameters(ApplicableCandidate one, ApplicableCandidate two, NarrowingLevel level) {
 741            Debug.Assert(one.Method.ParameterCount == two.Method.ParameterCount);
 742            var args = GetActualArguments();
 743
 744            Candidate result = Candidate.Equivalent;
 745            for (int i = 0; i < args.Count; i++) {
 746                Candidate preferred = GetPreferredParameter(one.GetParameter(i), two.GetParameter(i), args[i], level);
 747
 748                switch (result) {
 749                    case Candidate.Equivalent:
 750                        result = preferred;
 751                        break;
 752
 753                    case Candidate.One:
 754                        if (preferred == Candidate.Two) return Candidate.Ambiguous;
 755                        break;
 756
 757                    case Candidate.Two:
 758                        if (preferred == Candidate.One) return Candidate.Ambiguous;
 759                        break;
 760
 761                    case Candidate.Ambiguous:
 762                        if (preferred != Candidate.Equivalent) {
 763                            result = preferred;
 764                        }
 765                        break;
 766
 767                    default:
 768                        throw new InvalidOperationException();
 769                }
 770            }
 771
 772            // TODO: process collapsed arguments:
 773
 774            return result;
 775        }
 776
 777        private Candidate GetPreferredParameter(ParameterWrapper candidateOne, ParameterWrapper candidateTwo, DynamicMetaObject arg, NarrowingLevel level) {
 778            Assert.NotNull(candidateOne, candidateTwo);
 779
 780            if (ParametersEquivalent(candidateOne, candidateTwo)) {
 781                return Candidate.Equivalent;
 782            }
 783
 784            Candidate candidate = SelectBestConversionFor(arg, candidateOne, candidateTwo, level);
 785            if (candidate.Chosen()) {
 786                return candidate;
 787            }
 788
 789            if (CanConvertFrom(candidateTwo, candidateOne)) {
 790                if (CanConvertFrom(candidateOne, candidateTwo)) {
 791                    return Candidate.Ambiguous;
 792                } else {
 793                    return Candidate.Two;
 794                }
 795            } else if (CanConvertFrom(candidateOne, candidateTwo)) {
 796                return Candidate.One;
 797            }
 798
 799            // Special additional rules to order numeric value types
 800            Type t1 = candidateOne.Type;
 801            Type t2 = candidateTwo.Type;
 802
 803            Candidate preferred = PreferConvert(t1, t2);
 804            if (preferred.Chosen()) {
 805                return preferred;
 806            }
 807
 808            preferred = PreferConvert(t2, t1).TheOther();
 809            if (preferred.Chosen()) {
 810                return preferred;
 811            }
 812
 813            // consider the actual argument type:
 814            Type argType = arg.GetLimitType();
 815            NarrowingLevel levelOne = NarrowingLevel.None;
 816            while (levelOne < level && !CanConvertFrom(argType, arg, candidateOne, levelOne)) {
 817                if (levelOne == NarrowingLevel.All) {
 818                    Debug.Assert(false, "Each argument should be convertible to the corresponding parameter");
 819                    break;
 820                }
 821                levelOne++;
 822            }
 823
 824            NarrowingLevel levelTwo = NarrowingLevel.None;
 825            while (levelTwo < level && !CanConvertFrom(argType, arg, candidateTwo, levelTwo)) {
 826                if (levelTwo == NarrowingLevel.All) {
 827                    Debug.Assert(false, "Each argument should be convertible to the corresponding parameter");
 828                    break;
 829                }
 830                levelTwo++;
 831            }
 832
 833            if (levelOne < levelTwo) {
 834                return Candidate.One;
 835            } else if (levelOne > levelTwo) {
 836                return Candidate.Two;
 837            } else {
 838                return Candidate.Ambiguous;
 839            }
 840        }
 841
 842        private ApplicableCandidate SelectBestCandidate(List<ApplicableCandidate> candidates, NarrowingLevel level) {
 843            foreach (var candidate in candidates) {
 844                if (IsBest(candidate, candidates, level)) {
 845                    return candidate;
 846                }
 847            }
 848            return null;
 849        }
 850
 851        private BindingTarget MakeSuccessfulBindingTarget(ApplicableCandidate result, List<ApplicableCandidate> potentialCandidates,
 852            NarrowingLevel level, CandidateSet targetSet) {
 853
 854            return new BindingTarget(
 855                _methodName,
 856                _actualArguments.VisibleCount,
 857                result.Method,
 858                level,
 859                GetRestrictedArgs(result, potentialCandidates, targetSet.Arity)
 860            );
 861        }
 862
 863        private BindingTarget MakeFailedBindingTarget(CallFailure[] failures) {
 864            return new BindingTarget(_methodName, _actualArguments.VisibleCount, failures);
 865        }
 866
 867        private BindingTarget MakeAmbiguousBindingTarget(List<ApplicableCandidate> result) {
 868            var methods = new MethodCandidate[result.Count];
 869            for (int i = 0; i < result.Count; i++) {
 870                methods[i] = result[i].Method;
 871            }
 872
 873            return new BindingTarget(_methodName, _actualArguments.VisibleCount, methods);
 874        }
 875
 876        #endregion
 877
 878        #region Step 4: Argument Building, Conversions
 879
 880        public virtual bool ParametersEquivalent(ParameterWrapper parameter1, ParameterWrapper parameter2) {
 881            return parameter1.Type == parameter2.Type && parameter1.ProhibitNull == parameter2.ProhibitNull;
 882        }
 883
 884        public virtual bool CanConvertFrom(ParameterWrapper parameter1, ParameterWrapper parameter2) {
 885            return CanConvertFrom(parameter1.Type, null, parameter2, NarrowingLevel.None);
 886        }
 887
 888        public virtual bool CanConvertFrom(Type fromType, DynamicMetaObject fromArgument, ParameterWrapper toParameter, NarrowingLevel level) {
 889            Assert.NotNull(fromType, toParameter);
 890
 891            Type toType = toParameter.Type;
 892
 893            if (fromType == typeof(DynamicNull)) {
 894                if (toParameter.ProhibitNull) {
 895                    return false;
 896                }
 897
 898                if (toType.IsGenericType() && toType.GetGenericTypeDefinition() == typeof(Nullable<>)) {
 899                    return true;
 900                }
 901
 902                if (!toType.IsValueType()) {
 903                    return true;
 904                }
 905            }
 906
 907            if (fromType == toType) {
 908                return true;
 909            }
 910
 911            return _binder.CanConvertFrom(fromType, toType, toParameter.ProhibitNull, level);
 912        }
 913
 914        /// <summary>
 915        /// Selects the best (of two) candidates for conversion from actualType
 916        /// </summary>
 917        public virtual Candidate SelectBestConversionFor(DynamicMetaObject arg, ParameterWrapper candidateOne, ParameterWrapper candidateTwo, NarrowingLevel level) {
 918            return Candidate.Equivalent;
 919        }
 920
 921        /// <summary>
 922        /// Provides ordering for two parameter types if there is no conversion between the two parameter types.
 923        /// </summary>
 924        public virtual Candidate PreferConvert(Type t1, Type t2) {
 925            return _binder.PreferConvert(t1, t2);
 926        }
 927
 928        // TODO: revisit
 929        public virtual Expression Convert(DynamicMetaObject metaObject, Type restrictedType, ParameterInfo info, Type toType) {
 930            Assert.NotNull(metaObject, toType);
 931
 932            return _binder.ConvertExpression(metaObject.Expression, toType, ConversionResultKind.ExplicitCast, null);
 933        }
 934
 935        // TODO: revisit
 936        public virtual Expression GetDynamicConversion(Expression value, Type type) {
 937            return Expression.Convert(value, type);
 938        }
 939
 940        #endregion
 941
 942        #region Step 5: Results, Errors
 943        
 944        private int[] GetExpectedArgCounts() {
 945            if (_candidateSets.Count == 0 && _paramsCandidates == null) {
 946                return new int[0];
 947            }
 948
 949            int minParamsArray = Int32.MaxValue;
 950            if (_paramsCandidates != null) {
 951                foreach (var candidate in _paramsCandidates) {
 952                    if (candidate.HasParamsArray) {
 953                        minParamsArray = System.Math.Min(minParamsArray, candidate.GetVisibleParameterCount() - 1);
 954                    }
 955                }
 956            }
 957
 958            var result = new List<int>();
 959            if (_candidateSets.Count > 0) {
 960                var arities = new BitArray(System.Math.Min(_candidateSets.Keys.Max(), minParamsArray) + 1);
 961
 962                foreach (var targetSet in _candidateSets.Values) {
 963                    foreach (var candidate in targetSet.Candidates) {
 964                        if (!candidate.HasParamsArray) {
 965                            int visibleCount = candidate.GetVisibleParameterCount();
 966                            if (visibleCount < arities.Length) {
 967                                arities[visibleCount] = true;
 968                            }
 969                        }
 970                    }
 971                }
 972
 973                for (int i = 0; i < arities.Length; i++) {
 974                    if (arities[i] || i == minParamsArray) {
 975                        result.Add(i);
 976                    }
 977                }
 978            } else if (minParamsArray < Int32.MaxValue) {
 979                result.Add(minParamsArray);
 980            }
 981
 982            // all arities starting from minParamsArray are available:
 983            if (minParamsArray < Int32.MaxValue) {
 984                result.Add(Int32.MaxValue);
 985            }
 986
 987            return result.ToArray();
 988        }
 989
 990        public virtual ErrorInfo MakeInvalidParametersError(BindingTarget target) {
 991            switch (target.Result) {
 992                case BindingResult.CallFailure: return MakeCallFailureError(target);
 993                case BindingResult.AmbiguousMatch: return MakeAmbiguousCallError(target);
 994                case BindingResult.IncorrectArgumentCount: return MakeIncorrectArgumentCountError(target);
 995                case BindingResult.InvalidArguments: return MakeInvalidArgumentsError();
 996                case BindingResult.NoCallableMethod: return MakeNoCallableMethodError();
 997                default: throw new InvalidOperationException();
 998            }
 999        }
1000
1001        private static ErrorInfo MakeIncorrectArgumentCountError(BindingTarget target) {
1002            int minArgs = Int32.MaxValue;
1003            int maxArgs = Int32.MinValue;
1004            foreach (int argCnt in target.ExpectedArgumentCount) {
1005                minArgs = System.Math.Min(minArgs, argCnt);
1006                maxArgs = System.Math.Max(maxArgs, argCnt);
1007            }
1008
1009            return ErrorInfo.FromException(
1010                Ast.Call(
1011                    typeof(BinderOps).GetMethod("TypeErrorForIncorrectArgumentCount", new Type[] {
1012                                typeof(string), typeof(int), typeof(int) , typeof(int), typeof(int), typeof(bool), typeof(bool)
1013                            }),
1014                    AstUtils.Constant(target.Name, typeof(string)),  // name
1015                    AstUtils.Constant(minArgs),                      // min formal normal arg cnt
1016                    AstUtils.Constant(maxArgs),                      // max formal normal arg cnt
1017                    AstUtils.Constant(0),                            // default cnt
1018                    AstUtils.Constant(target.ActualArgumentCount),   // args provided
1019                    AstUtils.Constant(false),                        // hasArgList
1020                    AstUtils.Constant(false)                         // kwargs provided
1021                )
1022            );
1023        }
1024
1025        private ErrorInfo MakeAmbiguousCallError(BindingTarget target) {
1026            StringBuilder sb = new StringBuilder("Multiple targets could match: ");
1027            string outerComma = "";
1028            foreach (MethodCandidate candidate in target.AmbiguousMatches) {
1029                Type[] types = candidate.GetParameterTypes();
1030                string innerComma = "";
1031
1032                sb.Append(outerComma);
1033                sb.Append(target.Name);
1034                sb.Append('(');
1035                foreach (Type t in types) {
1036                    sb.Append(innerComma);
1037                    sb.Append(_binder.GetTypeName(t));
1038                    innerComma = ", ";
1039                }
1040
1041                sb.Append(')');
1042                outerComma = ", ";
1043            }
1044
1045            return ErrorInfo.FromException(
1046                Ast.Call(
1047                    typeof(BinderOps).GetMethod("SimpleTypeError"),
1048                    AstUtils.Constant(sb.ToString(), typeof(string))
1049                )
1050            );
1051        }
1052
1053        private ErrorInfo MakeCallFailureError(BindingTarget target) {
1054            foreach (CallFailure cf in target.CallFailures) {
1055                switch (cf.Reason) {
1056                    case CallFailureReason.ConversionFailure:
1057                        foreach (ConversionResult cr in cf.ConversionResults) {
1058                            if (cr.Failed) {
1059                                return ErrorInfo.FromException(
1060                                    Ast.Call(
1061                                        typeof(BinderOps).GetMethod("SimpleTypeError"),
1062                                        AstUtils.Constant(String.Format("expected {0}, got {1}", _binder.GetTypeName(cr.To), cr.GetArgumentTypeName(_binder)))
1063                                    )
1064                                );
1065                            }
1066                        }
1067                        break;
1068                    case CallFailureReason.DuplicateKeyword:
1069                        return ErrorInfo.FromException(
1070                                Ast.Call(
1071                                    typeof(BinderOps).GetMethod("TypeErrorForDuplicateKeywordArgument"),
1072                                    AstUtils.Constant(target.Name, typeof(string)),
1073                                    AstUtils.Constant(cf.KeywordArguments[0], typeof(string))    // TODO: Report all bad arguments?
1074                            )
1075                        );
1076                    case CallFailureReason.UnassignableKeyword:
1077                        return ErrorInfo.FromException(
1078                                Ast.Call(
1079                                    typeof(BinderOps).GetMethod("TypeErrorForExtraKeywordArgument"),
1080                                    AstUtils.Constant(target.Name, typeof(string)),
1081                                    AstUtils.Constant(cf.KeywordArguments[0], typeof(string))    // TODO: Report all bad arguments?
1082                            )
1083                        );
1084                    case CallFailureReason.TypeInference:
1085                        return ErrorInfo.FromException(
1086                                Ast.Call(
1087                                    typeof(BinderOps).GetMethod("TypeErrorForNonInferrableMethod"),
1088                                    AstUtils.Constant(target.Name, typeof(string))
1089                            )
1090                        );
1091                    default: throw new InvalidOperationException();
1092                }
1093            }
1094            throw new InvalidOperationException();
1095        }
1096
1097        private ErrorInfo MakeInvalidArgumentsError() {
1098            return ErrorInfo.FromException(Ast.Call(typeof(BinderOps).GetMethod("SimpleTypeError"), AstUtils.Constant("Invalid arguments.")));
1099        }
1100
1101        private ErrorInfo MakeNoCallableMethodError() {
1102            return ErrorInfo.FromException(
1103                Ast.New(typeof(InvalidOperationException).GetConstructor(new[] { typeof(string) }), AstUtils.Constant("No callable method."))
1104            );
1105        }
1106
1107        #endregion
1108
1109        #region Splatting
1110
1111        // Get minimal number of arguments that must precede/follow splat mark in actual arguments.
1112        private void GetSplatLimits(out int preSplatLimit, out int postSplatLimit) {
1113            Debug.Assert(_candidateSets != null);
1114
1115            if (_paramsCandidates != null) {
1116                int preCount = -1;
1117                int postCount = -1;
1118
1119                // For all the methods that take a params array, create MethodCandidates that clash with the 
1120                // other overloads of the method
1121                foreach (MethodCandidate candidate in _paramsCandidates) {
1122                    preCount = System.Math.Max(preCount, candidate.ParamsArrayIndex);
1123                    postCount = System.Math.Max(postCount, candidate.ParameterCount - candidate.ParamsArrayIndex - 1);
1124                }
1125
1126                int maxArity = _candidateSets.Keys.Max();
1127                if (preCount + postCount < maxArity) {
1128                    preCount = maxArity - postCount;
1129                }
1130
1131                // +1 ensures that there is at least one expanded parameter before splatIndex (see MethodCandidate.TryConvertCollapsedArguments):
1132                preSplatLimit = preCount + 1;
1133                postSplatLimit = postCount;
1134            } else {
1135                // no limits, expand splatted arg fully:
1136                postSplatLimit = Int32.MaxValue;
1137                preSplatLimit = Int32.MaxValue;
1138            }
1139        }
1140
1141        /// <summary>
1142        /// The method is called each time an item of lazily splatted argument is needed.
1143        /// </summary>
1144        internal Expression GetSplattedItemExpression(Expression indexExpression) {
1145            // TODO: move up?
1146            return Expression.Call(GetSplattedExpression(), typeof(IList).GetMethod("get_Item"), indexExpression);
1147        }
1148
1149        protected abstract Expression GetSplattedExpression();
1150        protected abstract object GetSplattedItem(int index);
1151
1152        internal object GetCollapsedArgumentValue(int collapsedArgIndex) {
1153     

Large files files are truncated, but you can click here to view the full file