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