Skip to content

Commit fdfa3b6

Browse files
davidmorganlrhn
authored andcommitted
[macros] Add writing of augmentations, Equals and HashCode macros. (#3853)
1 parent e483e6d commit fdfa3b6

File tree

20 files changed

+317
-47
lines changed

20 files changed

+317
-47
lines changed

working/macros/dart_model/.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
**/*.a.dart

working/macros/dart_model/analysis_options.yaml

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,15 @@
1+
analyzer:
2+
enable-experiment:
3+
# For augmentations.
4+
- macros
5+
6+
exclude:
7+
- /**.a.dart
8+
19
linter:
210
rules:
11+
# Additional lints.
12+
- unawaited_futures
313
# From package:lints/core.yaml
414
- avoid_empty_else
515
- avoid_relative_lib_imports

working/macros/dart_model/dart_model/lib/model.dart

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ extension type Model.fromJson(Map<String, Object?> node) {
3838
Model() : this.fromJson({});
3939

4040
Iterable<String> get uris => node.keys;
41+
Iterable<Library> get libraries => node.values.cast();
4142

4243
Library? library(String uri) => node[uri] as Library?;
4344
Scope? scope(QualifiedName qualifiedName) =>
@@ -83,7 +84,12 @@ extension type Model.fromJson(Map<String, Object?> node) {
8384

8485
void updateAtPath(Path path, Object? value) {
8586
if (path.path.length == 1) {
87+
final name = path.path.single;
8688
node[path.path.single] = value;
89+
90+
if (value is Map<String, Object?>) {
91+
_updateNames(name, value);
92+
}
8793
} else {
8894
if (!node.containsKey(path.path.first)) {
8995
node[path.path.first] = <String, Object?>{};
@@ -92,6 +98,15 @@ extension type Model.fromJson(Map<String, Object?> node) {
9298
}
9399
}
94100

101+
void _updateNames(String name, Map<String, Object?> value) {
102+
_names[value] = name;
103+
for (final entry in value.entries) {
104+
if (entry.value is Map<String, Object?>) {
105+
_updateNames(entry.key, entry.value as Map<String, Object?>);
106+
}
107+
}
108+
}
109+
95110
void removeAtPath(Path path) {
96111
if (path.path.length == 1) {
97112
node.remove(path.path.single);
@@ -121,6 +136,7 @@ extension type Library.fromJson(Map<String, Object?> node) implements Object {
121136
Library() : this.fromJson({});
122137

123138
Iterable<String> get names => node.keys;
139+
Iterable<Scope> get scopes => node.values.cast();
124140

125141
Scope? scope(String uri) => node[uri] as Scope?;
126142

working/macros/dart_model/dart_model_analyzer_service/lib/dart_model_analyzer_service.dart

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,10 @@ class DartModelAnalyzerService implements Service {
3535
for (final file in Directory(context!.contextRoot.root.path)
3636
.listSync(recursive: true)
3737
.whereType<File>()
38-
.where((f) => f.path.endsWith('.dart'))) {
38+
.where((f) =>
39+
f.path.endsWith('.dart') &&
40+
// TODO(davidmorgan): exclude augmentation files in a better way.
41+
!f.path.endsWith('.a.dart'))) {
3942
final library = (await session!.getResolvedLibrary(file.path))
4043
as ResolvedLibraryResult;
4144
for (final classElement

working/macros/dart_model/macro_client/lib/macro.dart

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,10 @@
22
// for details. All rights reserved. Use of this source code is governed by a
33
// BSD-style license that can be found in the LICENSE file.
44

5-
import 'package:dart_model/query.dart';
5+
import 'package:dart_model/model.dart';
6+
import 'package:macro_protocol/host.dart';
67

78
abstract interface class Macro {
8-
void start(Service host) {}
9+
QualifiedName get name;
10+
void start(Host host) {}
911
}

working/macros/dart_model/macro_client/lib/macro_client.dart

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import 'dart:io';
66

77
import 'package:macro_client/macro.dart';
88

9-
import 'socket_service.dart';
9+
import 'socket_host.dart';
1010

1111
class MacroClient {
1212
final List<String> arguments;
@@ -15,7 +15,7 @@ class MacroClient {
1515

1616
Future<void> host(List<Macro> macros) async {
1717
final socket = await Socket.connect('localhost', 26199);
18-
final host = SocketService(socket);
18+
final host = SocketHost(socket);
1919
for (final macro in macros) {
2020
macro.start(host);
2121
}

working/macros/dart_model/macro_client/lib/socket_service.dart renamed to working/macros/dart_model/macro_client/lib/socket_host.dart

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,17 +9,18 @@ import 'dart:io';
99
import 'package:dart_model/delta.dart';
1010
import 'package:dart_model/model.dart';
1111
import 'package:dart_model/query.dart';
12+
import 'package:macro_protocol/host.dart';
1213
import 'package:macro_protocol/message.dart';
1314

14-
class SocketService implements Service {
15+
class SocketHost implements Service, Host {
1516
final Socket socket;
1617
final StreamController<Object?> messagesController =
1718
StreamController.broadcast();
1819
Stream<Object?> get messages => messagesController.stream;
1920
int _id = 0;
2021
final Map<int, StreamController<Delta>> _deltaStreams = {};
2122

22-
SocketService(this.socket) {
23+
SocketHost(this.socket) {
2324
socket
2425
.cast<List<int>>()
2526
.transform(utf8.decoder)
@@ -50,4 +51,16 @@ class SocketService implements Service {
5051
messagesController.add(message);
5152
}
5253
}
54+
55+
@override
56+
Service get service => this;
57+
58+
@override
59+
Future<void> augment(
60+
{required QualifiedName macro,
61+
required String uri,
62+
required String augmentation}) async {
63+
socket.writeln(json.encode(
64+
AugmentRequest(macro: macro, uri: uri, augmentation: augmentation)));
65+
}
5366
}

working/macros/dart_model/macro_host/bin/main.dart

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22
// for details. All rights reserved. Use of this source code is governed by a
33
// BSD-style license that can be found in the LICENSE file.
44

5+
import 'dart:io';
6+
57
import 'package:analyzer/dart/analysis/context_builder.dart';
68
import 'package:analyzer/dart/analysis/context_locator.dart';
79
import 'package:dart_model_analyzer_service/dart_model_analyzer_service.dart';
@@ -14,5 +16,9 @@ Future<void> main(List<String> arguments) async {
1416
contextRoot:
1517
ContextLocator().locateRoots(includedPaths: [workspace]).first);
1618
final host = DartModelAnalyzerService(context: analysisContext);
17-
MacroHost(host).run();
19+
await MacroHost(host, (uri) {
20+
final path = analysisContext.currentSession.uriConverter.uriToPath(uri);
21+
if (path == null) return null;
22+
return File(path);
23+
}).run();
1824
}

working/macros/dart_model/macro_host/lib/macro_host.dart

Lines changed: 37 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,28 +2,61 @@
22
// for details. All rights reserved. Use of this source code is governed by a
33
// BSD-style license that can be found in the LICENSE file.
44

5+
import 'dart:async';
56
import 'dart:io';
67

78
import 'package:dart_model/query.dart';
9+
import 'package:macro_protocol/host.dart';
810

911
import 'socket_client.dart';
1012

11-
class MacroHost {
13+
class MacroHost implements Host {
14+
@override
15+
final Service service;
16+
final File? Function(Uri) uriConverter;
1217
ServerSocket? serverSocket;
13-
Service host;
18+
final Map<String, Map<Object, String>> _augmentationsByUri = {};
1419

15-
MacroHost(this.host);
20+
MacroHost(this.service, this.uriConverter);
1621

1722
Future<void> run() async {
1823
serverSocket = await ServerSocket.bind('localhost', 26199);
1924
print('macro_host listening on localhost:26199');
2025

2126
await for (final socket in serverSocket!) {
22-
SocketClient(host, socket);
27+
SocketClient(this, socket);
2328
}
2429
}
2530

2631
void handle(Socket socket) {
2732
print('Got $socket');
2833
}
34+
35+
@override
36+
Future<void> augment(
37+
{required Object macro,
38+
required String uri,
39+
required String augmentation}) async {
40+
print('Augment: $uri $augmentation');
41+
final baseFile = uriConverter(Uri.parse(uri))!;
42+
final baseName =
43+
baseFile.path.substring(baseFile.path.lastIndexOf('/') + 1);
44+
final augmentationFile = File(baseFile.path.replaceAll('.dart', '.a.dart'));
45+
46+
print(_augmentationsByUri);
47+
if (_augmentationsByUri[uri] == null) {
48+
print('create map');
49+
_augmentationsByUri[uri] = Map.identity();
50+
}
51+
final augmentations = _augmentationsByUri[uri]!;
52+
augmentations[macro] = augmentation;
53+
print(_augmentationsByUri);
54+
55+
// TODO(davidmorgan): write async? Needs locking.
56+
augmentationFile.writeAsStringSync('''
57+
augment library '$baseName';
58+
59+
${augmentations.values.join('\n\n')}
60+
''');
61+
}
2962
}

working/macros/dart_model/macro_host/lib/socket_client.dart

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,14 +2,17 @@
22
// for details. All rights reserved. Use of this source code is governed by a
33
// BSD-style license that can be found in the LICENSE file.
44

5+
import 'dart:async';
56
import 'dart:convert';
67
import 'dart:io';
78

9+
import 'package:dart_model/model.dart';
810
import 'package:dart_model/query.dart';
11+
import 'package:macro_protocol/host.dart';
912
import 'package:macro_protocol/message.dart';
1013

1114
class SocketClient {
12-
final Service host;
15+
final Host host;
1316
final Socket socket;
1417

1518
SocketClient(this.host, this.socket) {
@@ -20,19 +23,29 @@ class SocketClient {
2023
.listen(handle);
2124
}
2225

26+
Service get service => host.service;
27+
2328
void handle(String line) async {
2429
final message = Message.fromJson(json.decode(line));
2530

2631
if (message.isQueryRequest) {
27-
final response = await host.query(message.asQueryRequest.query);
32+
final response = await service.query(message.asQueryRequest.query);
2833
socket.writeln(json.encode(QueryResponse(response)));
2934
} else if (message.isWatchRequest) {
3035
final request = message.asWatchRequest;
31-
final response = await host.watch(request.query);
36+
final response = await service.watch(request.query);
3237
response.listen((delta) {
3338
socket
3439
.writeln(json.encode(WatchResponse(id: request.id, delta: delta)));
3540
});
41+
} else if (message.isAugmentRequest) {
42+
final request = message.asAugmentRequest;
43+
unawaited(augment(request.macro, request.uri, request.augmentation));
3644
}
3745
}
46+
47+
Future<void> augment(
48+
QualifiedName macro, String uri, String augmentation) async {
49+
await host.augment(macro: macro, uri: uri, augmentation: augmentation);
50+
}
3851
}
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
// Copyright (c) 2024, the Dart project authors. Please see the AUTHORS file
2+
// for details. All rights reserved. Use of this source code is governed by a
3+
// BSD-style license that can be found in the LICENSE file.
4+
5+
import 'package:dart_model/model.dart';
6+
import 'package:dart_model/query.dart';
7+
8+
abstract interface class Host {
9+
Service get service;
10+
Future<void> augment(
11+
{required QualifiedName macro,
12+
required String uri,
13+
required String augmentation});
14+
}

working/macros/dart_model/macro_protocol/lib/message.dart

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,11 @@ extension type Message.fromJson(Map<String, Object?> node) {
1616
QueryRequest get asQueryRequest => this as QueryRequest;
1717
bool get isQueryResponse => node['type'] == 'queryResponse';
1818
QueryResponse get asQueryResponse => this as QueryResponse;
19+
20+
bool get isAugmentRequest => node['type'] == 'augment';
21+
AugmentRequest get asAugmentRequest => this as AugmentRequest;
22+
bool get isAugmentResponse => node['type'] == 'augmentResponse';
23+
AugmentResponse get asAugmentResponse => this as AugmentResponse;
1924
}
2025

2126
extension type QueryRequest.fromJson(Map<String, Object?> node)
@@ -64,3 +69,28 @@ extension type WatchResponse.fromJson(Map<String, Object?> node) {
6469
int get id => node['id'] as int;
6570
Delta get delta => node['delta'] as Delta;
6671
}
72+
73+
extension type AugmentRequest.fromJson(Map<String, Object?> node)
74+
implements Message {
75+
AugmentRequest(
76+
{required QualifiedName macro,
77+
required String uri,
78+
required String augmentation})
79+
: this.fromJson({
80+
'type': 'augment',
81+
'macro': macro.toString(),
82+
'uri': uri,
83+
'augmentation': augmentation,
84+
});
85+
86+
QualifiedName get macro => QualifiedName.tryParse(node['macro'] as String)!;
87+
String get uri => node['uri'] as String;
88+
String get augmentation => node['augmentation'] as String;
89+
}
90+
91+
extension type AugmentResponse.fromJson(Map<String, Object?> node) {
92+
AugmentResponse()
93+
: this.fromJson({
94+
'type': 'augmentResponse',
95+
});
96+
}

working/macros/dart_model/testing/presubmit

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,21 @@ for package in dart_model dart_model_analyzer_service dart_model_repl \
88
testing/test_macro_annotations testing/test_macros; do
99
pushd "$package"
1010
dart pub get
11-
dart analyze --fatal-infos
11+
12+
# Skip analyzing packages that need augmentations to be generated.
13+
if ! grep -qr 'import augment' lib; then
14+
dart analyze --fatal-infos
15+
fi
16+
17+
# Delete augmentation files.
18+
find . -name \*.a.dart | xargs --no-run-if-empty rm
19+
# Remove augmentation imports.
20+
sed -i -e 's#^import augment#// import augment#' $(find . -name \*.dart)
21+
# Format.
1222
dart format .
23+
# Put back augmentation imports.
24+
sed -i -e 's#^// import augment#import augment#' $(find . -name \*.dart)
25+
1326
# Run tests if the test folder is present.
1427
if test -d test; then
1528
dart run test

working/macros/dart_model/testing/scratch/lib/scratch.dart

Lines changed: 14 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -3,14 +3,19 @@
33
// BSD-style license that can be found in the LICENSE file.
44

55
import 'package:test_macro_annotations/annotations.dart';
6+
import augment 'scratch.a.dart';
67

7-
@FirstMacro()
8-
class Foo {}
8+
@Equals()
9+
@HashCode()
10+
class Foo {
11+
int? x;
12+
int? y;
13+
String? b;
14+
}
915

10-
@FirstMacro()
11-
class Bar {}
12-
13-
class Baz {}
14-
15-
@FirstMacro()
16-
class Hmm {}
16+
@Equals()
17+
class Bar {
18+
int? x;
19+
int? y;
20+
String? b;
21+
}

0 commit comments

Comments
 (0)