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