PageRenderTime 54ms CodeModel.GetById 25ms RepoModel.GetById 0ms app.codeStats 1ms

/Microsoft.Scripting/Actions/DoOperationBinderHelper.cs

https://bitbucket.org/stefanrusek/xronos
C# | 559 lines | 455 code | 63 blank | 41 comment | 110 complexity | 8ebacb2d9fdbab15bc55bddaa30d1fea 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. #if CODEPLEX_40
  23. using System.Linq.Expressions;
  24. #else
  25. using Microsoft.Linq.Expressions;
  26. #endif
  27. using System.Reflection;
  28. #if CODEPLEX_40
  29. using System.Dynamic;
  30. #else
  31. using Microsoft.Scripting;
  32. #endif
  33. using System.Text;
  34. using Microsoft.Scripting.Actions.Calls;
  35. using Microsoft.Scripting.Generation;
  36. using Microsoft.Scripting.Runtime;
  37. using Microsoft.Scripting.Utils;
  38. using AstUtils = Microsoft.Scripting.Ast.Utils;
  39. namespace Microsoft.Scripting.Actions {
  40. #if CODEPLEX_40
  41. using Ast = System.Linq.Expressions.Expression;
  42. #else
  43. using Ast = Microsoft.Linq.Expressions.Expression;
  44. #endif
  45. public class DoOperationBinderHelper : BinderHelper<OldDoOperationAction> {
  46. private object[] _args; // arguments we were created with
  47. private Type[] _types; // types of our arguments
  48. private readonly RuleBuilder _rule; // the rule we're building and returning
  49. public DoOperationBinderHelper(CodeContext context, OldDoOperationAction action, object[] args, RuleBuilder rule)
  50. : base(context, action) {
  51. _args = args;
  52. _types = CompilerHelpers.GetTypes(args);
  53. _rule = rule;
  54. _rule.MakeTest(_types);
  55. }
  56. public void MakeRule() {
  57. if (Action.Operation == Operators.GetItem ||
  58. Action.Operation == Operators.SetItem) {
  59. // try default member first, then look for special name methods.
  60. if (MakeDefaultMemberRule(Action.Operation)) {
  61. return;
  62. }
  63. }
  64. ExpressionType? et = OperatorInfo.OperatorToExpressionType(Action.Operation);
  65. if (et == null) {
  66. throw new InvalidOperationException(String.Format("unknown ExpressionType: {0}", Action.Operation));
  67. }
  68. OperatorInfo info = OperatorInfo.GetOperatorInfo(et.Value);
  69. if (Action.IsComparision) {
  70. MakeComparisonRule(info);
  71. } else {
  72. MakeOperatorRule(info);
  73. }
  74. }
  75. #region Comparison operator
  76. private void MakeComparisonRule(OperatorInfo info) {
  77. // check the first type if it has an applicable method
  78. MethodInfo[] targets = GetApplicableMembers(info);
  79. if (targets.Length > 0 && TryMakeBindingTarget(targets)) {
  80. return;
  81. }
  82. // then check the 2nd type.
  83. targets = GetApplicableMembers(_types[1], info);
  84. if (targets.Length > 0 && TryMakeBindingTarget(targets)) {
  85. return;
  86. }
  87. // try Compare: cmp(x,y) (>, <, >=, <=, ==, !=) 0
  88. if (TryNumericComparison(info)) {
  89. return;
  90. }
  91. // try inverting the operator & result (e.g. if looking for Equals try NotEquals, LessThan for GreaterThan)...
  92. Operators revOp = GetInvertedOperator(OperatorInfo.ExpressionTypeToOperator(info.Operator));
  93. OperatorInfo revInfo = OperatorInfo.GetOperatorInfo(OperatorInfo.OperatorToExpressionType(revOp).Value);
  94. Debug.Assert(revInfo != null);
  95. // try the 1st type's opposite function result negated
  96. targets = GetApplicableMembers(revInfo);
  97. if (targets.Length > 0 && TryMakeInvertedBindingTarget(targets)) {
  98. return;
  99. }
  100. // then check the 2nd type.
  101. targets = GetApplicableMembers(_types[1], revInfo);
  102. if (targets.Length > 0 && TryMakeInvertedBindingTarget(targets)) {
  103. return;
  104. }
  105. // see if we're comparing to null w/ an object ref or a Nullable<T>
  106. if (TryMakeNullComparisonRule()) {
  107. return;
  108. }
  109. // see if this is a primitive type where we're comparing the two values.
  110. if (TryPrimitiveCompare()) {
  111. return;
  112. }
  113. SetErrorTarget(info);
  114. }
  115. private bool TryNumericComparison(OperatorInfo info) {
  116. MethodInfo[] targets = FilterNonMethods(_types[0], Binder.GetMember(Action, _types[0], "Compare"));
  117. if (targets.Length > 0) {
  118. MethodBinder mb = MethodBinder.MakeBinder(Binder, targets[0].Name, targets);
  119. BindingTarget target = mb.MakeBindingTarget(CallTypes.None, _types);
  120. if (target.Success) {
  121. Expression call = Ast.Convert(target.MakeExpression(_rule, _rule.Parameters), typeof(int));
  122. switch (info.Operator) {
  123. case ExpressionType.GreaterThan: call = Ast.GreaterThan(call, AstUtils.Constant(0)); break;
  124. case ExpressionType.LessThan: call = Ast.LessThan(call, AstUtils.Constant(0)); break;
  125. case ExpressionType.GreaterThanOrEqual: call = Ast.GreaterThanOrEqual(call, AstUtils.Constant(0)); break;
  126. case ExpressionType.LessThanOrEqual: call = Ast.LessThanOrEqual(call, AstUtils.Constant(0)); break;
  127. case ExpressionType.Equal: call = Ast.Equal(call, AstUtils.Constant(0)); break;
  128. case ExpressionType.NotEqual: call = Ast.NotEqual(call, AstUtils.Constant(0)); break;
  129. }
  130. _rule.Target = _rule.MakeReturn(Binder, call);
  131. return true;
  132. }
  133. }
  134. return false;
  135. }
  136. private bool TryPrimitiveCompare() {
  137. if (TypeUtils.GetNonNullableType(_types[0]) == TypeUtils.GetNonNullableType(_types[1]) &&
  138. TypeUtils.IsNumeric(_types[0])) {
  139. // TODO: Nullable<PrimitveType> Support
  140. Expression expr;
  141. switch (Action.Operation) {
  142. case Operators.Equals: expr = Ast.Equal(Param0, Param1); break;
  143. case Operators.NotEquals: expr = Ast.NotEqual(Param0, Param1); break;
  144. case Operators.GreaterThan: expr = Ast.GreaterThan(Param0, Param1); break;
  145. case Operators.LessThan: expr = Ast.LessThan(Param0, Param1); break;
  146. case Operators.GreaterThanOrEqual: expr = Ast.GreaterThanOrEqual(Param0, Param1); break;
  147. case Operators.LessThanOrEqual: expr = Ast.LessThanOrEqual(Param0, Param1); break;
  148. default: throw new InvalidOperationException();
  149. }
  150. _rule.Target = _rule.MakeReturn(Binder, expr);
  151. return true;
  152. }
  153. return false;
  154. }
  155. /// <summary>
  156. /// Produces a rule for comparing a value to null - supports comparing object references and nullable types.
  157. /// </summary>
  158. private bool TryMakeNullComparisonRule() {
  159. if (_types[0] == typeof(DynamicNull)) {
  160. if (!_types[1].IsValueType) {
  161. _rule.Target = _rule.MakeReturn(Binder, Ast.Equal(_rule.Parameters[1], AstUtils.Constant(null)));
  162. } else if (_types[1].GetGenericTypeDefinition() == typeof(Nullable<>)) {
  163. _rule.Target = _rule.MakeReturn(Binder, Ast.Property(Param1, _types[1].GetProperty("HasValue")));
  164. } else {
  165. return false;
  166. }
  167. return true;
  168. } else if (_types[1] == typeof(DynamicNull)) {
  169. if (!_types[0].IsValueType) {
  170. _rule.Target = _rule.MakeReturn(Binder, Ast.Equal(_rule.Parameters[0], AstUtils.Constant(null)));
  171. } else if (_types[0].GetGenericTypeDefinition() == typeof(Nullable<>)) {
  172. _rule.Target = _rule.MakeReturn(Binder, Ast.Property(Param0, _types[1].GetProperty("HasValue")));
  173. } else {
  174. return false;
  175. }
  176. return true;
  177. }
  178. return false;
  179. }
  180. #endregion
  181. #region Operator Rule
  182. [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Maintainability", "CA1502:AvoidExcessiveComplexity")] // TODO: fix
  183. private void MakeOperatorRule(OperatorInfo info) {
  184. MethodInfo[] targets = GetApplicableMembers(info);
  185. if (targets.Length > 0 && TryMakeBindingTarget(targets)) {
  186. return;
  187. }
  188. if (_types.Length > 1) {
  189. targets = GetApplicableMembers(_types[1], info);
  190. if (targets.Length > 0 && TryMakeBindingTarget(targets)) {
  191. return;
  192. }
  193. }
  194. if (_types.Length == 2 &&
  195. TypeUtils.GetNonNullableType(_types[0]) == TypeUtils.GetNonNullableType(_types[1]) &&
  196. TypeUtils.IsArithmetic(_types[0])) {
  197. // TODO: Nullable<PrimitveType> Support
  198. Expression expr;
  199. switch (info.Operator) {
  200. case ExpressionType.Add: expr = Ast.Add(Param0, Param1); break;
  201. case ExpressionType.Subtract: expr = Ast.Subtract(Param0, Param1); break;
  202. case ExpressionType.Divide: expr = Ast.Divide(Param0, Param1); break;
  203. case ExpressionType.Modulo: expr = Ast.Modulo(Param0, Param1); break;
  204. case ExpressionType.Multiply: expr = Ast.Multiply(Param0, Param1); break;
  205. case ExpressionType.LeftShift: expr = Ast.LeftShift(Param0, Param1); break;
  206. case ExpressionType.RightShift: expr = Ast.RightShift(Param0, Param1); break;
  207. case ExpressionType.And: expr = Ast.And(Param0, Param1); break;
  208. case ExpressionType.Or: expr = Ast.Or(Param0, Param1); break;
  209. case ExpressionType.ExclusiveOr: expr = Ast.ExclusiveOr(Param0, Param1); break;
  210. default: throw new InvalidOperationException();
  211. }
  212. _rule.Target = _rule.MakeReturn(Binder, expr);
  213. return;
  214. } else if(_types.Length == 1 && TryMakeDefaultUnaryRule(info)) {
  215. return;
  216. }
  217. SetErrorTarget(info);
  218. }
  219. private bool TryMakeDefaultUnaryRule(OperatorInfo info) {
  220. switch (info.Operator) {
  221. case ExpressionType.IsTrue:
  222. if (_types[0] == typeof(bool)) {
  223. _rule.Target = _rule.MakeReturn(Binder, Param0);
  224. return true;
  225. }
  226. break;
  227. case ExpressionType.Negate:
  228. if (TypeUtils.IsArithmetic(_types[0])) {
  229. _rule.Target = _rule.MakeReturn(Binder, Ast.Negate(Param0));
  230. return true;
  231. }
  232. break;
  233. case ExpressionType.Not:
  234. if (TypeUtils.IsIntegerOrBool(_types[0])) {
  235. _rule.Target = _rule.MakeReturn(Binder, Ast.Not(Param0));
  236. return true;
  237. }
  238. break;
  239. }
  240. return false;
  241. }
  242. private void MakeIMembersListRule() {
  243. _rule.Target =
  244. _rule.MakeReturn(
  245. Binder,
  246. Ast.Call(
  247. typeof(ScriptingRuntimeHelpers).GetMethod("GetStringMembers"),
  248. Ast.Call(
  249. AstUtils.Convert(_rule.Parameters[0], typeof(IMembersList)),
  250. typeof(IMembersList).GetMethod("GetMemberNames"),
  251. _rule.Context
  252. )
  253. )
  254. );
  255. }
  256. private void MakeCallSignatureResult(MethodBase[] methods) {
  257. List<string> arrres = new List<string>();
  258. foreach (MethodBase mb in methods) {
  259. StringBuilder res = new StringBuilder();
  260. string comma = "";
  261. foreach (ParameterInfo param in mb.GetParameters()) {
  262. if (param.ParameterType == typeof(CodeContext)) continue;
  263. res.Append(comma);
  264. res.Append(param.ParameterType.Name);
  265. res.Append(" ");
  266. res.Append(param.Name);
  267. comma = ", ";
  268. }
  269. arrres.Add(res.ToString());
  270. }
  271. _rule.Target = _rule.MakeReturn(Binder, AstUtils.Constant(arrres.ToArray()));
  272. }
  273. #endregion
  274. #region Indexer Rule
  275. private bool MakeDefaultMemberRule(Operators oper) {
  276. if (_types[0].IsArray) {
  277. if (Binder.CanConvertFrom(_types[1], typeof(int), false, NarrowingLevel.All)) {
  278. if(oper == Operators.GetItem) {
  279. _rule.Target = _rule.MakeReturn(Binder,
  280. Ast.ArrayAccess(
  281. Param0,
  282. ConvertIfNeeded(Param1, typeof(int))
  283. )
  284. );
  285. } else {
  286. _rule.Target = _rule.MakeReturn(Binder,
  287. Ast.Assign(
  288. Ast.ArrayAccess(
  289. Param0,
  290. ConvertIfNeeded(Param1, typeof(int))
  291. ),
  292. ConvertIfNeeded(Param2, _types[0].GetElementType())
  293. )
  294. );
  295. }
  296. return true;
  297. }
  298. }
  299. MethodInfo[] defaults = GetMethodsFromDefaults(_types[0].GetDefaultMembers(), oper);
  300. if (defaults.Length != 0) {
  301. MethodBinder binder = MethodBinder.MakeBinder(Binder,
  302. oper == Operators.GetItem ? "get_Item" : "set_Item",
  303. defaults);
  304. BindingTarget target = binder.MakeBindingTarget(CallTypes.ImplicitInstance, _types);
  305. if (target.Success) {
  306. if (oper == Operators.GetItem) {
  307. _rule.Target = _rule.MakeReturn(Binder, target.MakeExpression(_rule, _rule.Parameters));
  308. } else {
  309. _rule.Target = _rule.MakeReturn(Binder,
  310. Ast.Block(
  311. target.MakeExpression(_rule, _rule.Parameters),
  312. _rule.Parameters[2]
  313. )
  314. );
  315. }
  316. } else {
  317. _rule.Target = Binder.MakeInvalidParametersError(target).MakeErrorForRule(_rule, Binder);
  318. }
  319. return true;
  320. }
  321. return false;
  322. }
  323. private MethodInfo[] GetMethodsFromDefaults(MemberInfo[] defaults, Operators op) {
  324. List<MethodInfo> methods = new List<MethodInfo>();
  325. foreach (MemberInfo mi in defaults) {
  326. PropertyInfo pi = mi as PropertyInfo;
  327. if (pi != null) {
  328. if (op == Operators.GetItem) {
  329. MethodInfo method = pi.GetGetMethod(PrivateBinding);
  330. if (method != null) methods.Add(method);
  331. } else if (op == Operators.SetItem) {
  332. MethodInfo method = pi.GetSetMethod(PrivateBinding);
  333. if (method != null) methods.Add(method);
  334. }
  335. }
  336. }
  337. // if we received methods from both declaring type & base types we need to filter them
  338. Dictionary<MethodSignatureInfo, MethodInfo> dict = new Dictionary<MethodSignatureInfo, MethodInfo>();
  339. foreach (MethodInfo mb in methods) {
  340. MethodSignatureInfo args = new MethodSignatureInfo(mb.IsStatic, mb.GetParameters());
  341. MethodInfo other;
  342. if (dict.TryGetValue(args, out other)) {
  343. if (other.DeclaringType.IsAssignableFrom(mb.DeclaringType)) {
  344. // derived type replaces...
  345. dict[args] = mb;
  346. }
  347. } else {
  348. dict[args] = mb;
  349. }
  350. }
  351. return new List<MethodInfo>(dict.Values).ToArray();
  352. }
  353. #endregion
  354. #region Common helpers
  355. public Expression Param0 {
  356. get { return GetParamater(0); }
  357. }
  358. public Expression Param1 {
  359. get { return GetParamater(1); }
  360. }
  361. public Expression Param2 {
  362. get { return GetParamater(2); }
  363. }
  364. private Expression GetParamater(int index) {
  365. Expression expr = _rule.Parameters[index];
  366. if (_types[index].IsAssignableFrom(expr.Type)) return expr;
  367. return AstUtils.Convert(expr, _types[index]);
  368. }
  369. private bool TryMakeBindingTarget(MethodInfo[] targets) {
  370. MethodBinder mb = MethodBinder.MakeBinder(Binder, targets[0].Name, targets);
  371. BindingTarget target = mb.MakeBindingTarget(CallTypes.None, _types);
  372. if (target.Success) {
  373. Expression call = target.MakeExpression(_rule, _rule.Parameters);
  374. _rule.Target = _rule.MakeReturn(Binder, call);
  375. return true;
  376. }
  377. return false;
  378. }
  379. private bool TryMakeInvertedBindingTarget(MethodInfo[] targets) {
  380. MethodBinder mb = MethodBinder.MakeBinder(Binder, targets[0].Name, targets);
  381. BindingTarget target = mb.MakeBindingTarget(CallTypes.None, _types);
  382. if (target.Success) {
  383. Expression call = target.MakeExpression(_rule, _rule.Parameters);
  384. _rule.Target = _rule.MakeReturn(Binder, Ast.Not(call));
  385. return true;
  386. }
  387. return false;
  388. }
  389. private static Operators GetInvertedOperator(Operators op) {
  390. switch (op) {
  391. case Operators.LessThan: return Operators.GreaterThanOrEqual;
  392. case Operators.LessThanOrEqual: return Operators.GreaterThan;
  393. case Operators.GreaterThan: return Operators.LessThanOrEqual;
  394. case Operators.GreaterThanOrEqual: return Operators.LessThan;
  395. case Operators.Equals: return Operators.NotEquals;
  396. case Operators.NotEquals: return Operators.Equals;
  397. default: throw new InvalidOperationException();
  398. }
  399. }
  400. private Expression ConvertIfNeeded(Expression expression, Type type) {
  401. if (expression.Type != type) {
  402. return Expression.Dynamic(
  403. OldConvertToAction.Make(Binder, type, ConversionResultKind.ExplicitCast),
  404. type,
  405. _rule.Context,
  406. expression
  407. );
  408. }
  409. return expression;
  410. }
  411. private void SetErrorTarget(OperatorInfo info) {
  412. _rule.Target =
  413. _rule.MakeError(
  414. AstUtils.ComplexCallHelper(
  415. typeof(BinderOps).GetMethod("BadArgumentsForOperation"),
  416. ArrayUtils.Insert((Expression)AstUtils.Constant(info.Operator), _rule.Parameters)
  417. )
  418. );
  419. }
  420. private MethodInfo[] GetApplicableMembers(OperatorInfo info) {
  421. return GetApplicableMembers(CompilerHelpers.GetType(_args[0]), info);
  422. }
  423. private MethodInfo[] GetApplicableMembers(Type t, OperatorInfo info) {
  424. MemberGroup members = Binder.GetMember(Action, t, info.Name);
  425. if (members.Count == 0 && info.AlternateName != null) {
  426. members = Binder.GetMember(Action, t, info.AlternateName);
  427. }
  428. // filter down to just methods
  429. return FilterNonMethods(t, members);
  430. }
  431. private void AddFallbackMemberTest(Type t, EventTracker et) {
  432. if(t == typeof(EventTracker)){
  433. //
  434. // Test Generated:
  435. // ScriptingRuntimeHelpers.GetEventHandlerType(((EventTracker)args[0]).Event) == et.Event.EventHandlerType
  436. //
  437. _rule.AddTest(
  438. Ast.Equal(
  439. Ast.Call(
  440. typeof(ScriptingRuntimeHelpers).GetMethod("GetEventHandlerType"),
  441. Ast.Property(
  442. Ast.Convert(
  443. _rule.Parameters[0],
  444. typeof(EventTracker)
  445. ),
  446. typeof(EventTracker).GetProperty("Event")
  447. )
  448. ),
  449. AstUtils.Constant(et.Event.EventHandlerType)
  450. )
  451. );
  452. } else if( t == typeof(BoundMemberTracker)){
  453. //
  454. // Test Generated:
  455. // ScriptingRuntimeHelpers.GetEventHandlerType(((EventTracker)((BoundMemberTracker)args[0]).BountTo).Event) == et.Event.EventHandlerType
  456. //
  457. _rule.AddTest(
  458. Ast.Equal(
  459. Ast.Call(
  460. typeof(ScriptingRuntimeHelpers).GetMethod("GetEventHandlerType"),
  461. Ast.Property(
  462. Ast.Convert(
  463. Ast.Property(
  464. Ast.Convert(
  465. _rule.Parameters[0],
  466. typeof(BoundMemberTracker)
  467. ),
  468. typeof(BoundMemberTracker).GetProperty("BoundTo")
  469. ),
  470. typeof(EventTracker)
  471. ),
  472. typeof(EventTracker).GetProperty("Event")
  473. )
  474. ),
  475. AstUtils.Constant(et.Event.EventHandlerType)
  476. )
  477. );
  478. }
  479. }
  480. private static MethodInfo[] FilterNonMethods(Type t, MemberGroup members) {
  481. List<MethodInfo> methods = new List<MethodInfo>(members.Count);
  482. foreach (MemberTracker mi in members) {
  483. if (mi.MemberType == TrackerTypes.Method) {
  484. MethodInfo method = ((MethodTracker)mi).Method ;
  485. // don't call object methods for None type, but if someone added
  486. // methods to null we'd call those.
  487. if (method.DeclaringType != typeof(object) || t != typeof(DynamicNull)) {
  488. methods.Add(method);
  489. }
  490. }
  491. }
  492. return methods.ToArray();
  493. }
  494. #endregion
  495. }
  496. }