Skip to content

Commit d03dbe9

Browse files
authored
specify the annotation introspection API and constant evaluation API (#3408)
Updates to annotation introspection based on recent discussions, and in anticipation of changing the rules around shadowing. The primary concrete change here is that we only allow access to identifiers/constructors from separate strongly connected components. This works around any issues with the constants changing between phases. I decided not to try and restrict the API to code objects that came from annotations, so this does enable general purpose const evaluation, but it doesn't seem any more problematic to me than allowing the evaluation of const expressions from annotations. Please correct me if I am wrong :).
1 parent 80072c1 commit d03dbe9

File tree

4 files changed

+54
-36
lines changed

4 files changed

+54
-36
lines changed

working/macros/aspect-macros.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,7 @@ identifier for the resulting declaration.
5858
### Applying an Aspect Macro
5959

6060
Aspect Macros can only be applied by other macros (including aspect macros),
61-
while the macro is running. They do this through the following api:
61+
while the macro is running. They do this through the following API:
6262

6363
```dart
6464
Future<Identifier> applyAspect(AspectMacro macro, Declaration declaration);
@@ -110,7 +110,7 @@ macro class FromJson extends ClassDeclarationsAspectMacro {
110110
- The serializable aspect is enforced by not permitting Aspect Macros to have
111111
any fields, and they only have a single const constructor with no arguments.
112112
- **TODO**: Should we make this constructor explicit?
113-
- **TODO**: Should the api only take the Type of the aspect instead of an
113+
- **TODO**: Should the API only take the Type of the aspect instead of an
114114
instance?
115115

116116
### Ordering of Aspect Macros

working/macros/example/benchmark/src/shared.dart

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
// There is no public API exposed yet, the in progress api lives here.
1+
// There is no public API exposed yet, the in progress API lives here.
22
import 'package:_fe_analyzer_shared/src/macros/api.dart';
33
import 'package:_fe_analyzer_shared/src/macros/executor/introspection_impls.dart';
44
import 'package:_fe_analyzer_shared/src/macros/executor/remote_instance.dart';

working/macros/feature-specification.md

Lines changed: 49 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -87,7 +87,7 @@ There are two things you can do with an `OmittedTypeAnnotation`:
8787
file as the macro annotation, so they can always do this.
8888
- When the final augmentation library is created, the actual type that was
8989
inferred will be used (or `dynamic` if no type was inferred).
90-
- Explicitly ask to infer the type of it through the builder apis (only
90+
- Explicitly ask to infer the type of it through the builder APIs (only
9191
available in phase 3).
9292
- We don't allow augmentations of existing declarations to contribute to
9393
inference, so in phase 3 type inference can be performed.
@@ -658,9 +658,9 @@ constructors are invoked, and their limitations.
658658
types in the user code instantiating the macro are not necessarily present
659659
in the macros own transitive imports.
660660

661-
*Note: The Macro API is still being designed, and lives [here][api].*
661+
*Note: The Macro API is still being designed, and lives [here][API].*
662662

663-
[api]: https://github.com/dart-lang/sdk/blob/main/pkg/_fe_analyzer_shared/lib/src/macros/api.dart
663+
[API]: https://github.com/dart-lang/sdk/blob/main/pkg/_fe_analyzer_shared/lib/src/macros/api.dart
664664

665665
### Writing a Macro
666666

@@ -716,7 +716,7 @@ return results. For instance when generating a constructor, a macro will likely
716716
just iterate over the fields and create a parameter for each.
717717

718718
We need generated augmentations to be identical on all platforms for all the
719-
same inputs, so we need to have a defined ordering when introspection apis are
719+
same inputs, so we need to have a defined ordering when introspection APIs are
720720
returning lists of declarations.
721721

722722
Therefore, whenever an implementation is returning a list of declarations, they
@@ -736,41 +736,59 @@ to introspect over non-macro metadata annotations applied to declarations.
736736
For example, a `@JsonSerialization()` class macro might want to look for an
737737
`@unseralized` annotation on fields to exclude them from serialization.
738738

739-
**TODO**: The following subsections read more like a design discussion that a
740-
proposal. Figure out what we want to do here and rewrite (#1930).
739+
Some macros may need to evaluate the real values of metadata arguments, while
740+
others may only need the ability to emit that same code back into the program.
741741

742742
#### The annotation introspection API
743743

744-
We could try to give users access to an actual instance of the annotation, or
745-
we could give something more like the [DartObject][] class from the analyzer.
744+
All declarations which can be annotated will have an
745+
`Iterable<MetadataAnnotation> get metadata` getter.
746746

747-
[DartObject]: https://pub.dev/documentation/analyzer/latest/dart_constant_value/DartObject-class.html
747+
All `MetadataAnnotation` objects have a `Code get code` getter, which gives
748+
access to the annotation as a `Code` object.
749+
750+
In addition, there will be two subtypes of `MetadataAnnotation`:
751+
752+
- `IdentifierMetadataAnnotation`: A simple const identifier, has a single
753+
`Identifier get identifier` getter.
754+
- `ConstructorMetadataAnnotation`: A const constructor invocation. This will
755+
have the following getters:
756+
- `Identifier get type`
757+
- `Identifier get constructor`
758+
- `Arguments get arguments`
759+
- The `Arguments` class will provide access to the positional and named
760+
arguments as separate `Code` objects.
748761

749-
Since annotations may contain references to types or identifiers that the macro
750-
does not import, we choose to expose a more abstract API (similar to
751-
[DartObject][]).
762+
For any macro which only wants to emit code from annotations back into the
763+
program, these `Code` objects are sufficient.
752764

753-
**TODO**: Define the exact API.
765+
For a macro which wants to access the actual _value_ of a given argument or
766+
the metadata annotation as a whole, they can evaluate `Code` instances as
767+
constants (see next section).
754768

755-
#### Annotations that require macro expansion
769+
### Constant evaluation
756770

757-
This could happen if the annotation class has macros applied to it, or if
758-
some argument(s) to the annotation constructor use macros.
771+
Macros may want the ability to evaluate constant expressions, in particular
772+
those found as arguments to metadata annotations.
759773

760-
Because macros are not allowed to generate code that shadows an identifier
761-
in the same library, we know that if an annotation class or any arguments to it
762-
could be resolved, then we can assume that resolution is correct.
774+
We expose this ability through the `DartObject evaluate(Code code)` API, which
775+
is available in all phases, with the following restrictions:
763776

764-
This allows us to provide an API for macro authors to attempt to evaluate an
765-
annotation in _any phase_. The API may fail (if it requires more macro
766-
expansion to be done), but that is not expected to be a common situation. In
767-
the case where it does fail, users should typically be able to move some of
768-
their code to a separate library (which they import). Then things from that
769-
library can safely be used in annotations in the current library, and evaluated
770-
by macros.
777+
- No identifier in `code` may refer to a constant which refers to any
778+
system environment variable, Dart define, or other configuration which is not
779+
otherwise visible to macros.
780+
- All identifiers in `code` must be defined outside of the current
781+
[strongly connected component][] (that is, the strongly connected component
782+
which triggered the current macro expansion).
771783

772-
Evaluation must fail if there are any macros left to be expanded on the
773-
annotation class or any arguments to the annotation constructor.
784+
The `DartObject` API is an abstract representation of an object, which can
785+
represent types which are not visible to the macro itself. It will closely
786+
mirror the [same API in the analyzer][DartObject].
787+
788+
The call to `evaluate` will throw a `ConstantEvaluationException` if the
789+
evaluation fails due to a violation of one of the restrictions above.
790+
791+
[DartObject]: https://pub.dev/documentation/analyzer/latest/dart_constant_value/DartObject-class.html
774792

775793
#### Are macro applications introspectable?
776794

@@ -1088,7 +1106,7 @@ a Dart program containing macro applications:
10881106

10891107
Starting at the entrypoint library, traverse all imports, exports, and
10901108
augmentation imports to collect the full graph of libraries to be compiled.
1091-
Calculate the [strongly connected components][] of this graph. Each component is
1109+
Calculate the [strongly connected component][]s of this graph. Each component is
10921110
a library cycle, and the edges between them determine how the cycles depend on
10931111
each other. Sort the library cycles in topological order based on the connected
10941112
component graph.
@@ -1101,7 +1119,7 @@ library cycle, it is guaranteed that all macros used by the cycle have already
11011119
been compiled. Also, any types or other declarations used by that cycle have
11021120
either already been compiled, or are defined in that cycle.
11031121

1104-
[strongly connected components]: https://en.wikipedia.org/wiki/Strongly_connected_component
1122+
[strongly connected component]: https://en.wikipedia.org/wiki/Strongly_connected_component
11051123

11061124
#### 2. Compile each cycle
11071125

@@ -1560,7 +1578,7 @@ resources outside of `lib`, which has both benefits and drawbacks.
15601578

15611579
**TODO**: Evaluate APIs for listing files and directories.
15621580

1563-
**TODO**: Consider adding `RandomAccessResource` api.
1581+
**TODO**: Consider adding `RandomAccessResource` API.
15641582

15651583
The specific API is as follows, and would only be available at compile time:
15661584

working/macros/motivation.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -313,7 +313,7 @@ stated more concretely as:
313313
4. We propose to consider designing a quotation syntax so that macros that
314314
generate Dart code can generate code in a readable fashion. This also means
315315
code generators only depend on language *syntax* for code generation and not
316-
an imperative api, which will better stand the test of time.
316+
an imperative API, which will better stand the test of time.
317317

318318
Most of this is as-yet-undesigned, but the key piece is that **we think macros
319319
should be written in normal imperative Dart code which is executed at compile
@@ -417,7 +417,7 @@ you should:
417417
* Possibly provide an opt out for this, if feasible/desirable.
418418
* Be able to trace errors that flow through generated code, as well as navigate
419419
back to the line in the macro implementation that produced the code.
420-
* Be able to auto-complete macro generated apis.
420+
* Be able to auto-complete macro generated APIs.
421421

422422
### Performance
423423

0 commit comments

Comments
 (0)