/// <summary>
        /// Reduces the expression node to a simpler expression.
        /// </summary>
        /// <returns>The reduced expression.</returns>
        public override Expression Reduce()
        {
            if (IsLifted)
            {
                if (Helpers.IsAlwaysNull(Operand))
                {
                    return(Expression.Default(Type));
                }

                if (Helpers.IsNeverNull(Operand))
                {
                    // CONSIDER: Peek into Operand and try to extract non-null value.

                    return
                        (Expression.Convert(
                             MakeFromEnd(Helpers.MakeNullableGetValueOrDefault(Operand)),
                             Type
                             ));
                }

                if (Operand is ParameterExpression i)
                {
                    return(MakeLiftedFromEnd(i));
                }
                else
                {
                    var temp = Expression.Parameter(Operand.Type, "__t");

                    return
                        (Expression.Block(
                             new[] { temp },
                             Expression.Assign(temp, Operand),
                             MakeLiftedFromEnd(temp)
                             ));
                }
            }
            else
            {
                return(MakeFromEnd(Operand));
            }

            Expression MakeLiftedFromEnd(ParameterExpression operand) =>
            Expression.Condition(
                Helpers.MakeNullableHasValue(operand),
                Expression.Convert(
                    MakeFromEnd(Helpers.MakeNullableGetValueOrDefault(operand)),
                    Type
                    ),
                Expression.Default(Type)
                );

            Expression MakeFromEnd(Expression operand)
            {
                var method = Method ?? FromEnd;

                switch (method)
                {
                case MethodInfo m:
                    switch (m.GetParametersCached().Length)
                    {
                    case 1:
                        return(Expression.Call(m, operand));

                    case 2:
                        return(Expression.Call(m, operand, Expression.Constant(true)));
                    }
                    break;

                case ConstructorInfo c:
                    switch (c.GetParametersCached().Length)
                    {
                    case 1:
                        return(Expression.New(c, operand));

                    case 2:
                        return(Expression.New(c, operand, Expression.Constant(true)));
                    }
                    break;
                }

                throw ContractUtils.Unreachable;
            }
        }
예제 #2
0
        private Expression ReduceNullable(SwitchAnalysis analysis)
        {
            var governingType        = SwitchValue.Type;
            var governingTypeNonNull = governingType.GetNonNullableType();

            var valueLocal        = Expression.Parameter(governingType, "__value");
            var vars              = new[] { valueLocal };
            var assignSwitchValue = Expression.Assign(valueLocal, SwitchValue);

            Expression body;

            // NB: The DLR switch expression does not optimize the nullable case into a switch table after a null check.
            //     We can do this here instead, but we could consider moving this logic to the DLR too.

            if (analysis.NullCase != null)
            {
                if (analysis.IsNullLonely)
                {
                    // We found a case with only a 'null' test value; we can lower to a null-check followed by a non-null switch,
                    // and move the 'null' case to an else branch.

                    var hasValue = Helpers.MakeNullableHasValue(valueLocal);
                    var value    = Helpers.MakeNullableGetValueOrDefault(valueLocal);

                    var lowered = LowerSwitchStatement(analysis, governingTypeNonNull, hoistNull: true);

                    var @switch = lowered.Make(value, lowered.DefaultCase);

                    body = Expression.IfThenElse(hasValue, @switch, lowered.NullCase);
                }
                else
                {
                    // We have a case with a 'null' test value but it has other non-null test values too; we can't lower to non-
                    // null types. This should be the rare case. The DLR will further lower to if-then-else branches.

                    var lowered = LowerSwitchStatement(analysis, governingType, hoistNull: false);

                    body = lowered.Make(valueLocal, lowered.DefaultCase);
                }
            }
            else
            {
                // We have no 'null' test value whatsoever; we can lower to a null-check followed by a non-null switch.

                var hasValue = Helpers.MakeNullableHasValue(valueLocal);
                var value    = Helpers.MakeNullableGetValueOrDefault(valueLocal);

                var lowered = LowerSwitchStatement(analysis, governingTypeNonNull, hoistNull: false);

                var defaultBody = lowered.DefaultCase;

                if (defaultBody != null)
                {
                    var defaultLabel = Expression.Label("__default");

                    var @switch = lowered.Make(value, Expression.Goto(defaultLabel));

                    var defaultCase = Expression.Block(Expression.Label(defaultLabel), defaultBody);

                    body = Expression.IfThenElse(hasValue, @switch, defaultCase);
                }
                else
                {
                    var @switch = lowered.Make(value, null);

                    body = Expression.IfThen(hasValue, @switch);
                }
            }

            // NB: Variable scope should not include the switch value.

            var exprs = new Expression[]
            {
                assignSwitchValue,
                WithVariableScope(body),
                Expression.Label(BreakLabel),
            };

            return(Expression.Block(new TrueReadOnlyCollection <ParameterExpression>(vars), new TrueReadOnlyCollection <Expression>(exprs)));
        }
        private Expression ReduceLifted()
        {
            // TODO: More optimizations based on IsAlwaysNull and IsNeverNull are possible.

            var temps        = default(List <ParameterExpression>);
            var stmts        = default(List <Expression>);
            var notNullCheck = default(Expression);

            var left  = PrepareOperand(Left);
            var right = PrepareOperand(Right);

            var make = MakeRange(left, right);

            make = Expression.Convert(make, Type);

            if (notNullCheck != null)
            {
                make = Expression.Condition(notNullCheck, make, Expression.Default(Type));
            }

            if (temps == null)
            {
                return(make);
            }
            else
            {
                stmts.Add(make);
                return(Expression.Block(temps, stmts));
            }

            Expression PrepareOperand(Expression operand)
            {
                if (operand == null)
                {
                    return(null);
                }

                if (!operand.Type.IsNullableType())
                {
                    return(operand);
                }

                temps ??= new List <ParameterExpression>(2);
                stmts ??= new List <Expression>(2);

                var temp = Expression.Parameter(operand.Type);

                temps.Add(temp);

                stmts.Add(Expression.Assign(temp, operand));

                var hasValue = Helpers.MakeNullableHasValue(temp);

                if (notNullCheck == null)
                {
                    notNullCheck = hasValue;
                }
                else
                {
                    notNullCheck = Expression.AndAlso(notNullCheck, hasValue);
                }

                return(Helpers.MakeNullableGetValueOrDefault(temp));
            }
        }
        /// <summary>
        /// Reduces the expression node to a simpler expression.
        /// </summary>
        /// <returns>The reduced expression.</returns>
        public override Expression Reduce()
        {
            var operand = Operand;

            var temps = new List<ParameterExpression>();
            var stmts = new List<Expression>();


            //
            // NB: We can't check IsPure with the readOnly parameter set to true, because the user conversions may assign to a variable.
            //     To mitigate this, we'd have to check all element conversions to make sure they don't assign to the variable, which is
            //     very unlikely, though a hand-written conversion lambda could have such a side-effect. One could argue that a side-
            //     effecting conversion that mutates the parent tuple yields undefined behavior.
            //

            if (!Helpers.IsPure(Operand))
            {
                var operandVariable = Expression.Parameter(Operand.Type, "__t");
                
                temps.Add(operandVariable);
                stmts.Add(Expression.Assign(operandVariable, Operand));

                operand = operandVariable;
            }

            Expression res;

            if (operand.Type.IsNullableType())
            {
                var nonNullOperand = Helpers.MakeNullableGetValue(operand); // NB: Use of Nullable<T>.Value to ensure proper exception is thrown if null.
                var nonNullOperandVariable = Expression.Parameter(nonNullOperand.Type, "__nonNull");

                var args = GetConversions(nonNullOperandVariable);

                if (Type.IsNullableType())
                {
                    //
                    // T? -> U?
                    //
                    // var t = operand;
                    //
                    // if (t.HasValue)
                    // {
                    //    var v = t.Value;
                    //    return (U?)new U(...);
                    // }
                    // else
                    // {
                    //    return default(U?);
                    // }
                    //

                    var hasValueTest = Helpers.MakeNullableHasValue(operand);

                    var nullValue = Expression.Default(Type);

                    var convertedTuple = Expression.Convert(CSharpExpression.TupleLiteral(Type.GetNonNullableType(), args, argumentNames: null), Type);
                    var nonNullValue = Expression.Block(new[] { nonNullOperandVariable }, Expression.Assign(nonNullOperandVariable, nonNullOperand), convertedTuple);

                    res = Expression.Condition(hasValueTest, nonNullValue, nullValue);
                }
                else
                {
                    //
                    // T? -> U
                    //
                    // var t = operand;
                    // var v = operand.Value; // NB: May throw
                    // return new U(...);

                    temps.Add(nonNullOperandVariable);
                    stmts.Add(Expression.Assign(nonNullOperandVariable, nonNullOperand));

                    res = CSharpExpression.TupleLiteral(Type, args, argumentNames: null);
                }
            }
            else
            {
                var args = GetConversions(operand);

                var targetType = Type.GetNonNullableType();

                var convertedTuple = CSharpExpression.TupleLiteral(targetType, args, argumentNames: null);

                if (Type.IsNullableType())
                {
                    //
                    // T -> U?
                    //
                    // var t = operand;
                    // return (U?)new U(...);

                    res = Expression.Convert(convertedTuple, Type);
                }
                else
                {
                    //
                    // T -> U
                    //
                    // var t = operand;
                    // return new U(...);
                    //

                    res = convertedTuple;
                }
            }

            stmts.Add(res);

            return Helpers.Comma(temps, stmts);

            List<Expression> GetConversions(Expression operand)
            {
                var n = ElementConversions.Count;

                var args = new List<Expression>(n);

                for (int i = 0; i < n; i++)
                {
                    var conversion = ElementConversions[i];

                    var item = Helpers.GetTupleItemAccess(operand, i);

                    var conversionParameter = conversion.Parameters[0];

                    if (conversion.Body is UnaryExpression { Operand: var unaryOperand } unary && unaryOperand == conversionParameter && IsConvert(unary.NodeType))
                    {
                        args.Add(unary.Update(item));
                    }