Merge pull request #3446 from lambdageek/bug-42584
[mono.git] / mcs / class / dlr / Runtime / Microsoft.Scripting.Core / Actions / DynamicObject.cs
1 /* ****************************************************************************
2  *
3  * Copyright (c) Microsoft Corporation. 
4  *
5  * This source code is subject to terms and conditions of the Apache License, Version 2.0. A 
6  * copy of the license can be found in the License.html file at the root of this distribution. If 
7  * you cannot locate the  Apache License, Version 2.0, please send an email to 
8  * dlr@microsoft.com. By using this source code in any fashion, you are agreeing to be bound 
9  * by the terms of the Apache License, Version 2.0.
10  *
11  * You must not remove this notice, or any other, from this software.
12  *
13  *
14  * ***************************************************************************/
15
16 #if !FEATURE_CORE_DLR
17 using Microsoft.Scripting.Ast;
18 #else
19 using System.Linq.Expressions;
20 #endif
21
22 using System.Diagnostics;
23 using System.Dynamic.Utils;
24 using System.Reflection;
25 using System.Runtime.CompilerServices;
26
27 namespace System.Dynamic {
28     /// <summary>
29     /// Provides a simple class that can be inherited from to create an object with dynamic behavior
30     /// at runtime.  Subclasses can override the various binder methods (GetMember, SetMember, Call, etc...)
31     /// to provide custom behavior that will be invoked at runtime.  
32     /// 
33     /// If a method is not overridden then the DynamicObject does not directly support that behavior and 
34     /// the call site will determine how the binding should be performed.
35     /// </summary>
36     [Serializable]
37     public class DynamicObject : IDynamicMetaObjectProvider {
38
39         /// <summary>
40         /// Enables derived types to create a new instance of DynamicObject.  DynamicObject instances cannot be
41         /// directly instantiated because they have no implementation of dynamic behavior.
42         /// </summary>
43         protected DynamicObject() {
44         }
45
46         #region Public Virtual APIs
47
48         /// <summary>
49         /// Provides the implementation of getting a member.  Derived classes can override
50         /// this method to customize behavior.  When not overridden the call site requesting the
51         /// binder determines the behavior.
52         /// </summary>
53         /// <param name="binder">The binder provided by the call site.</param>
54         /// <param name="result">The result of the get operation.</param>
55         /// <returns>true if the operation is complete, false if the call site should determine behavior.</returns>
56         [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1007:UseGenericsWhereAppropriate")]
57         public virtual bool TryGetMember(GetMemberBinder binder, out object result) {
58             result = null;
59             return false;
60         }
61
62         /// <summary>
63         /// Provides the implementation of setting a member.  Derived classes can override
64         /// this method to customize behavior.  When not overridden the call site requesting the
65         /// binder determines the behavior.
66         /// </summary>
67         /// <param name="binder">The binder provided by the call site.</param>
68         /// <param name="value">The value to set.</param>
69         /// <returns>true if the operation is complete, false if the call site should determine behavior.</returns>
70         public virtual bool TrySetMember(SetMemberBinder binder, object value) {
71             return false;
72         }
73
74         /// <summary>
75         /// Provides the implementation of deleting a member.  Derived classes can override
76         /// this method to customize behavior.  When not overridden the call site requesting the
77         /// binder determines the behavior.
78         /// </summary>
79         /// <param name="binder">The binder provided by the call site.</param>
80         /// <returns>true if the operation is complete, false if the call site should determine behavior.</returns>
81         public virtual bool TryDeleteMember(DeleteMemberBinder binder) {
82             return false;
83         }
84
85         /// <summary>
86         /// Provides the implementation of calling a member.  Derived classes can override
87         /// this method to customize behavior.  When not overridden the call site requesting the
88         /// binder determines the behavior.
89         /// </summary>
90         /// <param name="binder">The binder provided by the call site.</param>
91         /// <param name="args">The arguments to be used for the invocation.</param>
92         /// <param name="result">The result of the invocation.</param>
93         /// <returns>true if the operation is complete, false if the call site should determine behavior.</returns>
94         [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1007:UseGenericsWhereAppropriate")]
95         public virtual bool TryInvokeMember(InvokeMemberBinder binder, object[] args, out object result) {
96             result = null;
97             return false;
98         }
99
100         /// <summary>
101         /// Provides the implementation of converting the DynamicObject to another type.  Derived classes
102         /// can override this method to customize behavior.  When not overridden the call site
103         /// requesting the binder determines the behavior.
104         /// </summary>
105         /// <param name="binder">The binder provided by the call site.</param>
106         /// <param name="result">The result of the conversion.</param>
107         /// <returns>true if the operation is complete, false if the call site should determine behavior.</returns>
108         [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1007:UseGenericsWhereAppropriate")]
109         public virtual bool TryConvert(ConvertBinder binder, out object result) {
110             result = null;
111             return false;
112         }
113
114         /// <summary>
115         /// Provides the implementation of creating an instance of the DynamicObject.  Derived classes
116         /// can override this method to customize behavior.  When not overridden the call site requesting
117         /// the binder determines the behavior.
118         /// </summary>
119         /// <param name="binder">The binder provided by the call site.</param>
120         /// <param name="args">The arguments used for creation.</param>
121         /// <param name="result">The created instance.</param>
122         /// <returns>true if the operation is complete, false if the call site should determine behavior.</returns>
123         [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1007:UseGenericsWhereAppropriate")]
124         public virtual bool TryCreateInstance(CreateInstanceBinder binder, object[] args, out object result) {
125             result = null;
126             return false;
127         }
128
129         /// <summary>
130         /// Provides the implementation of invoking the DynamicObject.  Derived classes can
131         /// override this method to customize behavior.  When not overridden the call site requesting
132         /// the binder determines the behavior.
133         /// </summary>
134         /// <param name="binder">The binder provided by the call site.</param>
135         /// <param name="args">The arguments to be used for the invocation.</param>
136         /// <param name="result">The result of the invocation.</param>
137         /// <returns>true if the operation is complete, false if the call site should determine behavior.</returns>
138         [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1007:UseGenericsWhereAppropriate")]
139         public virtual bool TryInvoke(InvokeBinder binder, object[] args, out object result) {
140             result = null;
141             return false;
142         }
143
144         /// <summary>
145         /// Provides the implementation of performing a binary operation.  Derived classes can
146         /// override this method to customize behavior.  When not overridden the call site requesting
147         /// the binder determines the behavior.
148         /// </summary>
149         /// <param name="binder">The binder provided by the call site.</param>
150         /// <param name="arg">The right operand for the operation.</param>
151         /// <param name="result">The result of the operation.</param>
152         /// <returns>true if the operation is complete, false if the call site should determine behavior.</returns>
153         [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1007:UseGenericsWhereAppropriate")]
154         public virtual bool TryBinaryOperation(BinaryOperationBinder binder, object arg, out object result) {
155             result = null;
156             return false;
157         }
158
159         /// <summary>
160         /// Provides the implementation of performing a unary operation.  Derived classes can
161         /// override this method to customize behavior.  When not overridden the call site requesting
162         /// the binder determines the behavior.
163         /// </summary>
164         /// <param name="binder">The binder provided by the call site.</param>
165         /// <param name="result">The result of the operation.</param>
166         /// <returns>true if the operation is complete, false if the call site should determine behavior.</returns>
167         [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1007:UseGenericsWhereAppropriate")]
168         public virtual bool TryUnaryOperation(UnaryOperationBinder binder, out object result) {
169             result = null;
170             return false;
171         }
172
173         /// <summary>
174         /// Provides the implementation of performing a get index operation.  Derived classes can
175         /// override this method to customize behavior.  When not overridden the call site requesting
176         /// the binder determines the behavior.
177         /// </summary>
178         /// <param name="binder">The binder provided by the call site.</param>
179         /// <param name="indexes">The indexes to be used.</param>
180         /// <param name="result">The result of the operation.</param>
181         /// <returns>true if the operation is complete, false if the call site should determine behavior.</returns>
182         [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1007:UseGenericsWhereAppropriate")]
183         public virtual bool TryGetIndex(GetIndexBinder binder, object[] indexes, out object result) {
184             result = null;
185             return false;
186         }
187
188         /// <summary>
189         /// Provides the implementation of performing a set index operation.  Derived classes can
190         /// override this method to custmize behavior.  When not overridden the call site requesting
191         /// the binder determines the behavior.
192         /// </summary>
193         /// <param name="binder">The binder provided by the call site.</param>
194         /// <param name="indexes">The indexes to be used.</param>
195         /// <param name="value">The value to set.</param>
196         /// <returns>true if the operation is complete, false if the call site should determine behavior.</returns>
197         [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1007:UseGenericsWhereAppropriate")]
198         public virtual bool TrySetIndex(SetIndexBinder binder, object[] indexes, object value) {
199             return false;
200         }
201
202         /// <summary>
203         /// Provides the implementation of performing a delete index operation.  Derived classes
204         /// can override this method to custmize behavior.  When not overridden the call site
205         /// requesting the binder determines the behavior.
206         /// </summary>
207         /// <param name="binder">The binder provided by the call site.</param>
208         /// <param name="indexes">The indexes to be deleted.</param>
209         /// <returns>true if the operation is complete, false if the call site should determine behavior.</returns>
210         public virtual bool TryDeleteIndex(DeleteIndexBinder binder, object[] indexes) {
211             return false;
212         }
213
214         /// <summary>
215         /// Returns the enumeration of all dynamic member names.
216         /// </summary>
217         /// <returns>The list of dynamic member names.</returns>
218         [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1024:UsePropertiesWhereAppropriate")]
219         public virtual System.Collections.Generic.IEnumerable<string> GetDynamicMemberNames() {
220             return new string[0];
221         }
222         #endregion
223
224         #region MetaDynamic
225
226         private sealed class MetaDynamic : DynamicMetaObject {
227
228             internal MetaDynamic(Expression expression, DynamicObject value)
229                 : base(expression, BindingRestrictions.Empty, value) {
230             }
231
232             public override System.Collections.Generic.IEnumerable<string> GetDynamicMemberNames()
233             {
234                 return Value.GetDynamicMemberNames();
235             }
236
237             public override DynamicMetaObject BindGetMember(GetMemberBinder binder) {
238                 if (IsOverridden("TryGetMember")) {
239                     return CallMethodWithResult("TryGetMember", binder, NoArgs, (e) => binder.FallbackGetMember(this, e));
240                 }
241
242                 return base.BindGetMember(binder);
243             }
244
245             public override DynamicMetaObject BindSetMember(SetMemberBinder binder, DynamicMetaObject value) {
246                 if (IsOverridden("TrySetMember")) {
247                     return CallMethodReturnLast("TrySetMember", binder, NoArgs, value.Expression, (e) => binder.FallbackSetMember(this, value, e));
248                 }
249
250                 return base.BindSetMember(binder, value);
251             }
252
253             public override DynamicMetaObject BindDeleteMember(DeleteMemberBinder binder) {
254                 if (IsOverridden("TryDeleteMember")) {
255                     return CallMethodNoResult("TryDeleteMember", binder, NoArgs, (e) => binder.FallbackDeleteMember(this, e));
256                 }
257
258                 return base.BindDeleteMember(binder);
259             }
260
261             public override DynamicMetaObject BindConvert(ConvertBinder binder) {
262                 if (IsOverridden("TryConvert")) {
263                     return CallMethodWithResult("TryConvert", binder, NoArgs, (e) => binder.FallbackConvert(this, e));
264                 }
265
266                 return base.BindConvert(binder);
267             }
268
269             public override DynamicMetaObject BindInvokeMember(InvokeMemberBinder binder, DynamicMetaObject[] args) {
270                 // Generate a tree like:
271                 //
272                 // {
273                 //   object result;
274                 //   TryInvokeMember(payload, out result)
275                 //      ? result
276                 //      : TryGetMember(payload, out result)
277                 //          ? FallbackInvoke(result)
278                 //          : fallbackResult
279                 // }
280                 //
281                 // Then it calls FallbackInvokeMember with this tree as the
282                 // "error", giving the language the option of using this
283                 // tree or doing .NET binding.
284                 //
285                 Fallback fallback = e => binder.FallbackInvokeMember(this, args, e);
286
287                 var call = BuildCallMethodWithResult(
288                     "TryInvokeMember",
289                     binder,
290                     DynamicMetaObject.GetExpressions(args),
291                     BuildCallMethodWithResult(
292                         "TryGetMember",
293                         new GetBinderAdapter(binder),
294                         NoArgs,
295                         fallback(null), 
296                         (e) => binder.FallbackInvoke(e, args, null)
297                     ),
298                     null
299                 );
300
301                 return fallback(call);
302             }
303
304
305             public override DynamicMetaObject BindCreateInstance(CreateInstanceBinder binder, DynamicMetaObject[] args) {
306                 if (IsOverridden("TryCreateInstance")) {
307                     return CallMethodWithResult("TryCreateInstance", binder, DynamicMetaObject.GetExpressions(args), (e) => binder.FallbackCreateInstance(this, args, e));
308                 }
309
310                 return base.BindCreateInstance(binder, args);
311             }
312
313             public override DynamicMetaObject BindInvoke(InvokeBinder binder, DynamicMetaObject[] args) {
314                 if (IsOverridden("TryInvoke")) {
315                     return CallMethodWithResult("TryInvoke", binder, DynamicMetaObject.GetExpressions(args), (e) => binder.FallbackInvoke(this, args, e));
316                 }
317
318                 return base.BindInvoke(binder, args);
319             }
320
321             public override DynamicMetaObject BindBinaryOperation(BinaryOperationBinder binder, DynamicMetaObject arg) {
322                 if (IsOverridden("TryBinaryOperation")) {
323                     return CallMethodWithResult("TryBinaryOperation", binder, DynamicMetaObject.GetExpressions(new DynamicMetaObject[] {arg}), (e) => binder.FallbackBinaryOperation(this, arg, e));
324                 }
325
326                 return base.BindBinaryOperation(binder, arg);
327             }
328
329             public override DynamicMetaObject BindUnaryOperation(UnaryOperationBinder binder) {
330                 if (IsOverridden("TryUnaryOperation")) {
331                     return CallMethodWithResult("TryUnaryOperation", binder, NoArgs, (e) => binder.FallbackUnaryOperation(this, e));
332                 }
333
334                 return base.BindUnaryOperation(binder);
335             }
336
337             public override DynamicMetaObject BindGetIndex(GetIndexBinder binder, DynamicMetaObject[] indexes) {
338                 if (IsOverridden("TryGetIndex")) {
339                     return CallMethodWithResult("TryGetIndex", binder, DynamicMetaObject.GetExpressions(indexes), (e) => binder.FallbackGetIndex(this, indexes, e));
340                 }
341
342                 return base.BindGetIndex(binder, indexes);
343             }
344
345             public override DynamicMetaObject BindSetIndex(SetIndexBinder binder, DynamicMetaObject[] indexes, DynamicMetaObject value) {
346                 if (IsOverridden("TrySetIndex")) {
347                     return CallMethodReturnLast("TrySetIndex", binder, DynamicMetaObject.GetExpressions(indexes), value.Expression, (e) => binder.FallbackSetIndex(this, indexes, value, e));
348                 }
349
350                 return base.BindSetIndex(binder, indexes, value);
351             }
352
353             public override DynamicMetaObject BindDeleteIndex(DeleteIndexBinder binder, DynamicMetaObject[] indexes) {
354                 if (IsOverridden("TryDeleteIndex")) {
355                     return CallMethodNoResult("TryDeleteIndex", binder, DynamicMetaObject.GetExpressions(indexes), (e) => binder.FallbackDeleteIndex(this, indexes, e));
356                 }
357
358                 return base.BindDeleteIndex(binder, indexes);
359             }
360
361             private delegate DynamicMetaObject Fallback(DynamicMetaObject errorSuggestion);
362
363             private readonly static Expression[] NoArgs = new Expression[0];
364
365             private static Expression[] GetConvertedArgs(params Expression[] args) {
366                 ReadOnlyCollectionBuilder<Expression> paramArgs = new ReadOnlyCollectionBuilder<Expression>(args.Length);
367
368                 for (int i = 0; i < args.Length; i++) {
369                     paramArgs.Add(Expression.Convert(args[i], typeof(object)));
370                 }
371
372                 return paramArgs.ToArray();
373             }
374
375             /// <summary>
376             /// Helper method for generating expressions that assign byRef call
377             /// parameters back to their original variables
378             /// </summary>
379             private static Expression ReferenceArgAssign(Expression callArgs, Expression[] args) {
380                 ReadOnlyCollectionBuilder<Expression> block = null;
381
382                 for (int i = 0; i < args.Length; i++) {
383                     ContractUtils.Requires(args[i] is ParameterExpression);
384                     if (((ParameterExpression)args[i]).IsByRef) {
385                         if (block == null)
386                             block = new ReadOnlyCollectionBuilder<Expression>();
387
388                         block.Add(
389                             Expression.Assign(
390                                 args[i],
391                                 Expression.Convert(
392                                     Expression.ArrayIndex(
393                                         callArgs,
394                                         Expression.Constant(i)
395                                     ),
396                                     args[i].Type
397                                 )
398                             )
399                         );
400                     }
401                 }
402
403                 if (block != null)
404                     return Expression.Block(block);
405                 else
406                     return Expression.Empty();
407             }
408
409             /// <summary>
410             /// Helper method for generating arguments for calling methods
411             /// on DynamicObject.  parameters is either a list of ParameterExpressions
412             /// to be passed to the method as an object[], or NoArgs to signify that
413             /// the target method takes no object[] parameter.
414             /// </summary>
415             private static Expression[] BuildCallArgs(DynamicMetaObjectBinder binder, Expression[] parameters, Expression arg0, Expression arg1) {
416                 if (!object.ReferenceEquals(parameters, NoArgs))
417                     return arg1 != null ? new Expression[] { Constant(binder), arg0, arg1 } : new Expression[] { Constant(binder), arg0 };
418                 else
419                     return arg1 != null ? new Expression[] { Constant(binder), arg1 } : new Expression[] { Constant(binder) };
420             }
421
422             private static ConstantExpression Constant(DynamicMetaObjectBinder binder) {
423                 Type t = binder.GetType();
424                 while (!t.IsVisible) {
425                     t = t.BaseType;
426                 }
427                 return Expression.Constant(binder, t);
428             }
429
430             /// <summary>
431             /// Helper method for generating a MetaObject which calls a
432             /// specific method on Dynamic that returns a result
433             /// </summary>
434             private DynamicMetaObject CallMethodWithResult(string methodName, DynamicMetaObjectBinder binder, Expression[] args, Fallback fallback) {
435                 return CallMethodWithResult(methodName, binder, args, fallback, null);
436             }
437
438             /// <summary>
439             /// Helper method for generating a MetaObject which calls a
440             /// specific method on Dynamic that returns a result
441             /// </summary>
442             private DynamicMetaObject CallMethodWithResult(string methodName, DynamicMetaObjectBinder binder, Expression[] args, Fallback fallback, Fallback fallbackInvoke) {
443                 //
444                 // First, call fallback to do default binding
445                 // This produces either an error or a call to a .NET member
446                 //
447                 DynamicMetaObject fallbackResult = fallback(null);
448
449                 var callDynamic = BuildCallMethodWithResult(methodName, binder, args, fallbackResult, fallbackInvoke);
450                 
451                 //
452                 // Now, call fallback again using our new MO as the error
453                 // When we do this, one of two things can happen:
454                 //   1. Binding will succeed, and it will ignore our call to
455                 //      the dynamic method, OR
456                 //   2. Binding will fail, and it will use the MO we created
457                 //      above.
458                 //
459                 return fallback(callDynamic);
460             }
461
462             /// <summary>
463             /// Helper method for generating a MetaObject which calls a
464             /// specific method on DynamicObject that returns a result.
465             /// 
466             /// args is either an array of arguments to be passed
467             /// to the method as an object[] or NoArgs to signify that
468             /// the target method takes no parameters.
469             /// </summary>
470             private DynamicMetaObject BuildCallMethodWithResult(string methodName, DynamicMetaObjectBinder binder, Expression[] args, DynamicMetaObject fallbackResult, Fallback fallbackInvoke) {
471                 if (!IsOverridden(methodName)) {
472                     return fallbackResult;
473                 }
474
475                 //
476                 // Build a new expression like:
477                 // {
478                 //   object result;
479                 //   TryGetMember(payload, out result) ? fallbackInvoke(result) : fallbackResult
480                 // }
481                 //
482                 var result = Expression.Parameter(typeof(object), null);
483                 ParameterExpression callArgs = methodName != "TryBinaryOperation" ? Expression.Parameter(typeof(object[]), null) : Expression.Parameter(typeof(object), null);
484                 var callArgsValue = GetConvertedArgs(args);
485
486                 var resultMO = new DynamicMetaObject(result, BindingRestrictions.Empty);
487
488                 // Need to add a conversion if calling TryConvert
489                 if (binder.ReturnType != typeof(object)) {
490                     Debug.Assert(binder is ConvertBinder && fallbackInvoke == null);
491
492                     var convert = Expression.Convert(resultMO.Expression, binder.ReturnType);
493                     // will always be a cast or unbox
494                     Debug.Assert(convert.Method == null);
495
496                     // Prepare a good exception message in case the convert will fail
497                     string convertFailed = Strings.DynamicObjectResultNotAssignable(
498                         "{0}",
499                         this.Value.GetType(),
500                         binder.GetType(),
501                         binder.ReturnType
502                     );
503
504 #if MONO // referencesource version
505                     Expression condition;
506                     // If the return type can not be assigned null then just check for type assignablity otherwise allow null.
507                     if (binder.ReturnType.IsValueType && Nullable.GetUnderlyingType(binder.ReturnType) == null) {
508                         condition = Expression.TypeIs(resultMO.Expression, binder.ReturnType);
509                     }
510                     else {
511                         condition = Expression.OrElse(
512                                         Expression.Equal(resultMO.Expression, Expression.Constant(null)),
513                                         Expression.TypeIs(resultMO.Expression, binder.ReturnType));
514                     }
515
516                     var checkedConvert = Expression.Condition(
517                         condition,
518                         convert,
519                         Expression.Throw(
520                             Expression.New(typeof(InvalidCastException).GetConstructor(new Type[]{typeof(string)}),
521                                 Expression.Call(
522                                     typeof(string).GetMethod("Format", new Type[] {typeof(string), typeof(object[])}),
523                                     Expression.Constant(convertFailed),
524                                     Expression.NewArrayInit(typeof(object),
525                                         Expression.Condition(
526                                             Expression.Equal(resultMO.Expression, Expression.Constant(null)),
527                                             Expression.Constant("null"),
528                                             Expression.Call(
529                                                 resultMO.Expression,
530                                                 typeof(object).GetMethod("GetType")
531                                             ),
532                                             typeof(object)
533                                         )
534                                     )
535                                 )
536                             ),
537                             binder.ReturnType
538                         ),
539                         binder.ReturnType
540                     );
541 #else
542                     var checkedConvert = Expression.Condition(
543                         Expression.TypeIs(resultMO.Expression, binder.ReturnType),
544                         convert,
545                         Expression.Throw(
546                             Expression.New(typeof(InvalidCastException).GetConstructor(new Type[]{typeof(string)}),
547                                 Expression.Call(
548                                     typeof(string).GetMethod("Format", new Type[] {typeof(string), typeof(object)}),
549                                     Expression.Constant(convertFailed),
550                                     Expression.Condition(
551                                         Expression.Equal(resultMO.Expression, Expression.Constant(null)),
552                                         Expression.Constant("null"),
553                                         Expression.Call(
554                                             resultMO.Expression,
555                                             typeof(object).GetMethod("GetType")
556                                         ),
557                                         typeof(object)
558                                     )
559                                 )
560                             ),
561                             binder.ReturnType
562                         ),
563                         binder.ReturnType
564                     );
565 #endif
566
567                     resultMO = new DynamicMetaObject(checkedConvert, resultMO.Restrictions);
568                 }
569
570                 if (fallbackInvoke != null) {
571                     resultMO = fallbackInvoke(resultMO);
572                 }
573
574                 var callDynamic = new DynamicMetaObject(
575                     Expression.Block(
576                         new[] { result, callArgs },
577                         methodName != "TryBinaryOperation" ? Expression.Assign(callArgs, Expression.NewArrayInit(typeof(object), callArgsValue)) : Expression.Assign(callArgs, callArgsValue[0]),
578                         Expression.Condition(
579                             Expression.Call(
580                                 GetLimitedSelf(),
581                                 typeof(DynamicObject).GetMethod(methodName),
582                                 BuildCallArgs(
583                                     binder,
584                                     args,
585                                     callArgs,
586                                     result
587                                 )
588                             ),
589                             Expression.Block(
590                                 methodName != "TryBinaryOperation" ? ReferenceArgAssign(callArgs, args) : Expression.Empty(),
591                                 resultMO.Expression
592                             ),
593                             fallbackResult.Expression,
594                             binder.ReturnType
595                         )
596                     ),
597                     GetRestrictions().Merge(resultMO.Restrictions).Merge(fallbackResult.Restrictions)
598                 );
599                 return callDynamic;
600             }
601
602
603             /// <summary>
604             /// Helper method for generating a MetaObject which calls a
605             /// specific method on Dynamic, but uses one of the arguments for
606             /// the result.
607             /// 
608             /// args is either an array of arguments to be passed
609             /// to the method as an object[] or NoArgs to signify that
610             /// the target method takes no parameters.
611             /// </summary>
612             private DynamicMetaObject CallMethodReturnLast(string methodName, DynamicMetaObjectBinder binder, Expression[] args, Expression value, Fallback fallback) {
613                 //
614                 // First, call fallback to do default binding
615                 // This produces either an error or a call to a .NET member
616                 //
617                 DynamicMetaObject fallbackResult = fallback(null);
618
619                 //
620                 // Build a new expression like:
621                 // {
622                 //   object result;
623                 //   TrySetMember(payload, result = value) ? result : fallbackResult
624                 // }
625                 //
626
627                 var result = Expression.Parameter(typeof(object), null);
628                 var callArgs = Expression.Parameter(typeof(object[]), null);
629                 var callArgsValue = GetConvertedArgs(args);
630
631                 var callDynamic = new DynamicMetaObject(
632                     Expression.Block(
633                         new[] { result, callArgs },
634                         Expression.Assign(callArgs, Expression.NewArrayInit(typeof(object), callArgsValue)),
635                         Expression.Condition(
636                             Expression.Call(
637                                 GetLimitedSelf(),
638                                 typeof(DynamicObject).GetMethod(methodName),
639                                 BuildCallArgs(
640                                     binder,
641                                     args,
642                                     callArgs,
643                                     Expression.Assign(result, Expression.Convert(value, typeof(object)))
644                                 )
645                             ),
646                             Expression.Block(
647                                 ReferenceArgAssign(callArgs, args),
648                                 result
649                             ),
650                             fallbackResult.Expression,
651                             typeof(object)
652                         )
653                     ),
654                     GetRestrictions().Merge(fallbackResult.Restrictions)
655                 );
656
657                 //
658                 // Now, call fallback again using our new MO as the error
659                 // When we do this, one of two things can happen:
660                 //   1. Binding will succeed, and it will ignore our call to
661                 //      the dynamic method, OR
662                 //   2. Binding will fail, and it will use the MO we created
663                 //      above.
664                 //
665                 return fallback(callDynamic);
666             }
667
668
669             /// <summary>
670             /// Helper method for generating a MetaObject which calls a
671             /// specific method on Dynamic, but uses one of the arguments for
672             /// the result.
673             /// 
674             /// args is either an array of arguments to be passed
675             /// to the method as an object[] or NoArgs to signify that
676             /// the target method takes no parameters.
677             /// </summary>
678             private DynamicMetaObject CallMethodNoResult(string methodName, DynamicMetaObjectBinder binder, Expression[] args, Fallback fallback) {
679                 //
680                 // First, call fallback to do default binding
681                 // This produces either an error or a call to a .NET member
682                 //
683                 DynamicMetaObject fallbackResult = fallback(null);
684                 var callArgs = Expression.Parameter(typeof(object[]), null);
685                 var callArgsValue = GetConvertedArgs(args);
686
687                 //
688                 // Build a new expression like:
689                 //   if (TryDeleteMember(payload)) { } else { fallbackResult }
690                 //
691                 var callDynamic = new DynamicMetaObject(
692                     Expression.Block(
693                         new[] { callArgs },
694                         Expression.Assign(callArgs, Expression.NewArrayInit(typeof(object), callArgsValue)),
695                         Expression.Condition(
696                             Expression.Call(
697                                 GetLimitedSelf(),
698                                 typeof(DynamicObject).GetMethod(methodName),
699                                 BuildCallArgs(
700                                     binder,
701                                     args,
702                                     callArgs,
703                                     null
704                                 )
705                             ),
706                             Expression.Block(
707                                 ReferenceArgAssign(callArgs, args),
708                                 Expression.Empty()
709                             ),
710                             fallbackResult.Expression,
711                             typeof(void)
712                         )
713                     ),
714                     GetRestrictions().Merge(fallbackResult.Restrictions)
715                 );
716
717                 //
718                 // Now, call fallback again using our new MO as the error
719                 // When we do this, one of two things can happen:
720                 //   1. Binding will succeed, and it will ignore our call to
721                 //      the dynamic method, OR
722                 //   2. Binding will fail, and it will use the MO we created
723                 //      above.
724                 //
725                 return fallback(callDynamic);
726             }
727
728             /// <summary>
729             /// Checks if the derived type has overridden the specified method.  If there is no
730             /// implementation for the method provided then Dynamic falls back to the base class
731             /// behavior which lets the call site determine how the binder is performed.
732             /// </summary>
733             private bool IsOverridden(string method) {
734                 var methods = Value.GetType().GetMember(method, MemberTypes.Method, BindingFlags.Public | BindingFlags.Instance);
735
736                 foreach (MethodInfo mi in methods) {
737                     if (mi.DeclaringType != typeof(DynamicObject) && mi.GetBaseDefinition().DeclaringType == typeof(DynamicObject)) {
738                         return true;
739                     }
740                 }
741
742                 return false;
743             }
744
745             /// <summary>
746             /// Returns a Restrictions object which includes our current restrictions merged
747             /// with a restriction limiting our type
748             /// </summary>
749             private BindingRestrictions GetRestrictions() {
750                 Debug.Assert(Restrictions == BindingRestrictions.Empty, "We don't merge, restrictions are always empty");
751
752                 return BindingRestrictions.GetTypeRestriction(this);
753             }
754
755             /// <summary>
756             /// Returns our Expression converted to DynamicObject
757             /// </summary>
758             private Expression GetLimitedSelf() {
759                 // Convert to DynamicObject rather than LimitType, because
760                 // the limit type might be non-public.
761                 if (TypeUtils.AreEquivalent(Expression.Type, typeof(DynamicObject))) {
762                     return Expression;
763                 }
764                 return Expression.Convert(Expression, typeof(DynamicObject));
765             }
766
767             private new DynamicObject Value {
768                 get {
769                     return (DynamicObject)base.Value;
770                 }
771             }
772
773             // It is okay to throw NotSupported from this binder. This object
774             // is only used by DynamicObject.GetMember--it is not expected to
775             // (and cannot) implement binding semantics. It is just so the DO
776             // can use the Name and IgnoreCase properties.
777             private sealed class GetBinderAdapter : GetMemberBinder {
778                 internal GetBinderAdapter(InvokeMemberBinder binder)
779                     : base(binder.Name, binder.IgnoreCase) {
780                 }
781
782                 public override DynamicMetaObject FallbackGetMember(DynamicMetaObject target, DynamicMetaObject errorSuggestion) {
783                     throw new NotSupportedException();
784                 }
785             }
786         }
787
788         #endregion
789
790         #region IDynamicMetaObjectProvider Members
791
792         /// <summary>
793         /// The provided MetaObject will dispatch to the Dynamic virtual methods.
794         /// The object can be encapsulated inside of another MetaObject to
795         /// provide custom behavior for individual actions.
796         /// </summary>
797         public virtual DynamicMetaObject GetMetaObject(Expression parameter) {
798             return new MetaDynamic(parameter, this);
799         }
800
801         #endregion
802     }
803 }