Skip to content

Commit

Permalink
add specific macro implementations and delegate to them (#97)
Browse files Browse the repository at this point in the history
Part of #91

Sending this out a bit early - this is I think the smallest chunk I could tackle independently, but it would be nice to get a review before you leave.

The next step would be passing the actual target through to the macro api, probably instead of the AugmentRequest.
  • Loading branch information
jakemac53 authored Oct 11, 2024
1 parent b13df4b commit 6de2869
Show file tree
Hide file tree
Showing 12 changed files with 480 additions and 55 deletions.
6 changes: 6 additions & 0 deletions goldens/foo/lib/foo.analyzer.json
Original file line number Diff line number Diff line change
Expand Up @@ -197,6 +197,9 @@
],
"namedParameters": []
}
},
"properties": {
"isClass": true
}
},
"Bar": {
Expand Down Expand Up @@ -234,6 +237,9 @@
}
}
}
},
"properties": {
"isClass": true
}
}
}
Expand Down
6 changes: 6 additions & 0 deletions goldens/foo/lib/foo.cfe.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,9 @@
"isStatic": false
}
}
},
"properties": {
"isClass": true
}
},
"Bar": {
Expand All @@ -26,6 +29,9 @@
"isStatic": false
}
}
},
"properties": {
"isClass": true
}
}
}
Expand Down
2 changes: 1 addition & 1 deletion pkgs/_analyzer_macros/lib/query_service.dart
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ class AnalyzerQueryService implements QueryService {
final types = AnalyzerTypeHierarchy(library.typeProvider)
..addInterfaceElement(clazz);

final interface = Interface();
final interface = Interface(properties: Properties(isClass: true));
for (final constructor in clazz.constructors) {
interface.members[constructor.name] = Member(
requiredPositionalParameters: constructor
Expand Down
2 changes: 1 addition & 1 deletion pkgs/_cfe_macros/lib/query_service.dart
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ class CfeQueryService implements QueryService {
if (classBuilder == null) throw StateError('Not found $target');
final fieldIterator =
classBuilder.fullMemberIterator<cfe.SourceFieldBuilder>();
final interface = Interface();
final interface = Interface(properties: Properties(isClass: true));
while (fieldIterator.moveNext()) {
final current = fieldIterator.current;
interface.members[current.name] = Member(
Expand Down
26 changes: 23 additions & 3 deletions pkgs/_macro_client/lib/macro_client.dart
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ import 'package:dart_model/dart_model.dart';
import 'package:macro/macro.dart';
import 'package:macro_service/macro_service.dart';

import 'src/execute_macro.dart';

/// Local macro client which runs macros as directed by requests from a remote
/// macro host.
///
Expand Down Expand Up @@ -109,9 +111,27 @@ class MacroClient {
error: 'No macro for annotation: '
'${hostRequest.macroAnnotation.asString}')));
} else {
await Scope.macro.runAsync(() async => _sendResponse(
Response.augmentResponse(
await macro.augment(_host, hostRequest.asAugmentRequest),
final augmentRequest = hostRequest.asAugmentRequest;
await Scope.macro
.runAsync(() async => _sendResponse(Response.augmentResponse(
switch (augmentRequest.phase) {
1 => macro.description.runsInPhases.contains(1)
? await executeTypesMacro(
macro, _host, augmentRequest)
: null,
2 => macro.description.runsInPhases.contains(2)
? await executeDeclarationsMacro(
macro, _host, augmentRequest)
: null,
3 => macro.description.runsInPhases.contains(3)
? await executeDefinitionsMacro(
macro, _host, augmentRequest)
: null,
_ => throw StateError(
'Unexpected phase ${augmentRequest.phase}, '
'expected 1, 2, or 3.')
} ??
AugmentResponse(augmentations: []),
requestId: hostRequest.id)));
}
default:
Expand Down
100 changes: 100 additions & 0 deletions pkgs/_macro_client/lib/src/execute_macro.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
// 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 'dart:async';

import 'package:dart_model/dart_model.dart';
import 'package:macro/macro.dart';
import 'package:macro_service/macro_service.dart';

/// Runs [macro] in the types phase and returns an [AugmentResponse].
Future<AugmentResponse> executeTypesMacro(
Macro macro, Host host, AugmentRequest request) async {
final target = request.target;
// TODO: https://github.com/dart-lang/macros/issues/100.
final queryResult = await host.query(Query(target: target));
final properties =
queryResult.uris[target.uri]!.scopes[target.name]!.properties;
switch ((properties, macro)) {
case (Properties(isClass: true), ClassTypesMacro macro):
return await macro.buildTypesForClass(host, request);
case (_, LibraryTypesMacro()):
case (_, ConstructorTypesMacro()):
case (_, MethodTypesMacro()):
case (_, FunctionTypesMacro()):
case (_, FieldTypesMacro()):
case (_, VariableTypesMacro()):
case (_, EnumTypesMacro()):
case (_, ExtensionTypesMacro()):
case (_, ExtensionTypeTypesMacro()):
case (_, MixinTypesMacro()):
case (_, EnumValueTypesMacro()):
case (_, TypeAliasTypesMacro()):
throw UnimplementedError('Unimplemented macro target');
default:
throw UnsupportedError('Unsupported macro type or invalid target:\n'
'macro: $macro\ntarget: $target');
}
}

/// Runs [macro] in the declarations phase and returns an [AugmentResponse].
Future<AugmentResponse> executeDeclarationsMacro(
Macro macro, Host host, AugmentRequest request) async {
final target = request.target;
// TODO: https://github.com/dart-lang/macros/issues/100.
final queryResult = await host.query(Query(target: target));
final properties =
queryResult.uris[target.uri]!.scopes[target.name]!.properties;

switch ((properties, macro)) {
case (Properties(isClass: true), ClassDeclarationsMacro macro):
return await macro.buildDeclarationsForClass(host, request);
case (_, LibraryDeclarationsMacro()):
case (_, EnumDeclarationsMacro()):
case (_, ExtensionDeclarationsMacro()):
case (_, ExtensionTypeDeclarationsMacro()):
case (_, MixinDeclarationsMacro()):
case (_, EnumValueDeclarationsMacro()):
case (_, ConstructorDeclarationsMacro()):
case (_, MethodDeclarationsMacro()):
case (_, FieldDeclarationsMacro()):
case (_, FunctionDeclarationsMacro()):
case (_, VariableDeclarationsMacro()):
case (_, TypeAliasDeclarationsMacro()):
throw UnimplementedError('Unimplemented macro target');
default:
throw UnsupportedError('Unsupported macro type or invalid target:\n'
'macro: $macro\ntarget: $target');
}
}

/// Runs [macro] in the definitions phase and returns an [AugmentResponse].
Future<AugmentResponse> executeDefinitionsMacro(
Macro macro, Host host, AugmentRequest request) async {
final target = request.target;
// TODO: https://github.com/dart-lang/macros/issues/100.
final queryResult = await host.query(Query(target: target));
final properties =
queryResult.uris[target.uri]!.scopes[target.name]!.properties;

switch ((properties, macro)) {
case (Properties(isClass: true), ClassDefinitionsMacro macro):
return await macro.buildDefinitionsForClass(host, request);
case (_, LibraryDefinitionsMacro()):
case (_, EnumDefinitionsMacro()):
case (_, ExtensionDefinitionsMacro()):
case (_, ExtensionTypeDefinitionsMacro()):
case (_, MixinDefinitionsMacro()):
case (_, EnumValueDefinitionsMacro()):
case (_, ConstructorDefinitionsMacro()):
case (_, MethodDefinitionsMacro()):
case (_, FieldDefinitionsMacro()):
case (_, FunctionDefinitionsMacro()):
case (_, VariableDefinitionsMacro()):
throw UnimplementedError('Unimplemented macro target');
default:
throw UnsupportedError('Unsupported macro type or invalid target:\n'
'macro: $macro\ntarget: $target');
}
}
56 changes: 38 additions & 18 deletions pkgs/_macro_client/test/macro_client_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ import 'package:macro_service/macro_service.dart';
import 'package:test/test.dart';

void main() {
final target = QualifiedName.parse('package:a/a.dart#A');

for (final protocol in [
Protocol(encoding: ProtocolEncoding.json, version: ProtocolVersion.macros1),
Protocol(
Expand All @@ -40,6 +42,29 @@ void main() {
return result;
}

void answerTargetQuery(MacroRequest targetQuery, Socket socket) {
expect(
targetQuery,
{
'id': targetQuery.id,
'type': 'QueryRequest',
'value': {
'query': {'target': target}
},
},
);
Scope.query.run(() => protocol.send(
socket.add,
Response.queryResponse(
QueryResponse(
model: Model()
..uris[target.uri] = (Library()
..scopes[target.name] = Interface(
properties: (Properties(isClass: true))))),
requestId: targetQuery.id)
.node));
}

test('connects to service', () async {
final serverSocket = await ServerSocket.bind('localhost', 0);
addTearDown(serverSocket.close);
Expand Down Expand Up @@ -87,7 +112,7 @@ void main() {
});
});

test('sends augmentation requests to macros, sends reponse', () async {
test('sends augmentation requests to macros, sends response', () async {
final serverSocket = await ServerSocket.bind('localhost', 0);

unawaited(MacroClient.run(
Expand Down Expand Up @@ -120,8 +145,10 @@ void main() {
macroAnnotation: QualifiedName(
uri: 'package:_test_macros/declare_x_macro.dart',
name: 'DeclareX'),
AugmentRequest(phase: 2))
AugmentRequest(phase: 2, target: target))
.node);
answerTargetQuery(MacroRequest.fromJson(await responses.next), socket);

final augmentResponse = await responses.next;
expect(augmentResponse, {
'requestId': requestId,
Expand Down Expand Up @@ -174,11 +201,10 @@ void main() {
macroAnnotation: QualifiedName(
uri: 'package:_test_macros/query_class.dart',
name: 'QueryClass'),
AugmentRequest(
phase: 3,
target:
QualifiedName(uri: 'package:foo/foo.dart', name: 'Foo')),
AugmentRequest(phase: 3, target: target),
).node);
answerTargetQuery(MacroRequest.fromJson(await responses.next), socket);

final queryRequest = await responses.next;
final queryRequestId = MacroRequest.fromJson(queryRequest).id;
expect(
Expand All @@ -187,19 +213,15 @@ void main() {
'id': queryRequestId,
'type': 'QueryRequest',
'value': {
'query': {
'target': {'uri': 'package:foo/foo.dart', 'name': 'Foo'}
}
'query': {'target': target}
},
},
);

Scope.query.run(() => protocol.send(
socket.add,
Response.queryResponse(
QueryResponse(
model: Model()
..uris['package:foo/foo.dart'] = Library()),
QueryResponse(model: Model()..uris[target.uri] = Library()),
requestId: queryRequestId)
.node));

Expand All @@ -215,8 +237,7 @@ void main() {
'code': [
{
'type': 'String',
'value':
'// {"uris":{"package:foo/foo.dart":{"scopes":{}}}}'
'value': '// {"uris":{"${target.uri}":{"scopes":{}}}}'
}
]
}
Expand Down Expand Up @@ -249,11 +270,10 @@ void main() {
macroAnnotation: QualifiedName(
uri: 'package:_test_macros/query_class.dart',
name: 'QueryClass'),
AugmentRequest(
phase: 3,
target: QualifiedName(
uri: 'package:foo/foo.dart', name: 'Foo')),
AugmentRequest(phase: 3, target: target),
).node);
unawaited(responses.next.then((response) =>
answerTargetQuery(MacroRequest.fromJson(response), socket)));
}

final queryRequest1 = await responses.next;
Expand Down
15 changes: 12 additions & 3 deletions pkgs/_macro_host/test/macro_host_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,12 @@ void main() {
await host.queryMacroPhases(packageConfig, macroAnnotation), {2});

expect(
await host.augment(macroAnnotation, AugmentRequest(phase: 2)),
await host.augment(
macroAnnotation,
AugmentRequest(
phase: 2,
target: QualifiedName(
name: 'Foo', uri: 'package:foo/foo.dart'))),
Scope.macro.run(() => AugmentResponse(augmentations: [
Augmentation(code: [Code.string('int get x => 3;')])
])));
Expand Down Expand Up @@ -65,7 +70,8 @@ void main() {
Scope.macro.run(() => AugmentResponse(augmentations: [
Augmentation(code: [
Code.string(
'// {"uris":{"package:foo/foo.dart":{"scopes":{}}}}')
'// {"uris":{"package:foo/foo.dart":{"scopes":{"Foo":{'
'"members":{},"properties":{"isClass":true}}}}}}')
])
])));
});
Expand Down Expand Up @@ -102,6 +108,9 @@ class TestQueryService implements QueryService {
@override
Future<QueryResponse> handle(QueryRequest request) async {
return QueryResponse(
model: Model()..uris['package:foo/foo.dart'] = Library());
model: Model()
..uris[request.query.target.uri] = (Library()
..scopes[request.query.target.name] =
Interface(properties: Properties(isClass: true))));
}
}
5 changes: 3 additions & 2 deletions pkgs/_test_macros/lib/declare_x_macro.dart
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ class DeclareX {
const DeclareX();
}

class DeclareXImplementation implements Macro {
class DeclareXImplementation implements ClassDeclarationsMacro {
// TODO(davidmorgan): this should be injected by the bootstrap script.
@override
MacroDescription get description => MacroDescription(
Expand All @@ -22,7 +22,8 @@ class DeclareXImplementation implements Macro {
runsInPhases: [2]);

@override
Future<AugmentResponse> augment(Host host, AugmentRequest request) async {
Future<AugmentResponse> buildDeclarationsForClass(
Host host, AugmentRequest request) async {
// TODO(davidmorgan): make the host only run in the phases requested so
// that this is not needed.
if (request.phase != 2) return AugmentResponse(augmentations: []);
Expand Down
Loading

0 comments on commit 6de2869

Please sign in to comment.