1 // Licensed to the .NET Foundation under one or more agreements.
2 // The .NET Foundation licenses this file to you under the MIT license.
3 // See the LICENSE file in the project root for more information.
4 
5 using System.Collections.ObjectModel;
6 using System.Dynamic.Utils;
7 using System.Linq.Expressions;
8 using System.Runtime.CompilerServices;
9 
10 using DelegateHelpers = System.Linq.Expressions.Compiler.DelegateHelpers;
11 
12 namespace System.Dynamic
13 {
14     /// <summary>
15     /// The dynamic call site binder that participates in the <see cref="DynamicMetaObject"/> binding protocol.
16     /// </summary>
17     /// <remarks>
18     /// The <see cref="CallSiteBinder"/> performs the binding of the dynamic operation using the runtime values
19     /// as input. On the other hand, the <see cref="DynamicMetaObjectBinder"/> participates in the <see cref="DynamicMetaObject"/>
20     /// binding protocol.
21     /// </remarks>
22     public abstract class DynamicMetaObjectBinder : CallSiteBinder
23     {
24         /// <summary>
25         /// Initializes a new instance of the <see cref="DynamicMetaObjectBinder"/> class.
26         /// </summary>
DynamicMetaObjectBinder()27         protected DynamicMetaObjectBinder()
28         {
29         }
30 
31         /// <summary>
32         /// The result type of the operation.
33         /// </summary>
34         public virtual Type ReturnType => typeof(object);
35 
36         /// <summary>
37         /// Performs the runtime binding of the dynamic operation on a set of arguments.
38         /// </summary>
39         /// <param name="args">An array of arguments to the dynamic operation.</param>
40         /// <param name="parameters">The array of <see cref="ParameterExpression"/> instances that represent the parameters of the call site in the binding process.</param>
41         /// <param name="returnLabel">A LabelTarget used to return the result of the dynamic binding.</param>
42         /// <returns>
43         /// An Expression that performs tests on the dynamic operation arguments, and
44         /// performs the dynamic operation if the tests are valid. If the tests fail on
45         /// subsequent occurrences of the dynamic operation, Bind will be called again
46         /// to produce a new <see cref="Expression"/> for the new argument types.
47         /// </returns>
Bind(object[] args, ReadOnlyCollection<ParameterExpression> parameters, LabelTarget returnLabel)48         public sealed override Expression Bind(object[] args, ReadOnlyCollection<ParameterExpression> parameters, LabelTarget returnLabel)
49         {
50             ContractUtils.RequiresNotNull(args, nameof(args));
51             ContractUtils.RequiresNotNull(parameters, nameof(parameters));
52             ContractUtils.RequiresNotNull(returnLabel, nameof(returnLabel));
53             if (args.Length == 0)
54             {
55                 throw System.Linq.Expressions.Error.OutOfRange("args.Length", 1);
56             }
57             if (parameters.Count == 0)
58             {
59                 throw System.Linq.Expressions.Error.OutOfRange("parameters.Count", 1);
60             }
61             if (args.Length != parameters.Count)
62             {
63                 throw new ArgumentOutOfRangeException(nameof(args));
64             }
65 
66             // Ensure that the binder's ReturnType matches CallSite's return
67             // type. We do this so meta objects and language binders can
68             // compose trees together without needing to insert converts.
69             Type expectedResult;
70             if (IsStandardBinder)
71             {
72                 expectedResult = ReturnType;
73 
74                 if (returnLabel.Type != typeof(void) &&
75                     !TypeUtils.AreReferenceAssignable(returnLabel.Type, expectedResult))
76                 {
77                     throw System.Linq.Expressions.Error.BinderNotCompatibleWithCallSite(expectedResult, this, returnLabel.Type);
78                 }
79             }
80             else
81             {
82                 // Even for non-standard binders, we have to at least make sure
83                 // it works with the CallSite's type to build the return.
84                 expectedResult = returnLabel.Type;
85             }
86 
87             DynamicMetaObject target = DynamicMetaObject.Create(args[0], parameters[0]);
88             DynamicMetaObject[] metaArgs = CreateArgumentMetaObjects(args, parameters);
89 
90             DynamicMetaObject binding = Bind(target, metaArgs);
91 
92             if (binding == null)
93             {
94                 throw System.Linq.Expressions.Error.BindingCannotBeNull();
95             }
96 
97             Expression body = binding.Expression;
98             BindingRestrictions restrictions = binding.Restrictions;
99 
100             // Ensure the result matches the expected result type.
101             if (expectedResult != typeof(void) &&
102                 !TypeUtils.AreReferenceAssignable(expectedResult, body.Type))
103             {
104                 //
105                 // Blame the last person that handled the result: assume it's
106                 // the dynamic object (if any), otherwise blame the language.
107                 //
108                 if (target.Value is IDynamicMetaObjectProvider)
109                 {
110                     throw System.Linq.Expressions.Error.DynamicObjectResultNotAssignable(body.Type, target.Value.GetType(), this, expectedResult);
111                 }
112                 else
113                 {
114                     throw System.Linq.Expressions.Error.DynamicBinderResultNotAssignable(body.Type, this, expectedResult);
115                 }
116             }
117 
118             // if the target is IDO, standard binders ask it to bind the rule so we may have a target-specific binding.
119             // it makes sense to restrict on the target's type in such cases.
120             // ideally IDO metaobjects should do this, but they often miss that type of "this" is significant.
121             if (IsStandardBinder && args[0] as IDynamicMetaObjectProvider != null)
122             {
123                 if (restrictions == BindingRestrictions.Empty)
124                 {
125                     throw System.Linq.Expressions.Error.DynamicBindingNeedsRestrictions(target.Value.GetType(), this);
126                 }
127             }
128 
129             // Add the return
130             if (body.NodeType != ExpressionType.Goto)
131             {
132                 body = Expression.Return(returnLabel, body);
133             }
134 
135             // Finally, add restrictions
136             if (restrictions != BindingRestrictions.Empty)
137             {
138                 body = Expression.IfThen(restrictions.ToExpression(), body);
139             }
140 
141             return body;
142         }
143 
CreateArgumentMetaObjects(object[] args, ReadOnlyCollection<ParameterExpression> parameters)144         private static DynamicMetaObject[] CreateArgumentMetaObjects(object[] args, ReadOnlyCollection<ParameterExpression> parameters)
145         {
146             DynamicMetaObject[] mos;
147             if (args.Length != 1)
148             {
149                 mos = new DynamicMetaObject[args.Length - 1];
150                 for (int i = 1; i < args.Length; i++)
151                 {
152                     mos[i - 1] = DynamicMetaObject.Create(args[i], parameters[i]);
153                 }
154             }
155             else
156             {
157                 mos = DynamicMetaObject.EmptyMetaObjects;
158             }
159             return mos;
160         }
161 
162         /// <summary>
163         /// When overridden in the derived class, performs the binding of the dynamic operation.
164         /// </summary>
165         /// <param name="target">The target of the dynamic operation.</param>
166         /// <param name="args">An array of arguments of the dynamic operation.</param>
167         /// <returns>The <see cref="DynamicMetaObject"/> representing the result of the binding.</returns>
Bind(DynamicMetaObject target, DynamicMetaObject[] args)168         public abstract DynamicMetaObject Bind(DynamicMetaObject target, DynamicMetaObject[] args);
169 
170         /// <summary>
171         /// Gets an expression that will cause the binding to be updated. It
172         /// indicates that the expression's binding is no longer valid.
173         /// This is typically used when the "version" of a dynamic object has
174         /// changed.
175         /// </summary>
176         /// <param name="type">The <see cref="Expression.Type">Type</see> property of the resulting expression; any type is allowed.</param>
177         /// <returns>The update expression.</returns>
178         [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1822:MarkMembersAsStatic")]
GetUpdateExpression(Type type)179         public Expression GetUpdateExpression(Type type)
180         {
181             return Expression.Goto(CallSiteBinder.UpdateLabel, type);
182         }
183 
184         /// <summary>
185         /// Defers the binding of the operation until later time when the runtime values of all dynamic operation arguments have been computed.
186         /// </summary>
187         /// <param name="target">The target of the dynamic operation.</param>
188         /// <param name="args">An array of arguments of the dynamic operation.</param>
189         /// <returns>The <see cref="DynamicMetaObject"/> representing the result of the binding.</returns>
Defer(DynamicMetaObject target, params DynamicMetaObject[] args)190         public DynamicMetaObject Defer(DynamicMetaObject target, params DynamicMetaObject[] args)
191         {
192             ContractUtils.RequiresNotNull(target, nameof(target));
193 
194             if (args == null)
195             {
196                 return MakeDeferred(target.Restrictions, target);
197             }
198             else
199             {
200                 return MakeDeferred(
201                     target.Restrictions.Merge(BindingRestrictions.Combine(args)),
202                     args.AddFirst(target)
203                 );
204             }
205         }
206 
207         /// <summary>
208         /// Defers the binding of the operation until later time when the runtime values of all dynamic operation arguments have been computed.
209         /// </summary>
210         /// <param name="args">An array of arguments of the dynamic operation.</param>
211         /// <returns>The <see cref="DynamicMetaObject"/> representing the result of the binding.</returns>
Defer(params DynamicMetaObject[] args)212         public DynamicMetaObject Defer(params DynamicMetaObject[] args)
213         {
214             return MakeDeferred(BindingRestrictions.Combine(args), args);
215         }
216 
MakeDeferred(BindingRestrictions rs, params DynamicMetaObject[] args)217         private DynamicMetaObject MakeDeferred(BindingRestrictions rs, params DynamicMetaObject[] args)
218         {
219             var exprs = DynamicMetaObject.GetExpressions(args);
220 
221             Type delegateType = DelegateHelpers.MakeDeferredSiteDelegate(args, ReturnType);
222 
223             // Because we know the arguments match the delegate type (we just created the argument types)
224             // we go directly to DynamicExpression.Make to avoid a bunch of unnecessary argument validation
225             return new DynamicMetaObject(
226                 DynamicExpression.Make(ReturnType, delegateType, this, new TrueReadOnlyCollection<Expression>(exprs)),
227                 rs
228             );
229         }
230 
231         /// <summary>
232         /// Returns <c>true</c> for standard <see cref="DynamicMetaObjectBinder"/>s; otherwise, <c>false</c>.
233         /// </summary>
234         internal virtual bool IsStandardBinder => false;
235     }
236 }
237