/* ****************************************************************************
*
* 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);
}
}
}
}