Skip to content

Commit 39bfd1f

Browse files
authored
Alternate macro api proposal, less visible phases (#1964)
In this proposal, there are no explicit classes you implement for each phase, there is only a single class to implement based on the type of declaration you will annotate. Each of these classes has a single visit method which gets a declaration argument and a context argument. The context has methods like `buildTypes`, `buildDeclarations`, and `buildDefinition`. These take a callback as an argument, and the callback receives the corresponding builder. It won't be executed until in the right phase. Some advantages of this api are: - Can have some shared state between phases, managed by local variables in the `visit` method. - Less complicated class heirarchy - far fewer classes to understand. - The phases are less directly exposed, you don't really have to understand them so much any more. Some disadvantages of this api are: - Can result in runtime errors if violating phases - you can't call the `buildTypes` method from within the callback passed to `buildDeclarations` for instance. That would violate ordering, and we would have to throw at runtime. We could lint for this though. - People may try to share state that isn't shareable across phases (for instance store the TypeBuilder and use it in the declaration phase). - If only implementing one phase there is more boilerplate.
1 parent 0948e0a commit 39bfd1f

File tree

5 files changed

+773
-0
lines changed

5 files changed

+773
-0
lines changed

working/macros/api/builders.dart

Lines changed: 202 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,202 @@
1+
import 'dart:async';
2+
3+
import 'code.dart';
4+
import 'introspection.dart';
5+
import 'macros.dart'; // For dart docs.
6+
7+
/// The interface used by [Macro]s to modify the current library.
8+
abstract class LibraryContext {
9+
/// Used to contribute new type declarations to this library.
10+
void buildTypes(FutureOr<void> Function(TypeBuilder) callback);
11+
12+
/// Used to contribute new non-type declarations to this library.
13+
void buildDeclarations(FutureOr<void> Function(DeclarationBuilder) callback);
14+
}
15+
16+
/// The interface used by [FunctionMacro]s to modify the function.
17+
abstract class FunctionContext implements LibraryContext {
18+
/// Used to augment a function definition.
19+
void buildDefinition(
20+
FutureOr<void> Function(FunctionDefinitionBuilder) callback);
21+
}
22+
23+
/// The interface used by [ClassMacro]s to modify the class.
24+
abstract class ClassContext implements LibraryContext {
25+
/// Used to contribute new non-type declarations to this library or class.
26+
@override
27+
void buildDeclarations(
28+
FutureOr<void> Function(ClassDeclarationBuilder) callback);
29+
30+
/// Used to augment any members in this class.
31+
void buildDefinitions(
32+
FutureOr<void> Function(ClassDefinitionBuilder) callback);
33+
}
34+
35+
/// The interface used by all [Macro]s that run on a member of a class, to add
36+
/// declarations to that class or the surrounding library.
37+
abstract class ClassMemberContext implements LibraryContext {
38+
/// Used to contribute new non-type declarations to the surrounding library
39+
/// or class.
40+
@override
41+
void buildDeclarations(
42+
FutureOr<void> Function(ClassMemberDeclarationBuilder) callback);
43+
}
44+
45+
/// The interface used by [FieldMacro]s to modify the field.
46+
abstract class FieldContext implements ClassMemberContext {
47+
/// Used to augment a field definition.
48+
void buildDefinition(
49+
FutureOr<void> Function(FieldDefinitionBuilder) callback);
50+
}
51+
52+
/// The interface used by [MethodMacro]s to modify the method.
53+
abstract class MethodContext implements ClassMemberContext {
54+
/// Used to augment a method definition.
55+
void buildDefinition(
56+
FutureOr<void> Function(FunctionDefinitionBuilder) callback);
57+
}
58+
59+
/// The interface used by [ConstructorMacro]s to modify the constructor.
60+
abstract class ConstructorContext implements ClassMemberContext {
61+
/// Used to augment a constructor definition.
62+
void buildDefinition(
63+
FutureOr<void> Function(ConstructorDefinitionBuilder) callback);
64+
}
65+
66+
/// The base interface used to add declarations to the program as well
67+
/// as augment existing ones.
68+
abstract class Builder {
69+
/// Used to construct a [TypeAnnotation] to a runtime type available to the
70+
/// the macro implementation code.
71+
///
72+
/// This can be used to emit a reference to it in generated code, or do
73+
/// subtype checks (depending on support in the current phase).
74+
TypeAnnotation typeAnnotationOf<T>();
75+
}
76+
77+
/// The api used by [Macro]s to contribute new type declarations to the
78+
/// current library, and get [TypeAnnotation]s from runtime [Type] objects.
79+
abstract class TypeBuilder implements Builder {
80+
/// Adds a new type declaration to the surrounding library.
81+
void declareType(DeclarationCode typeDeclaration);
82+
}
83+
84+
/// The api used by [Macro]s to contribute new (non-type)
85+
/// declarations to the current library.
86+
///
87+
/// Can also be used to do subtype checks on types.
88+
abstract class DeclarationBuilder implements Builder {
89+
/// Adds a new regular declaration to the surrounding library.
90+
///
91+
/// Note that type declarations are not supported.
92+
Declaration declareInLibrary(DeclarationCode declaration);
93+
94+
/// Returns true if [leftType] is a subtype of [rightType].
95+
bool isSubtypeOf(TypeAnnotation leftType, TypeAnnotation rightType);
96+
97+
/// Retruns true if [leftType] is an identical type to [rightType].
98+
bool isExactly(TypeAnnotation leftType, TypeAnnotation rightType);
99+
}
100+
101+
/// The api used by [Macro]s to contribute new members to a class.
102+
abstract class ClassMemberDeclarationBuilder implements DeclarationBuilder {
103+
/// Adds a new declaration to the surrounding class.
104+
void declareInClass(DeclarationCode declaration);
105+
}
106+
107+
/// The api used to introspect on a [ClassDeclaration].
108+
abstract class ClassIntrospector {
109+
/// The fields available for [clazz].
110+
///
111+
/// This may be incomplete if in the declaration phase and additional macros
112+
/// are going to run on this class.
113+
Future<List<FieldDeclaration>> fieldsOf(ClassDeclaration clazz);
114+
115+
/// The methods available so far for the current class.
116+
///
117+
/// This may be incomplete if additional declaration macros are running on
118+
/// this class.
119+
Future<List<MethodDeclaration>> methodsOf(ClassDeclaration clazz);
120+
121+
/// The constructors available so far for the current class.
122+
///
123+
/// This may be incomplete if additional declaration macros are running on
124+
/// this class.
125+
Future<List<ConstructorDeclaration>> constructorsOf(ClassDeclaration clazz);
126+
127+
/// The class that is directly extended via an `extends` clause.
128+
Future<ClassDeclaration?> superclassOf(ClassDeclaration clazz);
129+
130+
/// All of the classes that are mixed in with `with` clauses.
131+
Future<List<ClassDeclaration>> mixinsOf(ClassDeclaration clazz);
132+
}
133+
134+
/// The api used by [Macro]s to reflect on the currently available
135+
/// members, superclass, and mixins for a given [ClassDeclaration]
136+
abstract class ClassDeclarationBuilder
137+
implements ClassMemberDeclarationBuilder, ClassIntrospector {}
138+
139+
/// The api used by [Macro] to get a [TypeDeclaration] for any given
140+
/// [TypeAnnotation].
141+
abstract class TypeIntrospector {
142+
/// TODO: Figure out how to deal with `FutureOr<T>`, function types, and
143+
/// other non-nominal types.
144+
Future<TypeDeclaration> resolve(TypeAnnotation annotation);
145+
}
146+
147+
/// The base class for builders in the definition phase. These can convert
148+
/// any [TypeAnnotation] into its corresponding [TypeDeclaration], and also
149+
/// reflect more deeply on those.
150+
abstract class DefinitionBuilder
151+
implements Builder, ClassIntrospector, TypeIntrospector {}
152+
153+
/// The apis used by [Macro]s that run on classes, to fill in the definitions
154+
/// of any external declarations within that class.
155+
abstract class ClassDefinitionBuilder implements DefinitionBuilder {
156+
/// Retrieve a [FieldDefinitionBuilder] for a field by [name].
157+
///
158+
/// Throws an [ArgumentError] if there is no field by that name.
159+
Future<void> buildField(String name,
160+
FutureOr<void> Function(FieldDefinitionBuilder builder) callback);
161+
162+
/// Retrieve a [FunctionDefinitionBuilder] for a method by [name].
163+
///
164+
/// Throws an [ArgumentError] if there is no method by that name.
165+
Future<void> buildMethod(String name,
166+
FutureOr<void> Function(FunctionDefinitionBuilder builder) callback);
167+
168+
/// Retrieve a [ConstructorDefinitionBuilder] for a constructor by [name].
169+
///
170+
/// Throws an [ArgumentError] if there is no constructor by that name.
171+
Future<void> buildConstructor(String name,
172+
FutureOr<void> Function(ConstructorDefinitionBuilder builder) callback);
173+
}
174+
175+
/// The apis used by [Macro]s to define the body of a constructor
176+
/// or wrap the body of an existing constructor with additional statements.
177+
abstract class ConstructorDefinitionBuilder implements DefinitionBuilder {
178+
/// Augments an existing constructor body with [body].
179+
///
180+
/// TODO: Link the library augmentations proposal to describe the semantics.
181+
void augment({FunctionBodyCode? body, List<Code>? initializers});
182+
}
183+
184+
/// The apis used by [Macro]s to augment functions or methods.
185+
abstract class FunctionDefinitionBuilder implements DefinitionBuilder {
186+
/// Augments the function.
187+
///
188+
/// TODO: Link the library augmentations proposal to describe the semantics.
189+
void augment(FunctionBodyCode body);
190+
}
191+
192+
/// The api used by [Macro]s to augment a field.
193+
abstract class FieldDefinitionBuilder implements DefinitionBuilder {
194+
/// Augments the field.
195+
///
196+
/// TODO: Link the library augmentations proposal to describe the semantics.
197+
void augment({
198+
DeclarationCode? getter,
199+
DeclarationCode? setter,
200+
ExpressionCode? initializer,
201+
});
202+
}

working/macros/api/code.dart

Lines changed: 123 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,123 @@
1+
/// A scope in which to resolve a chunk of code.
2+
///
3+
/// TODO: Specify more what these mean, what fields are available (if any), etc.
4+
abstract class Scope {}
5+
6+
/// The base class representing an arbitrary chunk of Dart code, which may or
7+
/// may not be syntactically or semantically valid yet.
8+
class Code {
9+
/// The scope in which to resolve anything from [parts] that does not have its
10+
/// own scope already defined.
11+
final Scope? scope;
12+
13+
/// All the chunks of [Code] or raw [String]s that comprise this [Code]
14+
/// object.
15+
final List<Object> parts;
16+
17+
Code.fromString(String code, {this.scope}) : parts = [code];
18+
19+
Code.fromParts(this.parts, {this.scope});
20+
}
21+
22+
/// A piece of code representing a syntactically valid declaration.
23+
class DeclarationCode extends Code {
24+
DeclarationCode.fromString(String code, {Scope? scope})
25+
: super.fromString(code, scope: scope);
26+
27+
DeclarationCode.fromParts(List<Object> parts, {Scope? scope})
28+
: super.fromParts(parts, scope: scope);
29+
}
30+
31+
/// A piece of code representing a syntactically valid element.
32+
///
33+
/// Should not include any trailing commas,
34+
class ElementCode extends Code {
35+
ElementCode.fromString(String code, {Scope? scope})
36+
: super.fromString(code, scope: scope);
37+
38+
ElementCode.fromParts(List<Object> parts, {Scope? scope})
39+
: super.fromParts(parts, scope: scope);
40+
}
41+
42+
/// A piece of code representing a syntactically valid expression.
43+
class ExpressionCode extends Code {
44+
ExpressionCode.fromString(String code, {Scope? scope})
45+
: super.fromString(code, scope: scope);
46+
47+
ExpressionCode.fromParts(List<Object> parts, {Scope? scope})
48+
: super.fromParts(parts, scope: scope);
49+
}
50+
51+
/// A piece of code representing a syntactically valid function body.
52+
///
53+
/// This includes any and all code after the parameter list of a function,
54+
/// including modifiers like `async`.
55+
///
56+
/// Both arrow and block function bodies are allowed.
57+
class FunctionBodyCode extends Code {
58+
FunctionBodyCode.fromString(String code, {Scope? scope})
59+
: super.fromString(code, scope: scope);
60+
61+
FunctionBodyCode.fromParts(List<Object> parts, {Scope? scope})
62+
: super.fromParts(parts, scope: scope);
63+
}
64+
65+
/// A piece of code representing a syntactically valid identifier.
66+
class IdentifierCode extends Code {
67+
IdentifierCode.fromString(String code, {Scope? scope})
68+
: super.fromString(code, scope: scope);
69+
70+
IdentifierCode.fromParts(List<Object> parts, {Scope? scope})
71+
: super.fromParts(parts, scope: scope);
72+
}
73+
74+
/// A piece of code identifying a named argument.
75+
///
76+
/// This should not include any trailing commas.
77+
class NamedArgumentCode extends Code {
78+
NamedArgumentCode.fromString(String code, {Scope? scope})
79+
: super.fromString(code, scope: scope);
80+
81+
NamedArgumentCode.fromParts(List<Object> parts, {Scope? scope})
82+
: super.fromParts(parts, scope: scope);
83+
}
84+
85+
/// A piece of code identifying a syntactically valid function parameter.
86+
///
87+
/// This should not include any trailing commas, but may include modifiers
88+
/// such as `required`, and default values.
89+
///
90+
/// There is no distinction here made between named and positional parameters,
91+
/// nor between optional or required parameters. It is the job of the user to
92+
/// construct and combine these together in a way that creates valid parameter
93+
/// lists.
94+
class ParameterCode extends Code {
95+
ParameterCode.fromString(String code, {Scope? scope})
96+
: super.fromString(code, scope: scope);
97+
98+
ParameterCode.fromParts(List<Object> parts, {Scope? scope})
99+
: super.fromParts(parts, scope: scope);
100+
}
101+
102+
/// A piece of code representing a syntactically valid statement.
103+
///
104+
/// Should always end with a semicolon.
105+
class StatementCode extends Code {
106+
StatementCode.fromString(String code, {Scope? scope})
107+
: super.fromString(code, scope: scope);
108+
109+
StatementCode.fromParts(List<Object> parts, {Scope? scope})
110+
: super.fromParts(parts, scope: scope);
111+
}
112+
113+
extension Join<T extends Code> on List<T> {
114+
/// Joins all the items in [this] with [separator], and returns
115+
/// a new list.
116+
List<Code> joinAsCode(String separator) => [
117+
for (var i = 0; i < length - 1; i++) ...[
118+
this[i],
119+
Code.fromString(separator),
120+
],
121+
last,
122+
];
123+
}

0 commit comments

Comments
 (0)