ExpressionTreeToString icon indicating copy to clipboard operation
ExpressionTreeToString copied to clipboard

ConstantExpression in scope

Open zspitz opened this issue 6 years ago • 3 comments

Expressions that use fields within a class:

class Foo {
    string bar;
    Expression<Func<string>> GetExpression() => () => bar;
}

are generated as:

LambdaExpression
    - Body: MemberExpression<string>
        - Expression: Constant<Foo>

which when called with the ToString extension method looks like this:

() => #Foo.bar

What is happening is that the access on bar has an implicit reference to this, or the instance of Foo. But the compiler allows us to elide this in the original code -- instead of this.bar, we can write bar.

AFAICT there is no way to tell when the instance is in the implicit scope, and therefore doesn't require explicitly rendering the ConstantExpression corresponding to the instance. IOW, just because something is a ConstantExpression, doesn't automatically mean its unreferenced members are in scope, e.g. the following:

// using System.Linq.Expressions;
// using static System.Linq.Expressions.Expression;
Expression expr = MakeMemberAccess(
    Constant(new List<object>()), 
    typeof(List<object>).GetMember("Count").First()
);

should render the ConstantExpression as #List<object>.Count (and not just Count), because the List<object> is not part of the current scope.

The implementation for closed-over variables is similar -- there is a hidden class which accompanies the delegate. However, that case is relatively easy to detect, because the type has the CompilerGenerated attribute on it, and also has specific strings in its name:

    public static bool IsClosureClass(this Type type) =>
        type.HasAttribute<CompilerGeneratedAttribute>() && type.Name.ContainsAny("DisplayClass", "Closure$");

zspitz avatar Feb 04 '19 14:02 zspitz

https://stackoverflow.com/q/54738246/111794

zspitz avatar Mar 12 '19 06:03 zspitz

@dadhi, Could I ask for your advice on this?

zspitz avatar Nov 01 '20 20:11 zspitz

@zspitz Hello,

The bound constants is the hard topic, so again the handing depends on your goals.

As I wanted the compile-able output out-of-the-box, I am properly outputting the known primitive literals (not-bound constants) and outing the default(FooBar) for the rest. The only improvement would be to put the comment for the user to inform about those things, e.g.:

  New(fooCtor,
      Constant(default(Bar)) // todo: the `Bar` should be created by the user
  )

The same for the C# output.

dadhi avatar Nov 02 '20 06:11 dadhi