PageRenderTime 60ms CodeModel.GetById 29ms RepoModel.GetById 1ms app.codeStats 0ms

/Microsoft.Scripting/Actions/CallBinderHelper.cs

https://bitbucket.org/stefanrusek/xronos
C# | 566 lines | 419 code | 87 blank | 60 comment | 75 complexity | 2050467638bb68705605c37df5e51194 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;
  21. using System.Collections.Generic;
  22. using System.Diagnostics;
  23. #if CODEPLEX_40
  24. using System.Linq.Expressions;
  25. #else
  26. using Microsoft.Linq.Expressions;
  27. #endif
  28. using System.Reflection;
  29. using Microsoft.Scripting.Actions.Calls;
  30. using Microsoft.Scripting.Generation;
  31. using Microsoft.Scripting.Runtime;
  32. using Microsoft.Scripting.Utils;
  33. using AstUtils = Microsoft.Scripting.Ast.Utils;
  34. namespace Microsoft.Scripting.Actions {
  35. #if CODEPLEX_40
  36. using Ast = System.Linq.Expressions.Expression;
  37. #else
  38. using Ast = Microsoft.Linq.Expressions.Expression;
  39. #endif
  40. /// <summary>
  41. /// Creates rules for performing method calls. Currently supports calling built-in functions, built-in method descriptors (w/o
  42. /// a bound value) and bound built-in method descriptors (w/ a bound value), delegates, types defining a "Call" method marked
  43. /// with SpecialName.
  44. /// </summary>
  45. /// <typeparam name="TAction">The specific type of CallAction</typeparam>
  46. public class CallBinderHelper<TAction> : BinderHelper<TAction>
  47. where TAction : OldCallAction {
  48. private object[] _args; // the arguments the binder is binding to - args[0] is the target, args[1..n] are args to the target
  49. private Expression _instance; // the instance or null if this is a non-instance call
  50. private Type _instanceType; // the type of _instance, to override _instance.Type when doing private binding.
  51. private Expression _test; // the test expression, built up and assigned at the end
  52. private readonly RuleBuilder _rule; // the rule we end up producing
  53. private readonly bool _reversedOperator; // if we're producing a binary operator or a reversed operator (should go away, Python specific).
  54. private readonly MethodBase[] _targets;
  55. private readonly NarrowingLevel _maxLevel; // the maximum narrowing level allowed
  56. public CallBinderHelper(CodeContext context, TAction action, object[] args, RuleBuilder rule)
  57. : base(context, action) {
  58. ContractUtils.RequiresNotEmpty(args, "args");
  59. _maxLevel = NarrowingLevel.All;
  60. _args = RemoveExplicitInstanceArgument(action, args);
  61. _rule = rule;
  62. _test = _rule.MakeTypeTest(CompilerHelpers.GetType(Callable), 0);
  63. }
  64. public CallBinderHelper(CodeContext context, TAction action, object[] args, RuleBuilder rule, IList<MethodBase> targets)
  65. : this(context, action, args, rule) {
  66. _targets = ArrayUtils.ToArray(targets);
  67. _maxLevel = NarrowingLevel.All;
  68. }
  69. public CallBinderHelper(CodeContext context, TAction action, object[] args, RuleBuilder rule, IList<MethodBase> targets, NarrowingLevel maxLevel, bool isReversedOperator)
  70. : this(context, action, args, rule) {
  71. _targets = ArrayUtils.ToArray(targets);
  72. _reversedOperator = isReversedOperator;
  73. _maxLevel = maxLevel;
  74. }
  75. public virtual void MakeRule() {
  76. Type t = CompilerHelpers.GetType(Callable);
  77. MethodBase[] targets = GetTargetMethods();
  78. if (targets != null && targets.Length > 0) {
  79. // we're calling a well-known MethodBase
  80. MakeMethodBaseRule(targets);
  81. } else {
  82. // we can't call this object
  83. MakeCannotCallRule(t);
  84. }
  85. // if we produced an ActionOnCall rule we don't replace the test w/ our own.
  86. if (_rule.Test == null) {
  87. _rule.Test = _test;
  88. }
  89. }
  90. #region Method Call Rule
  91. private void MakeMethodBaseRule(MethodBase[] targets) {
  92. Type[] argTypes; // will not include implicit instance argument (if any)
  93. string[] argNames; // will include ArgumentKind.Dictionary keyword names
  94. GetArgumentNamesAndTypes(out argNames, out argTypes);
  95. Type[] bindingArgs = argTypes; // will include instance argument (if any)
  96. CallTypes callType = CallTypes.None;
  97. if (_instance != null) {
  98. bindingArgs = ArrayUtils.Insert(InstanceType, argTypes);
  99. callType = CallTypes.ImplicitInstance;
  100. }
  101. if (_reversedOperator && bindingArgs.Length >= 2) {
  102. // we swap the arguments before binding, and swap back before calling.
  103. ArrayUtils.SwapLastTwo(bindingArgs);
  104. if (argNames.Length >= 2) {
  105. ArrayUtils.SwapLastTwo(argNames);
  106. }
  107. }
  108. // attempt to bind to an individual method
  109. MethodBinder binder = MethodBinder.MakeBinder(Binder, GetTargetName(targets), targets, argNames, NarrowingLevel.None, _maxLevel);
  110. BindingTarget bt = binder.MakeBindingTarget(callType, bindingArgs);
  111. if (bt.Success) {
  112. // if we succeed make the target for the rule
  113. MethodBase target = bt.Method;
  114. MethodInfo targetMethod = target as MethodInfo;
  115. if (targetMethod != null) {
  116. target = CompilerHelpers.TryGetCallableMethod(targetMethod);
  117. }
  118. Expression[] exprargs = FinishTestForCandidate(bt.ArgumentTests, argTypes);
  119. _rule.Target = _rule.MakeReturn(
  120. Binder,
  121. bt.MakeExpression(_rule, exprargs));
  122. } else {
  123. // make an error rule
  124. MakeInvalidParametersRule(bt);
  125. }
  126. }
  127. private static object[] RemoveExplicitInstanceArgument(TAction action, object[] args) {
  128. //If an instance is explicitly passed in as an argument, ignore it.
  129. //Calls that need an instance will pick it up from the bound objects
  130. //passed in or the rule. CallType can differentiate between the type
  131. //of call during method binding.
  132. int instanceIndex = action.Signature.IndexOf(ArgumentType.Instance);
  133. if (instanceIndex > -1) {
  134. args = ArrayUtils.RemoveAt(args, instanceIndex + 1);
  135. }
  136. return args;
  137. }
  138. private static string GetTargetName(MethodBase[] targets) {
  139. return targets[0].IsConstructor ? targets[0].DeclaringType.Name : targets[0].Name;
  140. }
  141. protected Expression[] FinishTestForCandidate(IList<Type> testTypes, Type[] explicitArgTypes) {
  142. Expression[] exprArgs = MakeArgumentExpressions();
  143. Debug.Assert(exprArgs.Length == (explicitArgTypes.Length + ((_instance == null) ? 0 : 1)));
  144. Debug.Assert(testTypes == null || exprArgs.Length == testTypes.Count);
  145. MakeSplatTests();
  146. if (_reversedOperator) {
  147. ArrayUtils.SwapLastTwo(exprArgs);
  148. }
  149. if (explicitArgTypes.Length > 0 && testTypes != null) {
  150. // We've already tested the instance, no need to test it again. So remove it before adding
  151. // rules for the arguments
  152. Expression[] exprArgsWithoutInstance = exprArgs;
  153. List<Type> testTypesWithoutInstance = new List<Type>(testTypes);
  154. for (int i = 0; i < exprArgs.Length; i++) {
  155. if (exprArgs[i] == _instance) {
  156. // We found the instance, so remove it
  157. exprArgsWithoutInstance = ArrayUtils.RemoveAt(exprArgs, i);
  158. testTypesWithoutInstance.RemoveAt(i);
  159. break;
  160. }
  161. }
  162. _test = Ast.AndAlso(_test, MakeNecessaryTests(_rule, testTypesWithoutInstance.ToArray(), exprArgsWithoutInstance));
  163. }
  164. return exprArgs;
  165. }
  166. /// <summary>
  167. /// Gets expressions to access all the arguments. This includes the instance argument. Splat arguments are
  168. /// unpacked in the output. The resulting array is similar to Rule.Parameters (but also different in some ways)
  169. /// </summary>
  170. protected Expression[] MakeArgumentExpressions() {
  171. List<Expression> exprargs = new List<Expression>();
  172. if (_instance != null) {
  173. exprargs.Add(_instance);
  174. }
  175. for (int i = 0; i < Action.Signature.ArgumentCount; i++) { // ArgumentCount(Action, _rule)
  176. switch (Action.Signature.GetArgumentKind(i)) {
  177. case ArgumentType.Simple:
  178. case ArgumentType.Named:
  179. exprargs.Add(_rule.Parameters[i + 1]);
  180. break;
  181. case ArgumentType.List:
  182. IList<object> list = (IList<object>)_args[i + 1];
  183. for (int j = 0; j < list.Count; j++) {
  184. exprargs.Add(
  185. Ast.Call(
  186. Ast.Convert(
  187. _rule.Parameters[i + 1],
  188. typeof(IList<object>)
  189. ),
  190. typeof(IList<object>).GetMethod("get_Item"),
  191. AstUtils.Constant(j)
  192. )
  193. );
  194. }
  195. break;
  196. case ArgumentType.Dictionary:
  197. IDictionary dict = (IDictionary)_args[i + 1];
  198. IDictionaryEnumerator dictEnum = dict.GetEnumerator();
  199. while (dictEnum.MoveNext()) {
  200. DictionaryEntry de = dictEnum.Entry;
  201. string strKey = de.Key as string;
  202. if (strKey == null) continue;
  203. Expression dictExpr = _rule.Parameters[_rule.Parameters.Count - 1];
  204. exprargs.Add(
  205. Ast.Call(
  206. AstUtils.Convert(dictExpr, typeof(IDictionary)),
  207. typeof(IDictionary).GetMethod("get_Item"),
  208. AstUtils.Constant(strKey)
  209. )
  210. );
  211. }
  212. break;
  213. }
  214. }
  215. return exprargs.ToArray();
  216. }
  217. #endregion
  218. #region Target acquisition
  219. protected virtual MethodBase[] GetTargetMethods() {
  220. if (_targets != null) return _targets;
  221. object target = Callable;
  222. MethodBase[] targets;
  223. Delegate d;
  224. MemberGroup mg;
  225. MethodGroup mthgrp;
  226. BoundMemberTracker bmt;
  227. if ((d = target as Delegate) != null) {
  228. targets = GetDelegateTargets(d);
  229. } else if ((mg = target as MemberGroup) != null) {
  230. List<MethodInfo> foundTargets = new List<MethodInfo>();
  231. foreach (MemberTracker mt in mg) {
  232. if (mt.MemberType == TrackerTypes.Method) {
  233. foundTargets.Add(((MethodTracker)mt).Method);
  234. }
  235. }
  236. targets = foundTargets.ToArray();
  237. } else if ((mthgrp = target as MethodGroup) != null) {
  238. _test = Ast.AndAlso(_test, Ast.Equal(AstUtils.Convert(Rule.Parameters[0], typeof(object)), AstUtils.Constant(target)));
  239. List<MethodBase> foundTargets = new List<MethodBase>();
  240. foreach (MethodTracker mt in mthgrp.Methods) {
  241. foundTargets.Add(mt.Method);
  242. }
  243. targets = foundTargets.ToArray();
  244. } else if ((bmt = target as BoundMemberTracker) != null) {
  245. targets = GetBoundMemberTargets(bmt);
  246. } else {
  247. targets = GetOperatorTargets(target);
  248. }
  249. return targets;
  250. }
  251. private MethodBase[] GetBoundMemberTargets(BoundMemberTracker bmt) {
  252. Debug.Assert(bmt.Instance == null); // should be null for trackers that leak to user code
  253. MethodBase[] targets;
  254. _instance = AstUtils.Convert(
  255. Ast.Property(
  256. Ast.Convert(Rule.Parameters[0], typeof(BoundMemberTracker)),
  257. typeof(BoundMemberTracker).GetProperty("ObjectInstance")
  258. ),
  259. bmt.BoundTo.DeclaringType
  260. );
  261. _test = Ast.AndAlso(
  262. _test,
  263. Ast.Equal(
  264. Ast.Property(
  265. Ast.Convert(Rule.Parameters[0], typeof(BoundMemberTracker)),
  266. typeof(BoundMemberTracker).GetProperty("BoundTo")
  267. ),
  268. AstUtils.Constant(bmt.BoundTo)
  269. )
  270. );
  271. _test = Ast.AndAlso(
  272. _test,
  273. Rule.MakeTypeTest(
  274. CompilerHelpers.GetType(bmt.ObjectInstance),
  275. Ast.Property(
  276. Ast.Convert(Rule.Parameters[0], typeof(BoundMemberTracker)),
  277. typeof(BoundMemberTracker).GetProperty("ObjectInstance")
  278. )
  279. )
  280. );
  281. switch (bmt.BoundTo.MemberType) {
  282. case TrackerTypes.MethodGroup:
  283. targets = ((MethodGroup)bmt.BoundTo).GetMethodBases();
  284. break;
  285. case TrackerTypes.Method:
  286. targets = new MethodBase[] { ((MethodTracker)bmt.BoundTo).Method };
  287. break;
  288. default:
  289. throw new InvalidOperationException(); // nothing else binds yet
  290. }
  291. return targets;
  292. }
  293. private MethodBase[] GetDelegateTargets(Delegate d) {
  294. _instance = AstUtils.Convert(_rule.Parameters[0], d.GetType());
  295. return new MethodBase[] { d.GetType().GetMethod("Invoke") };
  296. }
  297. private MethodBase[] GetOperatorTargets(object target) {
  298. MethodBase[] targets = null;
  299. // see if the type defines a well known Call method
  300. Type targetType = CompilerHelpers.GetType(target);
  301. MemberGroup callMembers = Binder.GetMember(Action, targetType, "Call");
  302. List<MethodBase> callTargets = new List<MethodBase>();
  303. foreach (MemberTracker mi in callMembers) {
  304. if (mi.MemberType == TrackerTypes.Method) {
  305. MethodInfo method = ((MethodTracker)mi).Method;
  306. if (method.IsSpecialName) {
  307. callTargets.Add(method);
  308. }
  309. }
  310. }
  311. if (callTargets.Count > 0) {
  312. targets = callTargets.ToArray();
  313. _instance = Ast.Convert(_rule.Parameters[0], CompilerHelpers.GetType(Callable));
  314. }
  315. return targets;
  316. }
  317. #endregion
  318. #region Test support
  319. /// <summary>
  320. /// Makes test for param arrays and param dictionary parameters.
  321. /// </summary>
  322. protected void MakeSplatTests() {
  323. if (Action.Signature.HasListArgument()) {
  324. MakeParamsArrayTest();
  325. }
  326. if (Action.Signature.HasDictionaryArgument()) {
  327. MakeParamsDictionaryTest();
  328. }
  329. }
  330. private void MakeParamsArrayTest() {
  331. int listIndex = Action.Signature.IndexOf(ArgumentType.List);
  332. Debug.Assert(listIndex != -1);
  333. _test = Ast.AndAlso(_test, MakeParamsTest(_args[listIndex + 1], _rule.Parameters[listIndex + 1]));
  334. }
  335. private void MakeParamsDictionaryTest() {
  336. IDictionary dict = (IDictionary)_args[_args.Length - 1];
  337. IDictionaryEnumerator dictEnum = dict.GetEnumerator();
  338. // verify the dictionary has the same count and arguments.
  339. string[] names = new string[dict.Count];
  340. int index = 0;
  341. while (dictEnum.MoveNext()) {
  342. string name = dictEnum.Entry.Key as string;
  343. if (name == null) {
  344. throw new ArgumentTypeException(String.Format("expected string for dictionary argument got {0}", dictEnum.Entry.Key));
  345. }
  346. names[index++] = name;
  347. }
  348. _test = Ast.AndAlso(
  349. _test,
  350. Ast.AndAlso(
  351. Ast.TypeIs(_rule.Parameters[_rule.Parameters.Count - 1], typeof(IDictionary)),
  352. Ast.Call(
  353. typeof(ScriptingRuntimeHelpers).GetMethod("CheckDictionaryMembers"),
  354. Ast.Convert(_rule.Parameters[_rule.Parameters.Count - 1], typeof(IDictionary)),
  355. AstUtils.Constant(names)
  356. )
  357. )
  358. );
  359. }
  360. #endregion
  361. #region Error support
  362. protected virtual void MakeCannotCallRule(Type type) {
  363. _rule.Target =
  364. _rule.MakeError(
  365. Ast.New(
  366. typeof(ArgumentTypeException).GetConstructor(new Type[] { typeof(string) }),
  367. AstUtils.Constant(type.Name + " is not callable")
  368. )
  369. );
  370. }
  371. private void MakeInvalidParametersRule(BindingTarget bt) {
  372. MakeSplatTests();
  373. if (_args.Length > 1) {
  374. // we do an exact type check on all of the arguments types for a failed call.
  375. Expression[] argExpr = MakeArgumentExpressions();
  376. string[] names;
  377. Type[] vals;
  378. GetArgumentNamesAndTypes(out names, out vals);
  379. if (_instance != null) {
  380. // target type was added to test already
  381. argExpr = ArrayUtils.RemoveFirst(argExpr);
  382. }
  383. _test = Ast.AndAlso(_test, MakeNecessaryTests(_rule, vals, argExpr));
  384. }
  385. _rule.Target = Binder.MakeInvalidParametersError(bt).MakeErrorForRule(_rule, Binder);
  386. }
  387. #endregion
  388. #region Misc. Helpers
  389. /// <summary>
  390. /// Gets all of the argument names and types. The instance argument is not included
  391. /// </summary>
  392. /// <param name="argNames">The names correspond to the end of argTypes.
  393. /// ArgumentKind.Dictionary is unpacked in the return value.
  394. /// This is set to an array of size 0 if there are no keyword arguments</param>
  395. /// <param name="argTypes">Non named arguments are returned at the beginning.
  396. /// ArgumentKind.List is unpacked in the return value. </param>
  397. protected void GetArgumentNamesAndTypes(out string[] argNames, out Type[] argTypes) {
  398. // Get names of named arguments
  399. argNames = Action.Signature.GetArgumentNames();
  400. argTypes = GetArgumentTypes(Action, _args);
  401. if (Action.Signature.HasDictionaryArgument()) {
  402. // need to get names from dictionary argument...
  403. GetDictionaryNamesAndTypes(ref argNames, ref argTypes);
  404. }
  405. }
  406. private void GetDictionaryNamesAndTypes(ref string[] argNames, ref Type[] argTypes) {
  407. Debug.Assert(Action.Signature.GetArgumentKind(Action.Signature.ArgumentCount - 1) == ArgumentType.Dictionary);
  408. List<string> names = new List<string>(argNames);
  409. List<Type> types = new List<Type>(argTypes);
  410. IDictionary dict = (IDictionary)_args[_args.Length - 1];
  411. IDictionaryEnumerator dictEnum = dict.GetEnumerator();
  412. while (dictEnum.MoveNext()) {
  413. DictionaryEntry de = dictEnum.Entry;
  414. if (de.Key is string) {
  415. names.Add((string)de.Key);
  416. types.Add(CompilerHelpers.GetType(de.Value));
  417. }
  418. }
  419. argNames = names.ToArray();
  420. argTypes = types.ToArray();
  421. }
  422. [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1819:PropertiesShouldNotReturnArrays")] // TODO: fix
  423. protected object[] Arguments {
  424. get {
  425. return _args;
  426. }
  427. }
  428. protected object[] GetExplicitArguments() {
  429. return ArrayUtils.RemoveFirst(_args);
  430. }
  431. protected object Callable {
  432. get {
  433. return _args[0];
  434. }
  435. }
  436. public RuleBuilder Rule {
  437. get {
  438. return _rule;
  439. }
  440. }
  441. /// <summary>
  442. /// The instance for the target method, or null if this is a non-instance call.
  443. ///
  444. /// If it is set, it will typically be set to extract the instance from the Callable.
  445. /// </summary>
  446. public Expression Instance {
  447. get {
  448. return _instance;
  449. }
  450. set {
  451. Debug.Assert(!Action.Signature.HasInstanceArgument());
  452. _instance = value;
  453. }
  454. }
  455. public Type InstanceType {
  456. get {
  457. if (_instanceType != null) {
  458. return _instanceType;
  459. }
  460. if (_instance != null) {
  461. return _instance.Type;
  462. }
  463. return null;
  464. }
  465. set {
  466. _instanceType = value;
  467. }
  468. }
  469. protected Expression Test {
  470. get {
  471. return _test;
  472. }
  473. set {
  474. _test = value;
  475. }
  476. }
  477. #endregion
  478. }
  479. }