Skip to content

Commit d3adb00

Browse files
authored
add initial macro expansion protocol (#2021)
This adds a `MacroExecutor` interface which is intended for use by language frontends (cfe, analyzer) in order to load and execute macros, as well as create library augmention files. This should help more clearly define the role of a frontend when it comes to macros (discovery and introspection), as well as provide a unified api for producing the final library augmentations. It does not actually write files though, we still need to figure out how we want to coordinate that part, and make sure that the CFE and analyzer can consistently talk about these files. This also allows for multiple different implementations in terms of how macros are actually loaded and executed, without the frontends having to know about them. Followup PR here #2022 which starts an implementation using `IsolateMirror.loadUri`.
1 parent 1bedefa commit d3adb00

File tree

5 files changed

+193
-35
lines changed

5 files changed

+193
-35
lines changed

.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,8 @@
2222
*.vcxproj.filters
2323
/*.vcxproj.user
2424
*.stamp
25+
.dart_tool
26+
.packages
2527

2628
# Gyp generated files
2729
*.xcodeproj

working/macros/api/builders.dart

Lines changed: 50 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -6,14 +6,7 @@ import 'macros.dart'; // For dart docs.
66

77
/// The base interface used to add declarations to the program as well
88
/// as augment existing ones.
9-
abstract class Builder {
10-
/// Used to construct a [TypeAnnotation] to a runtime type available to the
11-
/// the macro implementation code.
12-
///
13-
/// This can be used to emit a reference to it in generated code, or do
14-
/// subtype checks (depending on support in the current phase).
15-
TypeAnnotation typeAnnotationOf<T>();
16-
}
9+
abstract class Builder {}
1710

1811
/// The api used by [Macro]s to contribute new type declarations to the
1912
/// current library, and get [TypeAnnotation]s from runtime [Type] objects.
@@ -22,30 +15,30 @@ abstract class TypeBuilder implements Builder {
2215
void declareType(DeclarationCode typeDeclaration);
2316
}
2417

25-
/// The api used by [Macro]s to contribute new (non-type)
26-
/// declarations to the current library.
18+
/// The interface to resolve a [TypeAnnotation] to a [StaticType].
2719
///
28-
/// Can also be used to do subtype checks on types.
29-
abstract class DeclarationBuilder implements Builder {
30-
/// Adds a new regular declaration to the surrounding library.
20+
/// The [StaticType]s can be compared against other [StaticType]s to see how
21+
/// they relate to each other.
22+
///
23+
/// This api is only available to the declaration and definition phases of
24+
/// macro expansion.
25+
abstract class TypeResolver {
26+
/// Resolves [typeAnnotation] to a [StaticType].
3127
///
32-
/// Note that type declarations are not supported.
33-
Declaration declareInLibrary(DeclarationCode declaration);
34-
35-
/// Returns true if [leftType] is a subtype of [rightType].
36-
bool isSubtypeOf(TypeAnnotation leftType, TypeAnnotation rightType);
37-
38-
/// Retruns true if [leftType] is an identical type to [rightType].
39-
bool isExactly(TypeAnnotation leftType, TypeAnnotation rightType);
40-
}
41-
42-
/// The api used by [Macro]s to contribute new members to a class.
43-
abstract class ClassMemberDeclarationBuilder implements DeclarationBuilder {
44-
/// Adds a new declaration to the surrounding class.
45-
void declareInClass(DeclarationCode declaration);
28+
/// Throws an error if the type annotation cannot be resolved. This should
29+
/// only happen in the case of incomplete or invalid programs, but macros
30+
/// may be asked to run in this state during the development cycle. It is
31+
/// helpful for users if macros provide a best effort implementation in that
32+
/// case or handle the error in a useful way.
33+
Future<StaticType> resolve(TypeAnnotation typeAnnotation);
4634
}
4735

4836
/// The api used to introspect on a [ClassDeclaration].
37+
///
38+
/// Available in the declaration and definition phases, but limited in the
39+
/// declaration phase to immediately annotated [ClassDeclaration]s. This is
40+
/// done by limiting the access to the [TypeDeclarationResolver] to the
41+
/// definition phase.
4942
abstract class ClassIntrospector {
5043
/// The fields available for [clazz].
5144
///
@@ -75,23 +68,47 @@ abstract class ClassIntrospector {
7568
Future<List<ClassDeclaration>> interfacesOf(ClassDeclaration clazz);
7669
}
7770

71+
/// The api used by [Macro]s to contribute new (non-type)
72+
/// declarations to the current library.
73+
///
74+
/// Can also be used to do subtype checks on types.
75+
abstract class DeclarationBuilder
76+
implements Builder, TypeResolver, ClassIntrospector {
77+
/// Adds a new regular declaration to the surrounding library.
78+
///
79+
/// Note that type declarations are not supported.
80+
void declareInLibrary(DeclarationCode declaration);
81+
}
82+
83+
/// The api used by [Macro]s to contribute new members to a class.
84+
abstract class ClassMemberDeclarationBuilder implements DeclarationBuilder {
85+
/// Adds a new declaration to the surrounding class.
86+
void declareInClass(DeclarationCode declaration);
87+
}
88+
7889
/// The api used by [Macro]s to reflect on the currently available
7990
/// members, superclass, and mixins for a given [ClassDeclaration]
8091
abstract class ClassDeclarationBuilder
8192
implements ClassMemberDeclarationBuilder, ClassIntrospector {}
8293

83-
/// The api used by [Macro] to get a [TypeDeclaration] for any given
84-
/// [TypeAnnotation].
85-
abstract class TypeIntrospector {
86-
/// Resolves a [NamedTypeAnnotation] to its declaration.
87-
Future<TypeDeclaration> resolve(NamedTypeAnnotation annotation);
94+
/// The interface used by [Macro]s to resolve any [NamedStaticType] to its
95+
/// declaration.
96+
///
97+
/// Only available in the definition phase of macro expansion.
98+
abstract class TypeDeclarationResolver {
99+
/// Resolves a [NamedStaticType] to its [TypeDeclaration].
100+
Future<TypeDeclaration> declarationOf(NamedStaticType annotation);
88101
}
89102

90103
/// The base class for builders in the definition phase. These can convert
91104
/// any [TypeAnnotation] into its corresponding [TypeDeclaration], and also
92105
/// reflect more deeply on those.
93106
abstract class DefinitionBuilder
94-
implements Builder, ClassIntrospector, TypeIntrospector {}
107+
implements
108+
Builder,
109+
TypeResolver,
110+
ClassIntrospector,
111+
TypeDeclarationResolver {}
95112

96113
/// The apis used by [Macro]s that run on classes, to fill in the definitions
97114
/// of any external declarations within that class.
Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
1+
import 'builders.dart';
2+
import 'code.dart';
3+
import 'introspection.dart';
4+
5+
/// The interface used by Dart language implementations, in order to load
6+
/// and execute macros, as well as produce library augmentations from those
7+
/// macro applications.
8+
///
9+
/// This class more clearly defines the role of a Dart language implementation
10+
/// during macro discovery and expansion, and unifies how augmentation libraries
11+
/// are produced.
12+
abstract class MacroExecutor {
13+
/// Invoked when an implementation discovers a new macro definition in a
14+
/// [library] with [name], and prepares this executor to run the macro.
15+
///
16+
/// May be invoked more than once for the same macro, which will cause the
17+
/// macro to be re-loaded. Previous [MacroClassIdentifier]s and
18+
/// [MacroInstanceIdentifier]s given for this macro will be invalid after
19+
/// that point and should be discarded.
20+
///
21+
/// Throws an exception if the macro fails to load.
22+
Future<MacroClassIdentifier> loadMacro(Uri library, String name);
23+
24+
/// Creates an instance of [macroClass] in the executor, and returns an
25+
/// identifier for that instance.
26+
///
27+
/// Throws an exception if an instance is not created.
28+
Future<MacroInstanceIdentifier> instantiateMacro(
29+
MacroClassIdentifier macroClass, String constructor, Arguments arguments);
30+
31+
/// Runs the type phase for [macro] on a given [declaration].
32+
///
33+
/// Throws an exception if there is an error executing the macro.
34+
Future<MacroExecutionResult> executeTypesPhase(
35+
MacroInstanceIdentifier macro, Declaration declaration);
36+
37+
/// Runs the declarations phase for [macro] on a given [declaration].
38+
///
39+
/// Throws an exception if there is an error executing the macro.
40+
Future<MacroExecutionResult> executeDeclarationsPhase(
41+
MacroInstanceIdentifier macro,
42+
Declaration declaration,
43+
TypeResolver typeResolver,
44+
ClassIntrospector classIntrospector);
45+
46+
/// Runs the definitions phase for [macro] on a given [declaration].
47+
///
48+
/// Throws an exception if there is an error executing the macro.
49+
Future<MacroExecutionResult> executeDefinitionsPhase(
50+
MacroInstanceIdentifier macro,
51+
Declaration declaration,
52+
TypeResolver typeResolver,
53+
ClassIntrospector classIntrospector,
54+
TypeDeclarationResolver typeDeclarationResolver);
55+
56+
/// Combines multiple [MacroExecutionResult]s into a single library
57+
/// augmentation file, and returns a [String] representing that file.
58+
Future<String> buildAugmentationLibrary(
59+
Iterable<MacroExecutionResult> macroResults);
60+
}
61+
62+
/// The arguments passed to a macro constructor.
63+
///
64+
/// All argument instances must be of type [Code] or a built-in value type that
65+
/// is serializable (num, bool, String, null, etc).
66+
class Arguments {
67+
final List<Object?> positional;
68+
69+
final Map<String, Object?> named;
70+
71+
Arguments(this.positional, this.named);
72+
}
73+
74+
/// An opaque identifier for a macro class, retrieved by
75+
/// [MacroExecutor.loadMacro].
76+
///
77+
/// Used to execute or reload this macro in the future.
78+
abstract class MacroClassIdentifier {}
79+
80+
/// An opaque identifier for an instance of a macro class, retrieved by
81+
/// [MacroExecutor.instantiateMacro].
82+
///
83+
/// Used to execute or reload this macro in the future.
84+
abstract class MacroInstanceIdentifier {}
85+
86+
/// A summary of the results of running a macro in a given phase.
87+
///
88+
/// All modifications are expressed in terms of library augmentation
89+
/// declarations.
90+
abstract class MacroExecutionResult {
91+
/// Any library imports that should be added to support the code used in
92+
/// the augmentations.
93+
Iterable<DeclarationCode> get imports;
94+
95+
/// Any augmentations that should be applied as a result of executing a macro.
96+
Iterable<DeclarationCode> get augmentations;
97+
}
98+
99+
/// Each of the different macro execution phases.
100+
enum Phase {
101+
/// Only new types are added in this phase.
102+
types,
103+
104+
/// New non-type declarations are added in this phase.
105+
declarations,
106+
107+
/// This phase allows augmenting existing declarations.
108+
defintions,
109+
}

working/macros/api/introspection.dart

Lines changed: 28 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,24 @@ abstract class NamedTypeAnnotation implements TypeAnnotation {
3939
Iterable<TypeAnnotation> get typeArguments;
4040
}
4141

42+
/// The interface representing a resolved type.
43+
///
44+
/// Resolved types understand exactly what type they represent, and can be
45+
/// compared to other static types.
46+
abstract class StaticType {
47+
/// Returns true if this is a subtype of [other].
48+
Future<bool> isSubtypeOf(StaticType other);
49+
50+
/// Returns true if this is an identical type to [other].
51+
Future<bool> isExactly(StaticType other);
52+
}
53+
54+
/// A subtype of [StaticType] representing types that can be resolved by name
55+
/// to a concrete declaration.
56+
abstract class NamedStaticType implements StaticType {
57+
String get name;
58+
}
59+
4260
/// The base class for all declarations.
4361
abstract class Declaration {
4462
/// The name of this declaration.
@@ -50,8 +68,16 @@ abstract class TypeDeclaration implements Declaration {
5068
/// The type parameters defined for this type declaration.
5169
Iterable<TypeParameterDeclaration> get typeParameters;
5270

53-
/// Create a type annotation representing this type with [typeArguments].
54-
TypeAnnotation instantiate({List<TypeAnnotation> typeArguments});
71+
/// Create a static type representing this type with [typeArguments].
72+
///
73+
/// If [isNullable] is `true`, then this type will behave as if it has a
74+
/// trailing `?`.
75+
///
76+
/// Throws an exception if the type could not be instantiated, typically due
77+
/// to one of the type arguments not matching the bounds of the corresponding
78+
/// type parameter.
79+
Future<StaticType> instantiate(
80+
{required List<StaticType> typeArguments, required bool isNullable});
5581
}
5682

5783
/// Class (and enum) introspection information.

working/macros/pubspec.yaml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
name: macro_proposal
2+
publish_to: none
3+
environment:
4+
sdk: ">=2.12.0 <3.0.0"

0 commit comments

Comments
 (0)