PageRenderTime 46ms CodeModel.GetById 16ms RepoModel.GetById 0ms app.codeStats 1ms

/Microsoft.Scripting/Actions/Calls/MethodBinder.cs

https://bitbucket.org/stefanrusek/xronos
C# | 1028 lines | 707 code | 143 blank | 178 comment | 210 complexity | 1cb83d74c67c2fa838c5634ec8b62bd4 MD5 | raw file
  1. /* ****************************************************************************
  2. *
  3. * Copyright (c) Microsoft Corporation.
  4. *
  5. * This source code is subject to terms and conditions of the Microsoft Public License. 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 Microsoft Public License, 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 Microsoft Public License.
  10. *
  11. * You must not remove this notice, or any other, from this software.
  12. *
  13. *
  14. * ***************************************************************************/
  15. #if CODEPLEX_40
  16. using System;
  17. #else
  18. using System; using Microsoft;
  19. #endif
  20. using System.Collections.Generic;
  21. using System.Diagnostics;
  22. using System.Reflection;
  23. using System.Runtime.CompilerServices;
  24. #if !CODEPLEX_40
  25. using Microsoft.Runtime.CompilerServices;
  26. #endif
  27. #if CODEPLEX_40
  28. using System.Dynamic;
  29. #else
  30. using Microsoft.Scripting;
  31. #endif
  32. using Microsoft.Contracts;
  33. using Microsoft.Scripting.Actions;
  34. using Microsoft.Scripting.Runtime;
  35. using Microsoft.Scripting.Utils;
  36. using Microsoft.Scripting.Generation;
  37. namespace Microsoft.Scripting.Actions.Calls {
  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 TargetSet 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 TargetSet on demand.
  57. ///
  58. /// Each TargetSet 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 TargetSet of size 3 the MethodCandidate takes 3 parameters - all of them
  63. /// ints; if it's in a TargetSet 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 sealed class MethodBinder {
  78. private readonly string _name; // the name of the method (possibly language specific name which isn't the same as the method base)
  79. private readonly Dictionary<int, TargetSet> _targetSets; // the methods as they map from # of arguments -> the possible TargetSet's.
  80. private readonly string[] _kwArgs; // the names of the keyword arguments being provided
  81. private readonly NarrowingLevel _minLevel, _maxLevel; // specifies the minimum and maximum narrowing levels for conversions during binding
  82. internal readonly DefaultBinder _binder; // the ActionBinder which is being used for conversions
  83. private List<MethodCandidate> _paramsCandidates; // the methods which are params methods which need special treatment because they don't have fixed # of args
  84. #region Constructors
  85. private MethodBinder(ActionBinder binder, string name, IList<MethodBase> methods, string[] kwArgs, NarrowingLevel minLevel, NarrowingLevel maxLevel) {
  86. ContractUtils.RequiresNotNull(binder, "binder");
  87. ContractUtils.RequiresNotNull(name, "name");
  88. ContractUtils.RequiresNotNullItems(methods, "methods");
  89. ContractUtils.RequiresNotNullItems(kwArgs, "kwArgs");
  90. _binder = binder as DefaultBinder;
  91. if (_binder == null) {
  92. throw new InvalidOperationException("MethodBinder requires an instance of DefaultBinder");
  93. }
  94. _name = name;
  95. _kwArgs = kwArgs;
  96. _targetSets = new Dictionary<int, TargetSet>(methods.Count);
  97. _minLevel = minLevel;
  98. _maxLevel = maxLevel;
  99. foreach (MethodBase method in methods) {
  100. if (IsUnsupported(method)) continue;
  101. AddBasicMethodTargets(binder, method);
  102. }
  103. if (_paramsCandidates != null) {
  104. // For all the methods that take a params array, create MethodCandidates that clash with the
  105. // other overloads of the method
  106. foreach (MethodCandidate maker in _paramsCandidates) {
  107. foreach (int count in _targetSets.Keys) {
  108. MethodCandidate target = maker.MakeParamsExtended(binder, count, _kwArgs);
  109. if (target != null) AddTarget(target);
  110. }
  111. }
  112. }
  113. }
  114. #endregion
  115. #region Public APIs
  116. /// <summary>
  117. /// Creates a new MethodBinder for binding to the specified methods that will attempt to bind
  118. /// at all defined NarrowingLevels.
  119. ///
  120. /// The provided ActionBinder is used for determining overload resolution.
  121. /// </summary>
  122. public static MethodBinder MakeBinder(ActionBinder binder, string name, IList<MethodBase> mis) {
  123. return new MethodBinder(binder, name, mis, ArrayUtils.EmptyStrings, NarrowingLevel.None, NarrowingLevel.All);
  124. }
  125. /// <summary>
  126. /// Creates a new MethodBinder for binding to the specified methods on a call which includes keyword arguments that
  127. /// will attempt to bind at all defined NarrowingLevels.
  128. ///
  129. /// The provided ActionBinder is used for determining overload resolution.
  130. /// </summary>
  131. public static MethodBinder MakeBinder(ActionBinder binder, string name, IList<MethodBase> mis, string[] keywordArgs) {
  132. return new MethodBinder(binder, name, mis, keywordArgs, NarrowingLevel.None, NarrowingLevel.All);
  133. }
  134. /// <summary>
  135. /// Creates a new MethodBinder for binding to the specified methods this will attempt to bind at
  136. /// the specified NarrowingLevels.
  137. ///
  138. /// The provided ActionBinder is used for determining overload resolution.
  139. /// </summary>
  140. public static MethodBinder MakeBinder(ActionBinder binder, string name, IList<MethodBase> mis, NarrowingLevel minLevel, NarrowingLevel maxLevel) {
  141. return new MethodBinder(binder, name, mis, ArrayUtils.EmptyStrings, minLevel, maxLevel);
  142. }
  143. /// <summary>
  144. /// Creates a new MethodBinder for binding to the specified methods on a call which includes keyword arguments that
  145. /// will attempt to bind at the specified NarrowingLevels.
  146. ///
  147. /// The provided ActionBinder is used for determining overload resolution.
  148. /// </summary>
  149. public static MethodBinder MakeBinder(ActionBinder binder, string name, IList<MethodBase> mis, string[] keywordArgs, NarrowingLevel minLevel, NarrowingLevel maxLevel) {
  150. return new MethodBinder(binder, name, mis, keywordArgs, minLevel, maxLevel);
  151. }
  152. /// <summary>
  153. /// Creates a BindingTarget given the specified CallType and parameter types.
  154. ///
  155. /// The BindingTarget can then be tested for the success or particular type of
  156. /// failure that prevents the method from being called. The BindingTarget can
  157. /// also be called reflectively at runtime, create an Expression for embedding in
  158. /// a RuleBuilder, or be used for performing an abstract call.
  159. ///
  160. /// OBSOLETE
  161. /// </summary>
  162. public BindingTarget MakeBindingTarget(CallTypes callType, Type[] types) {
  163. ContractUtils.RequiresNotNull(types, "types");
  164. ContractUtils.RequiresNotNullItems(types, "types");
  165. TargetSet ts = GetTargetSet(types.Length);
  166. if (ts != null && !ts.IsParamsDictionaryOnly()) {
  167. return ts.MakeBindingTarget(callType, types, _kwArgs, _minLevel, _maxLevel);
  168. }
  169. // no target set is applicable, report an error and the expected # of arguments.
  170. int[] expectedArgs = new int[_targetSets.Count + (_paramsCandidates != null ? 1 : 0)];
  171. int i = 0;
  172. foreach (KeyValuePair<int, TargetSet> kvp in _targetSets) {
  173. int count = kvp.Key;
  174. foreach (MethodCandidate cand in kvp.Value._targets) {
  175. foreach (var x in cand.Parameters) {
  176. if (x.IsParamsArray || x.IsParamsDict) {
  177. count--;
  178. }
  179. }
  180. }
  181. if (callType == CallTypes.ImplicitInstance) {
  182. foreach (MethodCandidate cand in kvp.Value._targets) {
  183. if (IsInstanceMethod(cand)) {
  184. // dispatch includes an instance method, bump
  185. // one parameter off.
  186. count--;
  187. break;
  188. }
  189. }
  190. }
  191. expectedArgs[i++] = count;
  192. }
  193. if (_paramsCandidates != null) {
  194. expectedArgs[expectedArgs.Length - 1] = Int32.MaxValue;
  195. }
  196. return new BindingTarget(Name, callType == CallTypes.None ? types.Length : types.Length - 1, expectedArgs);
  197. }
  198. /// <summary>
  199. /// Creates a BindingTarget given the specified CallType and argument meta-objects.
  200. ///
  201. /// The BindingTarget can then be tested for the success or particular type of
  202. /// failure that prevents the method from being called. If successfully bound the BindingTarget
  203. /// contains a list of argument meta-objects with additional restrictions that ensure the selection
  204. /// of the particular overload.
  205. /// </summary>
  206. public BindingTarget MakeBindingTarget(CallTypes callType, DynamicMetaObject[] metaObjects) {
  207. ContractUtils.RequiresNotNull(metaObjects, "metaObjects");
  208. ContractUtils.RequiresNotNullItems(metaObjects, "metaObjects");
  209. TargetSet ts = GetTargetSet(metaObjects.Length);
  210. if (ts != null && !ts.IsParamsDictionaryOnly()) {
  211. return ts.MakeBindingTarget(callType, metaObjects, _kwArgs, _minLevel, _maxLevel);
  212. }
  213. // no target set is applicable, report an error and the expected # of arguments.
  214. int[] expectedArgs = new int[_targetSets.Count + (_paramsCandidates != null ? 1 : 0)];
  215. int i = 0;
  216. foreach (KeyValuePair<int, TargetSet> kvp in _targetSets) {
  217. int count = kvp.Key;
  218. foreach (MethodCandidate cand in kvp.Value._targets) {
  219. foreach (var x in cand.Parameters) {
  220. if (x.IsParamsArray || x.IsParamsDict) {
  221. count--;
  222. }
  223. }
  224. }
  225. if (callType == CallTypes.ImplicitInstance) {
  226. foreach (MethodCandidate cand in kvp.Value._targets) {
  227. if (IsInstanceMethod(cand)) {
  228. // dispatch includes an instance method, bump
  229. // one parameter off.
  230. count--;
  231. break;
  232. }
  233. }
  234. }
  235. expectedArgs[i++] = count;
  236. }
  237. if (_paramsCandidates != null) {
  238. expectedArgs[expectedArgs.Length - 1] = Int32.MaxValue;
  239. }
  240. return new BindingTarget(Name, callType == CallTypes.None ? metaObjects.Length : metaObjects.Length - 1, expectedArgs);
  241. }
  242. private static bool IsInstanceMethod(MethodCandidate cand) {
  243. return !CompilerHelpers.IsStatic(cand.Target.Method) ||
  244. (cand.Target.Method.IsDefined(typeof(ExtensionAttribute), false));
  245. }
  246. /// <summary>
  247. /// Gets the name of the MethodBinder as provided at construction time.
  248. ///
  249. /// The name may differ from the name of the underlying method bases if the
  250. /// language provides some mapping from .NET method names to language specific
  251. /// method names. It is flowed through the MethodBinder primarily for error
  252. /// reporting purposes.
  253. /// </summary>
  254. public string Name {
  255. get {
  256. return _name;
  257. }
  258. }
  259. [Confined]
  260. public override string ToString() {
  261. string res = "";
  262. foreach (TargetSet ts in _targetSets.Values) {
  263. res += ts + Environment.NewLine;
  264. }
  265. return res;
  266. }
  267. #endregion
  268. #region TargetSet construction
  269. private TargetSet GetTargetSet(int nargs) {
  270. TargetSet ts;
  271. // see if we've precomputed the TargetSet...
  272. if (_targetSets.TryGetValue(nargs, out ts)) {
  273. return ts;
  274. } else if (_paramsCandidates != null) {
  275. // build a new target set specific to the number of
  276. // arguments we have
  277. ts = BuildTargetSet(nargs);
  278. if (ts._targets.Count > 0) {
  279. return ts;
  280. }
  281. }
  282. return null;
  283. }
  284. private TargetSet BuildTargetSet(int count) {
  285. TargetSet ts = new TargetSet(this, count);
  286. if (_paramsCandidates != null) {
  287. foreach (MethodCandidate maker in _paramsCandidates) {
  288. MethodCandidate target = maker.MakeParamsExtended(_binder, count, _kwArgs);
  289. if (target != null) ts.Add(target);
  290. }
  291. }
  292. return ts;
  293. }
  294. private void AddTarget(MethodCandidate target) {
  295. int count = target.Target.ParameterCount;
  296. TargetSet set;
  297. if (!_targetSets.TryGetValue(count, out set)) {
  298. set = new TargetSet(this, count);
  299. _targetSets[count] = set;
  300. }
  301. set.Add(target);
  302. }
  303. private void AddSimpleTarget(MethodCandidate target) {
  304. AddTarget(target);
  305. if (BinderHelpers.IsParamsMethod(target.Target.Method)) {
  306. if (_paramsCandidates == null) _paramsCandidates = new List<MethodCandidate>();
  307. _paramsCandidates.Add(target);
  308. }
  309. }
  310. private static ArgBuilder MakeInstanceBuilder(ActionBinder binder, MethodBase method, List<ParameterWrapper> parameters, ref int argIndex) {
  311. if (!CompilerHelpers.IsStatic(method)) {
  312. parameters.Add(new ParameterWrapper(binder, method.DeclaringType, null, true));
  313. return new SimpleArgBuilder(method.DeclaringType, argIndex++, false, false);
  314. } else {
  315. return new NullArgBuilder();
  316. }
  317. }
  318. private void AddBasicMethodTargets(ActionBinder binder, MethodBase method) {
  319. Assert.NotNull(binder, method);
  320. var parameterInfos = method.GetParameters();
  321. var parameters = new List<ParameterWrapper>();
  322. var arguments = new List<ArgBuilder>(parameterInfos.Length);
  323. var defaultArguments = new List<ArgBuilder>();
  324. int argIndex = 0;
  325. var instanceBuilder = MakeInstanceBuilder(binder, method, parameters, ref argIndex);
  326. bool hasByRefOrOut = false;
  327. bool hasDefaults = false;
  328. var infoIndex = binder.PrepareParametersBinding(parameterInfos, arguments, parameters, ref argIndex);
  329. for (; infoIndex < parameterInfos.Length; infoIndex++) {
  330. var pi = parameterInfos[infoIndex];
  331. if (binder.BindSpecialParameter(pi, arguments, parameters, ref argIndex)) {
  332. continue;
  333. }
  334. int indexForArgBuilder, kwIndex = GetKeywordIndex(pi);
  335. if (kwIndex == ParameterNotPassedByKeyword) {
  336. // positional argument, we simply consume the next argument
  337. indexForArgBuilder = argIndex++;
  338. } else {
  339. // keyword argument, we just tell the simple arg builder to consume arg 0.
  340. // KeywordArgBuilder will then pass in the correct single argument based
  341. // upon the actual argument number provided by the user.
  342. indexForArgBuilder = 0;
  343. }
  344. // if the parameter is default we need to build a default arg builder and then
  345. // build a reduced method at the end.
  346. if (!CompilerHelpers.IsMandatoryParameter(pi)) {
  347. // We need to build the default builder even if we have a parameter for it already to
  348. // get good consistency of our error messages. But consider a method like
  349. // def foo(a=1, b=2) and the user calls it as foo(b=3). Then adding the default
  350. // value breaks an otherwise valid call. This is because we only generate MethodCandidates
  351. // filling in the defaults from right to left (so the method - 1 arg requires a,
  352. // and the method minus 2 args requires b). So we only add the default if it's
  353. // a positional arg or we don't already have a default value.
  354. if (kwIndex == -1 || !hasDefaults) {
  355. defaultArguments.Add(new DefaultArgBuilder(pi));
  356. hasDefaults = true;
  357. } else {
  358. defaultArguments.Add(null);
  359. }
  360. } else if (defaultArguments.Count > 0) {
  361. // non-contigious default parameter
  362. defaultArguments.Add(null);
  363. }
  364. ArgBuilder ab;
  365. if (pi.ParameterType.IsByRef) {
  366. hasByRefOrOut = true;
  367. Type refType = typeof(StrongBox<>).MakeGenericType(pi.ParameterType.GetElementType());
  368. var param = new ParameterWrapper(_binder, pi, refType, pi.Name, true, false, false);
  369. parameters.Add(param);
  370. ab = new ReferenceArgBuilder(pi, refType, indexForArgBuilder);
  371. } else {
  372. hasByRefOrOut |= CompilerHelpers.IsOutParameter(pi);
  373. var param = new ParameterWrapper(_binder, pi);
  374. parameters.Add(param);
  375. ab = new SimpleArgBuilder(pi, indexForArgBuilder);
  376. }
  377. if (kwIndex == ParameterNotPassedByKeyword) {
  378. arguments.Add(ab);
  379. } else {
  380. Debug.Assert(KeywordArgBuilder.BuilderExpectsSingleParameter(ab));
  381. arguments.Add(new KeywordArgBuilder(ab, _kwArgs.Length, kwIndex));
  382. }
  383. }
  384. ReturnBuilder returnBuilder = MakeKeywordReturnBuilder(
  385. new ReturnBuilder(CompilerHelpers.GetReturnType(method)),
  386. parameterInfos,
  387. parameters,
  388. _binder.AllowKeywordArgumentSetting(method));
  389. if (hasDefaults) {
  390. for (int defaultsUsed = 1; defaultsUsed < defaultArguments.Count + 1; defaultsUsed++) {
  391. // if the left most default we'll use is not present then don't add a default. This happens in cases such as:
  392. // a(a=1, b=2, c=3) and then call with a(a=5, c=3). We'll come through once for c (no default, skip),
  393. // once for b (default present, emit) and then a (no default, skip again). W/o skipping we'd generate the same
  394. // method multiple times. This also happens w/ non-contigious default values, e.g. foo(a, b=3, c) where we don't want
  395. // to generate a default candidate for just c which matches the normal method.
  396. if (defaultArguments[defaultArguments.Count - defaultsUsed] != null) {
  397. AddSimpleTarget(MakeDefaultCandidate(
  398. method,
  399. parameters,
  400. instanceBuilder,
  401. arguments,
  402. defaultArguments,
  403. returnBuilder,
  404. defaultsUsed));
  405. }
  406. }
  407. }
  408. if (hasByRefOrOut) {
  409. AddSimpleTarget(MakeByRefReducedMethodTarget(binder, parameterInfos, method));
  410. }
  411. AddSimpleTarget(MakeMethodCandidate(method, parameters, instanceBuilder, arguments, returnBuilder));
  412. }
  413. private MethodCandidate MakeDefaultCandidate(MethodBase method, List<ParameterWrapper> parameters, ArgBuilder instanceBuilder, List<ArgBuilder> argBuilders, List<ArgBuilder> defaultBuilders, ReturnBuilder returnBuilder, int defaultsUsed) {
  414. List<ArgBuilder> defaultArgBuilders = new List<ArgBuilder>(argBuilders);
  415. List<ParameterWrapper> necessaryParams = parameters.GetRange(0, parameters.Count - defaultsUsed);
  416. for (int curDefault = 0; curDefault < defaultsUsed; curDefault++) {
  417. int readIndex = defaultBuilders.Count - defaultsUsed + curDefault;
  418. int writeIndex = defaultArgBuilders.Count - defaultsUsed + curDefault;
  419. if (defaultBuilders[readIndex] != null) {
  420. defaultArgBuilders[writeIndex] = defaultBuilders[readIndex];
  421. } else {
  422. necessaryParams.Add(parameters[parameters.Count - defaultsUsed + curDefault]);
  423. }
  424. }
  425. // shift any arguments forward that need to be...
  426. int curArg = CompilerHelpers.IsStatic(method) ? 0 : 1;
  427. for (int i = 0; i < defaultArgBuilders.Count; i++) {
  428. SimpleArgBuilder sab = defaultArgBuilders[i] as SimpleArgBuilder;
  429. if (sab != null) {
  430. defaultArgBuilders[i] = sab.MakeCopy(curArg++);
  431. }
  432. }
  433. return MakeMethodCandidate(method, necessaryParams, instanceBuilder, defaultArgBuilders, returnBuilder);
  434. }
  435. private MethodCandidate MakeByRefReducedMethodTarget(ActionBinder binder, ParameterInfo[] parameterInfos, MethodBase method) {
  436. Assert.NotNull(binder, parameterInfos, method);
  437. var parameters = new List<ParameterWrapper>();
  438. var arguments = new List<ArgBuilder>();
  439. int argIndex = 0;
  440. var instanceBuilder = MakeInstanceBuilder(binder, method, parameters, ref argIndex);
  441. List<int> returnArgs = new List<int>();
  442. if (CompilerHelpers.GetReturnType(method) != typeof(void)) {
  443. returnArgs.Add(-1);
  444. }
  445. var infoIndex = binder.PrepareParametersBinding(parameterInfos, arguments, parameters, ref argIndex);
  446. for (; infoIndex < parameterInfos.Length; infoIndex++) {
  447. var pi = parameterInfos[infoIndex];
  448. if (binder.BindSpecialParameter(pi, arguments, parameters, ref argIndex)) {
  449. continue;
  450. }
  451. // See KeywordArgBuilder.BuilderExpectsSingleParameter
  452. int indexForArgBuilder = 0;
  453. int kwIndex = ParameterNotPassedByKeyword;
  454. if (!CompilerHelpers.IsOutParameter(pi)) {
  455. kwIndex = GetKeywordIndex(pi);
  456. if (kwIndex == ParameterNotPassedByKeyword) {
  457. indexForArgBuilder = argIndex++;
  458. }
  459. }
  460. ArgBuilder ab;
  461. if (CompilerHelpers.IsOutParameter(pi)) {
  462. returnArgs.Add(arguments.Count);
  463. ab = new OutArgBuilder(pi);
  464. } else if (pi.ParameterType.IsByRef) {
  465. // if the parameter is marked as [In] it is not returned.
  466. if ((pi.Attributes & (ParameterAttributes.In | ParameterAttributes.Out)) != ParameterAttributes.In) {
  467. returnArgs.Add(arguments.Count);
  468. }
  469. ParameterWrapper param = new ParameterWrapper(_binder, pi, pi.ParameterType.GetElementType(), pi.Name, false, false, false);
  470. parameters.Add(param);
  471. ab = new ReturnReferenceArgBuilder(pi, indexForArgBuilder);
  472. } else {
  473. ParameterWrapper param = new ParameterWrapper(_binder, pi);
  474. parameters.Add(param);
  475. ab = new SimpleArgBuilder(pi, indexForArgBuilder);
  476. }
  477. if (kwIndex == ParameterNotPassedByKeyword) {
  478. arguments.Add(ab);
  479. } else {
  480. Debug.Assert(KeywordArgBuilder.BuilderExpectsSingleParameter(ab));
  481. arguments.Add(new KeywordArgBuilder(ab, _kwArgs.Length, kwIndex));
  482. }
  483. }
  484. ReturnBuilder returnBuilder = MakeKeywordReturnBuilder(
  485. new ByRefReturnBuilder(returnArgs),
  486. parameterInfos,
  487. parameters,
  488. _binder.AllowKeywordArgumentSetting(method));
  489. return MakeMethodCandidate(method, parameters, instanceBuilder, arguments, returnBuilder);
  490. }
  491. private MethodCandidate MakeMethodCandidate(MethodBase method, List<ParameterWrapper> parameters, ArgBuilder instanceBuilder, List<ArgBuilder> argBuilders, ReturnBuilder returnBuilder) {
  492. return new MethodCandidate(
  493. new MethodTarget(this, method, parameters.Count, instanceBuilder, argBuilders, returnBuilder),
  494. parameters);
  495. }
  496. private void GetMinAndMaxArgs(out int minArgs, out int maxArgs) {
  497. List<int> argCounts = new List<int>(_targetSets.Keys);
  498. argCounts.Sort();
  499. minArgs = argCounts[0];
  500. maxArgs = argCounts[argCounts.Count - 1];
  501. }
  502. private static bool IsUnsupported(MethodBase method) {
  503. return (method.CallingConvention & CallingConventions.VarArgs) != 0 || method.ContainsGenericParameters;
  504. }
  505. #endregion
  506. #region Keyword arg binding support
  507. private ReturnBuilder MakeKeywordReturnBuilder(ReturnBuilder returnBuilder, ParameterInfo[] methodParams, List<ParameterWrapper> parameters, bool isConstructor) {
  508. if (isConstructor) {
  509. List<string> unusedNames = GetUnusedKeywordParameters(methodParams);
  510. List<MemberInfo> bindableMembers = GetBindableMembers(returnBuilder, unusedNames);
  511. List<int> kwArgIndexs = new List<int>();
  512. if (unusedNames.Count == bindableMembers.Count) {
  513. foreach (MemberInfo mi in bindableMembers) {
  514. ParameterWrapper pw = new ParameterWrapper(
  515. _binder,
  516. mi.MemberType == MemberTypes.Property ?
  517. ((PropertyInfo)mi).PropertyType :
  518. ((FieldInfo)mi).FieldType,
  519. mi.Name,
  520. false);
  521. parameters.Add(pw);
  522. kwArgIndexs.Add(GetKeywordIndex(mi.Name));
  523. }
  524. KeywordConstructorReturnBuilder kwBuilder = new KeywordConstructorReturnBuilder(returnBuilder,
  525. _kwArgs.Length,
  526. kwArgIndexs.ToArray(),
  527. bindableMembers.ToArray(),
  528. _binder.PrivateBinding);
  529. return kwBuilder;
  530. }
  531. }
  532. return returnBuilder;
  533. }
  534. private static List<MemberInfo> GetBindableMembers(ReturnBuilder returnBuilder, List<string> unusedNames) {
  535. List<MemberInfo> bindableMembers = new List<MemberInfo>();
  536. foreach (string name in unusedNames) {
  537. Type curType = returnBuilder.ReturnType;
  538. MemberInfo[] mis = curType.GetMember(name);
  539. while (mis.Length != 1 && curType != null) {
  540. // see if we have a single member defined as the closest level
  541. mis = curType.GetMember(name, BindingFlags.DeclaredOnly | BindingFlags.Public | BindingFlags.SetField | BindingFlags.SetProperty | BindingFlags.Instance);
  542. if (mis.Length > 1) {
  543. break;
  544. }
  545. curType = curType.BaseType;
  546. }
  547. if (mis.Length == 1) {
  548. switch (mis[0].MemberType) {
  549. case MemberTypes.Property:
  550. case MemberTypes.Field:
  551. bindableMembers.Add(mis[0]);
  552. break;
  553. }
  554. }
  555. }
  556. return bindableMembers;
  557. }
  558. private List<string> GetUnusedKeywordParameters(ParameterInfo[] methodParams) {
  559. List<string> unusedNames = new List<string>();
  560. foreach (string name in _kwArgs) {
  561. bool found = false;
  562. foreach (ParameterInfo pi in methodParams) {
  563. if (pi.Name == name) {
  564. found = true;
  565. break;
  566. }
  567. }
  568. if (!found) {
  569. unusedNames.Add(name);
  570. }
  571. }
  572. return unusedNames;
  573. }
  574. private const int ParameterNotPassedByKeyword = -1;
  575. // Check if the given parameter from the candidate's signature matches a keyword argument from the callsite.
  576. // Return ParameterNotPassedByKeyword if no match. Else returns index into callsite's keyword arglist if there is a match.
  577. private int GetKeywordIndex(ParameterInfo pi) {
  578. return GetKeywordIndex(pi.Name);
  579. }
  580. private int GetKeywordIndex(string kwName) {
  581. for (int i = 0; i < _kwArgs.Length; i++) {
  582. if (kwName == _kwArgs[i]) {
  583. return i;
  584. }
  585. }
  586. return ParameterNotPassedByKeyword;
  587. }
  588. #endregion
  589. #region TargetSet
  590. /// <summary>
  591. /// Represents a collection of MethodCandidate's which all accept the
  592. /// same number of logical parameters. For example a params method
  593. /// and a method with 3 parameters would both be a TargetSet for 3 parameters.
  594. /// </summary>
  595. internal class TargetSet {
  596. private MethodBinder _binder;
  597. private int _count;
  598. internal List<MethodCandidate> _targets;
  599. internal TargetSet(MethodBinder binder, int count) {
  600. _count = count;
  601. _targets = new List<MethodCandidate>();
  602. _binder = binder;
  603. }
  604. internal bool IsParamsDictionaryOnly() {
  605. foreach (MethodCandidate target in _targets) {
  606. if (!target.HasParamsDictionary()) {
  607. return false;
  608. }
  609. }
  610. return true;
  611. }
  612. // OBSOLETE
  613. internal BindingTarget MakeBindingTarget(CallTypes callType, Type[] types, string[] names, NarrowingLevel minLevel, NarrowingLevel maxLevel) {
  614. List<ConversionResult> lastFail = new List<ConversionResult>();
  615. List<CallFailure> failures = null;
  616. // go through all available narrowing levels selecting candidates.
  617. for (NarrowingLevel level = minLevel; level <= maxLevel; level++) {
  618. List<MethodCandidate> applicableTargets = new List<MethodCandidate>();
  619. if (failures != null) {
  620. failures.Clear();
  621. }
  622. foreach (MethodCandidate target in _targets) {
  623. // skip params dictionaries - we want to only pick up the methods normalized
  624. // to have argument names (which we created because the MethodBinder gets
  625. // created w/ keyword arguments).
  626. if (!target.HasParamsDictionary()) {
  627. Type[] normalizedTypes;
  628. CallFailure callFailure;
  629. if (!target.TryGetNormalizedArguments(types, names, out normalizedTypes, out callFailure)) {
  630. // dup keyword arguments or unassigned keyword argument
  631. if (failures == null) failures = new List<CallFailure>(1);
  632. failures.Add(callFailure);
  633. } else if (target.IsApplicable(normalizedTypes, level, lastFail)) {
  634. // success, remember the candidate...
  635. applicableTargets.Add(GetCandidate(target, level));
  636. } else {
  637. // conversion failure, remember the failures...
  638. if (failures == null) failures = new List<CallFailure>(1);
  639. failures.Add(new CallFailure(target.Target, lastFail.ToArray()));
  640. lastFail.Clear();
  641. }
  642. }
  643. }
  644. // see if we managed to get a single method or if one method is better...
  645. List<MethodCandidate> result;
  646. if (TryGetApplicableTarget(callType, applicableTargets, types, out result)) {
  647. if (result.Count == 1) {
  648. // only a single method is callable, success!
  649. return MakeSuccessfulBindingTarget(callType, types, result);
  650. }
  651. // more than one method found, no clear best method, report an ambigious match
  652. return MakeAmbiguousBindingTarget(callType, types, result);
  653. }
  654. }
  655. Debug.Assert(failures != null);
  656. return new BindingTarget(_binder.Name, callType == CallTypes.None ? types.Length : types.Length - 1, failures.ToArray());
  657. }
  658. internal BindingTarget MakeBindingTarget(CallTypes callType, DynamicMetaObject[] metaObjects, string[] names, NarrowingLevel minLevel, NarrowingLevel maxLevel) {
  659. List<ConversionResult> lastFail = new List<ConversionResult>();
  660. List<CallFailure> failures = null;
  661. // go through all available narrowing levels selecting candidates.
  662. for (NarrowingLevel level = minLevel; level <= maxLevel; level++) {
  663. List<MethodCandidate> applicableTargets = new List<MethodCandidate>();
  664. if (failures != null) {
  665. failures.Clear();
  666. }
  667. foreach (MethodCandidate target in _targets) {
  668. // skip params dictionaries - we want to only pick up the methods normalized
  669. // to have argument names (which we created because the MethodBinder gets
  670. // created w/ keyword arguments).
  671. if (!target.HasParamsDictionary()) {
  672. DynamicMetaObject[] normalizedObjects;
  673. CallFailure callFailure;
  674. if (!target.TryGetNormalizedArguments(metaObjects, names, out normalizedObjects, out callFailure)) {
  675. // dup keyword arguments or unassigned keyword argument
  676. if (failures == null) failures = new List<CallFailure>(1);
  677. failures.Add(callFailure);
  678. } else if (target.IsApplicable(normalizedObjects, level, lastFail)) {
  679. // success, remember the candidate...
  680. applicableTargets.Add(GetCandidate(target, level));
  681. } else {
  682. // conversion failure, remember the failures...
  683. if (failures == null) failures = new List<CallFailure>(1);
  684. failures.Add(new CallFailure(target.Target, lastFail.ToArray()));
  685. lastFail.Clear();
  686. }
  687. }
  688. }
  689. // see if we managed to get a single method or if one method is better...
  690. List<MethodCandidate> result;
  691. if (TryGetApplicableTarget(callType, applicableTargets, metaObjects, out result)) {
  692. if (result.Count == 1) {
  693. // only a single method is callable, success!
  694. return MakeSuccessfulBindingTarget(callType, metaObjects, result);
  695. }
  696. // more than one method found, no clear best method, report an ambigious match
  697. return MakeAmbiguousBindingTarget(callType, metaObjects, result);
  698. }
  699. }
  700. Debug.Assert(failures != null);
  701. return new BindingTarget(_binder.Name, callType == CallTypes.None ? metaObjects.Length : metaObjects.Length - 1, failures.ToArray());
  702. }
  703. private static bool TryGetApplicableTarget(CallTypes callType, List<MethodCandidate> applicableTargets, Type[] actualTypes, out List<MethodCandidate> result) {
  704. result = null;
  705. if (applicableTargets.Count == 1) {
  706. result = applicableTargets;
  707. return true;
  708. }
  709. if (applicableTargets.Count > 1) {
  710. MethodCandidate target = FindBest(callType, applicableTargets, actualTypes);
  711. if (target != null) {
  712. result = new List<MethodCandidate>(new MethodCandidate[] { target });
  713. return true;
  714. } else {
  715. result = applicableTargets;
  716. return true;
  717. }
  718. }
  719. return false;
  720. }
  721. private static bool TryGetApplicableTarget(CallTypes callType, List<MethodCandidate> applicableTargets, DynamicMetaObject[] actualTypes, out List<MethodCandidate> result) {
  722. result = null;
  723. if (applicableTargets.Count == 1) {
  724. result = applicableTargets;
  725. return true;
  726. }
  727. if (applicableTargets.Count > 1) {
  728. MethodCandidate target = FindBest(callType, applicableTargets, actualTypes);
  729. if (target != null) {
  730. result = new List<MethodCandidate>(new MethodCandidate[] { target });
  731. return true;
  732. } else {
  733. result = applicableTargets;
  734. return true;
  735. }
  736. }
  737. return false;
  738. }
  739. // OBSOLETE
  740. private Type[] GetTypesForTest(Type[] types, IList<MethodCandidate> candidates) {
  741. // if we have a single target we need no tests.
  742. if (_targets.Count == 1) return null;
  743. Type[] tests = new Type[types.Length];
  744. for (int i = 0; i < types.Length; i++) {
  745. if (AreArgumentTypesOverloaded(i, types.Length, candidates)) {
  746. tests[i] = types[i];
  747. }
  748. }
  749. return tests;
  750. }
  751. private RestrictionInfo GetRestrictedMetaObjects(MethodCandidate target, DynamicMetaObject[] objects, IList<MethodCandidate> candidates) {
  752. IList<ParameterWrapper> parameters = target.Parameters;
  753. Debug.Assert(parameters.Count == objects.Length);
  754. DynamicMetaObject[] resObjects = new DynamicMetaObject[objects.Length];
  755. Type[] types = new Type[objects.Length];
  756. for (int i = 0; i < objects.Length; i++) {
  757. if (_targets.Count > 0 && AreArgumentTypesOverloaded(i, objects.Length, candidates)) {
  758. resObjects[i] = RestrictOne(objects[i], parameters[i]);
  759. types[i] = objects[i].GetLimitType();
  760. } else if (parameters[i].Type.IsAssignableFrom(objects[i].Expression.Type)) {
  761. // we have a strong enough type already
  762. resObjects[i] = objects[i];
  763. } else {
  764. resObjects[i] = RestrictOne(objects[i], parameters[i]);
  765. types[i] = objects[i].GetLimitType();
  766. }
  767. }
  768. return new RestrictionInfo(resObjects, types);
  769. }
  770. private DynamicMetaObject RestrictOne(DynamicMetaObject obj, ParameterWrapper forParam) {
  771. if (forParam.Type == typeof(object)) {
  772. // don't use Restrict as it'll box & unbox.
  773. return new DynamicMetaObject(obj.Expression, BindingRestrictionsHelpers.GetRuntimeTypeRestriction(obj.Expression, obj.GetLimitType()));
  774. } else {
  775. return obj.Restrict(obj.GetLimitType());
  776. }
  777. }
  778. private static bool AreArgumentTypesOverloaded(int argIndex, int argCount, IList<MethodCandidate> methods) {
  779. Type argType = null;
  780. for (int i = 0; i < methods.Count; i++) {
  781. IList<ParameterWrapper> pis = methods[i].Parameters;
  782. if (pis.Count == 0) continue;
  783. int readIndex = argIndex;
  784. if (pis[0].Type == typeof(CodeContext)) {
  785. readIndex++;
  786. }
  787. Type curType;
  788. if (readIndex < pis.Count) {
  789. if (readIndex == -1) {
  790. curType = methods[i].Target.Method.DeclaringType;
  791. } else if (pis[readIndex].IsParamsArray) {
  792. if (argIndex == argCount - (pis.Count - readIndex)) {
  793. // We're the params array argument and a single value is being passed
  794. // directly to it. The params array could be in the middle for
  795. // a params setter. so pis.Count - readIndex is usually 1 for the
  796. // params at the end, and therefore types.Length - 1 is usually if we're
  797. // the last argument. We always have to check this type to disambiguate
  798. // between passing an object which is compatible with the arg array and
  799. // passing an object which goes into the arg array. Maybe we could do
  800. // better sometimes.
  801. return true;
  802. }
  803. curType = pis[pis.Count - 1].Type.GetElementType();
  804. } else {
  805. curType = pis[readIndex].Type;
  806. }
  807. } else if (pis[pis.Count - 1].IsParamsArray) {
  808. curType = pis[pis.Count - 1].Type.GetElementType();
  809. } else {
  810. continue;
  811. }
  812. if (argType == null) {
  813. argType = curType;
  814. } else if (argType != curType) {
  815. return true;
  816. }
  817. }
  818. return false;
  819. }
  820. private static bool IsBest(MethodCandidate candidate, List<MethodCandidate> applicableTargets, CallTypes callType, Type[] actualTypes) {
  821. foreach (MethodCandidate target in applicableTargets) {
  822. if (candidate == target) {
  823. continue;
  824. }
  825. if (MethodCandidate.GetPreferredCandidate(candidate, target, callType, actualTypes) != Candidate.One) {
  826. return false;
  827. }
  828. }
  829. return true;
  830. }
  831. private static MethodCandidate FindBest(CallTypes callType, List<MethodCandidate> applicableTargets, Type[] actualTypes) {
  832. foreach (MethodCandidate candidate in applicableTargets) {
  833. if (IsBest(candidate, applicableTargets, callType, actualTypes)) return candidate;
  834. }
  835. return null;
  836. }
  837. private static bool IsBest(MethodCandidate candidate, List<MethodCandidate> applicableTargets, CallTypes callType, DynamicMetaObject[] actualTypes) {
  838. foreach (MethodCandidate target in applicableTargets) {
  839. if (candidate == target) {
  840. continue;
  841. }
  842. if (MethodCandidate.GetPreferredCandidate(candidate, target, callType, actualTypes) != Candidate.One) {
  843. return false;
  844. }
  845. }
  846. return true;
  847. }
  848. private static MethodCandidate FindBest(CallTypes callType, List<MethodCandidate> applicableTargets, DynamicMetaObject[] actualTypes) {
  849. foreach (MethodCandidate candidate in applicableTargets) {
  850. if (IsBest(candidate, applicableTargets, callType, actualTypes)) return candidate;
  851. }
  852. return null;
  853. }
  854. internal void Add(MethodCandidate target) {
  855. Debug.Assert(target.Parameters.Count == _count);
  856. _targets.Add(target);
  857. }
  858. private static MethodCandidate GetCandidate(MethodCandidate target, NarrowingLevel level) {
  859. if (level == NarrowingLevel.None) return target;
  860. return new MethodCandidate(target, level);
  861. }
  862. // OBSOLETE
  863. private BindingTarget MakeSuccessfulBindingTarget(CallTypes callType, Type[] types, List<MethodCandidate> result) {
  864. MethodCandidate resTarget = result[0];
  865. return new BindingTarget(_binder.Name, callType == CallTypes.None ? types.Length : types.Length - 1, resTarget.Target, resTarget.NarrowingLevel, GetTypesForTest(types, _targets));
  866. }
  867. private BindingTarget MakeSuccessfulBindingTarget(CallTypes callType, DynamicMetaObject[] objects, List<MethodCandidate> result) {
  868. MethodCandidate resTarget = result[0];
  869. return new BindingTarget(_binder.Name, callType == CallTypes.None ? objects.Length : objects.Length - 1, resTarget.Target, resTarget.NarrowingLevel, GetRestrictedMetaObjects(resTarget, objects, _targets));
  870. }
  871. private BindingTarget MakeAmbiguousBindingTarget<T>(CallTypes callType, T[] types, List<MethodCandidate> result) {
  872. MethodTarget[] methods = new MethodTarget[result.Count];
  873. for (int i = 0; i < result.Count; i++) {
  874. methods[i] = result[i].Target;
  875. }
  876. return new BindingTarget(_binder.Name, callType == CallTypes.None ? types.Length : types.Length - 1, methods);
  877. }
  878. [Confined]
  879. public override string ToString() {
  880. return string.Format("TargetSet({0} on {1}, nargs={2})", _targets[0].Target.Method.Name, _targets[0].Target.Method.DeclaringType.FullName, _count);
  881. }
  882. }
  883. #endregion
  884. }
  885. }