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)); }
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); }