/* **************************************************************************** * * Copyright (c) Microsoft Corporation. * * This source code is subject to terms and conditions of the Apache License, Version 2.0. A * copy of the license can be found in the License.html file at the root of this distribution. If * you cannot locate the Apache License, Version 2.0, please send an email to * dlr@microsoft.com. By using this source code in any fashion, you are agreeing to be bound * by the terms of the Apache License, Version 2.0. * * You must not remove this notice, or any other, from this software. * * * ***************************************************************************/ using System; using System.Collections.Generic; using System.Collections.ObjectModel; using System.Dynamic.Utils; using System.Globalization; using System.IO; using System.Reflection; using System.Runtime.CompilerServices; using System.Threading; #if !FEATURE_CORE_DLR namespace Microsoft.Scripting.Ast { using Microsoft.Scripting.Utils; #else namespace System.Linq.Expressions { #endif /// /// The base type for all nodes in Expression Trees. /// [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Maintainability", "CA1506:AvoidExcessiveClassCoupling")] public abstract partial class Expression { private delegate LambdaExpression LambdaFactory(Expression body, string name, bool tailCall, ReadOnlyCollection parameters); private static readonly CacheDict _LambdaDelegateCache = new CacheDict(40); private static CacheDict _LambdaFactories; // LINQ protected ctor from 3.5 // needs ConditionWeakTable in 4.0 // For 4.0, many frequently used Expression nodes have had their memory // footprint reduced by removing the Type and NodeType fields. This has // large performance benefits to all users of Expression Trees. // // To support the 3.5 protected constructor, we store the fields that // used to be here in a ConditionalWeakTable. private class ExtensionInfo { public ExtensionInfo(ExpressionType nodeType, Type type) { NodeType = nodeType; Type = type; } internal readonly ExpressionType NodeType; internal readonly Type Type; } private static ConditionalWeakTable _legacyCtorSupportTable; /// /// Constructs a new instance of . /// /// The of the . /// The of the . [Obsolete("use a different constructor that does not take ExpressionType. Then override NodeType and Type properties to provide the values that would be specified to this constructor.")] protected Expression(ExpressionType nodeType, Type type) { // Can't enforce anything that V1 didn't if (_legacyCtorSupportTable == null) { Interlocked.CompareExchange( ref _legacyCtorSupportTable, new ConditionalWeakTable(), null ); } _legacyCtorSupportTable.Add(this, new ExtensionInfo(nodeType, type)); } /// /// Constructs a new instance of . /// protected Expression() { } /// /// The of the . /// public virtual ExpressionType NodeType { get { ExtensionInfo extInfo; if (_legacyCtorSupportTable != null && _legacyCtorSupportTable.TryGetValue(this, out extInfo)) { return extInfo.NodeType; } // the extension expression failed to override NodeType throw Error.ExtensionNodeMustOverrideProperty("Expression.NodeType"); } } /// /// The of the value represented by this . /// [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1721:PropertyNamesShouldNotMatchGetMethods")] public virtual Type Type { get { ExtensionInfo extInfo; if (_legacyCtorSupportTable != null && _legacyCtorSupportTable.TryGetValue(this, out extInfo)) { return extInfo.Type; } // the extension expression failed to override Type throw Error.ExtensionNodeMustOverrideProperty("Expression.Type"); } } /// /// Indicates that the node can be reduced to a simpler node. If this /// returns true, Reduce() can be called to produce the reduced form. /// public virtual bool CanReduce { get { return false; } } /// /// Reduces this node to a simpler expression. If CanReduce returns /// true, this should return a valid expression. This method is /// allowed to return another node which itself must be reduced. /// /// The reduced expression. public virtual Expression Reduce() { if (CanReduce) throw Error.ReducibleMustOverrideReduce(); return this; } /// /// Reduces the node and then calls the visitor delegate on the reduced expression. /// Throws an exception if the node isn't reducible. /// /// An instance of . /// The expression being visited, or an expression which should replace it in the tree. /// /// Override this method to provide logic to walk the node's children. /// A typical implementation will call visitor.Visit on each of its /// children, and if any of them change, should return a new copy of /// itself with the modified children. /// protected internal virtual Expression VisitChildren(ExpressionVisitor visitor) { if (!CanReduce) throw Error.MustBeReducible(); return visitor.Visit(ReduceAndCheck()); } /// /// Dispatches to the specific visit method for this node type. For /// example, will call into /// . /// /// The visitor to visit this node with. /// The result of visiting this node. /// /// This default implementation for /// nodes will call . /// Override this method to call into a more specific method on a derived /// visitor class of ExprressionVisitor. However, it should still /// support unknown visitors by calling VisitExtension. /// protected internal virtual Expression Accept(ExpressionVisitor visitor) { return visitor.VisitExtension(this); } /// /// Reduces this node to a simpler expression. If CanReduce returns /// true, this should return a valid expression. This method is /// allowed to return another node which itself must be reduced. /// /// The reduced expression. /// /// Unlike Reduce, this method checks that the reduced node satisfies /// certain invariants. /// public Expression ReduceAndCheck() { if (!CanReduce) throw Error.MustBeReducible(); var newNode = Reduce(); // 1. Reduction must return a new, non-null node // 2. Reduction must return a new node whose result type can be assigned to the type of the original node if (newNode == null || newNode == this) throw Error.MustReduceToDifferent(); if (!TypeUtils.AreReferenceAssignable(Type, newNode.Type)) throw Error.ReducedNotCompatible(); return newNode; } /// /// Reduces the expression to a known node type (i.e. not an Extension node) /// or simply returns the expression if it is already a known type. /// /// The reduced expression. public Expression ReduceExtensions() { var node = this; while (node.NodeType == ExpressionType.Extension) { node = node.ReduceAndCheck(); } return node; } /// /// Creates a representation of the Expression. /// /// A representation of the Expression. public override string ToString() { return ExpressionStringBuilder.ExpressionToString(this); } #if !FEATURE_CORE_DLR /// /// Writes a representation of the to a . /// /// A that will be used to build the string representation. public void DumpExpression(TextWriter writer) { DebugViewWriter.WriteTo(this, writer); } /// /// Creates a representation of the Expression. /// /// A representation of the Expression. public string DebugView { #else private string DebugView { #endif get { using (System.IO.StringWriter writer = new System.IO.StringWriter(CultureInfo.CurrentCulture)) { DebugViewWriter.WriteTo(this, writer); return writer.ToString(); } } } /// /// Helper used for ensuring we only return 1 instance of a ReadOnlyCollection of T. /// /// This is called from various methods where we internally hold onto an IList of T /// or a readonly collection of T. We check to see if we've already returned a /// readonly collection of T and if so simply return the other one. Otherwise we do /// a thread-safe replacement of the list w/ a readonly collection which wraps it. /// /// Ultimately this saves us from having to allocate a ReadOnlyCollection for our /// data types because the compiler is capable of going directly to the IList of T. /// internal static ReadOnlyCollection ReturnReadOnly(ref IList collection) { IList value = collection; // if it's already read-only just return it. ReadOnlyCollection res = value as ReadOnlyCollection; if (res != null) { return res; } // otherwise make sure only readonly collection every gets exposed Interlocked.CompareExchange>( ref collection, value.ToReadOnly(), value ); // and return it return (ReadOnlyCollection)collection; } /// /// Helper used for ensuring we only return 1 instance of a ReadOnlyCollection of T. /// /// This is similar to the ReturnReadOnly of T. This version supports nodes which hold /// onto multiple Expressions where one is typed to object. That object field holds either /// an expression or a ReadOnlyCollection of Expressions. When it holds a ReadOnlyCollection /// the IList which backs it is a ListArgumentProvider which uses the Expression which /// implements IArgumentProvider to get 2nd and additional values. The ListArgumentProvider /// continues to hold onto the 1st expression. /// /// This enables users to get the ReadOnlyCollection w/o it consuming more memory than if /// it was just an array. Meanwhile The DLR internally avoids accessing which would force /// the readonly collection to be created resulting in a typical memory savings. /// internal static ReadOnlyCollection ReturnReadOnly(IArgumentProvider provider, ref object collection) { Expression tObj = collection as Expression; if (tObj != null) { // otherwise make sure only one readonly collection ever gets exposed Interlocked.CompareExchange( ref collection, new ReadOnlyCollection(new ListArgumentProvider(provider, tObj)), tObj ); } // and return what is not guaranteed to be a readonly collection return (ReadOnlyCollection)collection; } /// /// Helper which is used for specialized subtypes which use ReturnReadOnly(ref object, ...). /// This is the reverse version of ReturnReadOnly which takes an IArgumentProvider. /// /// This is used to return the 1st argument. The 1st argument is typed as object and either /// contains a ReadOnlyCollection or the Expression. We check for the Expression and if it's /// present we return that, otherwise we return the 1st element of the ReadOnlyCollection. /// internal static T ReturnObject(object collectionOrT) where T : class { T t = collectionOrT as T; if (t != null) { return t; } return ((ReadOnlyCollection)collectionOrT)[0]; } private static void RequiresCanRead(Expression expression, string paramName) { if (expression == null) { throw new ArgumentNullException(paramName); } // validate that we can read the node switch (expression.NodeType) { case ExpressionType.Index: IndexExpression index = (IndexExpression)expression; if (index.Indexer != null && !index.Indexer.CanRead) { throw new ArgumentException(Strings.ExpressionMustBeReadable, paramName); } break; case ExpressionType.MemberAccess: MemberExpression member = (MemberExpression)expression; MemberInfo memberInfo = member.Member; if (memberInfo.MemberType == MemberTypes.Property) { PropertyInfo prop = (PropertyInfo)memberInfo; if (!prop.CanRead) { throw new ArgumentException(Strings.ExpressionMustBeReadable, paramName); } } break; } } private static void RequiresCanRead(IEnumerable items, string paramName) { if (items != null) { // this is called a lot, avoid allocating an enumerator if we can... IList listItems = items as IList; if (listItems != null) { for (int i = 0; i < listItems.Count; i++) { RequiresCanRead(listItems[i], paramName); } return; } foreach (var i in items) { RequiresCanRead(i, paramName); } } } private static void RequiresCanWrite(Expression expression, string paramName) { if (expression == null) { throw new ArgumentNullException(paramName); } bool canWrite = false; switch (expression.NodeType) { case ExpressionType.Index: IndexExpression index = (IndexExpression)expression; if (index.Indexer != null) { canWrite = index.Indexer.CanWrite; } else { canWrite = true; } break; case ExpressionType.MemberAccess: MemberExpression member = (MemberExpression)expression; switch (member.Member.MemberType) { case MemberTypes.Property: PropertyInfo prop = (PropertyInfo)member.Member; canWrite = prop.CanWrite; break; case MemberTypes.Field: FieldInfo field = (FieldInfo)member.Member; canWrite = !(field.IsInitOnly || field.IsLiteral); break; } break; case ExpressionType.Parameter: canWrite = true; break; } if (!canWrite) { throw new ArgumentException(Strings.ExpressionMustBeWriteable, paramName); } } } }