Skip to content

Commit 9daaad1

Browse files
authored
Macro expansion protocol implementation example (#2022)
Followup to #2021. This partially implements one possible version of the MacroExecutor interface, and is a port of my early experiments using `IsolateMirror.loadUri` to load and execute macros. I could fully flesh this out, if the implementation teams think that would be helpful.
1 parent d3adb00 commit 9daaad1

File tree

7 files changed

+612
-0
lines changed

7 files changed

+612
-0
lines changed

working/macros/api/expansion_protocol.dart

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,10 @@ abstract class MacroExecutor {
5757
/// augmentation file, and returns a [String] representing that file.
5858
Future<String> buildAugmentationLibrary(
5959
Iterable<MacroExecutionResult> macroResults);
60+
61+
/// Tell the executor to shut down and clean up any resources it may have
62+
/// allocated.
63+
void close();
6064
}
6165

6266
/// The arguments passed to a macro constructor.
Lines changed: 131 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,131 @@
1+
import 'dart:async';
2+
import 'dart:isolate';
3+
import 'dart:mirrors';
4+
5+
import 'isolate_mirror_impl.dart';
6+
import 'protocol.dart';
7+
import '../../builders.dart';
8+
import '../../expansion_protocol.dart';
9+
import '../../introspection.dart';
10+
11+
/// A [MacroExecutor] implementation which relies on [IsolateMirror.loadUri]
12+
/// in order to load macros libraries.
13+
///
14+
/// All actual work happens in a separate [Isolate], and this class serves as
15+
/// a bridge between that isolate and the language frontends.
16+
class IsolateMirrorMacroExecutor implements MacroExecutor {
17+
/// The actual isolate doing macro loading and execution.
18+
final Isolate _macroIsolate;
19+
20+
/// The channel used to send requests to the [_macroIsolate].
21+
final SendPort _sendPort;
22+
23+
/// The stream of responses from the [_macroIsolate].
24+
final Stream<GenericResponse> _responseStream;
25+
26+
/// A map of response completers by request id.
27+
final _responseCompleters = <int, Completer<GenericResponse>>{};
28+
29+
/// A function that should be invoked when shutting down this executor
30+
/// to perform any necessary cleanup.
31+
final void Function() _onClose;
32+
33+
IsolateMirrorMacroExecutor._(
34+
this._macroIsolate, this._sendPort, this._responseStream, this._onClose) {
35+
_responseStream.listen((event) {
36+
var completer = _responseCompleters.remove(event.requestId);
37+
if (completer == null) {
38+
throw StateError(
39+
'Got a response for an unrecognized request id ${event.requestId}');
40+
}
41+
completer.complete(event);
42+
});
43+
}
44+
45+
/// Initialize an [IsolateMirrorMacroExecutor] and return it once ready.
46+
///
47+
/// Spawns the macro isolate and sets up a communication channel.
48+
static Future<MacroExecutor> start() async {
49+
var receivePort = ReceivePort();
50+
var sendPortCompleter = Completer<SendPort>();
51+
var responseStreamController =
52+
StreamController<GenericResponse>(sync: true);
53+
receivePort.listen((message) {
54+
if (!sendPortCompleter.isCompleted) {
55+
sendPortCompleter.complete(message as SendPort);
56+
} else {
57+
responseStreamController.add(message as GenericResponse);
58+
}
59+
}).onDone(responseStreamController.close);
60+
var macroIsolate = await Isolate.spawn(spawn, receivePort.sendPort);
61+
62+
return IsolateMirrorMacroExecutor._(
63+
macroIsolate,
64+
await sendPortCompleter.future,
65+
responseStreamController.stream,
66+
receivePort.close);
67+
}
68+
69+
@override
70+
Future<String> buildAugmentationLibrary(
71+
Iterable<MacroExecutionResult> macroResults) {
72+
// TODO: implement buildAugmentationLibrary
73+
throw UnimplementedError();
74+
}
75+
76+
@override
77+
void close() {
78+
_onClose();
79+
_macroIsolate.kill();
80+
}
81+
82+
@override
83+
Future<MacroExecutionResult> executeDeclarationsPhase(
84+
MacroInstanceIdentifier macro,
85+
Declaration declaration,
86+
TypeResolver typeResolver,
87+
ClassIntrospector classIntrospector) {
88+
// TODO: implement executeDeclarationsPhase
89+
throw UnimplementedError();
90+
}
91+
92+
@override
93+
Future<MacroExecutionResult> executeDefinitionsPhase(
94+
MacroInstanceIdentifier macro,
95+
Declaration declaration,
96+
TypeResolver typeResolver,
97+
ClassIntrospector classIntrospector,
98+
TypeDeclarationResolver typeDeclarationResolver) =>
99+
_sendRequest(ExecuteDefinitionsPhaseRequest(macro, declaration,
100+
typeResolver, classIntrospector, typeDeclarationResolver));
101+
102+
@override
103+
Future<MacroExecutionResult> executeTypesPhase(
104+
MacroInstanceIdentifier macro, Declaration declaration) {
105+
// TODO: implement executeTypesPhase
106+
throw UnimplementedError();
107+
}
108+
109+
@override
110+
Future<MacroInstanceIdentifier> instantiateMacro(
111+
MacroClassIdentifier macroClass,
112+
String constructor,
113+
Arguments arguments) =>
114+
_sendRequest(InstantiateMacroRequest(macroClass, constructor, arguments));
115+
116+
@override
117+
Future<MacroClassIdentifier> loadMacro(Uri library, String name) =>
118+
_sendRequest(LoadMacroRequest(library, name));
119+
120+
/// Sends a request and returns the response, casting it to the expected
121+
/// type.
122+
Future<T> _sendRequest<T>(Request request) async {
123+
_sendPort.send(request);
124+
var completer = Completer<GenericResponse<T>>();
125+
_responseCompleters[request.id] = completer;
126+
var response = await completer.future;
127+
var result = response.response;
128+
if (result != null) return result;
129+
throw response.error!;
130+
}
131+
}
Lines changed: 247 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,247 @@
1+
import 'dart:async';
2+
import 'dart:isolate';
3+
import 'dart:mirrors';
4+
5+
import '../../code.dart';
6+
import 'protocol.dart';
7+
import '../../builders.dart';
8+
import '../../expansion_protocol.dart';
9+
import '../../introspection.dart';
10+
import '../../macros.dart';
11+
12+
/// Spawns a new isolate for loading and executing macros.
13+
void spawn(SendPort sendPort) {
14+
var receivePort = ReceivePort();
15+
sendPort.send(receivePort.sendPort);
16+
receivePort.listen((message) async {
17+
if (message is LoadMacroRequest) {
18+
var response = await _loadMacro(message);
19+
sendPort.send(response);
20+
} else if (message is InstantiateMacroRequest) {
21+
var response = await _instantiateMacro(message);
22+
sendPort.send(response);
23+
} else if (message is ExecuteDefinitionsPhaseRequest) {
24+
var response = await _executeDefinitionsPhase(message);
25+
sendPort.send(response);
26+
} else {
27+
throw StateError('Unrecognized event type $message');
28+
}
29+
});
30+
}
31+
32+
/// Maps macro identifiers to class mirrors.
33+
final _macroClasses = <_MacroClassIdentifier, ClassMirror>{};
34+
35+
/// Handles [LoadMacroRequest]s.
36+
Future<GenericResponse<MacroClassIdentifier>> _loadMacro(
37+
LoadMacroRequest request) async {
38+
try {
39+
var identifier = _MacroClassIdentifier(request.library, request.name);
40+
if (_macroClasses.containsKey(identifier)) {
41+
throw UnsupportedError(
42+
'Reloading macros is not supported by this implementation');
43+
}
44+
var libMirror =
45+
await currentMirrorSystem().isolate.loadUri(request.library);
46+
var macroClass =
47+
libMirror.declarations[Symbol(request.name)] as ClassMirror;
48+
_macroClasses[identifier] = macroClass;
49+
return GenericResponse(response: identifier, requestId: request.id);
50+
} catch (e) {
51+
return GenericResponse(error: e, requestId: request.id);
52+
}
53+
}
54+
55+
/// Maps macro instance identifiers to instances.
56+
final _macroInstances = <_MacroInstanceIdentifier, Macro>{};
57+
58+
/// Handles [InstantiateMacroRequest]s.
59+
Future<GenericResponse<MacroInstanceIdentifier>> _instantiateMacro(
60+
InstantiateMacroRequest request) async {
61+
try {
62+
var clazz = _macroClasses[request.macroClass];
63+
if (clazz == null) {
64+
throw ArgumentError('Unrecognized macro class ${request.macroClass}');
65+
}
66+
var instance = clazz.newInstance(
67+
Symbol(request.constructorName), request.arguments.positional, {
68+
for (var entry in request.arguments.named.entries)
69+
Symbol(entry.key): entry.value,
70+
}).reflectee as Macro;
71+
var identifier = _MacroInstanceIdentifier();
72+
_macroInstances[identifier] = instance;
73+
return GenericResponse<MacroInstanceIdentifier>(
74+
response: identifier, requestId: request.id);
75+
} catch (e) {
76+
return GenericResponse(error: e, requestId: request.id);
77+
}
78+
}
79+
80+
Future<GenericResponse<MacroExecutionResult>> _executeDefinitionsPhase(
81+
ExecuteDefinitionsPhaseRequest request) async {
82+
try {
83+
var instance = _macroInstances[request.macro];
84+
if (instance == null) {
85+
throw StateError('Unrecognized macro instance ${request.macro}\n'
86+
'Known instances: $_macroInstances)');
87+
}
88+
var declaration = request.declaration;
89+
if (instance is FunctionDefinitionMacro &&
90+
declaration is FunctionDeclaration) {
91+
var builder = _FunctionDefinitionBuilder(
92+
declaration,
93+
request.typeResolver,
94+
request.typeDeclarationResolver,
95+
request.classIntrospector);
96+
await instance.buildDefinitionForFunction(declaration, builder);
97+
return GenericResponse(response: builder.result, requestId: request.id);
98+
} else {
99+
throw UnsupportedError(
100+
('Only FunctionDefinitionMacros are supported currently'));
101+
}
102+
} catch (e) {
103+
return GenericResponse(error: e, requestId: request.id);
104+
}
105+
}
106+
107+
/// Our implementation of [MacroClassIdentifier].
108+
class _MacroClassIdentifier implements MacroClassIdentifier {
109+
final String id;
110+
111+
_MacroClassIdentifier(Uri library, String name) : id = '$library#$name';
112+
113+
operator ==(other) => other is _MacroClassIdentifier && id == other.id;
114+
115+
int get hashCode => id.hashCode;
116+
}
117+
118+
/// Our implementation of [MacroInstanceIdentifier].
119+
class _MacroInstanceIdentifier implements MacroInstanceIdentifier {
120+
static int _next = 0;
121+
122+
final int id;
123+
124+
_MacroInstanceIdentifier() : id = _next++;
125+
126+
operator ==(other) => other is _MacroInstanceIdentifier && id == other.id;
127+
128+
int get hashCode => id;
129+
}
130+
131+
/// Our implementation of [MacroExecutionResult].
132+
class _MacroExecutionResult implements MacroExecutionResult {
133+
@override
134+
final List<DeclarationCode> augmentations = <DeclarationCode>[];
135+
136+
@override
137+
final List<DeclarationCode> imports = <DeclarationCode>[];
138+
}
139+
140+
/// Custom implementation of [FunctionDefinitionBuilder].
141+
class _FunctionDefinitionBuilder implements FunctionDefinitionBuilder {
142+
final TypeResolver typeResolver;
143+
final TypeDeclarationResolver typeDeclarationResolver;
144+
final ClassIntrospector classIntrospector;
145+
146+
/// The declaration this is a builder for.
147+
final FunctionDeclaration declaration;
148+
149+
/// The final result, will be built up over `augment` calls.
150+
final result = _MacroExecutionResult();
151+
152+
_FunctionDefinitionBuilder(this.declaration, this.typeResolver,
153+
this.typeDeclarationResolver, this.classIntrospector);
154+
155+
@override
156+
void augment(FunctionBodyCode body) {
157+
result.augmentations.add(DeclarationCode.fromParts([
158+
'augment ',
159+
declaration.returnType.code,
160+
' ',
161+
declaration.name,
162+
if (declaration.typeParameters.isNotEmpty) ...[
163+
'<',
164+
for (var typeParam in declaration.typeParameters) ...[
165+
typeParam.name,
166+
if (typeParam.bounds != null) ...['extends ', typeParam.bounds!.code],
167+
if (typeParam != declaration.typeParameters.last) ', ',
168+
],
169+
'>',
170+
],
171+
'(',
172+
for (var positionalRequired
173+
in declaration.positionalParameters.where((p) => p.isRequired)) ...[
174+
ParameterCode.fromParts([
175+
positionalRequired.type.code,
176+
' ',
177+
positionalRequired.name,
178+
]),
179+
', '
180+
],
181+
if (declaration.positionalParameters.any((p) => !p.isRequired)) ...[
182+
'[',
183+
for (var positionalOptional in declaration.positionalParameters
184+
.where((p) => !p.isRequired)) ...[
185+
ParameterCode.fromParts([
186+
positionalOptional.type.code,
187+
' ',
188+
positionalOptional.name,
189+
]),
190+
', ',
191+
],
192+
']',
193+
],
194+
if (declaration.namedParameters.isNotEmpty) ...[
195+
'{',
196+
for (var named in declaration.namedParameters) ...[
197+
ParameterCode.fromParts([
198+
if (named.isRequired) 'required ',
199+
named.type.code,
200+
' ',
201+
named.name,
202+
if (named.defaultValue != null) ...[
203+
' = ',
204+
named.defaultValue!,
205+
],
206+
]),
207+
', ',
208+
],
209+
'}',
210+
],
211+
') ',
212+
body,
213+
]));
214+
}
215+
216+
@override
217+
Future<List<ConstructorDeclaration>> constructorsOf(ClassDeclaration clazz) =>
218+
classIntrospector.constructorsOf(clazz);
219+
220+
@override
221+
Future<List<FieldDeclaration>> fieldsOf(ClassDeclaration clazz) =>
222+
classIntrospector.fieldsOf(clazz);
223+
224+
@override
225+
Future<List<ClassDeclaration>> interfacesOf(ClassDeclaration clazz) =>
226+
classIntrospector.interfacesOf(clazz);
227+
228+
@override
229+
Future<List<MethodDeclaration>> methodsOf(ClassDeclaration clazz) =>
230+
classIntrospector.methodsOf(clazz);
231+
232+
@override
233+
Future<List<ClassDeclaration>> mixinsOf(ClassDeclaration clazz) =>
234+
classIntrospector.mixinsOf(clazz);
235+
236+
@override
237+
Future<TypeDeclaration> declarationOf(NamedStaticType annotation) =>
238+
typeDeclarationResolver.declarationOf(annotation);
239+
240+
@override
241+
Future<ClassDeclaration?> superclassOf(ClassDeclaration clazz) =>
242+
classIntrospector.superclassOf(clazz);
243+
244+
@override
245+
Future<StaticType> resolve(TypeAnnotation typeAnnotation) =>
246+
typeResolver.resolve(typeAnnotation);
247+
}

0 commit comments

Comments
 (0)