Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Use package:_macro_host in _analyzer_macros. #28

Merged
merged 1 commit into from
Aug 8, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
173 changes: 173 additions & 0 deletions pkgs/_analyzer_macros/lib/macro_implementation.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,173 @@
// 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 {
final Uri packageConfig;
final Map<String, String> macroImplByName;
final MacroHost _host;

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

/// 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 =>
MacroImplementation._(
packageConfig,
macroImplByName,
await MacroHost.serve(
macroImplByName: macroImplByName,
queryService: AnalyzerQueryService()));

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

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

class AnalyzerQueryService implements QueryService {
@override
Future<QueryResponse> handle(QueryRequest request) async {
return QueryResponse();
}
}

class AnalyzerMacroPackageConfigs implements injected.MacroPackageConfigs {
final MacroImplementation _impl;

AnalyzerMacroPackageConfigs(this._impl);

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

class AnalyzerMacroRunner implements injected.MacroRunner {
final MacroImplementation _impl;

AnalyzerMacroRunner(this._impl);

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

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

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

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

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

/// Converts [AugmentResponse] to [injected.MacroExecutionResult].
///
/// TODO(davidmorgan): add to `AugmentationResponse` to cover all the
/// functionality of `MacroExecutionResult`.
class AnalyzerMacroExecutionResult implements injected.MacroExecutionResult {
final MacroTarget target;
final AugmentResponse augmentResponse;

AnalyzerMacroExecutionResult(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-114.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/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 ?? {};
}
16 changes: 12 additions & 4 deletions pkgs/_macro_host/lib/macro_host.dart
Original file line number Diff line number Diff line change
Expand Up @@ -15,25 +15,33 @@ 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 {
final Map<String, String> macroImplByName;
final _HostService _hostService;
final MacroServer macroServer;
final MacroBuilder macroBuilder = MacroBuilder();
final MacroRunner macroRunner = MacroRunner();

MacroHost._(this.macroServer, this._hostService);
MacroHost._(this.macroImplByName, this.macroServer, this._hostService);

/// Starts a macro host with introspection queries handled by [queryService].
static Future<MacroHost> serve({required QueryService queryService}) async {
///
/// [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.
static Future<MacroHost> serve({
required Map<String, String> macroImplByName,
required QueryService queryService,
}) async {
final hostService = _HostService(queryService);
final server = await MacroServer.serve(service: hostService);
return MacroHost._(server, hostService);
return MacroHost._(macroImplByName, server, hostService);
}

/// 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