Skip to content

Commit

Permalink
Use package:_macro_host in _analyzer_macros.
Browse files Browse the repository at this point in the history
  • Loading branch information
davidmorgan committed Aug 7, 2024
1 parent 5b70e02 commit 175c819
Show file tree
Hide file tree
Showing 6 changed files with 215 additions and 115 deletions.
163 changes: 163 additions & 0 deletions pkgs/_analyzer_macros/lib/analyzer_macro_implementation.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,163 @@
// 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';
// ignore: implementation_imports
import 'package:analyzer/src/summary2/macro_injected_impl.dart' as injected;
import 'package:dart_model/dart_model.dart';
import 'package:macro_service/macro_service.dart';
import 'package:macros/macros.dart';
// ignore: implementation_imports
import 'package:macros/src/executor.dart' as injected;

/// Injected macro implementation for the analyzer.
class MacroImplementation implements injected.MacroImplementation, HostService {
final Uri packageConfig;
final Map<String, String> macroImplByName;
late final MacroHost _host;

MacroImplementation._(this.packageConfig, this.macroImplByName);

/// Starts the macro host.
///
/// [packageConfig] is the package config of the workspace of the code being
/// edited.
///
/// [macroImplByName] identifies macros, it's a placeholder until we identify
/// macros using package config. Keys are macro annotation qualified names
/// (`uri#name`) and values are macro implementation qualified names.
static Future<MacroImplementation> start({
required Uri packageConfig,
required Map<String, String> macroImplByName,
}) async {
final result = MacroImplementation._(packageConfig, macroImplByName);
result._host = await MacroHost.serve(
macroImplByName: macroImplByName, service: result);
return result;
}

@override
injected.MacroPackageConfigs get packageConfigs => MacroPackageConfigs(this);

@override
injected.MacroRunner get runner => MacroRunner(this);

@override
Future<Response?> handle(MacroRequest request) async {
if (request.type != MacroRequestType.queryRequest) return null;
return Response.queryResponse(QueryResponse());
}
}

class MacroPackageConfigs implements injected.MacroPackageConfigs {
final MacroImplementation impl;

MacroPackageConfigs(this.impl);

@override
bool isMacro(Uri uri, String name) =>
impl._host.isMacro(impl.packageConfig, QualifiedName('$uri#$name'));
}

class MacroRunner implements injected.MacroRunner {
final MacroImplementation impl;

MacroRunner(this.impl);

@override
injected.RunningMacro run(Uri uri, String name) => RunningMacro.run(
impl,
QualifiedName('$uri#$name'),
// Look up from the macro name to its implementation.
QualifiedName(impl.macroImplByName['$uri#$name']!));
}

class RunningMacro implements injected.RunningMacro {
final MacroImplementation impl;
final QualifiedName name;
final QualifiedName implementation;
late final Future _started;

RunningMacro._(this.impl, this.name, this.implementation);

static RunningMacro run(MacroImplementation impl, QualifiedName name,
QualifiedName implementation) {
final result = RunningMacro._(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<MacroExecutionResult> executeDeclarationsPhase(MacroTarget target,
DeclarationPhaseIntrospector declarationsPhaseIntrospector) async {
await _started;
return MacroExecutionResult(
target, await impl._host.augment(name, AugmentRequest(phase: 2)));
}

@override
Future<MacroExecutionResult> executeDefinitionsPhase(MacroTarget target,
DefinitionPhaseIntrospector definitionPhaseIntrospector) async {
await _started;
return MacroExecutionResult(
target, await impl._host.augment(name, AugmentRequest(phase: 3)));
}

@override
Future<MacroExecutionResult> executeTypesPhase(
MacroTarget target, TypePhaseIntrospector typePhaseIntrospector) async {
await _started;
return MacroExecutionResult(
target, await impl._host.augment(name, AugmentRequest(phase: 1)));
}
}

/// Converts [AugmentResponse] to [injected.MacroExecutionResult].
class MacroExecutionResult implements injected.MacroExecutionResult {
final MacroTarget target;
final AugmentResponse augmentResponse;

MacroExecutionResult(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`.
(target as Declaration).identifier: augmentResponse.augmentations
.map((a) => DeclarationCode.fromParts([a.code]))
.toList(),
};
}
9 changes: 7 additions & 2 deletions pkgs/_analyzer_macros/pubspec.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,13 @@ resolution: workspace
environment:
sdk: ^3.6.0-48.0.dev

dev_dependencies:
dependencies:
_macro_host: any
analyzer: any
dart_flutter_team_lints: ^3.0.0
dart_model: any
macro_service: any
macros: any

dev_dependencies:
dart_flutter_team_lints: ^3.0.0
test: ^1.25.0
111 changes: 10 additions & 101 deletions pkgs/_analyzer_macros/test/analyzer_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -3,34 +3,33 @@
// BSD-style license that can be found in the LICENSE file.

import 'dart:io';
import 'dart:isolate';

import 'package:_analyzer_macros/analyzer_macro_implementation.dart';
import 'package:analyzer/dart/analysis/analysis_context.dart';
import 'package:analyzer/dart/analysis/analysis_context_collection.dart';
import 'package:analyzer/dart/analysis/results.dart';
import 'package:analyzer/src/summary2/macro_injected_impl.dart' as injected;
import 'package:macros/macros.dart';
import 'package:macros/src/executor.dart';
import 'package:test/test.dart';

void main() {
late AnalysisContext analysisContext;
late TestMacroPackageConfigs packageConfigs;
late TestMacroRunner runner;

group('analyzer with injected macro implementation', () {
setUp(() {
setUp(() async {
// Set up analyzer.
final directory =
Directory.fromUri(Uri.parse('./test/package_under_test')).absolute;
final contextCollection =
AnalysisContextCollection(includedPaths: [directory.path]);
analysisContext = contextCollection.contexts.first;

// Inject test macro implementation.
packageConfigs = TestMacroPackageConfigs();
runner = TestMacroRunner();
injected.macroImplementation = injected.MacroImplementation(
packageConfigs: packageConfigs, runner: runner);
injected.macroImplementation = await MacroImplementation.start(
packageConfig: Isolate.packageConfigSync!,
macroImplByName: {
'package:_test_macros/declare_x_macro.dart#DeclareX':
'package:_test_macros/declare_x_macro.dart'
'#DeclareXImplementation'
});
});

test('discovers macros, runs them, applies augmentations', () async {
Expand All @@ -44,10 +43,6 @@ void main() {
await analysisContext.currentSession.getErrors(path) as ErrorsResult;
expect(errors.errors, isEmpty);

// Macro was found by the analyzer and run.
expect(packageConfigs.macroWasFound, true);
expect(runner.macroWasRun, true);

// The expected new declaration augmentation was applied.
final resolvedLibrary = (await analysisContext.currentSession
.getResolvedLibrary(path)) as ResolvedLibraryResult;
Expand All @@ -58,89 +53,3 @@ void main() {
});
});
}

class TestMacroPackageConfigs implements injected.MacroPackageConfigs {
bool macroWasFound = false;

@override
bool isMacro(Uri uri, String name) {
final result =
uri.toString() == 'package:_test_macros/declare_x_macro.dart' &&
name == 'DeclareX';
if (result) {
macroWasFound = true;
}
return result;
}
}

class TestMacroRunner implements injected.MacroRunner {
bool macroWasRun = false;

@override
injected.RunningMacro run(Uri uri, String name) {
macroWasRun = true;
return TestRunningMacro();
}
}

class TestRunningMacro implements injected.RunningMacro {
@override
Future<MacroExecutionResult> executeDeclarationsPhase(MacroTarget target,
DeclarationPhaseIntrospector declarationsPhaseIntrospector) async {
return TestMacroExecutionResult(typeAugmentations: {
(target as Declaration).identifier: [
DeclarationCode.fromParts(['int get x => 3;'])
],
});
}

@override
Future<MacroExecutionResult> executeDefinitionsPhase(MacroTarget target,
DefinitionPhaseIntrospector definitionPhaseIntrospector) async {
return TestMacroExecutionResult();
}

@override
Future<MacroExecutionResult> executeTypesPhase(
MacroTarget target, TypePhaseIntrospector typePhaseIntrospector) async {
return TestMacroExecutionResult();
}
}

class TestMacroExecutionResult implements MacroExecutionResult {
@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>> typeAugmentations;

TestMacroExecutionResult(
{Map<Identifier, Iterable<DeclarationCode>>? typeAugmentations})
: typeAugmentations = typeAugmentations ?? {};
}
15 changes: 11 additions & 4 deletions pkgs/_macro_host/lib/macro_host.dart
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import 'package:macro_service/macro_service.dart';
/// Tools that want to support macros, such as the Analyzer and the CFE, can
/// do so by running a `MacroHost` and providing their own `HostService`.
class MacroHost implements HostService {
final Map<String, String> macroImplByName;
final MacroServer macroServer;
final ListOfServices services;
final MacroBuilder macroBuilder = MacroBuilder();
Expand All @@ -24,7 +25,7 @@ class MacroHost implements HostService {
// lifecycle state.
Completer<Set<int>>? _macroPhases;

MacroHost._(this.macroServer, this.services) {
MacroHost._(this.macroImplByName, this.macroServer, this.services) {
services.services.insert(0, this);
}

Expand All @@ -33,20 +34,26 @@ class MacroHost implements HostService {
/// The service passed in should handle introspection RPCs, it does not need
/// to handle others.
///
/// [macroImplByName] is a map from macro annotation qualified name to macro
/// implementation qualified name, it's needed until this information is
/// available in package configs.
///
/// TODO(davidmorgan): make this split clearer, it should be in the protocol
/// definition somewhere which requests the host handles.
static Future<MacroHost> serve({required HostService service}) async {
static Future<MacroHost> serve(
{required Map<String, String> macroImplByName,
required HostService service}) async {
final listOfServices = ListOfServices();
listOfServices.services.add(service);
final server = await MacroServer.serve(service: listOfServices);
return MacroHost._(server, listOfServices);
return MacroHost._(macroImplByName, server, listOfServices);
}

/// Whether [name] is a macro according to that package's `pubspec.yaml`.
bool isMacro(Uri packageConfig, QualifiedName name) {
// TODO(language/3728): this is a placeholder, use package config when
// available.
return true;
return macroImplByName.keys.contains(name.string);
}

/// Determines which phases the macro implemented at [name] runs in.
Expand Down
Loading

0 comments on commit 175c819

Please sign in to comment.