File: System\Linq\Expressions\Compiler\LambdaCompiler.Unary.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.Diagnostics;
using System.Dynamic.Utils;
using System.Reflection;
using System.Reflection.Emit;
using static System.Linq.Expressions.CachedReflectionInfo;
 
namespace System.Linq.Expressions.Compiler
{
    internal sealed partial class LambdaCompiler
    {
        private void EmitQuoteUnaryExpression(Expression expr)
        {
            EmitQuote((UnaryExpression)expr);
        }
 
 
        private void EmitQuote(UnaryExpression quote)
        {
            // emit the quoted expression as a runtime constant
            EmitConstant(quote.Operand, quote.Type);
 
            // Heuristic: only emit the tree rewrite logic if we have hoisted
            // locals.
            if (_scope.NearestHoistedLocals != null)
            {
                // HoistedLocals is internal so emit as System.Object
                EmitConstant(_scope.NearestHoistedLocals, typeof(object));
                _scope.EmitGet(_scope.NearestHoistedLocals.SelfVariable);
                _ilg.Emit(OpCodes.Call, RuntimeOps_Quote);
 
                Debug.Assert(typeof(LambdaExpression).IsAssignableFrom(quote.Type));
                _ilg.Emit(OpCodes.Castclass, quote.Type);
            }
        }
 
        private void EmitThrowUnaryExpression(Expression expr)
        {
            EmitThrow((UnaryExpression)expr, CompilationFlags.EmitAsDefaultType);
        }
 
        private void EmitThrow(UnaryExpression expr, CompilationFlags flags)
        {
            if (expr.Operand == null)
            {
                CheckRethrow();
 
                _ilg.Emit(OpCodes.Rethrow);
            }
            else
            {
                EmitExpression(expr.Operand);
                _ilg.Emit(OpCodes.Throw);
            }
 
            EmitUnreachable(expr, flags);
        }
 
        private void EmitUnaryExpression(Expression expr, CompilationFlags flags)
        {
            EmitUnary((UnaryExpression)expr, flags);
        }
 
        private void EmitUnary(UnaryExpression node, CompilationFlags flags)
        {
            if (node.Method != null)
            {
                EmitUnaryMethod(node, flags);
            }
            else if (node.NodeType == ExpressionType.NegateChecked && node.Operand.Type.IsInteger())
            {
                Type type = node.Type;
                Debug.Assert(type == node.Operand.Type);
                if (type.IsNullableType())
                {
                    Label nullOrZero = _ilg.DefineLabel();
                    Label end = _ilg.DefineLabel();
                    EmitExpression(node.Operand);
                    LocalBuilder loc = GetLocal(type);
 
                    // check for null or zero
                    _ilg.Emit(OpCodes.Stloc, loc);
                    _ilg.Emit(OpCodes.Ldloca, loc);
                    _ilg.EmitGetValueOrDefault(type);
                    _ilg.Emit(OpCodes.Brfalse_S, nullOrZero);
 
                    // calculate 0 - operand
                    Type nnType = type.GetNonNullableType();
                    _ilg.EmitDefault(nnType, locals: null); // locals won't be used.
                    _ilg.Emit(OpCodes.Ldloca, loc);
                    _ilg.EmitGetValueOrDefault(type);
                    EmitBinaryOperator(ExpressionType.SubtractChecked, nnType, nnType, nnType, liftedToNull: false);
 
                    // construct result
                    _ilg.Emit(OpCodes.Newobj, TypeUtils.GetNullableConstructor(type));
                    _ilg.Emit(OpCodes.Br_S, end);
 
                    // if null then push back on stack
                    _ilg.MarkLabel(nullOrZero);
                    _ilg.Emit(OpCodes.Ldloc, loc);
                    FreeLocal(loc);
                    _ilg.MarkLabel(end);
                }
                else
                {
                    _ilg.EmitDefault(type, locals: null); // locals won't be used.
                    EmitExpression(node.Operand);
                    EmitBinaryOperator(ExpressionType.SubtractChecked, type, type, type, liftedToNull: false);
                }
            }
            else
            {
                EmitExpression(node.Operand);
                EmitUnaryOperator(node.NodeType, node.Operand.Type, node.Type);
            }
        }
 
        private void EmitUnaryOperator(ExpressionType op, Type operandType, Type resultType)
        {
            bool operandIsNullable = operandType.IsNullableType();
 
            if (op == ExpressionType.ArrayLength)
            {
                _ilg.Emit(OpCodes.Ldlen);
                return;
            }
 
            if (operandIsNullable)
            {
                switch (op)
                {
                    case ExpressionType.UnaryPlus:
                        return;
                    case ExpressionType.TypeAs:
                        if (operandType != resultType)
                        {
                            _ilg.Emit(OpCodes.Box, operandType);
                            _ilg.Emit(OpCodes.Isinst, resultType);
                            if (resultType.IsNullableType())
                            {
                                _ilg.Emit(OpCodes.Unbox_Any, resultType);
                            }
                        }
 
                        return;
                    default:
                        Debug.Assert(TypeUtils.AreEquivalent(operandType, resultType));
                        Label labIfNull = _ilg.DefineLabel();
                        Label labEnd = _ilg.DefineLabel();
                        LocalBuilder loc = GetLocal(operandType);
 
                        // check for null
                        _ilg.Emit(OpCodes.Stloc, loc);
                        _ilg.Emit(OpCodes.Ldloca, loc);
                        _ilg.EmitHasValue(operandType);
                        _ilg.Emit(OpCodes.Brfalse_S, labIfNull);
 
                        // apply operator to non-null value
                        _ilg.Emit(OpCodes.Ldloca, loc);
                        _ilg.EmitGetValueOrDefault(operandType);
                        Type nnOperandType = resultType.GetNonNullableType();
                        EmitUnaryOperator(op, nnOperandType, nnOperandType);
 
                        // construct result
                        ConstructorInfo ci = TypeUtils.GetNullableConstructor(resultType);
                        _ilg.Emit(OpCodes.Newobj, ci);
                        _ilg.Emit(OpCodes.Br_S, labEnd);
 
                        // if null then push back on stack.
                        _ilg.MarkLabel(labIfNull);
                        _ilg.Emit(OpCodes.Ldloc, loc);
                        FreeLocal(loc);
                        _ilg.MarkLabel(labEnd);
                        return;
                }
            }
            else
            {
                switch (op)
                {
                    case ExpressionType.Not:
                        if (operandType == typeof(bool))
                        {
                            _ilg.Emit(OpCodes.Ldc_I4_0);
                            _ilg.Emit(OpCodes.Ceq);
                            return;
                        }
 
                        goto case ExpressionType.OnesComplement;
                    case ExpressionType.OnesComplement:
                        _ilg.Emit(OpCodes.Not);
                        if (!operandType.IsUnsigned())
                        {
                            // Guaranteed to fit within result type: no conversion
                            return;
                        }
                        break;
                    case ExpressionType.IsFalse:
                        _ilg.Emit(OpCodes.Ldc_I4_0);
                        _ilg.Emit(OpCodes.Ceq);
                        // Not an arithmetic operation -> no conversion
                        return;
                    case ExpressionType.IsTrue:
                        _ilg.Emit(OpCodes.Ldc_I4_1);
                        _ilg.Emit(OpCodes.Ceq);
                        // Not an arithmetic operation -> no conversion
                        return;
                    case ExpressionType.UnaryPlus:
                        // Guaranteed to fit within result type: no conversion
                        return;
                    case ExpressionType.Negate:
                    case ExpressionType.NegateChecked:
                        _ilg.Emit(OpCodes.Neg);
                        // Guaranteed to fit within result type: no conversion
                        // (integer NegateChecked was rewritten to 0 - operand and doesn't hit here).
                        return;
                    case ExpressionType.TypeAs:
                        if (operandType != resultType)
                        {
                            if (operandType.IsValueType)
                            {
                                _ilg.Emit(OpCodes.Box, operandType);
                            }
 
                            _ilg.Emit(OpCodes.Isinst, resultType);
                            if (resultType.IsNullableType())
                            {
                                _ilg.Emit(OpCodes.Unbox_Any, resultType);
                            }
                        }
 
                        // Not an arithmetic operation -> no conversion
                        return;
                    case ExpressionType.Increment:
                        EmitConstantOne(resultType);
                        _ilg.Emit(OpCodes.Add);
                        break;
                    case ExpressionType.Decrement:
                        EmitConstantOne(resultType);
                        _ilg.Emit(OpCodes.Sub);
                        break;
                }
 
                EmitConvertArithmeticResult(op, resultType);
            }
        }
 
        private void EmitConstantOne(Type type)
        {
            switch (type.GetTypeCode())
            {
                case TypeCode.Int64:
                case TypeCode.UInt64:
                    _ilg.Emit(OpCodes.Ldc_I4_1);
                    _ilg.Emit(OpCodes.Conv_I8);
                    break;
                case TypeCode.Single:
                    _ilg.Emit(OpCodes.Ldc_R4, 1.0f);
                    break;
                case TypeCode.Double:
                    _ilg.Emit(OpCodes.Ldc_R8, 1.0d);
                    break;
                default:
                    _ilg.Emit(OpCodes.Ldc_I4_1);
                    break;
            }
        }
 
        private void EmitUnboxUnaryExpression(Expression expr)
        {
            var node = (UnaryExpression)expr;
            Debug.Assert(node.Type.IsValueType);
 
            // Unbox_Any leaves the value on the stack
            EmitExpression(node.Operand);
            _ilg.Emit(OpCodes.Unbox_Any, node.Type);
        }
 
        private void EmitConvertUnaryExpression(Expression expr, CompilationFlags flags)
        {
            EmitConvert((UnaryExpression)expr, flags);
        }
 
        private void EmitConvert(UnaryExpression node, CompilationFlags flags)
        {
            if (node.Method != null)
            {
                // User-defined conversions are only lifted if both source and
                // destination types are value types.  The C# compiler gets this wrong.
                // In C#, if you have an implicit conversion from int->MyClass and you
                // "lift" the conversion to int?->MyClass then a null int? goes to a
                // null MyClass.  This is contrary to the specification, which states
                // that the correct behaviour is to unwrap the int?, throw an exception
                // if it is null, and then call the conversion.
                //
                // We cannot fix this in C# but there is no reason why we need to
                // propagate this behavior into the expression tree API.  Unfortunately
                // this means that when the C# compiler generates the lambda
                // (int? i)=>(MyClass)i, we will get different results for converting
                // that lambda to a delegate directly and converting that lambda to
                // an expression tree and then compiling it.  We can live with this
                // discrepancy however.
 
                if (node.IsLifted && (!node.Type.IsValueType || !node.Operand.Type.IsValueType))
                {
                    ParameterInfo[] pis = node.Method.GetParametersCached();
                    Debug.Assert(pis != null && pis.Length == 1);
                    Type paramType = pis[0].ParameterType;
                    if (paramType.IsByRef)
                    {
                        paramType = paramType.GetElementType()!;
                    }
 
                    UnaryExpression operand = Expression.Convert(node.Operand, paramType);
                    Debug.Assert(operand.Method == null);
 
                    node = Expression.Convert(Expression.Call(node.Method, operand), node.Type);
 
                    Debug.Assert(node.Method == null);
                }
                else
                {
                    EmitUnaryMethod(node, flags);
                    return;
                }
            }
 
            if (node.Type == typeof(void))
            {
                EmitExpressionAsVoid(node.Operand, flags);
            }
            else
            {
                if (TypeUtils.AreEquivalent(node.Operand.Type, node.Type))
                {
                    EmitExpression(node.Operand, flags);
                }
                else
                {
                    // A conversion is emitted after emitting the operand, no tail call is emitted
                    EmitExpression(node.Operand);
                    _ilg.EmitConvertToType(node.Operand.Type, node.Type, node.NodeType == ExpressionType.ConvertChecked, this);
                }
            }
        }
 
 
        private void EmitUnaryMethod(UnaryExpression node, CompilationFlags flags)
        {
            if (node.IsLifted)
            {
                ParameterExpression v = Expression.Variable(node.Operand.Type.GetNonNullableType(), name: null);
                MethodCallExpression mc = Expression.Call(node.Method!, v);
 
                Type resultType = mc.Type.GetNullableType();
                EmitLift(node.NodeType, resultType, mc, new ParameterExpression[] { v }, new Expression[] { node.Operand });
                _ilg.EmitConvertToType(resultType, node.Type, isChecked: false, locals: this);
            }
            else
            {
                EmitMethodCallExpression(Expression.Call(node.Method!, node.Operand), flags);
            }
        }
    }
}