Skip to content

Commit 8a24ca8

Browse files
authored
clarify identifier scope, allow bare Identifiers, allow Lists and Maps (#2094)
Attempt to close #2093, and #2092. Related to #2012. - Adds `Identifier`, `List`, and `Map` as valid parameter types for macro constructors (and thus valid arguments for macro applications). - List and Map are allowed to have type arguments that are any of the supported types. This allows for `List<Identifier>`, etc. - Specify the scope for identifiers in macro application arguments better (both bare and in code objects). - Some other unrelated cleanup (can remove if desired). - Fixed up some old links - Removed the section on `Fragment` (you can just use `Code` for this now).
1 parent 2f8b466 commit 8a24ca8

File tree

1 file changed

+96
-33
lines changed

1 file changed

+96
-33
lines changed

working/macros/feature-specification.md

Lines changed: 96 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -89,7 +89,7 @@ more of the macro interfaces, then the annotation is treated as an application
8989
of the `myCoolMacro` macro to the class MyClass.
9090

9191
Macro applications can also be passed arguments, either in the form of
92-
[Code][] expressions or certain types of literal values. See
92+
[Code][] expressions, [Identifier][]s, or certain types of literal values. See
9393
[Macro Arguments](#Macro-arguments) for more information on how these arguments
9494
are handled when executing macros.
9595

@@ -123,6 +123,37 @@ Most of the time, like here, a macro takes the arguments you pass it and
123123
interpolates them back into code that it generates, so passing the arguments as
124124
code is what you want.
125125

126+
### Identifier arguments
127+
128+
If you want to be able to introspect on an identifier passed in to you, you can
129+
do that as well, consider the following:
130+
131+
```dart
132+
@GenerateSerializers(MyType)
133+
library my.library;
134+
135+
class MyType {
136+
final String myField;
137+
138+
MyType({required this.myField});
139+
}
140+
141+
/// Generated by introspecting on the fields of [MyType].
142+
class MyTypeSerializer implements Serializer<MyType> {
143+
Map<String, Object?> serialize(MyType instance) => {
144+
'myField': instance.myField,
145+
};
146+
}
147+
148+
class MyTypeDeserializer implements Deserializer<MyType> {
149+
MyType deserialize(Map<String, Object> json) =>
150+
MyType(myField: json['myField'] as String);
151+
}
152+
```
153+
154+
Here the macro takes an `Identifier` argument, and introspects on it to know
155+
how to generate the desired serialization and deserialization classes.
156+
126157
### Value arguments
127158

128159
Sometimes, though, the macro wants to receive an actual argument value. For
@@ -466,18 +497,20 @@ This does have two interesting and possibly unexpected consequences:
466497
Macros attach new code to the declaration the macro is applied to by calling
467498
methods on the builder object given to the macro. For example, a
468499
declaration-phase macro applied to a class declaration is given a
469-
[ClassDeclarationBuilder]. That class exposes an [`addToClass()`][addtoclass]
470-
method that adds the given code to the class as a new member.
500+
[ClassMemberDeclarationBuilder]. That class exposes a
501+
[`declareInClass()`][declareInClass] method that adds the given code to the
502+
class as a new member.
471503

472-
[addtoclass]: https://jakemac53.github.io/macro_prototype/doc/api/definition/ClassDeclarationBuilder/addToClass.html
504+
[ClassMemberDeclarationBuilder]: https://github.com/dart-lang/sdk/blob/main/pkg/_fe_analyzer_shared/lib/src/macros/api/builders.dart#L93
505+
[declareInClass]: https://github.com/dart-lang/sdk/blob/main/pkg/_fe_analyzer_shared/lib/src/macros/api/builders.dart#L95
473506

474507
The code itself is an instance of a special [Code][] class (or one of its
475508
subclasses). This is a first-class object that represents a well-formed piece of
476509
Dart code. We use this instead of bare strings containing Dart syntax because a
477510
code object carries more than just the bare Dart code. In particular, it keeps
478511
track of how identifiers in the code are resolved.
479512

480-
[Code]: https://jakemac53.github.io/macro_prototype/doc/api/definition/Code-class.html
513+
[Code]: https://github.com/dart-lang/sdk/blob/main/pkg/_fe_analyzer_shared/lib/src/macros/api/code.dart#L9
481514

482515
Also, when code objects are creating by combining fragments of other code (for
483516
example arguments to macros), the resulting code object may keep track of the
@@ -512,21 +545,6 @@ of a given pieces of syntax.
512545
**TODO**: We are considering exposing more properties on Code objects to allow
513546
introspection (#1933).
514547

515-
### Fragments
516-
517-
Sometimes when building a piece of Dart code for a macro's output, it's
518-
convenient to work with fragments of Dart code that are not themselves valid
519-
complete pieces of Dart syntax. For example, imagine a macro that wraps a given
520-
expression in `{ return ` and `; }`. Those fragments that are prepended and
521-
appended around the expression are not valid standalone productions in the Dart
522-
grammar.
523-
524-
To represent those, we also have a [Fragment][] class. Fragments work more like
525-
unparsed strings. They can be concatenated and composed to produce a Code object
526-
when the result is valid Dart syntax.
527-
528-
[Fragment]: https://jakemac53.github.io/macro_prototype/doc/api/definition/Fragment-class.html
529-
530548
### Identifiers and resolution
531549

532550
The classic problem in macro systems since they were first invented in Lisp and
@@ -657,6 +675,8 @@ that top level declaration and insert that into the generated code.
657675

658676
**TODO: Define this API. See [here](https://github.com/dart-lang/language/pull/1779#discussion_r683843130).**
659677

678+
[Identifier]: https://github.com/dart-lang/sdk/blob/main/pkg/_fe_analyzer_shared/lib/src/macros/api/introspection.dart#L15
679+
660680
### Generating macro applications
661681

662682
Since macros are regular Dart code and classes, one macro can instantiate and
@@ -788,8 +808,8 @@ In a sandbox environment or isolate, create an instance of the corresponding
788808
macro class for each macro application. Pass in any macro application arguments
789809
to the macro's constructor. If a parameter's type is `Code` or a subclass,
790810
convert the argument expression to a `Code` object. Any bare identifiers in the
791-
argument expression are converted to `Identifier` instances whose scope is the
792-
library of the macro application.
811+
argument expression are converted to `Identifier` (see
812+
[Identifier Scope](#Identifier-Scope) for scoping information).
793813

794814
Run all of the macros in phase order:
795815

@@ -885,23 +905,35 @@ Each argument in the metadata annotation for the macro application is converted
885905
to a form that the corresponding constructor on the macro class expects, which
886906
it specifies through parameter types:
887907

888-
* If the parameter type is `bool`, `double`, `int`, `Null`, `num`, or `String`
889-
(or the nullable forms of any of those), then the argument expression must
890-
be a Boolean, number, null, or string literal. Number literals may be
891-
negated. String literals may not contain any interpolation, but may be
892-
adjacent strings.
908+
* If the parameter type is `bool`, `double`, `int`, `Null`, `num`, `String`,
909+
`List`, `Set`, or `Map`, (or the nullable forms of any of those), then the
910+
argument expression must be a boolean, number, null, string, list, set, or
911+
map literal.
912+
913+
* Number literals may be negated.
914+
* String literals may not contain any interpolation, but may be adjacent
915+
strings, and may be raw strings.
916+
* List, Set and Map literals may only contain entries matching any of the
917+
supported argument types. If the parameter type specifies a generic type
918+
argument, it must be one of the allowed parameter types or `Object`,
919+
recursively. Note that `Object` is allowed in order to exclude null items,
920+
but all the actual entries must be of one of the supported types.
893921

894922
**TODO**: Do we want to allow more complex expressions? Could we allow
895923
constant expressions whose identifiers can be successfully resolved before
896924
macro expansion (#1929)?
897925

898-
* Else, the argument expression is automatically converted to an object of
899-
type [Code][] representing the unevaluated expression.
926+
* If the parameter type is `Code` (or a subtype of `Code`), the argument
927+
expression is automatically converted to a corresponding `Code` instance.
928+
These provided code expressions may contain identifiers.
900929

901-
Note that this implicit lifting of the argument expression only happens when
902-
the macro constructor is invoked through a macro application. If a macro
903-
class is directly constructed in Dart (for example, in test code for the
904-
macro), then the caller is responsible for creating the Code object.
930+
* If the parameter type is `Identifier` then a single identifier must be
931+
passed, and it will be converted to a corresponding `Identifier` instance.
932+
933+
Note that this implicit lifting of the argument expression only happens when
934+
the macro constructor is invoked through a macro application. If a macro
935+
class is directly constructed in Dart (for example, in test code for the
936+
macro), then the caller is responsible for creating the Code object.
905937

906938
As usual, it is a compile-time error if the type of any argument value (which
907939
may be a Code object) is not a subtype of the corresponding parameter type.
@@ -910,6 +942,37 @@ It is a compile-time error if an macro class constructor invoked by a macro
910942
application has a parameter whose type is not Code (or any subtype of it) or
911943
one of the aforementioned primitive types (or a nullable type of any of those).
912944

945+
#### Identifier Scope
946+
947+
The following rules apply to any `Identifier` passed as an argument to a macro
948+
application, whether as a part of a `Code` expression or directly as an
949+
`Identifier` instance.
950+
951+
The scope of any `Identifier` argument is the same as the scope in which the
952+
identifier appears in the source code, which is the same as the argument scope
953+
for a metadata annotation on a declaration. This means:
954+
955+
* Identifiers in macro application arguments may only refer to static and top
956+
level members.
957+
* They cannot refer to local or instance variables, as those can never be in scope
958+
where a macro application appears.
959+
* Identifiers referring to static class members may be unqualified if the
960+
annotation appears on a member of that class.
961+
* For qualified references, only the unqualified name is visible to the macro.
962+
When the identifier is interpolated into an augmentation library, it may be
963+
converted back into a fully qualified reference if needed (although the
964+
prefix may change, or a prefix may be added). This means all of the
965+
following examples are supported, and for each you would only see `myMember`
966+
as the `name` of the identifier:
967+
- `@MyMacro(some_prefix.myMember)`
968+
- `@MyMacro(SomeClass.myMember)`
969+
- `@MyMacro(some_prefix.SomeClass.myMember)`
970+
971+
All identifiers passed to macro constructors must resolve to a real declaration
972+
by the time macro expansion has completed. They may resolve to generated
973+
identifiers, including ones generated by the macro they were passed to,
974+
although that design may be inadvisable.
975+
913976
### Runtime environment
914977

915978
Since macros are executed at compile time directly inside the compiler, they run

0 commit comments

Comments
 (0)