Facilitate the merge
[mono.git] / mcs / class / dlr / Runtime / Microsoft.Scripting.Core / Actions / DynamicMetaObjectBinder.cs
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
16 #if CLR2
17 using Microsoft.Scripting.Ast;
18 using Microsoft.Scripting.Ast.Compiler;
19 #else
20 using System.Linq.Expressions;
21 using System.Linq.Expressions.Compiler;
22 #endif
23
24 #if SILVERLIGHT
25 using System.Core;
26 #else
27 using System.Runtime.Remoting;
28 #endif
29
30 using System.Collections.ObjectModel;
31 using System.Diagnostics;
32 using System.Dynamic.Utils;
33 using System.Runtime.CompilerServices;
34
35 namespace System.Dynamic {
36     /// <summary>
37     /// The dynamic call site binder that participates in the <see cref="DynamicMetaObject"/> binding protocol.
38     /// </summary>
39     /// <remarks>
40     /// The <see cref="CallSiteBinder"/> performs the binding of the dynamic operation using the runtime values
41     /// as input. On the other hand, the <see cref="DynamicMetaObjectBinder"/> participates in the <see cref="DynamicMetaObject"/>
42     /// binding protocol.
43     /// </remarks>
44     public abstract class DynamicMetaObjectBinder : CallSiteBinder {
45
46         #region Public APIs
47
48         /// <summary>
49         /// Initializes a new instance of the <see cref="DynamicMetaObjectBinder"/> class.
50         /// </summary>
51         protected DynamicMetaObjectBinder() {
52         }
53
54         /// <summary>
55         /// The result type of the operation.
56         /// </summary>
57         public virtual Type ReturnType {
58             get { return typeof(object); }
59         }
60
61         /// <summary>
62         /// Performs the runtime binding of the dynamic operation on a set of arguments.
63         /// </summary>
64         /// <param name="args">An array of arguments to the dynamic operation.</param>
65         /// <param name="parameters">The array of <see cref="ParameterExpression"/> instances that represent the parameters of the call site in the binding process.</param>
66         /// <param name="returnLabel">A LabelTarget used to return the result of the dynamic binding.</param>
67         /// <returns>
68         /// An Expression that performs tests on the dynamic operation arguments, and
69         /// performs the dynamic operation if the tests are valid. If the tests fail on
70         /// subsequent occurrences of the dynamic operation, Bind will be called again
71         /// to produce a new <see cref="Expression"/> for the new argument types.
72         /// </returns>
73         public sealed override Expression Bind(object[] args, ReadOnlyCollection<ParameterExpression> parameters, LabelTarget returnLabel) {
74             ContractUtils.RequiresNotNull(args, "args");
75             ContractUtils.RequiresNotNull(parameters, "parameters");
76             ContractUtils.RequiresNotNull(returnLabel, "returnLabel");
77             if (args.Length == 0) {
78                 throw Error.OutOfRange("args.Length", 1);
79             }
80             if (parameters.Count == 0) {
81                 throw Error.OutOfRange("parameters.Count", 1);
82             }
83             if (args.Length != parameters.Count) {
84                 throw new ArgumentOutOfRangeException("args");
85             }
86
87             // Ensure that the binder's ReturnType matches CallSite's return
88             // type. We do this so meta objects and language binders can
89             // compose trees together without needing to insert converts.
90             Type expectedResult;
91             if (IsStandardBinder) {
92                 expectedResult = ReturnType;
93
94                 if (returnLabel.Type != typeof(void) &&
95                     !TypeUtils.AreReferenceAssignable(returnLabel.Type, expectedResult)) {
96                     throw Error.BinderNotCompatibleWithCallSite(expectedResult, this, returnLabel.Type);
97                 }
98             } else {
99                 // Even for non-standard binders, we have to at least make sure
100                 // it works with the CallSite's type to build the return.
101                 expectedResult = returnLabel.Type;
102             }
103
104             DynamicMetaObject target = DynamicMetaObject.Create(args[0], parameters[0]);
105             DynamicMetaObject[] metaArgs = CreateArgumentMetaObjects(args, parameters);
106
107             DynamicMetaObject binding = Bind(target, metaArgs);
108
109             if (binding == null) {
110                 throw Error.BindingCannotBeNull();
111             }
112
113             Expression body = binding.Expression;
114             BindingRestrictions restrictions = binding.Restrictions;
115
116             // Ensure the result matches the expected result type.
117             if (expectedResult != typeof(void) &&
118                 !TypeUtils.AreReferenceAssignable(expectedResult, body.Type)) {
119
120                 //
121                 // Blame the last person that handled the result: assume it's
122                 // the dynamic object (if any), otherwise blame the language.
123                 //
124                 if (target.Value is IDynamicMetaObjectProvider) {
125                     throw Error.DynamicObjectResultNotAssignable(body.Type, target.Value.GetType(), this, expectedResult);
126                 } else {
127                     throw Error.DynamicBinderResultNotAssignable(body.Type, this, expectedResult);
128                 }
129             }
130
131             // if the target is IDO, standard binders ask it to bind the rule so we may have a target-specific binding. 
132             // it makes sense to restrict on the target's type in such cases.
133             // ideally IDO metaobjects should do this, but they often miss that type of "this" is significant.
134             if (IsStandardBinder && args[0] as IDynamicMetaObjectProvider != null) {
135                 if (restrictions == BindingRestrictions.Empty) {
136                     throw Error.DynamicBindingNeedsRestrictions(target.Value.GetType(), this);
137                 }
138             }
139
140             restrictions = AddRemoteObjectRestrictions(restrictions, args, parameters);
141
142             // Add the return
143             if (body.NodeType != ExpressionType.Goto) {
144                 body = Expression.Return(returnLabel, body);
145             }
146
147             // Finally, add restrictions
148             if (restrictions != BindingRestrictions.Empty) {
149                 body = Expression.IfThen(restrictions.ToExpression(), body);
150             }
151
152             return body;
153         }
154
155         private static DynamicMetaObject[] CreateArgumentMetaObjects(object[] args, ReadOnlyCollection<ParameterExpression> parameters) {
156             DynamicMetaObject[] mos;
157             if (args.Length != 1) {
158                 mos = new DynamicMetaObject[args.Length - 1];
159                 for (int i = 1; i < args.Length; i++) {
160                     mos[i - 1] = DynamicMetaObject.Create(args[i], parameters[i]);
161                 }
162             } else {
163                 mos = DynamicMetaObject.EmptyMetaObjects;
164             }
165             return mos;
166         }
167
168         private static BindingRestrictions AddRemoteObjectRestrictions(BindingRestrictions restrictions, object[] args, ReadOnlyCollection<ParameterExpression> parameters) {
169 #if !SILVERLIGHT
170
171             for (int i = 0; i < parameters.Count; i++) {
172                 var expr = parameters[i];
173                 var value = args[i] as MarshalByRefObject;
174
175                 // special case for MBR objects.
176                 // when MBR objects are remoted they can have different conversion behavior
177                 // so bindings created for local and remote objects should not be mixed.
178                 if (value != null && !IsComObject(value)) {
179                     BindingRestrictions remotedRestriction;
180                     if (RemotingServices.IsObjectOutOfAppDomain(value)) {
181                         remotedRestriction = BindingRestrictions.GetExpressionRestriction(
182                             Expression.AndAlso(
183                                 Expression.NotEqual(expr, Expression.Constant(null)),
184                                 Expression.Call(
185                                     typeof(RemotingServices).GetMethod("IsObjectOutOfAppDomain"),
186                                     expr
187                                 )
188                             )
189                         );
190                     } else {
191                         remotedRestriction = BindingRestrictions.GetExpressionRestriction(
192                             Expression.AndAlso(
193                                 Expression.NotEqual(expr, Expression.Constant(null)),
194                                 Expression.Not(
195                                     Expression.Call(
196                                         typeof(RemotingServices).GetMethod("IsObjectOutOfAppDomain"),
197                                         expr
198                                     )
199                                 )
200                             )
201                         );
202                     }
203                     restrictions = restrictions.Merge(remotedRestriction);
204                 }
205             }
206
207 #endif
208             return restrictions;
209         }
210
211         /// <summary>
212         /// When overridden in the derived class, performs the binding of the dynamic operation.
213         /// </summary>
214         /// <param name="target">The target of the dynamic operation.</param>
215         /// <param name="args">An array of arguments of the dynamic operation.</param>
216         /// <returns>The <see cref="DynamicMetaObject"/> representing the result of the binding.</returns>
217         public abstract DynamicMetaObject Bind(DynamicMetaObject target, DynamicMetaObject[] args);
218
219         /// <summary>
220         /// Gets an expression that will cause the binding to be updated. It
221         /// indicates that the expression's binding is no longer valid.
222         /// This is typically used when the "version" of a dynamic object has
223         /// changed.
224         /// </summary>
225         /// <param name="type">The <see cref="Expression.Type">Type</see> property of the resulting expression; any type is allowed.</param>
226         /// <returns>The update expression.</returns>
227         [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1822:MarkMembersAsStatic")]
228         public Expression GetUpdateExpression(Type type) {
229             return Expression.Goto(CallSiteBinder.UpdateLabel, type);
230         }
231
232         /// <summary>
233         /// Defers the binding of the operation until later time when the runtime values of all dynamic operation arguments have been computed.
234         /// </summary>
235         /// <param name="target">The target of the dynamic operation.</param>
236         /// <param name="args">An array of arguments of the dynamic operation.</param>
237         /// <returns>The <see cref="DynamicMetaObject"/> representing the result of the binding.</returns>
238         public DynamicMetaObject Defer(DynamicMetaObject target, params DynamicMetaObject[] args) {
239             ContractUtils.RequiresNotNull(target, "target");
240
241             if (args == null) {
242                 return MakeDeferred(target.Restrictions, target);
243             } else {
244                 return MakeDeferred(
245                     target.Restrictions.Merge(BindingRestrictions.Combine(args)),
246                     args.AddFirst(target)
247                 );
248             }
249         }
250
251         /// <summary>
252         /// Defers the binding of the operation until later time when the runtime values of all dynamic operation arguments have been computed.
253         /// </summary>
254         /// <param name="args">An array of arguments of the dynamic operation.</param>
255         /// <returns>The <see cref="DynamicMetaObject"/> representing the result of the binding.</returns>
256         public DynamicMetaObject Defer(params DynamicMetaObject[] args) {
257             return MakeDeferred(BindingRestrictions.Combine(args), args);
258         }
259
260         private DynamicMetaObject MakeDeferred(BindingRestrictions rs, params DynamicMetaObject[] args) {
261             var exprs = DynamicMetaObject.GetExpressions(args);
262
263             Type delegateType = DelegateHelpers.MakeDeferredSiteDelegate(args, ReturnType);
264
265             // Because we know the arguments match the delegate type (we just created the argument types)
266             // we go directly to DynamicExpression.Make to avoid a bunch of unnecessary argument validation
267             return new DynamicMetaObject(
268                 DynamicExpression.Make(ReturnType, delegateType, this, new TrueReadOnlyCollection<Expression>(exprs)),
269                 rs
270             );
271         }
272
273         #endregion
274
275         // used to detect standard MetaObjectBinders.
276         internal virtual bool IsStandardBinder {
277             get {
278                 return false;
279             }
280         }
281
282 #if !SILVERLIGHT
283         private static readonly Type ComObjectType = typeof(object).Assembly.GetType("System.__ComObject");
284         private static bool IsComObject(object obj) {
285             // we can't use System.Runtime.InteropServices.Marshal.IsComObject(obj) since it doesn't work in partial trust
286             return obj != null && ComObjectType.IsAssignableFrom(obj.GetType());
287         }
288 #endif
289
290     }
291 }