blob: ed29787a4cf93471cd6467594c4ea07cc9739bfb [file] [log] [blame]
// Copyright (c) 2022, the Dart project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
import 'executor/serialization.dart'
show SerializationMode, SerializationModeHelpers;
/// Generates a Dart program for a given set of macros, which can be compiled
/// and then passed as a precompiled kernel file to `MacroExecutor.loadMacro`.
///
/// The [macroDeclarations] is a map from library URIs to macro classes for the
/// macros supported. The macro classes are provided as a map from macro class
/// names to the names of the macro class constructors.
///
/// The [serializationMode] must be a client variant.
String bootstrapMacroIsolate(
Map<String, Map<String, List<String>>> macroDeclarations,
SerializationMode serializationMode) {
if (!serializationMode.isClient) {
throw new ArgumentError(
'Got $serializationMode but expected a client version.');
}
StringBuffer imports = new StringBuffer();
StringBuffer constructorEntries = new StringBuffer();
macroDeclarations
.forEach((String macroImport, Map<String, List<String>> macroClasses) {
imports.writeln('import \'$macroImport\';');
macroClasses.forEach((String macroName, List<String> constructorNames) {
constructorEntries
.writeln("MacroClassIdentifierImpl(Uri.parse('$macroImport'), "
"'$macroName'): {");
for (String constructor in constructorNames) {
constructorEntries.writeln("'$constructor': "
"$macroName.${constructor.isEmpty ? 'new' : constructor},");
}
constructorEntries.writeln('},');
});
});
return template
.replaceFirst(_importMarker, imports.toString())
.replaceFirst(
_macroConstructorEntriesMarker, constructorEntries.toString())
.replaceFirst(_modeMarker, serializationMode.asCode);
}
const String _importMarker = '{{IMPORT}}';
const String _macroConstructorEntriesMarker = '{{MACRO_CONSTRUCTOR_ENTRIES}}';
const String _modeMarker = '{{SERIALIZATION_MODE}}';
const String template = '''
import 'dart:async';
import 'dart:convert';
import 'dart:io';
import 'dart:isolate';
import 'dart:typed_data';
import 'package:_fe_analyzer_shared/src/macros/executor/execute_macro.dart';
import 'package:_fe_analyzer_shared/src/macros/executor/introspection_impls.dart';
import 'package:_fe_analyzer_shared/src/macros/executor/message_grouper.dart';
import 'package:_fe_analyzer_shared/src/macros/executor/remote_instance.dart';
import 'package:_fe_analyzer_shared/src/macros/executor/response_impls.dart';
import 'package:_fe_analyzer_shared/src/macros/executor/serialization.dart';
import 'package:_fe_analyzer_shared/src/macros/executor/protocol.dart';
import 'package:_fe_analyzer_shared/src/macros/executor.dart';
import 'package:_fe_analyzer_shared/src/macros/api.dart';
$_importMarker
/// Entrypoint to be spawned with [Isolate.spawnUri] or [Process.start].
///
/// Supports the client side of the macro expansion protocol.
void main(_, [SendPort? sendPort]) {
// Function that sends the result of a [Serializer] using either [sendPort]
// or [stdout].
void Function(Serializer) sendResult;
// The stream for incoming messages, could be either a ReceivePort or stdin.
Stream<Object?> messageStream;
withSerializationMode($_modeMarker, () {
if (sendPort != null) {
ReceivePort receivePort = new ReceivePort();
messageStream = receivePort;
sendResult = (Serializer serializer) =>
_sendIsolateResult(serializer, sendPort);
// If using isolate communication, first send a sendPort to the parent
// isolate.
sendPort.send(receivePort.sendPort);
} else {
sendResult = _sendStdoutResult;
if (serializationMode == SerializationMode.byteDataClient) {
messageStream = MessageGrouper(stdin).messageStream;
} else if (serializationMode == SerializationMode.jsonClient) {
messageStream = stdin
.transform(const Utf8Decoder())
.transform(const LineSplitter())
.map((line) => jsonDecode(line)!);
} else {
throw new UnsupportedError(
'Unsupported serialization mode \$serializationMode for '
'ProcessExecutor');
}
}
messageStream.listen((message) => _handleMessage(message, sendResult));
});
}
void _handleMessage(
Object? message, void Function(Serializer) sendResult) async {
// Serializes `request` and send it using `sendResult`.
Future<Response> sendRequest(Request request) =>
_sendRequest(request, sendResult);
if (serializationMode == SerializationMode.byteDataClient
&& message is TransferableTypedData) {
message = message.materialize().asUint8List();
}
var deserializer = deserializerFactory(message)
..moveNext();
int zoneId = deserializer.expectInt();
deserializer..moveNext();
var type = MessageType.values[deserializer.expectInt()];
var serializer = serializerFactory();
switch (type) {
case MessageType.instantiateMacroRequest:
var request = new InstantiateMacroRequest.deserialize(deserializer, zoneId);
(await _instantiateMacro(request)).serialize(serializer);
break;
case MessageType.executeDeclarationsPhaseRequest:
var request = new ExecuteDeclarationsPhaseRequest.deserialize(deserializer, zoneId);
(await _executeDeclarationsPhase(request, sendRequest)).serialize(serializer);
break;
case MessageType.executeDefinitionsPhaseRequest:
var request = new ExecuteDefinitionsPhaseRequest.deserialize(deserializer, zoneId);
(await _executeDefinitionsPhase(request, sendRequest)).serialize(serializer);
break;
case MessageType.executeTypesPhaseRequest:
var request = new ExecuteTypesPhaseRequest.deserialize(deserializer, zoneId);
(await _executeTypesPhase(request, sendRequest)).serialize(serializer);
break;
case MessageType.response:
var response = new SerializableResponse.deserialize(deserializer, zoneId);
_responseCompleters.remove(response.requestId)!.complete(response);
return;
default:
throw new StateError('Unhandled event type \$type');
}
sendResult(serializer);
}
/// Maps macro identifiers to constructors.
final _macroConstructors = <MacroClassIdentifierImpl, Map<String, Macro Function()>>{
$_macroConstructorEntriesMarker
};
/// Maps macro instance identifiers to instances.
final _macroInstances = <MacroInstanceIdentifierImpl, Macro>{};
/// Handles [InstantiateMacroRequest]s.
Future<SerializableResponse> _instantiateMacro(
InstantiateMacroRequest request) async {
try {
var constructors = _macroConstructors[request.macroClass];
if (constructors == null) {
throw new ArgumentError('Unrecognized macro class \${request.macroClass}');
}
var constructor = constructors[request.constructorName];
if (constructor == null) {
throw new ArgumentError(
'Unrecognized constructor name "\${request.constructorName}" for '
'macro class "\${request.macroClass}".');
}
var instance = Function.apply(constructor, request.arguments.positional, {
for (MapEntry<String, Object?> entry in request.arguments.named.entries)
new Symbol(entry.key): entry.value,
}) as Macro;
var identifier = new MacroInstanceIdentifierImpl(instance, request.instanceId);
_macroInstances[identifier] = instance;
return new SerializableResponse(
responseType: MessageType.macroInstanceIdentifier,
response: identifier,
requestId: request.id,
serializationZoneId: request.serializationZoneId);
} catch (e, s) {
return new SerializableResponse(
responseType: MessageType.error,
error: e.toString(),
stackTrace: s.toString(),
requestId: request.id,
serializationZoneId: request.serializationZoneId);
}
}
Future<SerializableResponse> _executeTypesPhase(
ExecuteTypesPhaseRequest request,
Future<Response> Function(Request request) sendRequest) async {
try {
Macro? instance = _macroInstances[request.macro];
if (instance == null) {
throw new StateError('Unrecognized macro instance \${request.macro}\\n'
'Known instances: \$_macroInstances)');
}
var result = await executeTypesMacro(instance, request.declaration);
return new SerializableResponse(
responseType: MessageType.macroExecutionResult,
response: result,
requestId: request.id,
serializationZoneId: request.serializationZoneId);
} catch (e, s) {
return new SerializableResponse(
responseType: MessageType.error,
error: e.toString(),
stackTrace: s.toString(),
requestId: request.id,
serializationZoneId: request.serializationZoneId);
}
}
Future<SerializableResponse> _executeDeclarationsPhase(
ExecuteDeclarationsPhaseRequest request,
Future<Response> Function(Request request) sendRequest) async {
try {
Macro? instance = _macroInstances[request.macro];
if (instance == null) {
throw new StateError('Unrecognized macro instance \${request.macro}\\n'
'Known instances: \$_macroInstances)');
}
var classIntrospector = ClientClassIntrospector(
sendRequest,
remoteInstance: request.classIntrospector,
serializationZoneId: request.serializationZoneId);
var typeResolver = ClientTypeResolver(
sendRequest,
remoteInstance: request.typeResolver,
serializationZoneId: request.serializationZoneId);
var result = await executeDeclarationsMacro(
instance, request.declaration, classIntrospector, typeResolver);
return new SerializableResponse(
responseType: MessageType.macroExecutionResult,
response: result,
requestId: request.id,
serializationZoneId: request.serializationZoneId);
} catch (e, s) {
return new SerializableResponse(
responseType: MessageType.error,
error: e.toString(),
stackTrace: s.toString(),
requestId: request.id,
serializationZoneId: request.serializationZoneId);
}
}
Future<SerializableResponse> _executeDefinitionsPhase(
ExecuteDefinitionsPhaseRequest request,
Future<Response> Function(Request request) sendRequest) async {
try {
Macro? instance = _macroInstances[request.macro];
if (instance == null) {
throw new StateError('Unrecognized macro instance \${request.macro}\\n'
'Known instances: \$_macroInstances)');
}
var typeResolver = ClientTypeResolver(
sendRequest,
remoteInstance: request.typeResolver,
serializationZoneId: request.serializationZoneId);
var typeDeclarationResolver = ClientTypeDeclarationResolver(
sendRequest,
remoteInstance: request.typeDeclarationResolver,
serializationZoneId: request.serializationZoneId);
var classIntrospector = ClientClassIntrospector(
sendRequest,
remoteInstance: request.classIntrospector,
serializationZoneId: request.serializationZoneId);
var result = await executeDefinitionMacro(
instance, request.declaration, classIntrospector, typeResolver,
typeDeclarationResolver);
return new SerializableResponse(
responseType: MessageType.macroExecutionResult,
response: result,
requestId: request.id,
serializationZoneId: request.serializationZoneId);
} catch (e, s) {
return new SerializableResponse(
responseType: MessageType.error,
error: e.toString(),
stackTrace: s.toString(),
requestId: request.id,
serializationZoneId: request.serializationZoneId);
}
}
/// Holds on to response completers by request id.
final _responseCompleters = <int, Completer<Response>>{};
/// Serializes [request], passes it to [sendResult], and sets up a [Completer]
/// in [_responseCompleters] to handle the response.
Future<Response> _sendRequest(
Request request, void Function(Serializer serializer) sendResult) {
Completer<Response> completer = Completer();
_responseCompleters[request.id] = completer;
Serializer serializer = serializerFactory();
serializer.addInt(request.serializationZoneId);
request.serialize(serializer);
sendResult(serializer);
return completer.future;
}
/// Sends [serializer.result] to [sendPort], possibly wrapping it in a
/// [TransferableTypedData] object.
void _sendIsolateResult(Serializer serializer, SendPort sendPort) {
if (serializationMode == SerializationMode.byteDataClient) {
sendPort.send(
TransferableTypedData.fromList([serializer.result as Uint8List]));
} else {
sendPort.send(serializer.result);
}
}
/// Sends [serializer.result] to [stdout].
///
/// Serializes the result to a string if using JSON.
void _sendStdoutResult(Serializer serializer) {
if (serializationMode == SerializationMode.jsonClient) {
stdout.writeln(jsonEncode(serializer.result));
} else if (serializationMode == SerializationMode.byteDataClient) {
Uint8List result = (serializer as ByteDataSerializer).result;
int length = result.lengthInBytes;
stdout.add([
length >> 24 & 0xff,
length >> 16 & 0xff,
length >> 8 & 0xff,
length & 0xff,
]);
stdout.add(result);
} else {
throw new UnsupportedError(
'Unsupported serialization mode \$serializationMode for '
'ProcessExecutor');
}
}
''';