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