File: System\Linq\Expressions\Interpreter\TypeOperations.cs
Web Access
Project: src\src\libraries\System.Linq.Expressions\src\System.Linq.Expressions.csproj (System.Linq.Expressions)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
 
using System.Collections.Generic;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Dynamic.Utils;
using System.Reflection;
using System.Runtime.CompilerServices;
 
namespace System.Linq.Expressions.Interpreter
{
    internal sealed class CreateDelegateInstruction : Instruction
    {
        private readonly LightDelegateCreator _creator;
 
        internal CreateDelegateInstruction(LightDelegateCreator delegateCreator)
        {
            _creator = delegateCreator;
        }
 
        public override int ConsumedStack => _creator.Interpreter.ClosureSize;
        public override int ProducedStack => 1;
        public override string InstructionName => "CreateDelegate";
 
        public override int Run(InterpretedFrame frame)
        {
            IStrongBox[]? closure;
            if (ConsumedStack > 0)
            {
                closure = new IStrongBox[ConsumedStack];
                for (int i = closure.Length - 1; i >= 0; i--)
                {
                    closure[i] = (IStrongBox?)frame.Pop()!;
                }
            }
            else
            {
                closure = null;
            }
 
            Delegate d = _creator.CreateDelegate(closure);
 
            frame.Push(d);
            return 1;
        }
    }
 
    internal sealed class TypeIsInstruction : Instruction
    {
        private readonly Type _type;
 
        internal TypeIsInstruction(Type type)
        {
            _type = type;
        }
 
        public override int ConsumedStack => 1;
        public override int ProducedStack => 1;
        public override string InstructionName => "TypeIs";
 
        public override int Run(InterpretedFrame frame)
        {
            frame.Push(_type.IsInstanceOfType(frame.Pop()));
            return 1;
        }
 
        public override string ToString() => "TypeIs " + _type.ToString();
    }
 
    internal sealed class TypeAsInstruction : Instruction
    {
        private readonly Type _type;
 
        internal TypeAsInstruction(Type type)
        {
            _type = type;
        }
 
        public override int ConsumedStack => 1;
        public override int ProducedStack => 1;
        public override string InstructionName => "TypeAs";
 
        public override int Run(InterpretedFrame frame)
        {
            object? value = frame.Pop();
            frame.Push(_type.IsInstanceOfType(value) ? value : null);
            return 1;
        }
 
        public override string ToString() => "TypeAs " + _type.ToString();
    }
 
    internal sealed class TypeEqualsInstruction : Instruction
    {
        public static readonly TypeEqualsInstruction Instance = new TypeEqualsInstruction();
 
        public override int ConsumedStack => 2;
        public override int ProducedStack => 1;
        public override string InstructionName => "TypeEquals";
 
        private TypeEqualsInstruction() { }
 
        public override int Run(InterpretedFrame frame)
        {
            object? type = frame.Pop();
            object? obj = frame.Pop();
            frame.Push((object?)obj?.GetType() == type);
            return 1;
        }
    }
 
    internal abstract class NullableMethodCallInstruction : Instruction
    {
        private static NullableMethodCallInstruction? s_hasValue, s_value, s_equals, s_getHashCode, s_getValueOrDefault1, s_toString;
 
        public override int ConsumedStack => 1;
        public override int ProducedStack => 1;
        public override string InstructionName => "NullableMethod";
 
        private NullableMethodCallInstruction() { }
 
        private sealed class HasValue : NullableMethodCallInstruction
        {
            public override int Run(InterpretedFrame frame)
            {
                object? obj = frame.Pop();
                frame.Push(obj != null);
                return 1;
            }
        }
 
        private sealed class GetValue : NullableMethodCallInstruction
        {
            public override int Run(InterpretedFrame frame)
            {
                if (frame.Peek() == null)
                {
                    // Trigger InvalidOperationException with same localized method as if we'd called the Value getter.
                    return (int)default(int?)!;
                }
 
                return 1;
            }
        }
 
        private sealed class GetValueOrDefault : NullableMethodCallInstruction
        {
            private readonly Type _defaultValueType;
 
            public GetValueOrDefault(MethodInfo mi)
            {
                Debug.Assert(mi.ReturnType.IsValueType, "Nullable is only allowed on value types.");
                Debug.Assert(!mi.ReturnType.IsNullableType());
 
                _defaultValueType = mi.ReturnType;
            }
 
            [UnconditionalSuppressMessage("ReflectionAnalysis", "IL2077:UnrecognizedReflectionPattern",
                Justification = "_defaultValueType is a ValueType. You can always get an uninitialized ValueType.")]
            public override int Run(InterpretedFrame frame)
            {
                if (frame.Peek() == null)
                {
                    frame.Pop();
                    frame.Push(RuntimeHelpers.GetUninitializedObject(_defaultValueType));
                }
                return 1;
            }
        }
 
        private sealed class GetValueOrDefault1 : NullableMethodCallInstruction
        {
            public override int ConsumedStack => 2;
 
            public override int Run(InterpretedFrame frame)
            {
                object? dflt = frame.Pop();
                object? obj = frame.Pop();
                frame.Push(obj ?? dflt);
                return 1;
            }
        }
 
        private sealed class EqualsClass : NullableMethodCallInstruction
        {
            public override int ConsumedStack => 2;
 
            public override int Run(InterpretedFrame frame)
            {
                object? other = frame.Pop();
                object? obj = frame.Pop();
                if (obj == null)
                {
                    frame.Push(other == null);
                }
                else if (other == null)
                {
                    frame.Push(Utils.BoxedFalse);
                }
                else
                {
                    frame.Push(obj.Equals(other));
                }
                return 1;
            }
        }
 
        private sealed class ToStringClass : NullableMethodCallInstruction
        {
            public override int Run(InterpretedFrame frame)
            {
                object? obj = frame.Pop();
                frame.Push(obj == null ? "" : obj.ToString());
                return 1;
            }
        }
 
        private sealed class GetHashCodeClass : NullableMethodCallInstruction
        {
            public override int Run(InterpretedFrame frame)
            {
                object? obj = frame.Pop();
                frame.Push(obj?.GetHashCode() ?? 0);
                return 1;
            }
        }
 
        public static Instruction Create(string method, int argCount, MethodInfo mi)
        {
            switch (method)
            {
                case "get_HasValue": return s_hasValue ??= new HasValue();
                case "get_Value": return s_value ??= new GetValue();
                case "Equals": return s_equals ??= new EqualsClass();
                case "GetHashCode": return s_getHashCode ??= new GetHashCodeClass();
                case "GetValueOrDefault":
                    if (argCount == 0)
                    {
                        return new GetValueOrDefault(mi);
                    }
                    else
                    {
                        return s_getValueOrDefault1 ??= new GetValueOrDefault1();
                    }
                case "ToString": return s_toString ??= new ToStringClass();
                default:
                    // System.Nullable doesn't have other instance methods
                    throw ContractUtils.Unreachable;
            }
        }
 
        public static Instruction CreateGetValue()
        {
            return s_value ??= new GetValue();
        }
    }
 
    internal abstract class CastInstruction : Instruction
    {
        private static CastInstruction? s_Boolean, s_Byte, s_Char, s_DateTime, s_Decimal, s_Double, s_Int16, s_Int32, s_Int64, s_SByte, s_Single, s_String, s_UInt16, s_UInt32, s_UInt64;
 
        public override int ConsumedStack => 1;
        public override int ProducedStack => 1;
        public override string InstructionName => "Cast";
 
        private sealed class CastInstructionT<T> : CastInstruction
        {
            public override int Run(InterpretedFrame frame)
            {
                object? value = frame.Pop();
                frame.Push((T)value!);
                return 1;
            }
        }
 
        private abstract class CastInstructionNoT : CastInstruction
        {
            private readonly Type _t;
 
            protected CastInstructionNoT(Type t)
            {
                _t = t;
            }
 
            public static new CastInstruction Create(Type t)
            {
                if (t.IsValueType && !t.IsNullableType())
                {
                    return new Value(t);
                }
                else
                {
                    return new Ref(t);
                }
            }
 
            public override int Run(InterpretedFrame frame)
            {
                object? value = frame.Pop();
                if (value != null)
                {
                    Type valueType = value.GetType();
 
                    if (!valueType.HasReferenceConversionTo(_t) &&
                        !valueType.HasIdentityPrimitiveOrNullableConversionTo(_t))
                    {
                        throw new InvalidCastException();
                    }
 
                    if (!_t.IsAssignableFrom(valueType))
                    {
                        throw new InvalidCastException();
                    }
 
                    frame.Push(value);
                }
                else
                {
                    ConvertNull(frame);
                }
                return 1;
            }
 
            protected abstract void ConvertNull(InterpretedFrame frame);
 
            private sealed class Ref : CastInstructionNoT
            {
                public Ref(Type t)
                    : base(t)
                {
                }
 
                protected override void ConvertNull(InterpretedFrame frame)
                {
                    frame.Push(null);
                }
            }
 
            private sealed class Value : CastInstructionNoT
            {
                public Value(Type t)
                    : base(t)
                {
                }
 
                protected override void ConvertNull(InterpretedFrame frame)
                {
                    throw new NullReferenceException();
                }
            }
        }
 
        public static Instruction Create(Type t)
        {
            Debug.Assert(!t.IsEnum);
            return t.GetTypeCode() switch
            {
                TypeCode.Boolean => s_Boolean ??= new CastInstructionT<bool>(),
                TypeCode.Byte => s_Byte ??= new CastInstructionT<byte>(),
                TypeCode.Char => s_Char ??= new CastInstructionT<char>(),
                TypeCode.DateTime => s_DateTime ??= new CastInstructionT<DateTime>(),
                TypeCode.Decimal => s_Decimal ??= new CastInstructionT<decimal>(),
                TypeCode.Double => s_Double ??= new CastInstructionT<double>(),
                TypeCode.Int16 => s_Int16 ??= new CastInstructionT<short>(),
                TypeCode.Int32 => s_Int32 ??= new CastInstructionT<int>(),
                TypeCode.Int64 => s_Int64 ??= new CastInstructionT<long>(),
                TypeCode.SByte => s_SByte ??= new CastInstructionT<sbyte>(),
                TypeCode.Single => s_Single ??= new CastInstructionT<float>(),
                TypeCode.String => s_String ??= new CastInstructionT<string>(),
                TypeCode.UInt16 => s_UInt16 ??= new CastInstructionT<ushort>(),
                TypeCode.UInt32 => s_UInt32 ??= new CastInstructionT<uint>(),
                TypeCode.UInt64 => s_UInt64 ??= new CastInstructionT<ulong>(),
 
                _ => CastInstructionNoT.Create(t),
            };
        }
    }
 
    internal sealed class CastToEnumInstruction : CastInstruction
    {
        private readonly Type _t;
 
        public CastToEnumInstruction(Type t)
        {
            Debug.Assert(t.IsEnum);
            _t = t;
        }
 
        public override int Run(InterpretedFrame frame)
        {
            object? from = frame.Pop();
            Debug.Assert(
                new[]
                {
                    TypeCode.Empty, TypeCode.Int32, TypeCode.SByte, TypeCode.Int16, TypeCode.Int64, TypeCode.UInt32,
                    TypeCode.Byte, TypeCode.UInt16, TypeCode.UInt64, TypeCode.Char, TypeCode.Boolean
                }.Contains(Convert.GetTypeCode(from)));
            frame.Push(from == null ? null : Enum.ToObject(_t, from));
            return 1;
        }
    }
 
    internal sealed class CastReferenceToEnumInstruction : CastInstruction
    {
        private readonly Type _t;
 
        public CastReferenceToEnumInstruction(Type t)
        {
            Debug.Assert(t.IsEnum);
            _t = t;
        }
 
        public override int Run(InterpretedFrame frame)
        {
            object? from = frame.Pop();
            Debug.Assert(from != null);
 
            // If from is neither a T nor a type assignable to T (viz. an T-backed enum)
            // this will cause an InvalidCastException, which is what this operation should
            // throw in this case.
 
            switch (_t.GetTypeCode())
            {
                case TypeCode.Int32:
                    frame.Push(Enum.ToObject(_t, (int)from));
                    break;
                case TypeCode.Int64:
                    frame.Push(Enum.ToObject(_t, (long)from));
                    break;
                case TypeCode.UInt32:
                    frame.Push(Enum.ToObject(_t, (uint)from));
                    break;
                case TypeCode.UInt64:
                    frame.Push(Enum.ToObject(_t, (ulong)from));
                    break;
                case TypeCode.Byte:
                    frame.Push(Enum.ToObject(_t, (byte)from));
                    break;
                case TypeCode.SByte:
                    frame.Push(Enum.ToObject(_t, (sbyte)from));
                    break;
                case TypeCode.Int16:
                    frame.Push(Enum.ToObject(_t, (short)from));
                    break;
                case TypeCode.UInt16:
                    frame.Push(Enum.ToObject(_t, (ushort)from));
                    break;
                case TypeCode.Char:
                    // Disallowed in C#, but allowed in CIL
                    frame.Push(Enum.ToObject(_t, (char)from));
                    break;
                default:
                    // Only remaining possible type.
                    // Disallowed in C#, but allowed in CIL
                    Debug.Assert(_t.GetTypeCode() == TypeCode.Boolean);
                    frame.Push(Enum.ToObject(_t, (bool)from));
                    break;
            }
 
            return 1;
        }
    }
 
    internal sealed class QuoteInstruction : Instruction
    {
        private readonly Expression _operand;
        private readonly Dictionary<ParameterExpression, LocalVariable>? _hoistedVariables;
 
        public QuoteInstruction(Expression operand, Dictionary<ParameterExpression, LocalVariable>? hoistedVariables)
        {
            _operand = operand;
            _hoistedVariables = hoistedVariables;
        }
 
        public override int ProducedStack => 1;
 
        public override string InstructionName => "Quote";
 
        public override int Run(InterpretedFrame frame)
        {
            Expression? operand = _operand;
            if (_hoistedVariables != null)
            {
                operand = new ExpressionQuoter(_hoistedVariables, frame).Visit(operand);
            }
            frame.Push(operand);
            return 1;
        }
 
        // Modifies a quoted Expression instance by changing hoisted variables and
        // parameters into hoisted local references. The variable's StrongBox is
        // burned as a constant, and all hoisted variables/parameters are rewritten
        // as indexing expressions.
        //
        // The behavior of Quote is intended to be like C# and VB expression quoting
        private sealed class ExpressionQuoter : ExpressionVisitor
        {
            private readonly Dictionary<ParameterExpression, LocalVariable> _variables;
            private readonly InterpretedFrame _frame;
 
            // A stack of variables that are defined in nested scopes. We search
            // this first when resolving a variable in case a nested scope shadows
            // one of our variable instances.
            private readonly Stack<HashSet<ParameterExpression>> _shadowedVars = new Stack<HashSet<ParameterExpression>>();
 
            internal ExpressionQuoter(Dictionary<ParameterExpression, LocalVariable> hoistedVariables, InterpretedFrame frame)
            {
                _variables = hoistedVariables;
                _frame = frame;
            }
 
            protected internal override Expression VisitLambda<T>(Expression<T> node)
            {
                if (node.ParameterCount > 0)
                {
                    var parameters = new HashSet<ParameterExpression>();
 
                    for (int i = 0, n = node.ParameterCount; i < n; i++)
                    {
                        parameters.Add(node.GetParameter(i));
                    }
 
                    _shadowedVars.Push(parameters);
                }
                Expression? b = Visit(node.Body);
                if (node.ParameterCount > 0)
                {
                    _shadowedVars.Pop();
                }
                if (b == node.Body)
                {
                    return node;
                }
                return node.Rewrite(b, parameters: null);
            }
 
            protected internal override Expression VisitBlock(BlockExpression node)
            {
                if (node.Variables.Count > 0)
                {
                    _shadowedVars.Push(new HashSet<ParameterExpression>(node.Variables));
                }
                Expression[]? b = ExpressionVisitorUtils.VisitBlockExpressions(this, node);
                if (node.Variables.Count > 0)
                {
                    _shadowedVars.Pop();
                }
                if (b == null)
                {
                    return node;
                }
                return node.Rewrite(node.Variables, b);
            }
 
            protected override CatchBlock VisitCatchBlock(CatchBlock node)
            {
                if (node.Variable != null)
                {
                    _shadowedVars.Push(new HashSet<ParameterExpression> { node.Variable });
                }
                Expression? b = Visit(node.Body);
                Expression? f = Visit(node.Filter);
                if (node.Variable != null)
                {
                    _shadowedVars.Pop();
                }
                if (b == node.Body && f == node.Filter)
                {
                    return node;
                }
                return Expression.MakeCatchBlock(node.Test, node.Variable, b, f);
            }
 
            protected internal override Expression VisitRuntimeVariables(RuntimeVariablesExpression node)
            {
                int count = node.Variables.Count;
                var boxes = new List<IStrongBox>();
                var vars = new List<ParameterExpression>();
                var indexes = new int[count];
                for (int i = 0; i < indexes.Length; i++)
                {
                    IStrongBox? box = GetBox(node.Variables[i]);
                    if (box == null)
                    {
                        indexes[i] = vars.Count;
                        vars.Add(node.Variables[i]);
                    }
                    else
                    {
                        indexes[i] = -1 - boxes.Count;
                        boxes.Add(box);
                    }
                }
 
                // No variables were rewritten. Just return the original node.
                if (boxes.Count == 0)
                {
                    return node;
                }
 
                ConstantExpression boxesConst = Expression.Constant(new RuntimeOps.RuntimeVariables(boxes.ToArray()), typeof(IRuntimeVariables));
                // All of them were rewritten. Just return the array as a constant
                if (vars.Count == 0)
                {
                    return boxesConst;
                }
 
                // Otherwise, we need to return an object that merges them.
                return Expression.Invoke(
                    Expression.Constant(new Func<IRuntimeVariables, IRuntimeVariables, int[], IRuntimeVariables>(MergeRuntimeVariables)),
                    Expression.RuntimeVariables(new TrueReadOnlyCollection<ParameterExpression>(vars.ToArray())),
                    boxesConst,
                    Expression.Constant(indexes)
                );
            }
 
            private static IRuntimeVariables MergeRuntimeVariables(IRuntimeVariables first, IRuntimeVariables second, int[] indexes)
            {
                return new RuntimeOps.MergedRuntimeVariables(first, second, indexes);
            }
 
            protected internal override Expression VisitParameter(ParameterExpression node)
            {
                IStrongBox? box = GetBox(node);
                if (box == null)
                {
                    return node;
                }
                return Expression.Convert(Utils.GetStrongBoxValueField(Expression.Constant(box)), node.Type);
            }
 
            private IStrongBox? GetBox(ParameterExpression variable)
            {
                if (_variables.TryGetValue(variable, out LocalVariable? var))
                {
                    if (var.InClosure)
                    {
                        return _frame.Closure![var.Index];
                    }
                    else
                    {
                        return (IStrongBox?)_frame.Data[var.Index];
                    }
                }
 
                return null;
            }
        }
    }
}