internal CatchBlock Reduce(Action <ParameterExpression> hoistVariable)
        {
            // NB: System.Linq.Expressions does not support locals that cross the filter and the body of a CatchBlock, so we have to
            //     hoist these variables to a surrounding scope. In order to avoid shadowing other uses of the same variable object,
            //     e.g. in the try block, in another catch block, or in the finally block, we perform variable substitution in the
            //     filter and the body expression in order to guarantee that the hoisted variable is unique.
            //
            //     For example:
            //
            //       Block({ a }, {
            //         Try { /* use a */ }
            //         Catch (e) When (e is A a) { /* use a */ }
            //       })
            //
            //     If we hoist 'a' from the 'When' clause to a new block surrounding 'Try', it will shadow the 'a' from the outer
            //     block and change the meaning of the try block's usage of 'a'.
            //
            //       Block({ a }, {
            //         Block({ a }, {                               // BUG
            //           Try { /* use a */ }                        // BUG
            //           Catch (e) When (e is A a) { /* use a */ }
            //         })
            //       })
            //
            //     Instead, we substitute the variables associated with the Catch block using new ones which are thus guaranteed
            //     not to be used elsewhere and are safe for hoisting.
            //
            //       Block({ a }, {
            //         Block({ t }, {                               // FIX
            //           Try { /* use a */ }                        // OKAY, refers to the original 'a'
            //           Catch (e) When (e is A t) { /* use t */ }  // FIX, we introduce 't' in the catch clause subexpressions
            //         })
            //       })

            var filter = Filter;
            var body   = Body;

            if (Variables.Count > 0)
            {
                var variablesToRename = new Dictionary <ParameterExpression, Expression>();

                foreach (var variable in Variables)
                {
                    if (variable != Variable) // NB: Variable gets its own scope by CatchBlock so does not need hosting.
                    {
                        var newVariable = Expression.Parameter(variable.Type, variable.Name);
                        variablesToRename.Add(variable, newVariable);
                        hoistVariable(newVariable);
                    }
                }

                body   = ParameterSubstitutor.Substitute(body, variablesToRename);
                filter = ParameterSubstitutor.Substitute(filter, variablesToRename);
            }

            return(Expression.MakeCatchBlock(Test, Variable, body, filter));
        }
예제 #2
0
        public void ParameterSubstitutor_Basics()
        {
            var x = Expression.Parameter(typeof(int));
            var y = Expression.Parameter(typeof(int));

            var e1 = Expression.Add(x, Expression.Constant(1));
            var r1 = (BinaryExpression)ParameterSubstitutor.Substitute(e1, x, y);

            Assert.AreSame(y, r1.Left);

            var e2 = Expression.Block(new[] { x }, x);
            var r2 = (BlockExpression)ParameterSubstitutor.Substitute(e2, x, y);

            Assert.AreSame(x, r2.Expressions[0]);

            var e3 = Expression.Block(Expression.Add(x, Expression.Constant(3)));
            var r3 = (BinaryExpression)((BlockExpression)ParameterSubstitutor.Substitute(e3, x, y)).Expressions[0];

            Assert.AreSame(y, r3.Left);
        }
        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);
        }