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