Facilitate the merge
[mono.git] / mcs / class / dlr / Runtime / Microsoft.Scripting.Core / Ast / IndexExpression.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 using System;
17 using System.Collections.Generic;
18 using System.Collections.ObjectModel;
19 using System.Diagnostics;
20 using System.Dynamic.Utils;
21 using System.Reflection;
22 using System.Runtime.CompilerServices;
23 using System.Text;
24
25 #if SILVERLIGHT
26 using System.Core;
27 #endif
28
29 #if CLR2
30 namespace Microsoft.Scripting.Ast {
31 #else
32 namespace System.Linq.Expressions {
33 #endif
34     /// <summary>
35     /// Represents indexing a property or array.
36     /// </summary>
37 #if !SILVERLIGHT
38     [DebuggerTypeProxy(typeof(Expression.IndexExpressionProxy))]
39 #endif
40     public sealed class IndexExpression : Expression, IArgumentProvider {
41         private readonly Expression _instance;
42         private readonly PropertyInfo _indexer;
43         private IList<Expression> _arguments;
44
45         internal IndexExpression(
46             Expression instance,
47             PropertyInfo indexer,
48             IList<Expression> arguments) {
49
50             if (indexer == null) {
51                 Debug.Assert(instance != null && instance.Type.IsArray);
52                 Debug.Assert(instance.Type.GetArrayRank() == arguments.Count);
53             }
54
55             _instance = instance;
56             _indexer = indexer;
57             _arguments = arguments;
58         }
59
60         /// <summary>
61         /// Returns the node type of this <see cref="Expression" />. (Inherited from <see cref="Expression" />.)
62         /// </summary>
63         /// <returns>The <see cref="ExpressionType"/> that represents this expression.</returns>
64         public sealed override ExpressionType NodeType {
65             get { return ExpressionType.Index; }
66         }
67
68         /// <summary>
69         /// Gets the static type of the expression that this <see cref="Expression" /> represents. (Inherited from <see cref="Expression"/>.)
70         /// </summary>
71         /// <returns>The <see cref="Type"/> that represents the static type of the expression.</returns>
72         public sealed override Type Type {
73             get {
74                 if (_indexer != null) {
75                     return _indexer.PropertyType;
76                 }
77                 return _instance.Type.GetElementType();
78             }
79         }
80
81         /// <summary>
82         /// An object to index.
83         /// </summary>
84         public Expression Object {
85             get { return _instance; }
86         }
87
88         /// <summary>
89         /// Gets the <see cref="PropertyInfo"/> for the property if the expression represents an indexed property, returns null otherwise.
90         /// </summary>
91         public PropertyInfo Indexer {
92             get { return _indexer; }
93         }
94
95         /// <summary>
96         /// Gets the arguments to be used to index the property or array.
97         /// </summary>
98         public ReadOnlyCollection<Expression> Arguments {
99             get { return ReturnReadOnly(ref _arguments); }
100         }
101
102         /// <summary>
103         /// Creates a new expression that is like this one, but using the
104         /// supplied children. If all of the children are the same, it will
105         /// return this expression.
106         /// </summary>
107         /// <param name="object">The <see cref="Object" /> property of the result.</param>
108         /// <param name="arguments">The <see cref="Arguments" /> property of the result.</param>
109         /// <returns>This expression if no children changed, or an expression with the updated children.</returns>
110         public IndexExpression Update(Expression @object, IEnumerable<Expression> arguments) {
111             if (@object == Object && arguments == Arguments) {
112                 return this;
113             }
114             return Expression.MakeIndex(@object, Indexer, arguments);
115         }
116
117         Expression IArgumentProvider.GetArgument(int index) {
118             return _arguments[index];
119         }
120
121         int IArgumentProvider.ArgumentCount {
122             get {
123                 return _arguments.Count;
124             }
125         }
126
127         /// <summary>
128         /// Dispatches to the specific visit method for this node type.
129         /// </summary>
130         protected internal override Expression Accept(ExpressionVisitor visitor) {
131             return visitor.VisitIndex(this);
132         }
133
134         internal Expression Rewrite(Expression instance, Expression[] arguments) {
135             Debug.Assert(instance != null);
136             Debug.Assert(arguments == null || arguments.Length == _arguments.Count);
137
138             return Expression.MakeIndex(instance, _indexer, arguments ?? _arguments);
139         }
140     }
141
142     public partial class Expression {
143
144         /// <summary>
145         /// Creates an <see cref="IndexExpression"/> that represents accessing an indexed property in an object.
146         /// </summary>
147         /// <param name="instance">The object to which the property belongs. Should be null if the property is static(shared).</param>
148         /// <param name="indexer">An <see cref="Expression"/> representing the property to index.</param>
149         /// <param name="arguments">An IEnumerable{Expression} contaning the arguments to be used to index the property.</param>
150         /// <returns>The created <see cref="IndexExpression"/>.</returns>
151         public static IndexExpression MakeIndex(Expression instance, PropertyInfo indexer, IEnumerable<Expression> arguments) {
152             if (indexer != null) {
153                 return Property(instance, indexer, arguments);
154             } else {
155                 return ArrayAccess(instance, arguments);
156             }
157         }
158
159         #region ArrayAccess
160
161         /// <summary>
162         /// Creates an <see cref="IndexExpression"></see> to access an array.
163         /// </summary>
164         /// <param name="array">An expression representing the array to index.</param>
165         /// <param name="indexes">An array containing expressions used to index the array.</param>
166         /// <remarks>The expression representing the array can be obtained by using the MakeMemberAccess method, 
167         /// or through NewArrayBounds or NewArrayInit.</remarks>
168         /// <returns>The created <see cref="IndexExpression"/>.</returns>
169         public static IndexExpression ArrayAccess(Expression array, params Expression[] indexes) {
170             return ArrayAccess(array, (IEnumerable<Expression>)indexes);
171         }
172
173         /// <summary>
174         /// Creates an <see cref="IndexExpression"></see> to access an array.
175         /// </summary>
176         /// <param name="array">An expression representing the array to index.</param>
177         /// <param name="indexes">An <see cref="IEnumerable{Expression}"/> containing expressions used to index the array.</param>
178         /// <remarks>The expression representing the array can be obtained by using the MakeMemberAccess method, 
179         /// or through NewArrayBounds or NewArrayInit.</remarks>
180         /// <returns>The created <see cref="IndexExpression"/>.</returns>
181         public static IndexExpression ArrayAccess(Expression array, IEnumerable<Expression> indexes) {
182             RequiresCanRead(array, "array");
183
184             Type arrayType = array.Type;
185             if (!arrayType.IsArray) {
186                 throw Error.ArgumentMustBeArray();
187             }
188
189             var indexList = indexes.ToReadOnly();
190             if (arrayType.GetArrayRank() != indexList.Count) {
191                 throw Error.IncorrectNumberOfIndexes();
192             }
193
194             foreach (Expression e in indexList) {
195                 RequiresCanRead(e, "indexes");
196                 if (e.Type != typeof(int)) {
197                     throw Error.ArgumentMustBeArrayIndexType();
198                 }
199             }
200
201             return new IndexExpression(array, null, indexList);
202         }
203
204         #endregion
205
206         #region Property
207         /// <summary>
208         /// Creates an <see cref="IndexExpression"/> representing the access to an indexed property.
209         /// </summary>
210         /// <param name="instance">The object to which the property belongs. If the property is static/shared, it must be null.</param>
211         /// <param name="propertyName">The name of the indexer.</param>
212         /// <param name="arguments">An array of <see cref="Expression"/> objects that are used to index the property.</param>
213         /// <returns>The created <see cref="IndexExpression"/>.</returns>
214         public static IndexExpression Property(Expression instance, string propertyName, params Expression[] arguments) {
215             RequiresCanRead(instance, "instance");
216             ContractUtils.RequiresNotNull(propertyName, "indexerName");
217             PropertyInfo pi = FindInstanceProperty(instance.Type, propertyName, arguments);
218             return Property(instance, pi, arguments);
219         }
220
221         #region methods for finding a PropertyInfo by its name
222         /// <summary>
223         /// The method finds the instance property with the specified name in a type. The property's type signature needs to be compatible with
224         /// the arguments if it is a indexer. If the arguments is null or empty, we get a normal property.
225         /// </summary>
226         private static PropertyInfo FindInstanceProperty(Type type, string propertyName, Expression[] arguments) {
227             // bind to public names first
228             BindingFlags flags = BindingFlags.Instance | BindingFlags.Public | BindingFlags.IgnoreCase | BindingFlags.FlattenHierarchy;
229             PropertyInfo pi = FindProperty(type, propertyName, arguments, flags);
230             if (pi == null) {
231                 flags = BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.IgnoreCase | BindingFlags.FlattenHierarchy;
232                 pi = FindProperty(type, propertyName, arguments, flags);
233             }
234             if (pi == null) {
235                 if (arguments == null || arguments.Length == 0) {
236                     throw Error.InstancePropertyWithoutParameterNotDefinedForType(propertyName, type);
237                 } else {
238                     throw Error.InstancePropertyWithSpecifiedParametersNotDefinedForType(propertyName, GetArgTypesString(arguments), type);
239                 }
240             }
241             return pi;
242         }
243
244         private static string GetArgTypesString(Expression[] arguments) {
245             StringBuilder argTypesStr = new StringBuilder();
246             var isFirst = true;
247             argTypesStr.Append("(");
248             foreach (var t in arguments.Select(arg => arg.Type)) {
249                 if (!isFirst) {
250                     argTypesStr.Append(", ");
251                 }
252                 argTypesStr.Append(t.Name);
253                 isFirst = false;
254             }
255             argTypesStr.Append(")");
256             return argTypesStr.ToString();
257         }
258
259         private static PropertyInfo FindProperty(Type type, string propertyName, Expression[] arguments, BindingFlags flags) {
260             MemberInfo[] members = type.FindMembers(MemberTypes.Property, flags, Type.FilterNameIgnoreCase, propertyName);
261             if (members == null || members.Length == 0)
262                 return null;
263
264             PropertyInfo pi;
265             var propertyInfos = members.Map(t => (PropertyInfo)t);
266             int count = FindBestProperty(propertyInfos, arguments, out pi);
267
268             if (count == 0)
269                 return null;
270             if (count > 1)
271                 throw Error.PropertyWithMoreThanOneMatch(propertyName, type);
272             return pi;
273         }
274
275         private static int FindBestProperty(IEnumerable<PropertyInfo> properties, Expression[] args, out PropertyInfo property) {
276             int count = 0;
277             property = null;
278             foreach (PropertyInfo pi in properties) {
279                 if (pi != null && IsCompatible(pi, args)) {
280                     if (property == null) {
281                         property = pi;
282                         count = 1;
283                     }
284                     else {
285                         count++;
286                     }
287                 }
288             }
289             return count;
290         }
291
292         private static bool IsCompatible(PropertyInfo pi, Expression[] args) {
293             MethodInfo mi;
294
295             mi = pi.GetGetMethod(true);
296             ParameterInfo[] parms;
297             if (mi != null) {
298                 parms = mi.GetParametersCached();
299             } else {
300                 mi = pi.GetSetMethod(true);
301                 //The setter has an additional parameter for the value to set,
302                 //need to remove the last type to match the arguments.
303                 parms = mi.GetParametersCached().RemoveLast();
304             }
305             
306             if (mi == null) {
307                 return false;
308             }
309             if (args == null) {
310                 return parms.Length == 0;
311             }
312             
313             if (parms.Length != args.Length)
314                 return false;
315             for (int i = 0; i < args.Length; i++) {
316                 if (args[i] == null) return false;
317                 if (!TypeUtils.AreReferenceAssignable(parms[i].ParameterType, args[i].Type)) {
318                     return false;
319                 }
320             }
321             return true;
322         }
323         #endregion
324
325         /// <summary>
326         /// Creates an <see cref="IndexExpression"/> representing the access to an indexed property.
327         /// </summary>
328         /// <param name="instance">The object to which the property belongs. If the property is static/shared, it must be null.</param>
329         /// <param name="indexer">The <see cref="PropertyInfo"/> that represents the property to index.</param>
330         /// <param name="arguments">An array of <see cref="Expression"/> objects that are used to index the property.</param>
331         /// <returns>The created <see cref="IndexExpression"/>.</returns>
332         public static IndexExpression Property(Expression instance, PropertyInfo indexer, params Expression[] arguments) {
333             return Property(instance, indexer, (IEnumerable<Expression>)arguments);
334         }
335
336         /// <summary>
337         /// Creates an <see cref="IndexExpression"/> representing the access to an indexed property.
338         /// </summary>
339         /// <param name="instance">The object to which the property belongs. If the property is static/shared, it must be null.</param>
340         /// <param name="indexer">The <see cref="PropertyInfo"/> that represents the property to index.</param>
341         /// <param name="arguments">An <see cref="IEnumerable{T}"/> of <see cref="Expression"/> objects that are used to index the property.</param>
342         /// <returns>The created <see cref="IndexExpression"/>.</returns>
343         public static IndexExpression Property(Expression instance, PropertyInfo indexer, IEnumerable<Expression> arguments) {
344             var argList = arguments.ToReadOnly();
345             ValidateIndexedProperty(instance, indexer, ref argList);
346             return new IndexExpression(instance, indexer, argList);
347         }
348
349         // CTS places no restrictions on properties (see ECMA-335 8.11.3),
350         // so we validate that the property conforms to CLS rules here.
351         //
352         // Does reflection help us out at all? Expression.Property skips all of
353         // these checks, so either it needs more checks or we need less here.
354         private static void ValidateIndexedProperty(Expression instance, PropertyInfo property, ref ReadOnlyCollection<Expression> argList) {
355
356             // If both getter and setter specified, all their parameter types
357             // should match, with exception of the last setter parameter which
358             // should match the type returned by the get method.
359             // Accessor parameters cannot be ByRef.
360
361             ContractUtils.RequiresNotNull(property, "property");
362             if (property.PropertyType.IsByRef) throw Error.PropertyCannotHaveRefType();
363             if (property.PropertyType == typeof(void)) throw Error.PropertyTypeCannotBeVoid();
364
365             ParameterInfo[] getParameters = null;
366             MethodInfo getter = property.GetGetMethod(true);
367             if (getter != null) {
368                 getParameters = getter.GetParametersCached();
369                 ValidateAccessor(instance, getter, getParameters, ref argList);
370             }
371
372             MethodInfo setter = property.GetSetMethod(true);
373             if (setter != null) {
374                 ParameterInfo[] setParameters = setter.GetParametersCached();
375                 if (setParameters.Length == 0) throw Error.SetterHasNoParams();
376
377                 // valueType is the type of the value passed to the setter (last parameter)
378                 Type valueType = setParameters[setParameters.Length - 1].ParameterType;
379                 if (valueType.IsByRef) throw Error.PropertyCannotHaveRefType();
380                 if (setter.ReturnType != typeof(void)) throw Error.SetterMustBeVoid();
381                 if (property.PropertyType != valueType) throw Error.PropertyTyepMustMatchSetter();
382
383                 if (getter != null) {
384                     if (getter.IsStatic ^ setter.IsStatic) throw Error.BothAccessorsMustBeStatic();
385                     if (getParameters.Length != setParameters.Length - 1) throw Error.IndexesOfSetGetMustMatch();
386
387                     for (int i = 0; i < getParameters.Length; i++) {
388                         if (getParameters[i].ParameterType != setParameters[i].ParameterType) throw Error.IndexesOfSetGetMustMatch();
389                     }
390                 } else {
391                     ValidateAccessor(instance, setter, setParameters.RemoveLast(), ref argList);
392                 }
393             }
394
395             if (getter == null && setter == null) {
396                 throw Error.PropertyDoesNotHaveAccessor(property);
397             }
398         }
399
400         private static void ValidateAccessor(Expression instance, MethodInfo method, ParameterInfo[] indexes, ref ReadOnlyCollection<Expression> arguments) {
401             ContractUtils.RequiresNotNull(arguments, "arguments");
402
403             ValidateMethodInfo(method);
404             if ((method.CallingConvention & CallingConventions.VarArgs) != 0) throw Error.AccessorsCannotHaveVarArgs();
405             if (method.IsStatic) {
406                 if (instance != null) throw Error.OnlyStaticMethodsHaveNullInstance();
407             } else {
408                 if (instance == null) throw Error.OnlyStaticMethodsHaveNullInstance();
409                 RequiresCanRead(instance, "instance");
410                 ValidateCallInstanceType(instance.Type, method);
411             }
412
413             ValidateAccessorArgumentTypes(method, indexes, ref arguments);
414         }
415
416         private static void ValidateAccessorArgumentTypes(MethodInfo method, ParameterInfo[] indexes, ref ReadOnlyCollection<Expression> arguments) {
417             if (indexes.Length > 0) {
418                 if (indexes.Length != arguments.Count) {
419                     throw Error.IncorrectNumberOfMethodCallArguments(method);
420                 }
421                 Expression[] newArgs = null;
422                 for (int i = 0, n = indexes.Length; i < n; i++) {
423                     Expression arg = arguments[i];
424                     ParameterInfo pi = indexes[i];
425                     RequiresCanRead(arg, "arguments");
426
427                     Type pType = pi.ParameterType;
428                     if (pType.IsByRef) throw Error.AccessorsCannotHaveByRefArgs();
429                     TypeUtils.ValidateType(pType);
430
431                     if (!TypeUtils.AreReferenceAssignable(pType, arg.Type)) {
432                         if (TypeUtils.IsSameOrSubclass(typeof(LambdaExpression), pType) && pType.IsAssignableFrom(arg.GetType())) {
433                             arg = Expression.Quote(arg);
434                         } else {
435                             throw Error.ExpressionTypeDoesNotMatchMethodParameter(arg.Type, pType, method);
436                         }
437                     }
438                     if (newArgs == null && arg != arguments[i]) {
439                         newArgs = new Expression[arguments.Count];
440                         for (int j = 0; j < i; j++) {
441                             newArgs[j] = arguments[j];
442                         }
443                     }
444                     if (newArgs != null) {
445                         newArgs[i] = arg;
446                     }
447                 }
448                 if (newArgs != null) {
449                     arguments = new TrueReadOnlyCollection<Expression>(newArgs);
450                 }
451
452             } else if (arguments.Count > 0) {
453                 throw Error.IncorrectNumberOfMethodCallArguments(method);
454             }
455         }
456
457         #endregion
458     }
459 }