blob: 826a43c3595041234cd5437569c27dbebebdbe16 [file] [log] [blame]
// Copyright (c) 2021, 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 'dart:async';
import 'dart:isolate';
import '../api.dart';
import '../executor.dart';
import 'exception_impls.dart';
import 'introspection_impls.dart';
import 'protocol.dart';
import 'remote_instance.dart';
import 'serialization.dart';
import 'span.dart';
/// Base implementation for macro executors which communicate with some external
/// process to run macros.
///
/// Subtypes must extend this class and implement the [close] and [sendResult]
/// apis to handle communication with the external macro program.
abstract class ExternalMacroExecutorBase extends MacroExecutor {
/// The stream on which we receive messages from the external macro executor.
final Stream<Object> messageStream;
/// The mode to use for serialization - must be a `server` variant.
final SerializationMode serializationMode;
/// A map of response completers by request id.
final _responseCompleters = <int, Completer<Response>>{};
bool isClosed = false;
ExternalMacroExecutorBase(
{required this.messageStream, required this.serializationMode}) {
withSerializationMode(serializationMode, () {
messageStream.listen((message) {
// No need for a remote cache in this zone we only read a zone ID and
// then immediately run in that zone.
Deserializer deserializer = deserializerFactory(message);
// Every object starts with a zone ID which dictates the zone in which
// we should deserialize the message.
deserializer.moveNext();
int zoneId = deserializer.expectInt();
withRemoteInstanceZone(zoneId, () async {
deserializer.moveNext();
MessageType messageType =
MessageType.values[deserializer.expectInt()];
// A response to a request we sent, everything else is a request from
// the client.
if (messageType == MessageType.response) {
SerializableResponse response =
SerializableResponse.deserialize(deserializer, zoneId);
Completer<Response>? completer =
_responseCompleters.remove(response.requestId);
if (completer == null) {
throw StateError('Got a response for an unrecognized request id '
'${response.requestId}');
}
completer.complete(response);
return;
}
// These are initialized in the switch below.
final Serializable? result;
final MessageType resultType;
int? requestId;
// Initialized after the switch or in the catch handler.
late final SerializableResponse response;
try {
switch (messageType) {
case MessageType.resolveIdentifierRequest:
ResolveIdentifierRequest request =
ResolveIdentifierRequest.deserialize(deserializer, zoneId);
requestId = request.id;
result = await (request.introspector.instance
as TypePhaseIntrospector)
// ignore: deprecated_member_use_from_same_package
.resolveIdentifier(request.library, request.name)
as IdentifierImpl;
resultType = MessageType.remoteInstance;
case MessageType.resolveTypeRequest:
ResolveTypeRequest request =
ResolveTypeRequest.deserialize(deserializer, zoneId);
requestId = request.id;
result = await (request.introspector.instance
as DeclarationPhaseIntrospector)
.resolve(request.typeAnnotationCode) as StaticTypeImpl;
resultType = MessageType.remoteInstance;
case MessageType.inferTypeRequest:
InferTypeRequest request =
InferTypeRequest.deserialize(deserializer, zoneId);
requestId = request.id;
result = await (request.introspector.instance
as DefinitionPhaseIntrospector)
.inferType(request.omittedType) as TypeAnnotationImpl;
resultType = MessageType.remoteInstance;
case MessageType.isExactlyTypeRequest:
IsExactlyTypeRequest request =
IsExactlyTypeRequest.deserialize(deserializer, zoneId);
requestId = request.id;
StaticType leftType = request.leftType as StaticType;
StaticType rightType = request.rightType as StaticType;
result = BooleanValue(await leftType.isExactly(rightType));
resultType = MessageType.boolean;
case MessageType.isSubtypeOfRequest:
IsSubtypeOfRequest request =
IsSubtypeOfRequest.deserialize(deserializer, zoneId);
requestId = request.id;
StaticType leftType = request.leftType as StaticType;
StaticType rightType = request.rightType as StaticType;
result = BooleanValue(await leftType.isSubtypeOf(rightType));
resultType = MessageType.boolean;
case MessageType.declarationOfRequest:
DeclarationOfRequest request = DeclarationOfRequest.deserialize(
deserializer, zoneId, messageType);
requestId = request.id;
DefinitionPhaseIntrospector introspector = request
.introspector.instance as DefinitionPhaseIntrospector;
result = (await introspector.declarationOf(request.identifier))
// TODO: Consider refactoring to avoid the need for
// this cast.
as Serializable;
resultType = MessageType.remoteInstance;
case MessageType.typeDeclarationOfRequest:
DeclarationOfRequest request = DeclarationOfRequest.deserialize(
deserializer, zoneId, messageType);
requestId = request.id;
DeclarationPhaseIntrospector introspector = request
.introspector.instance as DeclarationPhaseIntrospector;
result =
(await introspector.typeDeclarationOf(request.identifier))
// TODO: Consider refactoring to avoid the need for
// this cast.
as Serializable;
resultType = MessageType.remoteInstance;
case MessageType.constructorsOfRequest:
TypeIntrospectorRequest request =
TypeIntrospectorRequest.deserialize(
deserializer, messageType, zoneId);
requestId = request.id;
DeclarationPhaseIntrospector introspector = request
.introspector.instance as DeclarationPhaseIntrospector;
result = DeclarationList((await introspector
.constructorsOf(request.declaration as TypeDeclaration))
// TODO: Consider refactoring to avoid the need for this.
.cast<ConstructorDeclarationImpl>());
resultType = MessageType.declarationList;
case MessageType.topLevelDeclarationsOfRequest:
DeclarationsOfRequest request =
DeclarationsOfRequest.deserialize(deserializer, zoneId);
requestId = request.id;
DefinitionPhaseIntrospector introspector = request
.introspector.instance as DefinitionPhaseIntrospector;
result = DeclarationList(// force newline
(await introspector.topLevelDeclarationsOf(request.library))
// TODO: Consider refactoring to avoid the need for
// this.
.cast<DeclarationImpl>());
resultType = MessageType.declarationList;
case MessageType.fieldsOfRequest:
TypeIntrospectorRequest request =
TypeIntrospectorRequest.deserialize(
deserializer, messageType, zoneId);
requestId = request.id;
DeclarationPhaseIntrospector introspector = request
.introspector.instance as DeclarationPhaseIntrospector;
result = DeclarationList((await introspector
.fieldsOf(request.declaration as TypeDeclaration))
// TODO: Consider refactoring to avoid the need for this.
.cast<FieldDeclarationImpl>());
resultType = MessageType.declarationList;
case MessageType.methodsOfRequest:
TypeIntrospectorRequest request =
TypeIntrospectorRequest.deserialize(
deserializer, messageType, zoneId);
requestId = request.id;
DeclarationPhaseIntrospector introspector = request
.introspector.instance as DeclarationPhaseIntrospector;
result = DeclarationList((await introspector
.methodsOf(request.declaration as TypeDeclaration))
// TODO: Consider refactoring to avoid the need for this.
.cast<MethodDeclarationImpl>());
resultType = MessageType.declarationList;
case MessageType.typesOfRequest:
TypeIntrospectorRequest request =
TypeIntrospectorRequest.deserialize(
deserializer, messageType, zoneId);
requestId = request.id;
DeclarationPhaseIntrospector introspector = request
.introspector.instance as DeclarationPhaseIntrospector;
result = DeclarationList((await introspector
.typesOf(request.declaration as Library))
// TODO: Consider refactoring to avoid the need for this.
.cast<TypeDeclarationImpl>());
resultType = MessageType.declarationList;
case MessageType.valuesOfRequest:
TypeIntrospectorRequest request =
TypeIntrospectorRequest.deserialize(
deserializer, messageType, zoneId);
requestId = request.id;
DeclarationPhaseIntrospector introspector = request
.introspector.instance as DeclarationPhaseIntrospector;
result = DeclarationList((await introspector
.valuesOf(request.declaration as EnumDeclaration))
// TODO: Consider refactoring to avoid the need for this.
.cast<EnumValueDeclarationImpl>());
resultType = MessageType.declarationList;
default:
throw StateError('Unexpected message type $messageType');
}
response = SerializableResponse(
response: result,
requestId: requestId,
responseType: resultType,
serializationZoneId: zoneId);
} catch (error, stackTrace) {
// TODO: Something better here.
if (requestId == null) rethrow;
response = SerializableResponse(
exception: MacroExceptionImpl.from(error, stackTrace),
requestId: requestId,
responseType: MessageType.exception,
serializationZoneId: zoneId);
}
Serializer serializer = serializerFactory();
response.serialize(serializer);
sendResult(serializer);
});
});
});
}
/// These calls are handled by the higher level executor.
@override
String buildAugmentationLibrary(
Uri augmentedLibraryUri,
Iterable<MacroExecutionResult> macroResults,
TypeDeclaration Function(Identifier) resolveDeclaration,
ResolvedIdentifier Function(Identifier) resolveIdentifier,
TypeAnnotation? Function(OmittedTypeAnnotation) inferOmittedType,
{Map<OmittedTypeAnnotation, String>? omittedTypes,
List<Span>? spans}) =>
throw StateError('Unreachable');
@override
Future<MacroExecutionResult> executeDeclarationsPhase(
MacroInstanceIdentifier macro,
MacroTarget target,
DeclarationPhaseIntrospector introspector) =>
_sendRequest((zoneId) => ExecuteDeclarationsPhaseRequest(
macro,
target as RemoteInstance,
RemoteInstanceImpl(
instance: introspector,
id: RemoteInstance.uniqueId,
kind: RemoteInstanceKind.declarationPhaseIntrospector),
serializationZoneId: zoneId));
@override
Future<MacroExecutionResult> executeDefinitionsPhase(
MacroInstanceIdentifier macro,
MacroTarget target,
DefinitionPhaseIntrospector introspector) =>
_sendRequest((zoneId) => ExecuteDefinitionsPhaseRequest(
macro,
target as RemoteInstance,
RemoteInstanceImpl(
instance: introspector,
id: RemoteInstance.uniqueId,
kind: RemoteInstanceKind.definitionPhaseIntrospector),
serializationZoneId: zoneId));
@override
Future<MacroExecutionResult> executeTypesPhase(MacroInstanceIdentifier macro,
MacroTarget target, TypePhaseIntrospector introspector) =>
_sendRequest((zoneId) => ExecuteTypesPhaseRequest(
macro,
target as RemoteInstance,
RemoteInstanceImpl(
instance: introspector,
id: RemoteInstance.uniqueId,
kind: RemoteInstanceKind.typePhaseIntrospector),
serializationZoneId: zoneId));
@override
Future<MacroInstanceIdentifier> instantiateMacro(
Uri library, String name, String constructor, Arguments arguments) =>
_sendRequest((zoneId) => InstantiateMacroRequest(
library, name, constructor, arguments, RemoteInstance.uniqueId,
serializationZoneId: zoneId));
@override
void disposeMacro(MacroInstanceIdentifier instance) => _sendRequest(
(zoneId) => DisposeMacroRequest(instance, serializationZoneId: zoneId));
/// Sends [serializer.result] to [sendPort], possibly wrapping it in a
/// [TransferableTypedData] object.
void sendResult(Serializer serializer);
/// Creates a [Request] with a given serialization zone ID, and handles the
/// response, casting it to the expected type or throwing the error provided.
Future<T> _sendRequest<T>(Request Function(int) requestFactory) {
if (isClosed) {
throw UnexpectedMacroExceptionImpl(
"Can't send request - $runtimeType is closed!");
}
return withSerializationMode(serializationMode, () {
final int zoneId = newRemoteInstanceZone();
return withRemoteInstanceZone(zoneId, () async {
Request request = requestFactory(zoneId);
Serializer serializer = serializerFactory();
// It is our responsibility to add the zone ID header.
serializer.addInt(zoneId);
request.serialize(serializer);
sendResult(serializer);
Completer<Response> completer = Completer<Response>();
_responseCompleters[request.id] = completer;
try {
Response response = await completer.future;
T? result = response.response as T?;
if (result != null) return result;
throw response.exception!;
} finally {
// Clean up the zone after the request is done.
destroyRemoteInstanceZone(zoneId);
// Tell the remote client to clean it up as well.
Serializer serializer = serializerFactory();
serializer.addInt(zoneId);
DestroyRemoteInstanceZoneRequest(serializationZoneId: zoneId)
.serialize(serializer);
sendResult(serializer);
}
});
});
}
}