protected Expression ReduceSingle(ParameterExpression variable, Expression resource, Expression body, HashSet <ParameterExpression> declaredVariables)
        {
            var madeTempVariable = false;

            void MakeTempIfNull(ref ParameterExpression variable, Type type)
            {
                if (variable == null)
                {
                    variable         = Expression.Parameter(type, "__resource");
                    madeTempVariable = true;
                }
                else
                {
                    declaredVariables.Add(variable);
                }
            }

            Expression CallPatternDispose(Expression nonNullResource)
            {
                return(ParameterSubstitutor.Substitute(PatternDispose.Body, PatternDispose.Parameters[0], nonNullResource));
            }

            Expression cleanup;
            var        checkNull = false;

            var resourceType = resource.Type;

            if (resourceType.IsValueType)
            {
                MakeTempIfNull(ref variable, resourceType);

                Expression variableValue;

                if (resourceType.IsNullableType())
                {
                    variableValue = Helpers.MakeNullableGetValueOrDefault(variable);
                    checkNull     = true;
                }
                else
                {
                    variableValue = variable;
                }

                if (PatternDispose != null)
                {
                    cleanup = CallPatternDispose(variableValue);
                }
                else
                {
                    var disposeMethod = variableValue.Type.FindDisposeMethod(IsAsync);
                    cleanup = Expression.Call(variableValue, disposeMethod);
                }
            }
            else
            {
                var disposableInterface = IsAsync ? typeof(IAsyncDisposable) : typeof(IDisposable);

                MakeTempIfNull(ref variable, disposableInterface);

                // NB: This optimization would be more effective if the expression compiler would emit a `call` instruction,
                //     but the JIT may still optimize it if it realizes the `callvirt` to the resource is predicated by a
                //     prior null check.
                var variableType = variable.Type;

                if (PatternDispose != null)
                {
                    cleanup = CallPatternDispose(variable);
                }
                else
                {
                    var disposeMethod =
                        variableType.IsSealed
                            ? variableType.FindDisposeMethod(IsAsync)
                            : (IsAsync ? DisposeAsyncMethod : DisposeMethod);

                    cleanup = Expression.Call(variable, disposeMethod);
                }

                checkNull = true;
            }

            if (IsAsync)
            {
                cleanup = Await(cleanup);
            }

            if (checkNull)
            {
                cleanup =
                    Expression.IfThen(
                        Expression.NotEqual(variable, Expression.Constant(null, variable.Type)),
                        cleanup
                        );
            }

            ParameterExpression temp = default;
            Expression          innerResource;

            if (!madeTempVariable)
            {
                // NB: Resource could contain a reference to Variable that needs to be bound in the
                //     enclosing scope. This isn't possible to write in C# due to scoping rules for
                //     variables, but it's valid in the LINQ APIs in general.
                //
                //                          +-------------+
                //                          v             |
                //       Block({ x }, Using(x, R(x), Call(x, foo)))
                //               ^               |
                //               +---------------+
                //
                //     If we're not careful about scoping, we could end up creating:
                //
                //                          +-------------+
                //                          v             |
                //       Block({ x }, Using(x, R(x), Call(x, foo)))
                //                          ^    |
                //                          +----+
                //
                //     So we rewrite the whole thing by adding another temporary variable:
                //
                //                                                        +----------+
                //                                                        v          |
                //       Block({ x }, Block({ t }, Assign(t, R(x)), Using(x, t, Call(x, foo))))
                //               ^                             |
                //               +-----------------------------+
                //
                // NB: We could do a scope tracking visit to Resource to check whether the variable
                //     is being referred to. For now, we'll just apply the additional assignment all
                //     the time, but we could optimize this later (or we could invest in a /o+ type
                //     of flag for all of the expression APIs, so we can optimize the user's code as
                //     well when it exhibits patterns like this; additional Blocks seems common when
                //     generating code from extension nodes of a higher abstraction kind).

                temp          = Expression.Parameter(variable.Type, "__temp");
                innerResource = temp;
            }
            else
            {
                innerResource = resource;
            }

            var res =
                Expression.Block(
                    new[] { variable },
                    Expression.Assign(variable, innerResource),
                    Expression.TryFinally(
                        body,
                        cleanup
                        )
                    );

            if (temp != null)
            {
                // NB: See remarks above for an explation of the need for this additional scope.

                res =
                    Expression.Block(
                        new[] { temp },
                        Expression.Assign(temp, resource),
                        res
                        );
            }

            return(res);
        }
        /// <summary>
        /// Reduces the expression node to a simpler expression.
        /// </summary>
        /// <returns>The reduced expression.</returns>
        protected override Expression ReduceCore()
        {
            var variable  = default(ParameterExpression);
            var cleanup   = default(Expression);
            var checkNull = false;

            var resourceType = Resource.Type;

            if (resourceType.IsValueType)
            {
                var variableValue = default(Expression);

                if (resourceType.IsNullableType())
                {
                    variable      = Variable ?? Expression.Parameter(resourceType, "__resource");
                    variableValue = Helpers.MakeNullableGetValueOrDefault(variable);
                    checkNull     = true;
                }
                else
                {
                    variable      = Variable ?? Expression.Parameter(resourceType, "__resource");
                    variableValue = variable;
                }

                var disposeMethod = variableValue.Type.FindDisposeMethod();
                cleanup = Expression.Call(variableValue, disposeMethod);
            }
            else
            {
                variable = Variable ?? Expression.Parameter(typeof(IDisposable), "__resource");

                // NB: This optimization would be more effective if the expression compiler would emit a `call` instruction,
                //     but the JIT may still optimize it if it realizes the `callvirt` to the resource is predicated by a
                //     prior null check.
                var variableType  = variable.Type;
                var disposeMethod = variableType.IsSealed ? variableType.FindDisposeMethod() : typeof(IDisposable).GetMethod(nameof(IDisposable.Dispose));
                cleanup = Expression.Call(variable, disposeMethod);

                checkNull = true;
            }

            if (checkNull)
            {
                cleanup =
                    Expression.IfThen(
                        Expression.NotEqual(variable, Expression.Constant(null, variable.Type)),
                        cleanup
                        );
            }

            var temp     = default(ParameterExpression);
            var resource = default(Expression);

            if (variable == Variable)
            {
                // NB: Resource could contain a reference to Variable that needs to be bound in the
                //     enclosing scope. This isn't possible to write in C# due to scoping rules for
                //     variables, but it's valid in the LINQ APIs in general.
                //
                //                          +-------------+
                //                          v             |
                //       Block({ x }, Using(x, R(x), Call(x, foo)))
                //               ^               |
                //               +---------------+
                //
                //     If we're not careful about scoping, we could end up creating:
                //
                //                          +-------------+
                //                          v             |
                //       Block({ x }, Using(x, R(x), Call(x, foo)))
                //                          ^    |
                //                          +----+
                //
                //     So we rewrite the whole thing by adding another temporary variable:
                //
                //                                                        +----------+
                //                                                        v          |
                //       Block({ x }, Block({ t }, Assign(t, R(x)), Using(x, t, Call(x, foo))))
                //               ^                             |
                //               +-----------------------------+
                //
                // NB: We could do a scope tracking visit to Resource to check whether the variable
                //     is being referred to. For now, we'll just apply the additional assignment all
                //     the time, but we could optimize this later (or we could invest in a /o+ type
                //     of flag for all of the expression APIs, so we can optimize the user's code as
                //     well when it exhibits patterns like this; additional Blocks seems common when
                //     generating code from extension nodes of a higher abstraction kind).

                temp     = Expression.Parameter(variable.Type, "__temp");
                resource = temp;
            }
            else
            {
                resource = Resource;
            }

            var res =
                Expression.Block(
                    new[] { variable },
                    Expression.Assign(variable, resource),
                    Expression.TryFinally(
                        Body,
                        cleanup
                        )
                    );

            if (temp != null)
            {
                // NB: See remarks above for an explation of the need for this addition scope.

                res =
                    Expression.Block(
                        new[] { temp },
                        Expression.Assign(temp, Resource),
                        res
                        );
            }

            return(res);
        }
        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()
        {
            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;
            }
        }
Exemple #5
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)));
        }