blob: aa90a22a8e2a4f41004822fd8f351eb6448a4207 [file] [edit]
// Copyright (c) 2024, 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 'package:_macro_host/macro_host.dart';
import 'package:dart_model/dart_model.dart';
// ignore: implementation_imports
import 'package:front_end/src/kernel/macro/identifiers.dart' as cfe;
// ignore: implementation_imports
import 'package:front_end/src/macros/macro_injected_impl.dart' as injected;
import 'package:macro_service/macro_service.dart';
import 'package:macros/macros.dart' as macros_api_v1;
// ignore: implementation_imports
import 'package:macros/src/executor.dart' as macros_api_v1;
import 'query_service.dart';
/// Injected macro implementation for the analyzer.
class CfeMacroImplementation implements injected.MacroImplementation {
final Uri packageConfig;
final MacroHost _host;
CfeMacroImplementation._(this.packageConfig, this._host);
/// Starts the macro host.
///
/// [packageConfig] is the package config of the workspace of the code being
/// edited.
static Future<CfeMacroImplementation> start({
// TODO(davidmorgan): support serving multiple protocols.
required Protocol protocol,
required Uri packageConfig,
}) async =>
CfeMacroImplementation._(
packageConfig,
await MacroHost.serve(
protocol: protocol,
packageConfig: packageConfig,
queryService: CfeQueryService()));
@override
injected.MacroPackageConfigs get packageConfigs =>
CfeMacroPackageConfigs(this);
@override
injected.MacroRunner get macroRunner => CfeMacroRunner(this);
}
class CfeMacroPackageConfigs implements injected.MacroPackageConfigs {
final CfeMacroImplementation _impl;
CfeMacroPackageConfigs(this._impl);
@override
bool isMacro(Uri uri, String name) =>
_impl._host.isMacro(QualifiedName(uri: '$uri', name: name));
}
class CfeMacroRunner implements injected.MacroRunner {
final CfeMacroImplementation _impl;
CfeMacroRunner(this._impl);
@override
injected.RunningMacro run(Uri uri, String name) => CfeRunningMacro.run(
_impl,
QualifiedName(uri: '$uri', name: name),
_impl._host
.lookupMacroImplementation(QualifiedName(uri: '$uri', name: name))!);
}
class CfeRunningMacro implements injected.RunningMacro {
final CfeMacroImplementation _impl;
final QualifiedName name;
final QualifiedName implementation;
CfeRunningMacro._(this._impl, this.name, this.implementation);
static CfeRunningMacro run(CfeMacroImplementation impl, QualifiedName name,
QualifiedName implementation) {
return CfeRunningMacro._(impl, name, implementation);
}
/// Queries for [target] and returns the [Model] representing the result.
///
/// TODO: Make this a more limited query which doesn't fetch members.
Future<Model> _queryTarget(QualifiedName target) =>
Scope.query.run(() async => (await _impl._host.hostService.handle(
MacroRequest.queryRequest(
QueryRequest(query: Query(target: target)),
id: nextRequestId)))
.asQueryResponse
.model);
@override
Future<CfeMacroExecutionResult> executeDeclarationsPhase(
macros_api_v1.MacroTarget target,
macros_api_v1.DeclarationPhaseIntrospector
declarationsPhaseIntrospector) async {
// TODO(davidmorgan): this is a hack to access CFE internals; remove.
introspector = declarationsPhaseIntrospector;
return await CfeMacroExecutionResult.dartModelToInjected(
target,
await _impl._host.augment(
name,
AugmentRequest(
phase: 2,
target: target.qualifiedName,
model: await _queryTarget(target.qualifiedName))));
}
@override
Future<CfeMacroExecutionResult> executeDefinitionsPhase(
macros_api_v1.MacroTarget target,
macros_api_v1.DefinitionPhaseIntrospector
definitionPhaseIntrospector) async {
// TODO(davidmorgan): this is a hack to access CFE internals; remove.
introspector = definitionPhaseIntrospector;
return await CfeMacroExecutionResult.dartModelToInjected(
target,
await _impl._host.augment(
name,
AugmentRequest(
phase: 3,
target: target.qualifiedName,
model: await _queryTarget(target.qualifiedName))));
}
@override
Future<CfeMacroExecutionResult> executeTypesPhase(
macros_api_v1.MacroTarget target,
macros_api_v1.TypePhaseIntrospector typePhaseIntrospector) async {
// TODO(davidmorgan): this is a hack to access CFE internals; remove.
introspector = typePhaseIntrospector;
return await CfeMacroExecutionResult.dartModelToInjected(
target,
await _impl._host.augment(
name,
AugmentRequest(
phase: 1,
target: target.qualifiedName,
model: await _queryTarget(target.qualifiedName))));
}
}
/// Converts [AugmentResponse] to [macros_api_v1.MacroExecutionResult].
///
/// TODO(davidmorgan): add to `AugmentationResponse` to cover all the
/// functionality of `MacroExecutionResult`.
class CfeMacroExecutionResult implements macros_api_v1.MacroExecutionResult {
final macros_api_v1.MacroTarget target;
@override
final Map<macros_api_v1.Identifier, Iterable<macros_api_v1.DeclarationCode>>
typeAugmentations;
CfeMacroExecutionResult(
this.target, Iterable<macros_api_v1.DeclarationCode> declarations)
// TODO(davidmorgan): this assumes augmentations are for the macro
// application target. Instead, it should be explicit in
// `AugmentResponse`.
: typeAugmentations = {
// TODO(davidmorgan): empty augmentations response breaks the test,
// it's not clear why.
if (declarations.isNotEmpty)
(target as macros_api_v1.Declaration).identifier: declarations
};
static Future<CfeMacroExecutionResult> dartModelToInjected(
macros_api_v1.MacroTarget target, AugmentResponse augmentResponse) async {
final declarations = <macros_api_v1.DeclarationCode>[];
if (augmentResponse.typeAugmentations?.isNotEmpty == true) {
// TODO: Handle multiple type augmentations, or augmentations where the
// target is itself a member of a type and not the type.
final entry = augmentResponse.typeAugmentations!.entries.single;
if (entry.key != target.qualifiedName.name) {
throw UnimplementedError(
'Type augmentations are only implemented when the type is the '
'target of the augmentation.');
}
for (final augmentation in entry.value) {
declarations.add(macros_api_v1.DeclarationCode.fromParts(
await _resolveNames(augmentation.code)));
}
}
if (augmentResponse.enumValueAugmentations?.isNotEmpty == true) {
throw UnimplementedError('Enum value augmentations are not implemented');
}
if (augmentResponse.extendsTypeAugmentations?.isNotEmpty == true ||
augmentResponse.interfaceAugmentations?.isNotEmpty == true ||
augmentResponse.mixinAugmentations?.isNotEmpty == true) {
throw UnimplementedError('Type augmentations are not implemented');
}
if (augmentResponse.libraryAugmentations?.isNotEmpty == true ||
augmentResponse.newTypeNames?.isNotEmpty == true) {
throw UnimplementedError('Library augmentations are not implemented');
}
return CfeMacroExecutionResult(target, declarations);
}
@override
List<macros_api_v1.Diagnostic> get diagnostics => [];
@override
Map<macros_api_v1.Identifier, Iterable<macros_api_v1.DeclarationCode>>
get enumValueAugmentations => {};
@override
macros_api_v1.MacroException? get exception => null;
@override
Map<macros_api_v1.Identifier, macros_api_v1.NamedTypeAnnotationCode>
get extendsTypeAugmentations => {};
@override
Map<macros_api_v1.Identifier, Iterable<macros_api_v1.TypeAnnotationCode>>
get interfaceAugmentations => {};
@override
Iterable<macros_api_v1.DeclarationCode> get libraryAugmentations => {};
@override
Map<macros_api_v1.Identifier, Iterable<macros_api_v1.TypeAnnotationCode>>
get mixinAugmentations => {};
@override
Iterable<String> get newTypeNames => [];
@override
void serialize(Object serializer) => throw UnimplementedError();
}
extension MacroTargetExtension on macros_api_v1.MacroTarget {
QualifiedName get qualifiedName {
final identifier =
((this as macros_api_v1.Declaration).identifier as cfe.IdentifierImpl)
.resolveIdentifier();
return QualifiedName(
uri: '${identifier.uri}',
name: identifier.name,
scope: switch (identifier.kind) {
macros_api_v1.IdentifierKind.local ||
macros_api_v1.IdentifierKind.topLevelMember =>
null,
macros_api_v1.IdentifierKind.instanceMember =>
throw UnimplementedError(
'We dont have access to the parent scope for instance '
'members in the CFE yet'),
macros_api_v1.IdentifierKind.staticInstanceMember =>
identifier.staticScope!,
},
isStatic: identifier.staticScope != null);
}
}
/// Converts [codes] to a list of `String` and `Identifier`.
// TODO(davidmorgan): share this code with `package:_analyzer_macros`.
Future<List<Object>> _resolveNames(List<Code> codes) async {
// Find the set of unique [QualifiedName]s used.
final qualifiedNameStrings = <String>{};
for (final code in codes) {
if (code.type == CodeType.qualifiedName) {
qualifiedNameStrings.add(code.asQualifiedName.asString);
}
}
// Create futures looking up their [Identifier]s, then `await` in parallel.
final qualifiedNamesList =
qualifiedNameStrings.map(QualifiedName.parse).toList();
final identifierFutures = <Future<macros_api_v1.Identifier>>[];
for (final qualifiedName in qualifiedNamesList) {
identifierFutures.add((introspector as macros_api_v1.TypePhaseIntrospector)
// ignore: deprecated_member_use
.resolveIdentifier(Uri.parse(qualifiedName.uri), qualifiedName.name));
}
final identifiers = await Future.wait(identifierFutures);
// Build the result using the looked up [Identifier]s.
final identifiersByQualifiedNameStrings =
Map.fromIterables(qualifiedNameStrings, identifiers);
final result = <Object>[];
for (final code in codes) {
if (code.type == CodeType.string) {
result.add(code.asString);
} else if (code.type == CodeType.qualifiedName) {
final qualifiedName = code.asQualifiedName;
result.add(identifiersByQualifiedNameStrings[qualifiedName.asString]!);
}
}
return result;
}