diff --git a/working/macros/feature-specification.md b/working/macros/feature-specification.md index dc768b58d4..0e906a5b59 100644 --- a/working/macros/feature-specification.md +++ b/working/macros/feature-specification.md @@ -150,19 +150,22 @@ Macros are applied to declarations using the existing metadata annotation syntax. For example: ```dart -@myCoolMacro +@MyCoolMacro() class MyClass {} ``` -Here, if `myCoolMacro` resolves to an instance of a class implementing one or -more of the macro interfaces, then the annotation is treated as an application -of the `myCoolMacro` macro to the class MyClass. +Here, if the `MyCoolMacro` type is a `macro class`, then the annotation is +treated as an application of the `MyCoolMacro()` macro to the class MyClass. Macro applications can also be passed arguments, either in the form of [Code][] expressions, [TypeAnnotation][]s, or certain types of literal values. See [Macro Arguments](#Macro-arguments) for more information on how these arguments are handled when executing macros. +Macro applications must always be constructor invocations. It is an error to +annotate a declaration with a constant reference to a macro instance. In the +future we may explore a more concise macro application syntax. + ### Code Arguments Consider this example macro application: @@ -257,25 +260,25 @@ order of macros: example: ```dart - @third - @second - @first + @Third() + @Second() + @First() class C {} ``` - Here, the macros applied to C are run `first`, `second`, then `third`. + Here, the macros applied to C are run `First()`, `Second()`, then `Third()`. * **Macros are applied to superclasses, mixins, and interfaces first, in** **Phase 2** For example: ```dart - @third + @Third() class B extends A with C implements D {} - @second + @Second() class A implements C {} - @first + @First() class C {} @first @@ -415,22 +418,22 @@ ordering problem to discuss. Imagine you have these two classes for tracking pets and their humans: ```dart -@jsonSerializable +@JsonSerializable() class Human { final String name; final Pet? pet; // Optional, might not have a pet. } -@jsonSerializable +@JsonSerializable() class Pet { final String name; final Owner? owner; // Optional, might be feral. } ``` -You want to be able to save these to the cloud, so you use a `@jsonSerializable` -macro that generates a `toJson()` method on each class the macro is applied to. -You want the methods to look like this: +You want to be able to save these to the cloud, so you use a +`@JsonSerializable()` macro that generates a `toJson()` method on each class the +macro is applied to. You want the methods to look like this: ```dart class Human { @@ -451,10 +454,10 @@ class Pet { ``` Note that the `pet` and `owner` fields are serialized by recursively calling -their `toJson()` methods. To generate that code, the `@jsonSerializable` macro +their `toJson()` methods. To generate that code, the `@JsonSerializable()` macro needs to look at the type of each field to see if it declares a `toJson()` method. The problem is that there is *no* order of macro application that will -give the right result. If we apply `@jsonSerializable` to Human first, then it +give the right result. If we apply `@JsonSerializable()` to Human first, then it won't call `toJson()` on `pet` because Pet doesn't have a `toJson()` method yet. We get the opposite problem if we apply the macro to Pet first. @@ -621,7 +624,7 @@ With macros, many of those metadata annotations would instead either *become* macros or be *read* by them. The latter means that macros also need to be able to introspect over non-macro metadata annotations applied to declarations. -For example, a `@jsonSerialization` class macro might want to look for an +For example, a `@JsonSerialization()` class macro might want to look for an `@unseralized` annotation on fields to exclude them from serialization. **TODO**: The following subsections read more like a design discussion that a @@ -788,7 +791,7 @@ user-written code is clear. Consider the following example: ```dart int get x => 1; -@generateX +@GenerateX() class Bar { // Generated: int get x => 2; @@ -912,19 +915,19 @@ Both of these mechanisms allow for normal macro ordering to be circumvented. Consider the following example, where all macros run in the Declaration phase: ```dart -@macroA -@macroB +@MacroA() +@MacroB() class X { - @macroC // Added by `@macroA`, runs after both `@macroB` and `@macroA` + @MacroC() // Added by `@MacroA()`, runs after both `@MacroB()` and `@MacroA()` int? a; - // Generated by `@macroC`, not visible to `@macroB`. + // Generated by `@MacroC()`, not visible to `@MacroB()`. int? b; } ``` -Normally, macros always run "inside-out". But in this case `@macroC` runs after -both `@macroB` and `@macroA` which were applied to the class. +Normally, macros always run "inside-out". But in this case `@MacroC()` runs after +both `@MacroB()` and `@MacroA()` which were applied to the class. We still allow this because it doesn't cause any ambiguity in ordering, even though it violates the normal rules. We could instead only allow adding macros @@ -998,23 +1001,24 @@ their libraries. At this point, you have a set of mutually interdependent libraries. They may contain references to declarations that don't exist because macros have yet to produce them. -Collect all the metadata annotations whose names can be resolved and that -resolve to macro classes. Report an error if any application refers to a macro -declared in this cycle. +Collect all the metadata annotations which are constructor invocations of +macro classes. Report an error if any application refers to a macro declared in +this cycle, or if any annotation is a reference to a const instance of a macro +class (they must be explicit constructor invocations). -**TODO**: The above resolution rules may change based on +**NOTE**: We may in the future allow constant references to macros in +annotations, or something similar to that, see discussion in https://github.com/dart-lang/language/issues/1890. #### 3. Apply macros -In a sandbox environment or isolate, create an instance of the corresponding -macro class for each macro application. Pass in any macro application arguments -to the macro's constructor. If a parameter's type is `Code` or a subclass, -convert the argument expression to a `Code` object. Any bare identifiers in the -argument expression are converted to `Identifier` (see -[Identifier Scope](#Identifier-Scope) for scoping information). +In a sandbox environment, likely a separate isolate or process, create an +instance of the corresponding macro class for each macro application. See +[Executing macros](#Executing-macros) for more explanation of how macros are +constructed and how their arguments are handled. -Run all of the macros in phase order: +Run all of the macros in phase order (see also +[Application order](#Application-order) for ordering within each phase): 1. Invoke the corresponding visit method for all macros that implement phase 1 APIs. @@ -1095,14 +1099,14 @@ are ready to be loaded and executed when applied in libraries in later cycles. ## Executing macros To apply a macro, a Dart compiler constructs an instance of the applied macro's -class and then invokes methods that implement macro API interfaces. The macro is -a full-featured Dart program with complete access to the entire Dart language. -Macros are Turing-complete. +class and then invokes methods that implement macro API interfaces. Then it +disposes of the macro instance. Typically this is all done in a separate isolate +or process from the compiler itself. -### Macro arguments +Macros are full-featured Dart programs with complete access to the entire Dart +language (but limited access to core libraries). Macros are Turing-complete. -**TODO**: How are metadata annotations that refer to constant objects handled -(#1890)? +### Macro arguments Each argument in the metadata annotation for the macro application is converted to a form that the corresponding constructor on the macro class expects, which