Skip to content

Commit 66baf8b

Browse files
authored
Extend benchmark generator to apply arbitrary macros to classes. (dart-archive#146)
1 parent 4613111 commit 66baf8b

File tree

8 files changed

+141
-160
lines changed

8 files changed

+141
-160
lines changed

goldens/foo/lib/built_value/built_value_test.analyzer.augmentations

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -33,18 +33,18 @@ prefix1.Empty rebuild(void Function(prefix1.EmptyBuilder) updates) =>
3333
augment class PrimitiveFields {
3434
factory PrimitiveFields([void Function(prefix1.PrimitiveFieldsBuilder)? updates]) =>
3535
(prefix1.PrimitiveFieldsBuilder()..update(updates)).build();
36-
PrimitiveFields._({required this.anInt,required this.aString,}) {}
36+
PrimitiveFields._({required this.anInt,required this.aString,required this.aNullableString,}) {}
3737

3838
prefix1.PrimitiveFieldsBuilder toBuilder() => prefix1.PrimitiveFieldsBuilder()..replace(this);
3939
prefix1.PrimitiveFields rebuild(void Function(prefix1.PrimitiveFieldsBuilder) updates) =>
4040
(toBuilder()..update(updates)).build();
4141

42-
prefix2.int get hashCode => prefix2.Object.hashAll([anInt,aString,]);
42+
prefix2.int get hashCode => prefix2.Object.hashAll([anInt,aString,aNullableString,]);
4343

4444
prefix2.bool operator==(prefix2.Object other) =>
45-
other is prefix1.PrimitiveFields&& anInt == other.anInt&& aString == other.aString;
45+
other is prefix1.PrimitiveFields&& anInt == other.anInt&& aString == other.aString&& aNullableString == other.aNullableString;
4646

47-
prefix2.String toString() => 'PrimitiveFields(anInt: $anInt, aString: $aString)';
47+
prefix2.String toString() => 'PrimitiveFields(anInt: $anInt, aString: $aString, aNullableString: $aNullableString)';
4848

4949
}
5050
augment class NestedFields {
@@ -73,11 +73,11 @@ prefix1.NestedFields build() => prefix1.NestedFields._(aPrimitiveFields: aPrimit
7373

7474
}
7575
augment class PrimitiveFieldsBuilder {
76-
prefix2.int? anInt;prefix2.String? aString;
76+
prefix2.int? anInt;prefix2.String? aString;prefix2.String? aNullableString;
7777

78-
void replace(prefix1.PrimitiveFields other) { this.anInt = other.anInt;this.aString = other.aString; }
78+
void replace(prefix1.PrimitiveFields other) { this.anInt = other.anInt;this.aString = other.aString;this.aNullableString = other.aNullableString; }
7979
void update(void Function(prefix1.PrimitiveFieldsBuilder)? updates) => updates?.call(this);
80-
prefix1.PrimitiveFields build() => prefix1.PrimitiveFields._(anInt: anInt!,aString: aString!,);
80+
prefix1.PrimitiveFields build() => prefix1.PrimitiveFields._(anInt: anInt!,aString: aString!,aNullableString: aNullableString,);
8181

8282
}
8383
augment class EmptyBuilder {

goldens/foo/lib/built_value/built_value_test.dart

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,10 @@ void main() {
3636
);
3737
expect(value2, isNot(value));
3838
expect(value2.hashCode, isNot(value.hashCode));
39-
expect(value2.toString(), 'PrimitiveFields(anInt: 4, aString: five)');
39+
expect(
40+
value2.toString(),
41+
'PrimitiveFields(anInt: 4, aString: five, aNullableString: null)',
42+
);
4043

4144
final sameValue = value.rebuild((b) => b);
4245
expect(sameValue, value);
@@ -56,7 +59,7 @@ void main() {
5659
expect(
5760
value.toString(),
5861
'NestedFields(aPrimitiveFields: PrimitiveFields('
59-
'anInt: 3, aString: four), aString: five)',
62+
'anInt: 3, aString: four, aNullableString: null), aString: five)',
6063
);
6164
});
6265
});
@@ -69,6 +72,7 @@ class Empty {}
6972
class PrimitiveFields {
7073
final int anInt;
7174
final String aString;
75+
final String? aNullableString;
7276
}
7377

7478
@BuiltValue()

pkgs/_macro_tool/README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ All examples are run from the root of this repo.
1919
Benchmarks require that you first create sources to benchmark with:
2020

2121
```bash
22-
dart run benchmark_generator large macro 16
22+
dart run benchmark_generator large 16 BuiltValue JsonCodable
2323
```
2424

2525
Benchmark running macros:

pkgs/_test_macros/lib/built_value.dart

Lines changed: 19 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -152,7 +152,7 @@ class BuiltValueBuilderImplementation implements ClassDeclarationsMacro {
152152
// TODO(davidmorgan): there should be a way to do this in one query.
153153
final fieldTypes = <String>{};
154154
for (final field in fields) {
155-
final qualifiedName = field.value.returnType.asNamedTypeDesc.name;
155+
final qualifiedName = field.value.returnType.qualifiedName;
156156
if (qualifiedName.uri != 'dart:core') {
157157
fieldTypes.add(qualifiedName.asString);
158158
}
@@ -191,8 +191,7 @@ class BuiltValueBuilderImplementation implements ClassDeclarationsMacro {
191191

192192
final fieldDeclarations = StringBuffer();
193193
for (final field in fields) {
194-
final fieldTypeQualifiedName =
195-
field.value.returnType.asNamedTypeDesc.name;
194+
final fieldTypeQualifiedName = field.value.returnType.qualifiedName;
196195
if (nestedBuilderTypes.contains(fieldTypeQualifiedName.asString)) {
197196
final fieldBuilderQualifiedName = QualifiedName(
198197
uri: fieldTypeQualifiedName.uri,
@@ -211,8 +210,7 @@ class BuiltValueBuilderImplementation implements ClassDeclarationsMacro {
211210

212211
final copyFields = StringBuffer();
213212
for (final field in fields) {
214-
final fieldTypeQualifiedName =
215-
field.value.returnType.asNamedTypeDesc.name;
213+
final fieldTypeQualifiedName = field.value.returnType.qualifiedName;
216214
if (nestedBuilderTypes.contains(fieldTypeQualifiedName.asString)) {
217215
copyFields.write('this.${field.key} = other.${field.key}.toBuilder();');
218216
} else {
@@ -222,12 +220,12 @@ class BuiltValueBuilderImplementation implements ClassDeclarationsMacro {
222220

223221
final buildParams = StringBuffer();
224222
for (final field in fields) {
225-
final fieldTypeQualifiedName =
226-
field.value.returnType.asNamedTypeDesc.name;
223+
final fieldTypeQualifiedName = field.value.returnType.qualifiedName;
227224
if (nestedBuilderTypes.contains(fieldTypeQualifiedName.asString)) {
228225
buildParams.write('${field.key}: ${field.key}.build(),');
229226
} else {
230-
buildParams.write('${field.key}: ${field.key}!,');
227+
final maybeNotNull = field.value.returnType.isNullable ? '' : '!';
228+
buildParams.write('${field.key}: ${field.key}$maybeNotNull,');
231229
}
232230
}
233231

@@ -242,3 +240,16 @@ ${valueName.code} build() => ${valueName.code}._($buildParams);
242240
);
243241
}
244242
}
243+
244+
extension StaticTypeDescExtension on StaticTypeDesc {
245+
bool get isNullable => type == StaticTypeDescType.nullableTypeDesc;
246+
247+
QualifiedName get qualifiedName {
248+
return switch (type) {
249+
StaticTypeDescType.namedTypeDesc => asNamedTypeDesc.name,
250+
StaticTypeDescType.nullableTypeDesc =>
251+
asNullableTypeDesc.inner.qualifiedName,
252+
_ => throw ArgumentError(type),
253+
};
254+
}
255+
}

tool/benchmark_generator/README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ Generates code that uses macros, for benchmarking.
55
Example use, from the root of this repo:
66

77
```
8-
dart run benchmark_generator large macro 64
8+
dart run benchmark_generator large 64 BuiltValue JsonCodable
99
dart run _macro_tool \
1010
--workspace=goldens/foo \
1111
--packageConfig=.dart_tool/package_config.json \

tool/benchmark_generator/bin/benchmark_generator.dart

Lines changed: 28 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -4,30 +4,50 @@
44

55
import 'dart:io';
66

7-
import 'package:benchmark_generator/json_encodable/input_generator.dart';
7+
import 'package:benchmark_generator/input_generator.dart';
88
import 'package:benchmark_generator/workspace.dart';
9+
import 'package:dart_model/dart_model.dart';
910

1011
Future<void> main(List<String> arguments) async {
11-
if (arguments.length != 3) {
12+
if (arguments.length < 3) {
1213
print('''
13-
Creates packages to benchmark macro performance. Usage:
14+
Creates packages to benchmark macro performance.
1415
15-
dart run benchmark_generator <workspace name> <macro|manual|none> <# libraries>
16+
Available macro names: BuiltValue, JsonCodable
17+
18+
Usage:
19+
20+
dart run benchmark_generator <workspace name> <# libraries> <macro name> [additional macro names]
1621
''');
1722
exit(1);
1823
}
1924

2025
final workspaceName = arguments[0];
21-
final strategy = Strategy.values.where((e) => e.name == arguments[1]).single;
22-
final libraryCount = int.parse(arguments[2]);
26+
final libraryCount = int.parse(arguments[1]);
27+
28+
final macroNames = arguments.skip(2).toList();
29+
final macros = <QualifiedName>[
30+
for (final macroName in macroNames)
31+
switch (macroName) {
32+
'BuiltValue' => QualifiedName(
33+
uri: 'package:_test_macros/built_value.dart',
34+
name: 'BuiltValue',
35+
),
36+
'JsonCodable' => QualifiedName(
37+
uri: 'package:_test_macros/json_codable.dart',
38+
name: 'JsonCodable',
39+
),
40+
_ => throw ArgumentError(macroName),
41+
},
42+
];
2343

2444
final workspace = Workspace(workspaceName);
2545
print('Creating under: ${workspace.directory.path}');
26-
final inputGenerator = JsonEncodableInputGenerator(
46+
final inputGenerator = ClassesAndFieldsInputGenerator(
47+
macros: macros,
2748
fieldsPerClass: 100,
2849
classesPerLibrary: 10,
2950
librariesPerCycle: libraryCount,
30-
strategy: strategy,
3151
);
3252
inputGenerator.generate(workspace);
3353
}
Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
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:benchmark_generator/workspace.dart';
6+
import 'package:dart_model/dart_model.dart';
7+
8+
class ClassesAndFieldsInputGenerator {
9+
final List<QualifiedName> macros;
10+
final int fieldsPerClass;
11+
final int classesPerLibrary;
12+
final int librariesPerCycle;
13+
14+
ClassesAndFieldsInputGenerator({
15+
required this.macros,
16+
required this.fieldsPerClass,
17+
required this.classesPerLibrary,
18+
required this.librariesPerCycle,
19+
});
20+
21+
void generate(Workspace workspace) {
22+
for (var i = 0; i != librariesPerCycle; ++i) {
23+
workspace.write('a$i.dart', source: _generateLibrary(i));
24+
}
25+
}
26+
27+
String _generateLibrary(int index) {
28+
final buffer = StringBuffer();
29+
30+
for (final qualifiedName in macros) {
31+
buffer.writeln("import '${qualifiedName.uri}';");
32+
}
33+
34+
if (librariesPerCycle != 1) {
35+
final nextLibrary = (index + 1) % librariesPerCycle;
36+
buffer.writeln('import "a$nextLibrary.dart" as next_in_cycle;');
37+
buffer.writeln('next_in_cycle.A0? referenceOther;');
38+
}
39+
40+
for (var j = 0; j != classesPerLibrary; ++j) {
41+
buffer.write(_generateClass(index, j));
42+
}
43+
44+
return buffer.toString();
45+
}
46+
47+
String _generateClass(int libraryIndex, int index) {
48+
final className = 'A$index';
49+
String fieldName(int fieldIndex) {
50+
if (libraryIndex == 0 && index == 0 && fieldIndex == 0) {
51+
return 'aCACHEBUSTER';
52+
}
53+
return 'a$fieldIndex';
54+
}
55+
56+
final result = StringBuffer();
57+
for (final qualifiedName in macros) {
58+
result.writeln('@${qualifiedName.name}()');
59+
}
60+
61+
result.writeln('class $className {');
62+
63+
if (macros.any(
64+
(m) => m.asString == 'package:_test_macros/json_codable.dart#JsonCodable',
65+
)) {
66+
result.writeln('''
67+
// TODO(davidmorgan): see https://github.com/dart-lang/macros/issues/80.
68+
external $className.fromJson(Map<String, Object?> json);
69+
external Map<String, Object?> toJson();''');
70+
}
71+
72+
for (var i = 0; i != fieldsPerClass; ++i) {
73+
result.writeln('int? ${fieldName(i)};');
74+
}
75+
76+
result.writeln('}');
77+
return result.toString();
78+
}
79+
}

0 commit comments

Comments
 (0)