1 /* ****************************************************************************
3 * Copyright (c) Microsoft Corporation.
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.
11 * You must not remove this notice, or any other, from this software.
14 * ***************************************************************************/
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;
30 namespace Microsoft.Scripting.Ast {
32 namespace System.Linq.Expressions {
35 /// Represents indexing a property or array.
38 [DebuggerTypeProxy(typeof(Expression.IndexExpressionProxy))]
40 public sealed class IndexExpression : Expression, IArgumentProvider {
41 private readonly Expression _instance;
42 private readonly PropertyInfo _indexer;
43 private IList<Expression> _arguments;
45 internal IndexExpression(
48 IList<Expression> arguments) {
50 if (indexer == null) {
51 Debug.Assert(instance != null && instance.Type.IsArray);
52 Debug.Assert(instance.Type.GetArrayRank() == arguments.Count);
57 _arguments = arguments;
61 /// Returns the node type of this <see cref="Expression" />. (Inherited from <see cref="Expression" />.)
63 /// <returns>The <see cref="ExpressionType"/> that represents this expression.</returns>
64 public sealed override ExpressionType NodeType {
65 get { return ExpressionType.Index; }
69 /// Gets the static type of the expression that this <see cref="Expression" /> represents. (Inherited from <see cref="Expression"/>.)
71 /// <returns>The <see cref="Type"/> that represents the static type of the expression.</returns>
72 public sealed override Type Type {
74 if (_indexer != null) {
75 return _indexer.PropertyType;
77 return _instance.Type.GetElementType();
82 /// An object to index.
84 public Expression Object {
85 get { return _instance; }
89 /// Gets the <see cref="PropertyInfo"/> for the property if the expression represents an indexed property, returns null otherwise.
91 public PropertyInfo Indexer {
92 get { return _indexer; }
96 /// Gets the arguments to be used to index the property or array.
98 public ReadOnlyCollection<Expression> Arguments {
99 get { return ReturnReadOnly(ref _arguments); }
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.
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) {
114 return Expression.MakeIndex(@object, Indexer, arguments);
117 Expression IArgumentProvider.GetArgument(int index) {
118 return _arguments[index];
121 int IArgumentProvider.ArgumentCount {
123 return _arguments.Count;
128 /// Dispatches to the specific visit method for this node type.
130 protected internal override Expression Accept(ExpressionVisitor visitor) {
131 return visitor.VisitIndex(this);
134 internal Expression Rewrite(Expression instance, Expression[] arguments) {
135 Debug.Assert(instance != null);
136 Debug.Assert(arguments == null || arguments.Length == _arguments.Count);
138 return Expression.MakeIndex(instance, _indexer, arguments ?? _arguments);
142 public partial class Expression {
145 /// Creates an <see cref="IndexExpression"/> that represents accessing an indexed property in an object.
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);
155 return ArrayAccess(instance, arguments);
162 /// Creates an <see cref="IndexExpression"></see> to access an array.
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);
174 /// Creates an <see cref="IndexExpression"></see> to access an array.
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");
184 Type arrayType = array.Type;
185 if (!arrayType.IsArray) {
186 throw Error.ArgumentMustBeArray();
189 var indexList = indexes.ToReadOnly();
190 if (arrayType.GetArrayRank() != indexList.Count) {
191 throw Error.IncorrectNumberOfIndexes();
194 foreach (Expression e in indexList) {
195 RequiresCanRead(e, "indexes");
196 if (e.Type != typeof(int)) {
197 throw Error.ArgumentMustBeArrayIndexType();
201 return new IndexExpression(array, null, indexList);
208 /// Creates an <see cref="IndexExpression"/> representing the access to an indexed property.
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);
221 #region methods for finding a PropertyInfo by its name
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.
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);
231 flags = BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.IgnoreCase | BindingFlags.FlattenHierarchy;
232 pi = FindProperty(type, propertyName, arguments, flags);
235 if (arguments == null || arguments.Length == 0) {
236 throw Error.InstancePropertyWithoutParameterNotDefinedForType(propertyName, type);
238 throw Error.InstancePropertyWithSpecifiedParametersNotDefinedForType(propertyName, GetArgTypesString(arguments), type);
244 private static string GetArgTypesString(Expression[] arguments) {
245 StringBuilder argTypesStr = new StringBuilder();
247 argTypesStr.Append("(");
248 foreach (var t in arguments.Select(arg => arg.Type)) {
250 argTypesStr.Append(", ");
252 argTypesStr.Append(t.Name);
255 argTypesStr.Append(")");
256 return argTypesStr.ToString();
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)
265 var propertyInfos = members.Map(t => (PropertyInfo)t);
266 int count = FindBestProperty(propertyInfos, arguments, out pi);
271 throw Error.PropertyWithMoreThanOneMatch(propertyName, type);
275 private static int FindBestProperty(IEnumerable<PropertyInfo> properties, Expression[] args, out PropertyInfo property) {
278 foreach (PropertyInfo pi in properties) {
279 if (pi != null && IsCompatible(pi, args)) {
280 if (property == null) {
292 private static bool IsCompatible(PropertyInfo pi, Expression[] args) {
295 mi = pi.GetGetMethod(true);
296 ParameterInfo[] parms;
298 parms = mi.GetParametersCached();
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();
310 return parms.Length == 0;
313 if (parms.Length != args.Length)
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)) {
326 /// Creates an <see cref="IndexExpression"/> representing the access to an indexed property.
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);
337 /// Creates an <see cref="IndexExpression"/> representing the access to an indexed property.
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);
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.
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) {
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.
361 ContractUtils.RequiresNotNull(property, "property");
362 if (property.PropertyType.IsByRef) throw Error.PropertyCannotHaveRefType();
363 if (property.PropertyType == typeof(void)) throw Error.PropertyTypeCannotBeVoid();
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);
372 MethodInfo setter = property.GetSetMethod(true);
373 if (setter != null) {
374 ParameterInfo[] setParameters = setter.GetParametersCached();
375 if (setParameters.Length == 0) throw Error.SetterHasNoParams();
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();
383 if (getter != null) {
384 if (getter.IsStatic ^ setter.IsStatic) throw Error.BothAccessorsMustBeStatic();
385 if (getParameters.Length != setParameters.Length - 1) throw Error.IndexesOfSetGetMustMatch();
387 for (int i = 0; i < getParameters.Length; i++) {
388 if (getParameters[i].ParameterType != setParameters[i].ParameterType) throw Error.IndexesOfSetGetMustMatch();
391 ValidateAccessor(instance, setter, setParameters.RemoveLast(), ref argList);
395 if (getter == null && setter == null) {
396 throw Error.PropertyDoesNotHaveAccessor(property);
400 private static void ValidateAccessor(Expression instance, MethodInfo method, ParameterInfo[] indexes, ref ReadOnlyCollection<Expression> arguments) {
401 ContractUtils.RequiresNotNull(arguments, "arguments");
403 ValidateMethodInfo(method);
404 if ((method.CallingConvention & CallingConventions.VarArgs) != 0) throw Error.AccessorsCannotHaveVarArgs();
405 if (method.IsStatic) {
406 if (instance != null) throw Error.OnlyStaticMethodsHaveNullInstance();
408 if (instance == null) throw Error.OnlyStaticMethodsHaveNullInstance();
409 RequiresCanRead(instance, "instance");
410 ValidateCallInstanceType(instance.Type, method);
413 ValidateAccessorArgumentTypes(method, indexes, ref arguments);
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);
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");
427 Type pType = pi.ParameterType;
428 if (pType.IsByRef) throw Error.AccessorsCannotHaveByRefArgs();
429 TypeUtils.ValidateType(pType);
431 if (!TypeUtils.AreReferenceAssignable(pType, arg.Type)) {
432 if (TypeUtils.IsSameOrSubclass(typeof(LambdaExpression), pType) && pType.IsAssignableFrom(arg.GetType())) {
433 arg = Expression.Quote(arg);
435 throw Error.ExpressionTypeDoesNotMatchMethodParameter(arg.Type, pType, method);
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];
444 if (newArgs != null) {
448 if (newArgs != null) {
449 arguments = new TrueReadOnlyCollection<Expression>(newArgs);
452 } else if (arguments.Count > 0) {
453 throw Error.IncorrectNumberOfMethodCallArguments(method);