diff --git a/goldens/foo/lib/built_value/built_value_test.analyzer.augmentations b/goldens/foo/lib/built_value/built_value_test.analyzer.augmentations index 9a92cff..38674ff 100644 --- a/goldens/foo/lib/built_value/built_value_test.analyzer.augmentations +++ b/goldens/foo/lib/built_value/built_value_test.analyzer.augmentations @@ -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 { @@ -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 { diff --git a/goldens/foo/lib/built_value/built_value_test.dart b/goldens/foo/lib/built_value/built_value_test.dart index 3d5f4e6..c874506 100644 --- a/goldens/foo/lib/built_value/built_value_test.dart +++ b/goldens/foo/lib/built_value/built_value_test.dart @@ -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); @@ -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)', ); }); }); @@ -69,6 +72,7 @@ class Empty {} class PrimitiveFields { final int anInt; final String aString; + final String? aNullableString; } @BuiltValue() diff --git a/pkgs/_macro_tool/README.md b/pkgs/_macro_tool/README.md index fbc7109..d4b6382 100644 --- a/pkgs/_macro_tool/README.md +++ b/pkgs/_macro_tool/README.md @@ -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: diff --git a/pkgs/_test_macros/lib/built_value.dart b/pkgs/_test_macros/lib/built_value.dart index e34542e..f928f42 100644 --- a/pkgs/_test_macros/lib/built_value.dart +++ b/pkgs/_test_macros/lib/built_value.dart @@ -152,7 +152,7 @@ class BuiltValueBuilderImplementation implements ClassDeclarationsMacro { // TODO(davidmorgan): there should be a way to do this in one query. final fieldTypes = {}; 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); } @@ -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, @@ -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 { @@ -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,'); } } @@ -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), + }; + } +} diff --git a/tool/benchmark_generator/README.md b/tool/benchmark_generator/README.md index f4f4e4c..ea94406 100644 --- a/tool/benchmark_generator/README.md +++ b/tool/benchmark_generator/README.md @@ -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 \ diff --git a/tool/benchmark_generator/bin/benchmark_generator.dart b/tool/benchmark_generator/bin/benchmark_generator.dart index 0e60aa8..a6df677 100644 --- a/tool/benchmark_generator/bin/benchmark_generator.dart +++ b/tool/benchmark_generator/bin/benchmark_generator.dart @@ -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 main(List 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 <# libraries> +Available macro names: BuiltValue, JsonCodable + +Usage: + + dart run benchmark_generator <# libraries> [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 = [ + 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); } diff --git a/tool/benchmark_generator/lib/input_generator.dart b/tool/benchmark_generator/lib/input_generator.dart new file mode 100644 index 0000000..bdd50c0 --- /dev/null +++ b/tool/benchmark_generator/lib/input_generator.dart @@ -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 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 json); + external Map toJson();'''); + } + + for (var i = 0; i != fieldsPerClass; ++i) { + result.writeln('int? ${fieldName(i)};'); + } + + result.writeln('}'); + return result.toString(); + } +} diff --git a/tool/benchmark_generator/lib/json_encodable/input_generator.dart b/tool/benchmark_generator/lib/json_encodable/input_generator.dart deleted file mode 100644 index 0e4a349..0000000 --- a/tool/benchmark_generator/lib/json_encodable/input_generator.dart +++ /dev/null @@ -1,133 +0,0 @@ -// 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'; - -enum Strategy { - manual, - macro, - none; - - String annotation(String name) { - switch (this) { - case Strategy.manual: - case Strategy.none: - return ''; - case Strategy.macro: - return '@m.$name()'; - } - } -} - -class JsonEncodableInputGenerator { - final int fieldsPerClass; - final int classesPerLibrary; - final int librariesPerCycle; - final Strategy strategy; - - JsonEncodableInputGenerator({ - required this.fieldsPerClass, - required this.classesPerLibrary, - required this.librariesPerCycle, - required this.strategy, - }); - - void generate(Workspace workspace) { - for (var i = 0; i != librariesPerCycle; ++i) { - workspace.write('a$i.dart', source: _generateLibrary(i)); - } - } - - String _generateLibrary( - int index, { - bool topLevelCacheBuster = false, - bool fieldCacheBuster = false, - }) { - final buffer = StringBuffer(); - - if (strategy == Strategy.macro) { - buffer.writeln("import 'package:_test_macros/json_codable.dart';"); - } - - 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( - strategy == Strategy.macro ? '@JsonCodable()' : '', - ); - - result.writeln('class $className {'); - result.writeln(''' - // TODO(davidmorgan): see https://github.com/dart-lang/macros/issues/80. - external $className.fromJson(Map json); - external Map toJson();'''); - - for (var i = 0; i != fieldsPerClass; ++i) { - result.writeln('int? ${fieldName(i)};'); - } - - if (strategy == Strategy.manual) { - result.writeln('$className._({'); - for (var i = 0; i != fieldsPerClass; ++i) { - result.writeln('required this.${fieldName(i)},'); - } - result.writeln('});'); - - result.writeln('Map toJson() {'); - result.writeln(' final result = {};'); - for (var i = 0; i != fieldsPerClass; ++i) { - result.writeln( - "if (${fieldName(i)} != null) result['${fieldName(i)}'] = ${fieldName(i)};", - ); - } - result.writeln('return result;'); - result.writeln('}'); - result.writeln( - 'factory $className.fromJson(Map json) {', - ); - result.writeln('return $className._('); - for (var i = 0; i != fieldsPerClass; ++i) { - result.writeln("${fieldName(i)}: json['${fieldName(i)}'] as int,"); - } - result.writeln(');'); - result.writeln('}'); - } - - result.writeln('}'); - return result.toString(); - } - - void changeIrrelevantInput(Workspace workspace) { - workspace.write( - 'a0.dart', - source: _generateLibrary(0, topLevelCacheBuster: true), - ); - } - - void changeRevelantInput(Workspace workspace) { - workspace.write( - 'a0.dart', - source: _generateLibrary(0, fieldCacheBuster: true), - ); - } -}