Skip to content

Macro argument scopes (specifically, Code objects), and augmentation libraries #2012

Closed
@jakemac53

Description

@jakemac53

We want to be able to model the Macro output in terms of an augmentation library, and in general we want the code output by macros to not have special powers.

At the same time, we allow Code arguments to macro constructors, and we want the scope of that code to be essentially the same scope as the macro annotation. However, we also possibly want to be able to refer to local variables within those expressions.

Example:

abstract class Thing {
  int get x;
  int get y;
  
  @MemoizedVar(x + y) // `x` and `y` here are local variables. 
  int get z;
}

But what if z is a function, and it has parameters named x or y? We need to clarify whether the macro arguments scope is that of the body of the declaration it annotates, or the class, or something else more custom.

Some factors to be taken into consideration:

  • It might be useful for a macro arguments to be able to access the arguments to a function.
  • A macro on z can emit code for declarations outside the scope of the body of z (for instance it can add fields and methods to the surrounding class and/or library, or add whole new classes).
  • Macros may annotate static methods/fields on classes.

There are a few possible solutions I have been considering to this:

Solution 1: No access to local variables or params in macro argument expressions

The scope is the lexical scope of the body of the class, static members of the class are visible (possibly without being qualified by ClassName.?), but no local variables.

This could be modeled by generating a static getter next to the original annotated declaration:

library augment 'original.dart';

augment class Thing {
  static  get _$someGeneratedName => x + y;
}

Pros

  • Easiest to define.
  • Valid anywhere in the library (may need some munging to add the qualified ClassName. in front, or just require it).

Cons

  • Very limiting, can't access parameters or local variables.

Solution 2: Its own lexical scope, as if the expression was inside the body of a new declaration

We could model this in the same way as Solution 1, except the getter wouldn't always be static - it would only be static if the annotated declaration was static.

Pros

  • I think this mostly behaves how users would expect, the scope is essentially the same as the annotated declaration.
  • Allows access to local variables.

Cons

  • Possibly a bit magical, might be unexpected that expressions in macro annotations on static fields have a different scope.
  • These expressions cannot be used in parent scopes (unless the annotated declaration was static).

Solution 3: The scope is the same as the scope at the top of the body of the annotated declaration.

We could again model this in essentially same way, except with a local function at the top of the body of the annotated declaration.

library augment 'original.dart';

augment class Thing {
  int get z {
    // Note that the `Code` object in this case would have to be a function invocation not a reference to a getter.
    _$someGeneratedName() => x + y;
    // Rest of the generated code here (should contain an `augment super`).
  }
}

Pros

  • Has access to parameters as well as local variables, could be useful.

Cons

  • Breaks lexical scoping, the expression looks like normal code and lives outside the body of the declaration, so it likely isn't intuitive that the parameters are available.
  • The expression is only usable within the annotated declaration (can't be used even on other declarations in the class).

Metadata

Metadata

Assignees

No one assigned

    Labels

    augmentationsIssues related to the augmentations proposal.static-metaprogrammingIssues related to static metaprogramming

    Type

    No type

    Projects

    Status

    Done

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions