blob: faf7740dd2c1f15e040b4e98153299ce7c0bbad2 [file] [log] [blame] [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';
// ignore: implementation_imports
import 'package:macros/src/executor.dart' as injected;
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): this should be negotiated per client, not
// set here.
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#$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#$name'),
_impl._host.lookupMacroImplementation(QualifiedName('$uri#$name'))!);
}
class CfeRunningMacro implements injected.RunningMacro {
final CfeMacroImplementation _impl;
final QualifiedName name;
final QualifiedName implementation;
late final Future _started;
CfeRunningMacro._(this._impl, this.name, this.implementation);
// TODO(davidmorgan): should this be async, removing the need for `_started`?
// If so the API injected into analyzer+CFE needs to change to be async.
static CfeRunningMacro run(CfeMacroImplementation impl, QualifiedName name,
QualifiedName implementation) {
final result = CfeRunningMacro._(impl, name, implementation);
// TODO(davidmorgan): this is currently what starts the macro running,
// make it explicit.
result._started =
impl._host.queryMacroPhases(impl.packageConfig, implementation);
return result;
}
@override
Future<CfeMacroExecutionResult> executeDeclarationsPhase(MacroTarget target,
DeclarationPhaseIntrospector declarationsPhaseIntrospector) async {
await _started;
// TODO(davidmorgan): this is a hack to access CFE internals; remove.
introspector = declarationsPhaseIntrospector;
return CfeMacroExecutionResult(
target,
await _impl._host.augment(
name, AugmentRequest(phase: 2, target: target.qualifiedName)));
}
@override
Future<CfeMacroExecutionResult> executeDefinitionsPhase(MacroTarget target,
DefinitionPhaseIntrospector definitionPhaseIntrospector) async {
await _started;
// TODO(davidmorgan): this is a hack to access CFE internals; remove.
introspector = definitionPhaseIntrospector;
return CfeMacroExecutionResult(
target,
await _impl._host.augment(
name, AugmentRequest(phase: 3, target: target.qualifiedName)));
}
@override
Future<CfeMacroExecutionResult> executeTypesPhase(
MacroTarget target, TypePhaseIntrospector typePhaseIntrospector) async {
await _started;
// TODO(davidmorgan): this is a hack to access CFE internals; remove.
introspector = typePhaseIntrospector;
return CfeMacroExecutionResult(
target,
await _impl._host.augment(
name, AugmentRequest(phase: 1, target: target.qualifiedName)));
}
}
/// Converts [AugmentResponse] to [injected.MacroExecutionResult].
///
/// TODO(davidmorgan): add to `AugmentationResponse` to cover all the
/// functionality of `MacroExecutionResult`.
class CfeMacroExecutionResult implements injected.MacroExecutionResult {
final MacroTarget target;
final AugmentResponse augmentResponse;
CfeMacroExecutionResult(this.target, this.augmentResponse);
@override
List<Diagnostic> get diagnostics => [];
@override
Map<Identifier, Iterable<DeclarationCode>> get enumValueAugmentations => {};
@override
MacroException? get exception => null;
@override
Map<Identifier, NamedTypeAnnotationCode> get extendsTypeAugmentations => {};
@override
Map<Identifier, Iterable<TypeAnnotationCode>> get interfaceAugmentations =>
{};
@override
Iterable<DeclarationCode> get libraryAugmentations => {};
@override
Map<Identifier, Iterable<TypeAnnotationCode>> get mixinAugmentations => {};
@override
Iterable<String> get newTypeNames => [];
@override
void serialize(Object serializer) => throw UnimplementedError();
@override
Map<Identifier, Iterable<DeclarationCode>> get typeAugmentations => {
// TODO(davidmorgan): this assumes augmentations are for the macro
// application target. Instead, it should be explicit in
// `AugmentResponse`.
// TODO(davidmorgan): empty augmentations response breaks the test,
// it's not clear why.
if (augmentResponse.augmentations.isNotEmpty)
(target as Declaration).identifier: augmentResponse.augmentations
.map((a) => DeclarationCode.fromParts([a.code]))
.toList(),
};
}
extension MacroTargetExtension on MacroTarget {
QualifiedName get qualifiedName {
final identifier = ((this as Declaration).identifier as cfe.IdentifierImpl)
.resolveIdentifier();
return QualifiedName('${identifier.uri}#'
'${identifier.name}');
}
}