Skip to content

Commit

Permalink
Extend benchmark generator to apply arbitrary macros to classes.
Browse files Browse the repository at this point in the history
  • Loading branch information
davidmorgan committed Nov 21, 2024
1 parent a005818 commit 04fb59d
Show file tree
Hide file tree
Showing 8 changed files with 141 additions and 160 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -33,18 +33,18 @@ prefix1.Empty rebuild(void Function(prefix1.EmptyBuilder) updates) =>
augment class PrimitiveFields {
factory PrimitiveFields([void Function(prefix1.PrimitiveFieldsBuilder)? updates]) =>
(prefix1.PrimitiveFieldsBuilder()..update(updates)).build();
PrimitiveFields._({required this.anInt,required this.aString,}) {}
PrimitiveFields._({required this.anInt,required this.aString,required this.aNullableString,}) {}

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

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

prefix2.bool operator==(prefix2.Object other) =>
other is prefix1.PrimitiveFields&& anInt == other.anInt&& aString == other.aString;
other is prefix1.PrimitiveFields&& anInt == other.anInt&& aString == other.aString&& aNullableString == other.aNullableString;

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

}
augment class NestedFields {
Expand Down Expand Up @@ -73,11 +73,11 @@ prefix1.NestedFields build() => prefix1.NestedFields._(aPrimitiveFields: aPrimit

}
augment class PrimitiveFieldsBuilder {
prefix2.int? anInt;prefix2.String? aString;
prefix2.int? anInt;prefix2.String? aString;prefix2.String? aNullableString;

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

}
augment class EmptyBuilder {
Expand Down
8 changes: 6 additions & 2 deletions goldens/foo/lib/built_value/built_value_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,10 @@ void main() {
);
expect(value2, isNot(value));
expect(value2.hashCode, isNot(value.hashCode));
expect(value2.toString(), 'PrimitiveFields(anInt: 4, aString: five)');
expect(
value2.toString(),
'PrimitiveFields(anInt: 4, aString: five, aNullableString: null)',
);

final sameValue = value.rebuild((b) => b);
expect(sameValue, value);
Expand All @@ -56,7 +59,7 @@ void main() {
expect(
value.toString(),
'NestedFields(aPrimitiveFields: PrimitiveFields('
'anInt: 3, aString: four), aString: five)',
'anInt: 3, aString: four, aNullableString: null), aString: five)',
);
});
});
Expand All @@ -69,6 +72,7 @@ class Empty {}
class PrimitiveFields {
final int anInt;
final String aString;
final String? aNullableString;
}

@BuiltValue()
Expand Down
2 changes: 1 addition & 1 deletion pkgs/_macro_tool/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ All examples are run from the root of this repo.
Benchmarks require that you first create sources to benchmark with:

```bash
dart run benchmark_generator large macro 16
dart run benchmark_generator large 16 BuiltValue JsonCodable
```

Benchmark running macros:
Expand Down
27 changes: 19 additions & 8 deletions pkgs/_test_macros/lib/built_value.dart
Original file line number Diff line number Diff line change
Expand Up @@ -152,7 +152,7 @@ class BuiltValueBuilderImplementation implements ClassDeclarationsMacro {
// TODO(davidmorgan): there should be a way to do this in one query.
final fieldTypes = <String>{};
for (final field in fields) {
final qualifiedName = field.value.returnType.asNamedTypeDesc.name;
final qualifiedName = field.value.returnType.qualifiedName;
if (qualifiedName.uri != 'dart:core') {
fieldTypes.add(qualifiedName.asString);
}
Expand Down Expand Up @@ -191,8 +191,7 @@ class BuiltValueBuilderImplementation implements ClassDeclarationsMacro {

final fieldDeclarations = StringBuffer();
for (final field in fields) {
final fieldTypeQualifiedName =
field.value.returnType.asNamedTypeDesc.name;
final fieldTypeQualifiedName = field.value.returnType.qualifiedName;
if (nestedBuilderTypes.contains(fieldTypeQualifiedName.asString)) {
final fieldBuilderQualifiedName = QualifiedName(
uri: fieldTypeQualifiedName.uri,
Expand All @@ -211,8 +210,7 @@ class BuiltValueBuilderImplementation implements ClassDeclarationsMacro {

final copyFields = StringBuffer();
for (final field in fields) {
final fieldTypeQualifiedName =
field.value.returnType.asNamedTypeDesc.name;
final fieldTypeQualifiedName = field.value.returnType.qualifiedName;
if (nestedBuilderTypes.contains(fieldTypeQualifiedName.asString)) {
copyFields.write('this.${field.key} = other.${field.key}.toBuilder();');
} else {
Expand All @@ -222,12 +220,12 @@ class BuiltValueBuilderImplementation implements ClassDeclarationsMacro {

final buildParams = StringBuffer();
for (final field in fields) {
final fieldTypeQualifiedName =
field.value.returnType.asNamedTypeDesc.name;
final fieldTypeQualifiedName = field.value.returnType.qualifiedName;
if (nestedBuilderTypes.contains(fieldTypeQualifiedName.asString)) {
buildParams.write('${field.key}: ${field.key}.build(),');
} else {
buildParams.write('${field.key}: ${field.key}!,');
final maybeNotNull = field.value.returnType.isNullable ? '' : '!';
buildParams.write('${field.key}: ${field.key}$maybeNotNull,');
}
}

Expand All @@ -242,3 +240,16 @@ ${valueName.code} build() => ${valueName.code}._($buildParams);
);
}
}

extension StaticTypeDescExtension on StaticTypeDesc {
bool get isNullable => type == StaticTypeDescType.nullableTypeDesc;

QualifiedName get qualifiedName {
return switch (type) {
StaticTypeDescType.namedTypeDesc => asNamedTypeDesc.name,
StaticTypeDescType.nullableTypeDesc =>
asNullableTypeDesc.inner.qualifiedName,
_ => throw ArgumentError(type),
};
}
}
2 changes: 1 addition & 1 deletion tool/benchmark_generator/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ Generates code that uses macros, for benchmarking.
Example use, from the root of this repo:

```
dart run benchmark_generator large macro 64
dart run benchmark_generator large 64 BuiltValue JsonCodable
dart run _macro_tool \
--workspace=goldens/foo \
--packageConfig=.dart_tool/package_config.json \
Expand Down
36 changes: 28 additions & 8 deletions tool/benchmark_generator/bin/benchmark_generator.dart
Original file line number Diff line number Diff line change
Expand Up @@ -4,30 +4,50 @@

import 'dart:io';

import 'package:benchmark_generator/json_encodable/input_generator.dart';
import 'package:benchmark_generator/input_generator.dart';
import 'package:benchmark_generator/workspace.dart';
import 'package:dart_model/dart_model.dart';

Future<void> main(List<String> arguments) async {
if (arguments.length != 3) {
if (arguments.length < 3) {
print('''
Creates packages to benchmark macro performance. Usage:
Creates packages to benchmark macro performance.
dart run benchmark_generator <workspace name> <macro|manual|none> <# libraries>
Available macro names: BuiltValue, JsonCodable
Usage:
dart run benchmark_generator <workspace name> <# libraries> <macro name> [additional macro names]
''');
exit(1);
}

final workspaceName = arguments[0];
final strategy = Strategy.values.where((e) => e.name == arguments[1]).single;
final libraryCount = int.parse(arguments[2]);
final libraryCount = int.parse(arguments[1]);

final macroNames = arguments.skip(2).toList();
final macros = <QualifiedName>[
for (final macroName in macroNames)
switch (macroName) {
'BuiltValue' => QualifiedName(
uri: 'package:_test_macros/built_value.dart',
name: 'BuiltValue',
),
'JsonCodable' => QualifiedName(
uri: 'package:_test_macros/json_codable.dart',
name: 'JsonCodable',
),
_ => throw ArgumentError(macroName),
},
];

final workspace = Workspace(workspaceName);
print('Creating under: ${workspace.directory.path}');
final inputGenerator = JsonEncodableInputGenerator(
final inputGenerator = ClassesAndFieldsInputGenerator(
macros: macros,
fieldsPerClass: 100,
classesPerLibrary: 10,
librariesPerCycle: libraryCount,
strategy: strategy,
);
inputGenerator.generate(workspace);
}
79 changes: 79 additions & 0 deletions tool/benchmark_generator/lib/input_generator.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
// 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:benchmark_generator/workspace.dart';
import 'package:dart_model/dart_model.dart';

class ClassesAndFieldsInputGenerator {
final List<QualifiedName> macros;
final int fieldsPerClass;
final int classesPerLibrary;
final int librariesPerCycle;

ClassesAndFieldsInputGenerator({
required this.macros,
required this.fieldsPerClass,
required this.classesPerLibrary,
required this.librariesPerCycle,
});

void generate(Workspace workspace) {
for (var i = 0; i != librariesPerCycle; ++i) {
workspace.write('a$i.dart', source: _generateLibrary(i));
}
}

String _generateLibrary(int index) {
final buffer = StringBuffer();

for (final qualifiedName in macros) {
buffer.writeln("import '${qualifiedName.uri}';");
}

if (librariesPerCycle != 1) {
final nextLibrary = (index + 1) % librariesPerCycle;
buffer.writeln('import "a$nextLibrary.dart" as next_in_cycle;');
buffer.writeln('next_in_cycle.A0? referenceOther;');
}

for (var j = 0; j != classesPerLibrary; ++j) {
buffer.write(_generateClass(index, j));
}

return buffer.toString();
}

String _generateClass(int libraryIndex, int index) {
final className = 'A$index';
String fieldName(int fieldIndex) {
if (libraryIndex == 0 && index == 0 && fieldIndex == 0) {
return 'aCACHEBUSTER';
}
return 'a$fieldIndex';
}

final result = StringBuffer();
for (final qualifiedName in macros) {
result.writeln('@${qualifiedName.name}()');
}

result.writeln('class $className {');

if (macros.any(
(m) => m.asString == 'package:_test_macros/json_codable.dart#JsonCodable',
)) {
result.writeln('''
// TODO(davidmorgan): see https://github.com/dart-lang/macros/issues/80.
external $className.fromJson(Map<String, Object?> json);
external Map<String, Object?> toJson();''');
}

for (var i = 0; i != fieldsPerClass; ++i) {
result.writeln('int? ${fieldName(i)};');
}

result.writeln('}');
return result.toString();
}
}
Loading

0 comments on commit 04fb59d

Please sign in to comment.