├── .gitattributes ├── .github ├── dependabot.yaml └── workflows │ └── dart.yml ├── .gitignore ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── goldens ├── README.md └── foo │ ├── analysis_options.yaml │ ├── lib │ ├── built_value │ │ ├── built_value_test.analyzer.augmentations │ │ └── built_value_test.dart │ ├── declare_x.analyzer.augmentations │ ├── declare_x.cfe.augmentations │ ├── declare_x.dart │ ├── foo.analyzer.json │ ├── foo.cfe.json │ ├── foo.dart │ ├── json_codable_test.analyzer.augmentations │ ├── json_codable_test.dart │ ├── literal_params.analyzer.augmentations │ ├── literal_params.dart │ ├── metadata.analyzer.json │ └── metadata.dart │ ├── mono_pkg.yaml │ └── pubspec.yaml ├── mono_repo.yaml ├── pkgs ├── _analyzer_cfe_macros │ ├── analysis_options.yaml │ ├── lib │ │ └── metadata_converter.dart │ ├── mono_pkg.yaml │ ├── pubspec.yaml │ └── test │ │ └── metadata_converter_test.dart ├── _analyzer_macros │ ├── analysis_options.yaml │ ├── bin │ │ └── server.dart │ ├── lib │ │ ├── macro_implementation.dart │ │ ├── query_service.dart │ │ └── src │ │ │ └── type_translation.dart │ ├── mono_pkg.yaml │ ├── pubspec.yaml │ └── test │ │ ├── analyzer_test.dart │ │ ├── golden_test.dart │ │ └── package_under_test │ │ ├── analysis_options.yaml │ │ ├── lib │ │ ├── apply_declare_x.dart │ │ └── apply_query_class.dart │ │ └── pubspec.yaml ├── _cfe_macros │ ├── analysis_options.yaml │ ├── lib │ │ ├── macro_implementation.dart │ │ ├── query_service.dart │ │ └── src │ │ │ └── type_translation.dart │ ├── mono_pkg.yaml │ ├── pubspec.yaml │ └── test │ │ ├── cfe_test.dart │ │ ├── golden_test.dart │ │ └── package_under_test │ │ ├── analysis_options.yaml │ │ ├── lib │ │ ├── apply_declare_x.dart │ │ └── apply_query_class.dart │ │ └── pubspec.yaml ├── _macro_builder │ ├── analysis_options.yaml │ ├── lib │ │ ├── macro_builder.dart │ │ └── src │ │ │ └── bootstrap.dart │ ├── mono_pkg.yaml │ ├── pubspec.yaml │ └── test │ │ └── macro_builder_test.dart ├── _macro_client │ ├── analysis_options.yaml │ ├── lib │ │ ├── macro_client.dart │ │ └── src │ │ │ ├── builder_impls.dart │ │ │ └── execute_macro.dart │ ├── mono_pkg.yaml │ ├── pubspec.yaml │ └── test │ │ └── macro_client_test.dart ├── _macro_host │ ├── analysis_options.yaml │ ├── lib │ │ ├── macro_host.dart │ │ └── src │ │ │ ├── macro_cache.dart │ │ │ ├── macro_host.dart │ │ │ └── package_config.dart │ ├── mono_pkg.yaml │ ├── pubspec.yaml │ └── test │ │ ├── macro_host_test.dart │ │ └── package_config_test.dart ├── _macro_runner │ ├── analysis_options.yaml │ ├── lib │ │ └── macro_runner.dart │ ├── mono_pkg.yaml │ ├── pubspec.yaml │ └── test │ │ └── macro_runner_test.dart ├── _macro_server │ ├── analysis_options.yaml │ ├── lib │ │ └── macro_server.dart │ ├── mono_pkg.yaml │ ├── pubspec.yaml │ └── test │ │ └── macro_server_test.dart ├── _macro_tool │ ├── README.md │ ├── bin │ │ └── _macro_tool.dart │ ├── lib │ │ ├── analyzer_macro_runner.dart │ │ ├── cfe_macro_runner.dart │ │ ├── macro_runner.dart │ │ ├── macro_tool.dart │ │ ├── main.dart │ │ └── source_file.dart │ ├── mono_pkg.yaml │ ├── pubspec.yaml │ └── test │ │ ├── macro_runner_test.dart │ │ ├── macro_tool_test.dart │ │ └── package_under_test │ │ ├── analysis_options.yaml │ │ ├── lib │ │ ├── apply_declare_x.dart │ │ └── apply_query_class.dart │ │ └── pubspec.yaml ├── _test_macros │ ├── analysis_options.yaml │ ├── lib │ │ ├── built_value.dart │ │ ├── declare_x_macro.dart │ │ ├── json_codable.dart │ │ ├── literal_params.dart │ │ ├── query_class.dart │ │ └── templating.dart │ ├── mono_pkg.yaml │ └── pubspec.yaml ├── dart_model │ ├── README.md │ ├── analysis_options.yaml │ ├── benchmark │ │ ├── builder_maps_builder_wire_benchmark.dart │ │ ├── builder_maps_json_wire_benchmark.dart │ │ ├── json_buffer.dart │ │ ├── lazy_maps_buffer_wire_benchmark.dart │ │ ├── lazy_maps_json_wire_benchmark.dart │ │ ├── lazy_wrappers_buffer_wire_benchmark.dart │ │ ├── main.dart │ │ ├── regular_dart_classes.dart │ │ ├── sdk_maps_buffer_wire_benchmark.dart │ │ ├── sdk_maps_builder_wire_benchmark.dart │ │ ├── sdk_maps_json_wire_benchmark.dart │ │ └── serialization_benchmark.dart │ ├── lib │ │ ├── dart_model.dart │ │ ├── serialization.dart │ │ └── src │ │ │ ├── dart_model.dart │ │ │ ├── dart_model.g.dart │ │ │ ├── deep_cast_map.dart │ │ │ ├── json_buffer │ │ │ ├── closed_list.dart │ │ │ ├── closed_map.dart │ │ │ ├── explanations.dart │ │ │ ├── growable_map.dart │ │ │ ├── iterables.dart │ │ │ ├── json_buffer_builder.dart │ │ │ ├── type.dart │ │ │ └── typed_map.dart │ │ │ ├── lazy_merged_map.dart │ │ │ ├── macro_metadata.g.dart │ │ │ ├── scopes.dart │ │ │ ├── type.dart │ │ │ └── type_system.dart │ ├── mono_pkg.yaml │ ├── pubspec.yaml │ └── test │ │ ├── deep_cast_test.dart │ │ ├── json_buffer │ │ ├── closed_list_test.dart │ │ ├── closed_map_test.dart │ │ ├── growable_map_test.dart │ │ ├── json_buffer_builder_test.dart │ │ ├── testing.dart │ │ └── typed_map_test.dart │ │ ├── lazy_merged_map_test.dart │ │ ├── model_test.dart │ │ ├── scopes_test.dart │ │ ├── type_system_test.dart │ │ └── type_test.dart ├── macro │ ├── analysis_options.yaml │ ├── lib │ │ ├── macro.dart │ │ └── src │ │ │ ├── builders.dart │ │ │ └── macro.dart │ ├── mono_pkg.yaml │ └── pubspec.yaml └── macro_service │ ├── analysis_options.yaml │ ├── lib │ ├── macro_service.dart │ └── src │ │ ├── handshake.g.dart │ │ ├── macro_service.dart │ │ ├── macro_service.g.dart │ │ └── message_grouper.dart │ ├── mono_pkg.yaml │ ├── pubspec.yaml │ └── test │ └── protocol_test.dart ├── pubspec.yaml ├── schemas ├── dart_model.schema.json ├── handshake.schema.json ├── macro_metadata.schema.json └── macro_service.schema.json └── tool ├── benchmark_generator ├── README.md ├── bin │ └── benchmark_generator.dart ├── lib │ ├── input_generator.dart │ └── workspace.dart ├── mono_pkg.yaml └── pubspec.yaml ├── ci.sh ├── dart_model_generator ├── analysis_options.yaml ├── bin │ └── main.dart ├── lib │ ├── definitions.dart │ ├── generate_dart_model.dart │ └── macro_metadata_definitions.dart ├── mono_pkg.yaml ├── pubspec.yaml └── test │ └── generated_output_test.dart ├── generate_converter ├── generate_definitions ├── run_e2e_tests.sh └── set_sdk_ref /.gitattributes: -------------------------------------------------------------------------------- 1 | # Stop git from changing golden file line endings on checkout. 2 | *.augmentations binary 3 | *.json binary 4 | -------------------------------------------------------------------------------- /.github/dependabot.yaml: -------------------------------------------------------------------------------- 1 | # Dependabot configuration file. 2 | version: 2 3 | 4 | updates: 5 | - package-ecosystem: github-actions 6 | directory: / 7 | schedule: 8 | interval: monthly 9 | labels: 10 | - autosubmit 11 | groups: 12 | github-actions: 13 | patterns: 14 | - "*" 15 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | **/.dart_tool 2 | **/pubspec.lock 3 | goldens/foo/lib/generated/** 4 | *.macro_tool_output 5 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # How to Contribute 2 | 3 | We'd love to accept your patches and contributions to this project. There are 4 | just a few small guidelines you need to follow. 5 | 6 | ## Contributor License Agreement 7 | 8 | Contributions to this project must be accompanied by a Contributor License 9 | Agreement (CLA). You (or your employer) retain the copyright to your 10 | contribution; this simply gives us permission to use and redistribute your 11 | contributions as part of the project. Head over to 12 | to see your current agreements on file or 13 | to sign a new one. 14 | 15 | You generally only need to submit a CLA once, so if you've already submitted one 16 | (even if it was for a different project), you probably don't need to do it 17 | again. 18 | 19 | ## Code Reviews 20 | 21 | All submissions, including submissions by project members, require review. We 22 | use GitHub pull requests for this purpose. Consult 23 | [GitHub Help](https://help.github.com/articles/about-pull-requests/) for more 24 | information on using pull requests. 25 | 26 | ## Coding style 27 | 28 | The Dart source code in this repo follows the: 29 | 30 | * [Dart style guide](https://dart.dev/guides/language/effective-dart/style) 31 | 32 | You should familiarize yourself with those guidelines. 33 | 34 | ## File headers 35 | 36 | All files in the Dart project must start with the following header; if you add a 37 | new file please also add this. The year should be a single number stating the 38 | year the file was created (don't use a range like "2011-2012"). Additionally, if 39 | you edit an existing file, you shouldn't update the year. 40 | 41 | // Copyright (c) 2024, the Dart project authors. Please see the AUTHORS file 42 | // for details. All rights reserved. Use of this source code is governed by a 43 | // BSD-style license that can be found in the LICENSE file. 44 | 45 | ## Community Guidelines 46 | 47 | This project follows 48 | [Google's Open Source Community Guidelines](https://opensource.google/conduct/). 49 | 50 | We pledge to maintain an open and welcoming environment. For details, see our 51 | [code of conduct](https://dart.dev/code-of-conduct). 52 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright 2024, the Dart project authors. 2 | 3 | Redistribution and use in source and binary forms, with or without 4 | modification, are permitted provided that the following conditions are 5 | met: 6 | 7 | * Redistributions of source code must retain the above copyright 8 | notice, this list of conditions and the following disclaimer. 9 | * Redistributions in binary form must reproduce the above 10 | copyright notice, this list of conditions and the following 11 | disclaimer in the documentation and/or other materials provided 12 | with the distribution. 13 | * Neither the name of Google LLC nor the names of its 14 | contributors may be used to endorse or promote products derived 15 | from this software without specific prior written permission. 16 | 17 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 18 | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 19 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 20 | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 21 | OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 22 | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 23 | LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 24 | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 25 | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 26 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 27 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | ## Overview 4 | 5 | > [!IMPORTANT] 6 | > This repository was a work-in-progress prototype for the 7 | > [macros](https://github.com/dart-lang/language/blob/b268fe0380995b074176e335beec476553eb3c04/working/macros/feature-specification.md) 8 | > feature, which was [canceled](https://medium.com/dartlang/an-update-on-dart-macros-data-serialization-06d3037d4f12) 9 | > in January 2025. 10 | 11 | For related work, see: 12 | 13 | - [Augmentations](https://github.com/dart-lang/language/blob/main/working/augmentation-libraries/feature-specification.md) feature 14 | - [`build_runner`](https://github.com/dart-lang/build/issues/3800) performance improvements 15 | - [Static Metaprogramming](https://github.com/dart-lang/language/issues/1482) feature request 16 | 17 | ## Packages 18 | 19 | | Package | Description | Version | 20 | |---|---|---| 21 | | [_analyzer_macros](pkgs/_analyzer_macros/) | Macro support for the analyzer. | | 22 | | [_cfe_macros](pkgs/_cfe_macros/) | Macro support for the CFE. | | 23 | | [_macro_builder](pkgs/_macro_builder/) | Builds macros. | | 24 | | [_macro_client](pkgs/_macro_client/) | Connects user macro code to a macro host. | | 25 | | [_macro_host](pkgs/_macro_host/) | Hosts macros. | | 26 | | [_macro_runner](pkgs/_macro_runner/) | Runs macros. | | 27 | | [_macro_server](pkgs/_macro_server/) | Serves a `macro_service`. | | 28 | | [_test_macros](pkgs/_test_macros/) | Some test macros. | | 29 | | [dart_model](pkgs/dart_model/) | Data model for information about Dart code, queries about Dart code and augmentations to Dart code. Serializable with a versioned JSON schema for use by macros, generators and other tools. | [![pub package](https://img.shields.io/pub/v/dart_model.svg)](https://pub.dev/packages/dart_model) | 30 | | [macro](pkgs/macro/) | For implementing a macro. | [![pub package](https://img.shields.io/pub/v/macro.svg)](https://pub.dev/packages/macro) | 31 | | [macro_service](pkgs/macro_service/) | Macro communication with the macro host. | [![pub package](https://img.shields.io/pub/v/macro_service.svg)](https://pub.dev/packages/macro_service) | 32 | | [generate_dart_model](tool/dart_model_generator/) | | | 33 | 34 | ## Publishing automation 35 | 36 | For information about our publishing automation and release process, see 37 | https://github.com/dart-lang/ecosystem/wiki/Publishing-automation. 38 | 39 | For additional information about contributing, see our 40 | [contributing](CONTRIBUTING.md) page. 41 | -------------------------------------------------------------------------------- /goldens/README.md: -------------------------------------------------------------------------------- 1 | # Golden Tests 2 | 3 | Each `.dart` file under this path creates a test case in 4 | `pkgs/_analyzer_macros/test/golden_test.dart` and 5 | `pkgs/_cfe_macros/test/golden_test.dart`. 6 | 7 | The test case looks for golden files next to the `.dart` file and asserts 8 | that they match. 9 | 10 | ## Introspection 11 | 12 | To test introspection, add `.analyzer.json` and/or `.cfe.json` golden files. 13 | The JSON in the golden file is compared with the query output on the `.dart` 14 | file, which must be tagged with the `QueryClass` macro. 15 | 16 | ## Application 17 | 18 | To test macro application, add `.analyzer.augmentations` and/or 19 | `.cfe.augmentations` golden files. These are compared with the full 20 | macro augmentation output of the corresponding `.dart` file. 21 | -------------------------------------------------------------------------------- /goldens/foo/analysis_options.yaml: -------------------------------------------------------------------------------- 1 | analyzer: 2 | enable-experiment: 3 | - enhanced-parts 4 | - macros 5 | -------------------------------------------------------------------------------- /goldens/foo/lib/built_value/built_value_test.analyzer.augmentations: -------------------------------------------------------------------------------- 1 | part of 'package:foo/built_value/built_value_test.dart'; 2 | 3 | import 'package:_test_macros/built_value.dart' as prefix0; 4 | import 'package:foo/built_value/built_value_test.dart' as prefix1; 5 | import 'dart:core' as prefix2; 6 | 7 | @prefix0.BuiltValueBuilder() 8 | class EmptyBuilder {} 9 | 10 | @prefix0.BuiltValueBuilder() 11 | class PrimitiveFieldsBuilder {} 12 | 13 | @prefix0.BuiltValueBuilder() 14 | class NestedFieldsBuilder {} 15 | 16 | augment class Empty { 17 | factory Empty([void Function(prefix1.EmptyBuilder)? updates]) => 18 | (prefix1.EmptyBuilder()..update(updates)).build(); 19 | Empty._() {} 20 | 21 | prefix1.EmptyBuilder toBuilder() => prefix1.EmptyBuilder()..replace(this); 22 | prefix1.Empty rebuild(void Function(prefix1.EmptyBuilder) updates) => 23 | (toBuilder()..update(updates)).build(); 24 | 25 | prefix2.int get hashCode => prefix2.Object.hashAll([]); 26 | 27 | prefix2.bool operator==(prefix2.Object other) => 28 | other is prefix1.Empty; 29 | 30 | prefix2.String toString() => 'Empty()'; 31 | 32 | } 33 | augment class PrimitiveFields { 34 | factory PrimitiveFields([void Function(prefix1.PrimitiveFieldsBuilder)? updates]) => 35 | (prefix1.PrimitiveFieldsBuilder()..update(updates)).build(); 36 | PrimitiveFields._({required this.anInt,required this.aString,required this.aNullableString,}) {} 37 | 38 | prefix1.PrimitiveFieldsBuilder toBuilder() => prefix1.PrimitiveFieldsBuilder()..replace(this); 39 | prefix1.PrimitiveFields rebuild(void Function(prefix1.PrimitiveFieldsBuilder) updates) => 40 | (toBuilder()..update(updates)).build(); 41 | 42 | prefix2.int get hashCode => prefix2.Object.hashAll([anInt,aString,aNullableString,]); 43 | 44 | prefix2.bool operator==(prefix2.Object other) => 45 | other is prefix1.PrimitiveFields&& anInt == other.anInt&& aString == other.aString&& aNullableString == other.aNullableString; 46 | 47 | prefix2.String toString() => 'PrimitiveFields(anInt: $anInt, aString: $aString, aNullableString: $aNullableString)'; 48 | 49 | } 50 | augment class NestedFields { 51 | factory NestedFields([void Function(prefix1.NestedFieldsBuilder)? updates]) => 52 | (prefix1.NestedFieldsBuilder()..update(updates)).build(); 53 | NestedFields._({required this.aPrimitiveFields,required this.stringWrapper,required this.aString,}) {} 54 | 55 | prefix1.NestedFieldsBuilder toBuilder() => prefix1.NestedFieldsBuilder()..replace(this); 56 | prefix1.NestedFields rebuild(void Function(prefix1.NestedFieldsBuilder) updates) => 57 | (toBuilder()..update(updates)).build(); 58 | 59 | prefix2.int get hashCode => prefix2.Object.hashAll([aPrimitiveFields,stringWrapper,aString,]); 60 | 61 | prefix2.bool operator==(prefix2.Object other) => 62 | other is prefix1.NestedFields&& aPrimitiveFields == other.aPrimitiveFields&& stringWrapper == other.stringWrapper&& aString == other.aString; 63 | 64 | prefix2.String toString() => 'NestedFields(aPrimitiveFields: $aPrimitiveFields, stringWrapper: $stringWrapper, aString: $aString)'; 65 | 66 | } 67 | augment class NestedFieldsBuilder { 68 | prefix1.PrimitiveFieldsBuilder aPrimitiveFields = prefix1.PrimitiveFieldsBuilder();prefix1.StringWrapper? stringWrapper;prefix2.String? aString; 69 | 70 | void replace(prefix1.NestedFields other) { this.aPrimitiveFields = other.aPrimitiveFields.toBuilder();this.stringWrapper = other.stringWrapper;this.aString = other.aString; } 71 | void update(void Function(prefix1.NestedFieldsBuilder)? updates) => updates?.call(this); 72 | prefix1.NestedFields build() => prefix1.NestedFields._(aPrimitiveFields: aPrimitiveFields.build(),stringWrapper: stringWrapper!,aString: aString!,); 73 | 74 | } 75 | augment class PrimitiveFieldsBuilder { 76 | prefix2.int? anInt;prefix2.String? aString;prefix2.String? aNullableString; 77 | 78 | void replace(prefix1.PrimitiveFields other) { this.anInt = other.anInt;this.aString = other.aString;this.aNullableString = other.aNullableString; } 79 | void update(void Function(prefix1.PrimitiveFieldsBuilder)? updates) => updates?.call(this); 80 | prefix1.PrimitiveFields build() => prefix1.PrimitiveFields._(anInt: anInt!,aString: aString!,aNullableString: aNullableString,); 81 | 82 | } 83 | augment class EmptyBuilder { 84 | 85 | 86 | void replace(prefix1.Empty other) { } 87 | void update(void Function(prefix1.EmptyBuilder)? updates) => updates?.call(this); 88 | prefix1.Empty build() => prefix1.Empty._(); 89 | 90 | } 91 | -------------------------------------------------------------------------------- /goldens/foo/lib/built_value/built_value_test.dart: -------------------------------------------------------------------------------- 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:_test_macros/built_value.dart'; 6 | import 'package:test/test.dart'; 7 | 8 | void main() { 9 | group('Empty class', () { 10 | test('instantiation, builder, rebuild, comparison', () { 11 | final value = Empty(); 12 | final sameValue = value.rebuild((b) {}); 13 | expect(sameValue, value); 14 | 15 | // analyzer: The function 'EmptyBuilder' isn't defined. 16 | // final valueBuilder = EmptyBuilder(); 17 | // final value3 = valueBuilder.build(); 18 | // expect(value3, value); 19 | }); 20 | }); 21 | 22 | group('Class with primitive fields', () { 23 | test('instantiation, builder, rebuild, comparison, hash code, ' 24 | 'toString', () { 25 | final value = PrimitiveFields( 26 | (b) => 27 | b 28 | ..anInt = 3 29 | ..aString = 'four', 30 | ); 31 | final value2 = value.rebuild( 32 | (b) => 33 | b 34 | ..anInt = 4 35 | ..aString = 'five', 36 | ); 37 | expect(value2, isNot(value)); 38 | expect(value2.hashCode, isNot(value.hashCode)); 39 | expect( 40 | value2.toString(), 41 | 'PrimitiveFields(anInt: 4, aString: five, aNullableString: null)', 42 | ); 43 | 44 | final sameValue = value.rebuild((b) => b); 45 | expect(sameValue, value); 46 | expect(sameValue.hashCode, value.hashCode); 47 | }); 48 | }); 49 | 50 | group('Class with nested fields', () { 51 | test('has nested builder', () { 52 | final value = NestedFields( 53 | (b) => 54 | b 55 | ..aPrimitiveFields.anInt = 3 56 | ..aPrimitiveFields.aString = 'four' 57 | ..aString = 'five' 58 | ..stringWrapper = StringWrapper('six'), 59 | ); 60 | expect( 61 | value.toString(), 62 | 'NestedFields(aPrimitiveFields: PrimitiveFields(' 63 | 'anInt: 3, aString: four, aNullableString: null), ' 64 | 'stringWrapper: StringWrapper(aString: six), aString: five)', 65 | ); 66 | }); 67 | }); 68 | } 69 | 70 | class NonMacro { 71 | const NonMacro(); 72 | } 73 | 74 | @NonMacro() 75 | class StringWrapper { 76 | const StringWrapper(this.aString); 77 | 78 | final String aString; 79 | 80 | @override 81 | String toString() => 'StringWrapper(aString: $aString)'; 82 | } 83 | 84 | @BuiltValue() 85 | class Empty {} 86 | 87 | @BuiltValue() 88 | class PrimitiveFields { 89 | final int anInt; 90 | final String aString; 91 | final String? aNullableString; 92 | } 93 | 94 | @BuiltValue() 95 | class NestedFields { 96 | final PrimitiveFields aPrimitiveFields; 97 | final StringWrapper stringWrapper; 98 | final String aString; 99 | } 100 | -------------------------------------------------------------------------------- /goldens/foo/lib/declare_x.analyzer.augmentations: -------------------------------------------------------------------------------- 1 | part of 'package:foo/declare_x.dart'; 2 | 3 | augment class Foo { 4 | int get x => 3; 5 | } 6 | -------------------------------------------------------------------------------- /goldens/foo/lib/declare_x.cfe.augmentations: -------------------------------------------------------------------------------- 1 | augment library 'package:foo/declare_x.dart'; 2 | 3 | augment class Foo { 4 | int get x => 3; 5 | } 6 | -------------------------------------------------------------------------------- /goldens/foo/lib/declare_x.dart: -------------------------------------------------------------------------------- 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:_test_macros/declare_x_macro.dart'; 6 | 7 | @DeclareX() 8 | class Foo {} 9 | 10 | void main() => print(Foo().x); 11 | -------------------------------------------------------------------------------- /goldens/foo/lib/foo.cfe.json: -------------------------------------------------------------------------------- 1 | { 2 | "uris": { 3 | "package:foo/foo.dart": { 4 | "scopes": { 5 | "Foo": { 6 | "members": { 7 | "bar": { 8 | "properties": { 9 | "isAbstract": false, 10 | "isGetter": false, 11 | "isField": true, 12 | "isMethod": false, 13 | "isStatic": false 14 | } 15 | } 16 | }, 17 | "properties": { 18 | "isClass": true 19 | } 20 | }, 21 | "Bar": { 22 | "members": { 23 | "bar": { 24 | "properties": { 25 | "isAbstract": false, 26 | "isGetter": false, 27 | "isField": true, 28 | "isMethod": false, 29 | "isStatic": false 30 | } 31 | } 32 | }, 33 | "properties": { 34 | "isClass": true 35 | } 36 | } 37 | } 38 | } 39 | }, 40 | "types": { 41 | "named": { 42 | "package:foo/foo.dart#Foo": { 43 | "typeParameters": [], 44 | "self": { 45 | "name": { 46 | "uri": "package:foo/foo.dart", 47 | "name": "Foo" 48 | }, 49 | "instantiation": [] 50 | }, 51 | "supertypes": [ 52 | { 53 | "name": { 54 | "uri": "dart:core", 55 | "name": "Object" 56 | }, 57 | "instantiation": [] 58 | } 59 | ] 60 | }, 61 | "dart:core#Object": { 62 | "typeParameters": [], 63 | "self": { 64 | "name": { 65 | "uri": "dart:core", 66 | "name": "Object" 67 | }, 68 | "instantiation": [] 69 | }, 70 | "supertypes": [] 71 | }, 72 | "dart:core#Null": { 73 | "typeParameters": [], 74 | "self": { 75 | "name": { 76 | "uri": "dart:core", 77 | "name": "Null" 78 | }, 79 | "instantiation": [] 80 | }, 81 | "supertypes": [ 82 | { 83 | "name": { 84 | "uri": "dart:core", 85 | "name": "Object" 86 | }, 87 | "instantiation": [] 88 | } 89 | ] 90 | }, 91 | "dart:async#Future": { 92 | "typeParameters": [ 93 | { 94 | "identifier": 0, 95 | "bound": { 96 | "type": "NullableTypeDesc", 97 | "value": { 98 | "inner": { 99 | "type": "NamedTypeDesc", 100 | "value": { 101 | "name": { 102 | "uri": "dart:core", 103 | "name": "Object" 104 | }, 105 | "instantiation": [] 106 | } 107 | } 108 | } 109 | } 110 | } 111 | ], 112 | "self": { 113 | "name": { 114 | "uri": "dart:async", 115 | "name": "Future" 116 | }, 117 | "instantiation": [ 118 | { 119 | "type": "TypeParameterTypeDesc", 120 | "value": { 121 | "parameterId": 0 122 | } 123 | } 124 | ] 125 | }, 126 | "supertypes": [ 127 | { 128 | "name": { 129 | "uri": "dart:core", 130 | "name": "Object" 131 | }, 132 | "instantiation": [] 133 | } 134 | ] 135 | }, 136 | "package:foo/foo.dart#Bar": { 137 | "typeParameters": [], 138 | "self": { 139 | "name": { 140 | "uri": "package:foo/foo.dart", 141 | "name": "Bar" 142 | }, 143 | "instantiation": [] 144 | }, 145 | "supertypes": [ 146 | { 147 | "name": { 148 | "uri": "dart:core", 149 | "name": "Object" 150 | }, 151 | "instantiation": [] 152 | } 153 | ] 154 | } 155 | } 156 | } 157 | } -------------------------------------------------------------------------------- /goldens/foo/lib/foo.dart: -------------------------------------------------------------------------------- 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:_test_macros/query_class.dart'; 6 | 7 | @QueryClass() 8 | class Foo { 9 | final int bar = 3; 10 | 11 | Foo.construct(int x, {required int y}); 12 | Foo.construct2(int x, [String? y]); 13 | 14 | void method(int x, {int? y}) {} 15 | Bar? method2(int x, [String? y]) { 16 | return null; 17 | } 18 | } 19 | 20 | @QueryClass() 21 | class Bar { 22 | final int bar = 4; 23 | } 24 | -------------------------------------------------------------------------------- /goldens/foo/lib/literal_params.analyzer.augmentations: -------------------------------------------------------------------------------- 1 | part of 'package:foo/literal_params.dart'; 2 | 3 | augment class Foo { 4 | // anInt: 7, int 5 | // aNum: 8.0, double 6 | // aDouble: 9.0, double 7 | // aString: 10, String 8 | // anObject: {type: {type: NamedTypeAnnotation, value: {reference: {type: ClassReference, value: {name: Bar}}, typeArguments: []}}, constructor: {type: ConstructorReference, value: {name: new}}, arguments: [{type: NamedArgument, value: {name: a, expression: {type: BooleanLiteral, value: {value: true}}}}, {type: NamedArgument, value: {name: b, expression: {type: BooleanLiteral, value: {value: false}}}}]}, String 9 | // ints: [11, 12], List 10 | // nums: [13.0, 14], List 11 | // doubles: [15.0, 16], List 12 | // strings: [17, eighteen], List 13 | // objects: [19, {type: {type: NamedTypeAnnotation, value: {reference: {type: ClassReference, value: {name: Bar}}, typeArguments: []}}, constructor: {type: ConstructorReference, value: {name: new}}, arguments: [{type: NamedArgument, value: {name: a, expression: {type: BooleanLiteral, value: {value: true}}}}, {type: NamedArgument, value: {name: b, expression: {type: BooleanLiteral, value: {value: false}}}}]}], List 14 | } 15 | -------------------------------------------------------------------------------- /goldens/foo/lib/literal_params.dart: -------------------------------------------------------------------------------- 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:_test_macros/literal_params.dart'; 6 | 7 | @LiteralParams( 8 | anInt: 7, 9 | aNum: 8.0, 10 | aDouble: 9.0, 11 | aString: '10', 12 | anObject: Bar(a: true, b: false), 13 | ints: [11, 12], 14 | nums: [13.0, 14], 15 | doubles: [15.0, 16], 16 | strings: ['17', 'eighteen'], 17 | objects: [19, Bar(a: true, b: false)], 18 | ) 19 | class Foo {} 20 | 21 | class Bar { 22 | final bool? a; 23 | final bool? b; 24 | 25 | const Bar({this.a, this.b}); 26 | } 27 | 28 | void main() {} 29 | -------------------------------------------------------------------------------- /goldens/foo/lib/metadata.dart: -------------------------------------------------------------------------------- 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:_test_macros/query_class.dart'; 6 | 7 | @QueryClass() 8 | @Annotation(aBool: true, anInt: 23) 9 | class Foo {} 10 | 11 | class Annotation { 12 | final bool aBool; 13 | final int anInt; 14 | 15 | const Annotation({required this.aBool, required this.anInt}); 16 | } 17 | -------------------------------------------------------------------------------- /goldens/foo/mono_pkg.yaml: -------------------------------------------------------------------------------- 1 | sdk: 2 | - dev 3 | - pubspec 4 | 5 | stages: 6 | - unit_test: 7 | - command: ../../tool/run_e2e_tests.sh 8 | -------------------------------------------------------------------------------- /goldens/foo/pubspec.yaml: -------------------------------------------------------------------------------- 1 | name: foo 2 | publish-to: none 3 | resolution: workspace 4 | 5 | environment: 6 | sdk: ^3.7.0-157.0.dev 7 | 8 | dependencies: 9 | _test_macros: any 10 | -------------------------------------------------------------------------------- /mono_repo.yaml: -------------------------------------------------------------------------------- 1 | # See https://github.com/google/mono_repo.dart for details on this file 2 | 3 | # The stage to put the "mono_repo validate" check in. 4 | self_validate: analyze_and_format 5 | 6 | # These stages are fast, and we don't need parallelism of jobs, so we run them 7 | # all in the same job. 8 | merge_stages: 9 | - analyze_and_format 10 | -------------------------------------------------------------------------------- /pkgs/_analyzer_cfe_macros/analysis_options.yaml: -------------------------------------------------------------------------------- 1 | include: package:dart_flutter_team_lints/analysis_options.yaml 2 | -------------------------------------------------------------------------------- /pkgs/_analyzer_cfe_macros/mono_pkg.yaml: -------------------------------------------------------------------------------- 1 | sdk: 2 | # Only "pubspec" for when using SDK via a hash reference for latest 3 | # `_fe_analyzer_shared`. 4 | - pubspec 5 | 6 | stages: 7 | - analyze_and_format: 8 | - analyze: --fatal-infos . 9 | - format: 10 | sdk: 11 | - dev 12 | - unit_test: 13 | - test: --test-randomize-ordering-seed=random 14 | os: 15 | - linux 16 | - windows 17 | -------------------------------------------------------------------------------- /pkgs/_analyzer_cfe_macros/pubspec.yaml: -------------------------------------------------------------------------------- 1 | name: _analyzer_cfe_macros 2 | publish-to: none 3 | description: Macro support for the analyzer and CFE. 4 | resolution: workspace 5 | 6 | environment: 7 | sdk: ^3.7.0-157.0.dev 8 | 9 | dependencies: 10 | _fe_analyzer_shared: any 11 | dart_model: any 12 | 13 | dev_dependencies: 14 | dart_flutter_team_lints: ^3.0.0 15 | test: ^1.25.0 16 | -------------------------------------------------------------------------------- /pkgs/_analyzer_cfe_macros/test/metadata_converter_test.dart: -------------------------------------------------------------------------------- 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:_analyzer_cfe_macros/metadata_converter.dart'; 6 | import 'package:_fe_analyzer_shared/src/metadata/ast.dart'; 7 | import 'package:dart_model/dart_model.dart'; 8 | import 'package:test/test.dart'; 9 | 10 | void main() { 11 | test('converts with unions', () { 12 | final invocation = MethodInvocation( 13 | DoubleLiteral('1.23', 1.23), 14 | 'round', 15 | [], 16 | [], 17 | ); 18 | 19 | Scope.query.run(() { 20 | expect(convert(invocation), { 21 | 'receiver': { 22 | 'type': 'DoubleLiteral', 23 | 'value': {'text': '1.23', 'value': 1.23}, 24 | }, 25 | 'name': 'round', 26 | 'typeArguments': [], 27 | 'arguments': [], 28 | }); 29 | }); 30 | }); 31 | 32 | test('converts with enums', () { 33 | final expression = BinaryExpression( 34 | DoubleLiteral('1.23', 1.23), 35 | BinaryOperator.minus, 36 | DoubleLiteral('1.24', 1.24), 37 | ); 38 | 39 | Scope.query.run(() { 40 | expect(convert(expression), { 41 | 'left': { 42 | 'type': 'DoubleLiteral', 43 | 'value': {'text': '1.23', 'value': 1.23}, 44 | }, 45 | 'operator': 'minus', 46 | 'right': { 47 | 'type': 'DoubleLiteral', 48 | 'value': {'text': '1.24', 'value': 1.24}, 49 | }, 50 | }); 51 | }); 52 | }); 53 | 54 | test('converts with lists', () { 55 | final invocation = MethodInvocation( 56 | DoubleLiteral('1.23', 1.23), 57 | 'round', 58 | [], 59 | [PositionalArgument(IntegerLiteral.fromText('4'))], 60 | ); 61 | 62 | Scope.query.run(() { 63 | expect(convert(invocation), { 64 | 'receiver': { 65 | 'type': 'DoubleLiteral', 66 | 'value': {'text': '1.23', 'value': 1.23}, 67 | }, 68 | 'name': 'round', 69 | 'typeArguments': [], 70 | 'arguments': [ 71 | { 72 | 'type': 'PositionalArgument', 73 | 'value': { 74 | 'expression': { 75 | 'type': 'IntegerLiteral', 76 | 'value': {'text': '4'}, 77 | }, 78 | }, 79 | }, 80 | ], 81 | }); 82 | }); 83 | }); 84 | 85 | test('converts a type literal', () { 86 | final invocation = TypeLiteral(NamedTypeAnnotation(TestClassReference())); 87 | 88 | Scope.query.run(() { 89 | expect(convert(invocation), { 90 | 'typeAnnotation': { 91 | 'type': 'NamedTypeAnnotation', 92 | 'value': { 93 | 'reference': { 94 | 'type': 'ClassReference', 95 | 'value': {'name': 'Test'}, 96 | }, 97 | 'typeArguments': [], 98 | }, 99 | }, 100 | }); 101 | }); 102 | }); 103 | } 104 | 105 | class TestClassReference implements ClassReference { 106 | @override 107 | String get name => 'Test'; 108 | } 109 | -------------------------------------------------------------------------------- /pkgs/_analyzer_macros/analysis_options.yaml: -------------------------------------------------------------------------------- 1 | include: package:dart_flutter_team_lints/analysis_options.yaml 2 | -------------------------------------------------------------------------------- /pkgs/_analyzer_macros/bin/server.dart: -------------------------------------------------------------------------------- 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:_analyzer_macros/macro_implementation.dart'; 6 | import 'package:analysis_server/starter.dart'; 7 | import 'package:analyzer/src/summary2/macro_injected_impl.dart' as injected; 8 | import 'package:macro_service/macro_service.dart'; 9 | 10 | /// Analysis server with `dart_model` implementation injected. 11 | /// 12 | /// Run with your IDE by compiling and placing in the SDK your IDE is using, 13 | /// for example: 14 | /// 15 | /// dart compile kernel -DPACKAGE_CONFIG_PATH=$HOME/git/macros/.dart_tool/package_config.json bin/server.dart 16 | /// cp bin/server.dill ~/opt/dart-sdk-be/bin/snapshots/analysis_server.dart.snapshot 17 | /// 18 | /// Then restart, VSCode: Ctrl+Shift+P, Restart Analysis Server. 19 | /// 20 | /// Only works for one project, the one specified in the command line with 21 | /// `PACKAGE_CONFIG_PATH`. 22 | void main(List args) async { 23 | injected.macroImplementation = await AnalyzerMacroImplementation.start( 24 | protocol: Protocol( 25 | encoding: ProtocolEncoding.binary, 26 | version: ProtocolVersion.macros1, 27 | ), 28 | // TODO(davidmorgan): this needs to come from the analyzer, not be 29 | // hardcoded. 30 | packageConfig: Uri.file( 31 | const String.fromEnvironment('PACKAGE_CONFIG_PATH'), 32 | ), 33 | ); 34 | 35 | var starter = ServerStarter(); 36 | starter.start(args); 37 | } 38 | -------------------------------------------------------------------------------- /pkgs/_analyzer_macros/mono_pkg.yaml: -------------------------------------------------------------------------------- 1 | sdk: 2 | - pubspec 3 | - dev 4 | 5 | stages: 6 | - analyze_and_format: 7 | - analyze: --fatal-infos . 8 | - format: 9 | sdk: 10 | - dev 11 | - unit_test: 12 | - test: --test-randomize-ordering-seed=random 13 | os: 14 | - linux 15 | - windows 16 | -------------------------------------------------------------------------------- /pkgs/_analyzer_macros/pubspec.yaml: -------------------------------------------------------------------------------- 1 | name: _analyzer_macros 2 | publish-to: none 3 | description: Macro support for the analyzer. 4 | resolution: workspace 5 | 6 | environment: 7 | sdk: ^3.7.0-157.0.dev 8 | 9 | dependencies: 10 | _analyzer_cfe_macros: any 11 | _macro_host: any 12 | analysis_server: any 13 | analyzer: any 14 | dart_model: any 15 | macro_service: any 16 | macros: any 17 | 18 | dev_dependencies: 19 | dart_flutter_team_lints: ^3.0.0 20 | path: ^1.9.0 21 | test: ^1.25.0 22 | -------------------------------------------------------------------------------- /pkgs/_analyzer_macros/test/analyzer_test.dart: -------------------------------------------------------------------------------- 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 'dart:io'; 6 | import 'dart:isolate'; 7 | 8 | import 'package:_analyzer_macros/macro_implementation.dart'; 9 | import 'package:analyzer/dart/analysis/analysis_context.dart'; 10 | import 'package:analyzer/dart/analysis/analysis_context_collection.dart'; 11 | import 'package:analyzer/dart/analysis/results.dart'; 12 | import 'package:analyzer/src/summary2/macro_injected_impl.dart' as injected; 13 | import 'package:macro_service/macro_service.dart'; 14 | import 'package:test/test.dart'; 15 | 16 | void main() { 17 | late AnalysisContext analysisContext; 18 | 19 | group('analyzer with injected macro implementation', () { 20 | setUp(() async { 21 | // Set up analyzer. 22 | final directory = 23 | Directory.fromUri(Uri.parse('./test/package_under_test')).absolute; 24 | final contextCollection = AnalysisContextCollection( 25 | includedPaths: [directory.path], 26 | ); 27 | analysisContext = contextCollection.contexts.first; 28 | injected.macroImplementation = await AnalyzerMacroImplementation.start( 29 | protocol: Protocol( 30 | encoding: ProtocolEncoding.binary, 31 | version: ProtocolVersion.macros1, 32 | ), 33 | packageConfig: Isolate.packageConfigSync!, 34 | ); 35 | }); 36 | 37 | test('discovers macros, runs them, applies augmentations', () async { 38 | final path = 39 | File.fromUri( 40 | Uri.parse('./test/package_under_test/lib/apply_declare_x.dart'), 41 | ).absolute.path; 42 | 43 | // No analysis errors. 44 | final errors = 45 | await analysisContext.currentSession.getErrors(path) as ErrorsResult; 46 | expect(errors.errors, isEmpty); 47 | 48 | // The expected new declaration augmentation was applied. 49 | final resolvedLibrary = 50 | (await analysisContext.currentSession.getResolvedLibrary(path)) 51 | as ResolvedLibraryResult; 52 | final clazz = resolvedLibrary.element.getClass('ClassWithMacroApplied')!; 53 | expect(clazz.fields, isEmpty); 54 | expect(clazz.augmented.fields, isNotEmpty); 55 | expect(clazz.augmented.fields.single.name, 'x'); 56 | }); 57 | }); 58 | } 59 | -------------------------------------------------------------------------------- /pkgs/_analyzer_macros/test/package_under_test/analysis_options.yaml: -------------------------------------------------------------------------------- 1 | analyzer: 2 | enable-experiment: 3 | - macros 4 | -------------------------------------------------------------------------------- /pkgs/_analyzer_macros/test/package_under_test/lib/apply_declare_x.dart: -------------------------------------------------------------------------------- 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:_test_macros/declare_x_macro.dart'; 6 | 7 | @DeclareX() 8 | class ClassWithMacroApplied {} 9 | -------------------------------------------------------------------------------- /pkgs/_analyzer_macros/test/package_under_test/lib/apply_query_class.dart: -------------------------------------------------------------------------------- 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:_test_macros/query_class.dart'; 6 | 7 | @QueryClass() 8 | abstract class ClassWithMacroApplied { 9 | abstract final int x; 10 | } 11 | -------------------------------------------------------------------------------- /pkgs/_analyzer_macros/test/package_under_test/pubspec.yaml: -------------------------------------------------------------------------------- 1 | name: package_under_test_analyzer 2 | publish-to: none 3 | resolution: workspace 4 | 5 | environment: 6 | sdk: ^3.7.0-157.0.dev 7 | 8 | dependencies: 9 | _test_macros: any 10 | -------------------------------------------------------------------------------- /pkgs/_cfe_macros/analysis_options.yaml: -------------------------------------------------------------------------------- 1 | include: package:dart_flutter_team_lints/analysis_options.yaml 2 | -------------------------------------------------------------------------------- /pkgs/_cfe_macros/mono_pkg.yaml: -------------------------------------------------------------------------------- 1 | sdk: 2 | # Only "pubspec" because the test uses the platform dill from the SDK, and it 3 | # needs to be the same version. 4 | - pubspec 5 | 6 | stages: 7 | - analyze_and_format: 8 | - analyze: --fatal-infos . 9 | - format: 10 | sdk: 11 | - dev 12 | - unit_test: 13 | - test: --test-randomize-ordering-seed=random 14 | os: 15 | - linux 16 | - windows 17 | -------------------------------------------------------------------------------- /pkgs/_cfe_macros/pubspec.yaml: -------------------------------------------------------------------------------- 1 | name: _cfe_macros 2 | publish-to: none 3 | description: Macro support for the CFE. 4 | resolution: workspace 5 | 6 | environment: 7 | sdk: ^3.7.0-157.0.dev 8 | 9 | dependencies: 10 | _macro_host: any 11 | dart_model: any 12 | front_end: any 13 | kernel: any 14 | macro_service: any 15 | macros: any 16 | 17 | dev_dependencies: 18 | dart_flutter_team_lints: ^3.0.0 19 | frontend_server: any 20 | path: ^1.9.0 21 | test: ^1.25.0 22 | -------------------------------------------------------------------------------- /pkgs/_cfe_macros/test/cfe_test.dart: -------------------------------------------------------------------------------- 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 'dart:io'; 6 | import 'dart:isolate'; 7 | 8 | import 'package:_cfe_macros/macro_implementation.dart'; 9 | import 'package:front_end/src/macros/macro_injected_impl.dart' as injected; 10 | import 'package:frontend_server/compute_kernel.dart'; 11 | import 'package:macro_service/macro_service.dart'; 12 | import 'package:test/test.dart'; 13 | 14 | void main() { 15 | group('CFE with injected macro implementation', () { 16 | late File productPlatformDill; 17 | late Directory tempDir; 18 | 19 | setUp(() async { 20 | // Set up CFE. 21 | 22 | // TODO(davidmorgan): this dill comes from the Dart SDK running the test, 23 | // but `package:frontend_server` and `package:front_end` are used as a 24 | // library, so we will see version skew breakage. Find a better way. 25 | productPlatformDill = File( 26 | '${Platform.resolvedExecutable}/../../' 27 | 'lib/_internal/vm_platform_strong_product.dill', 28 | ); 29 | if (!File.fromUri(productPlatformDill.uri).existsSync()) { 30 | throw StateError('Failed to find platform dill: $productPlatformDill'); 31 | } 32 | tempDir = Directory.systemTemp.createTempSync('cfe_test'); 33 | 34 | // Inject test macro implementation. 35 | injected.macroImplementation = await CfeMacroImplementation.start( 36 | protocol: Protocol( 37 | encoding: ProtocolEncoding.json, 38 | version: ProtocolVersion.macros1, 39 | ), 40 | packageConfig: Isolate.packageConfigSync!, 41 | ); 42 | }); 43 | 44 | test('discovers macros, runs them, applies augmentations', () async { 45 | final packagesUri = Isolate.packageConfigSync; 46 | final sourceFile = File( 47 | 'test/package_under_test/lib/apply_declare_x.dart', 48 | ); 49 | final outputFile = File('${tempDir.path}/out.dill'); 50 | 51 | final computeKernelResult = await computeKernel([ 52 | '--enable-experiment=macros', 53 | '--no-summary', 54 | '--no-summary-only', 55 | '--target=vm', 56 | '--dart-sdk-summary=${productPlatformDill.uri}', 57 | '--output=${outputFile.path}', 58 | '--source=${sourceFile.uri}', 59 | '--packages-file=$packagesUri', 60 | // For augmentations. 61 | '--enable-experiment=macros', 62 | ]); 63 | 64 | // The source has a main method that uses the new declaration, so the 65 | // compile can only succeed if it was added by the macro. 66 | expect(computeKernelResult.succeeded, true); 67 | }); 68 | }); 69 | } 70 | -------------------------------------------------------------------------------- /pkgs/_cfe_macros/test/package_under_test/analysis_options.yaml: -------------------------------------------------------------------------------- 1 | analyzer: 2 | errors: 3 | undefined_getter: ignore 4 | enable-experiment: 5 | - macros 6 | -------------------------------------------------------------------------------- /pkgs/_cfe_macros/test/package_under_test/lib/apply_declare_x.dart: -------------------------------------------------------------------------------- 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:_test_macros/declare_x_macro.dart'; 6 | 7 | @DeclareX() 8 | class ClassWithMacroApplied {} 9 | 10 | void main() { 11 | ClassWithMacroApplied().x; 12 | } 13 | -------------------------------------------------------------------------------- /pkgs/_cfe_macros/test/package_under_test/lib/apply_query_class.dart: -------------------------------------------------------------------------------- 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:_test_macros/query_class.dart'; 6 | 7 | @QueryClass() 8 | abstract class ClassWithMacroApplied { 9 | abstract final int x; 10 | } 11 | -------------------------------------------------------------------------------- /pkgs/_cfe_macros/test/package_under_test/pubspec.yaml: -------------------------------------------------------------------------------- 1 | name: package_under_test_cfe 2 | publish-to: none 3 | resolution: workspace 4 | 5 | environment: 6 | sdk: ^3.7.0-157.0.dev 7 | 8 | dependencies: 9 | _test_macros: any 10 | -------------------------------------------------------------------------------- /pkgs/_macro_builder/analysis_options.yaml: -------------------------------------------------------------------------------- 1 | include: package:dart_flutter_team_lints/analysis_options.yaml 2 | -------------------------------------------------------------------------------- /pkgs/_macro_builder/lib/macro_builder.dart: -------------------------------------------------------------------------------- 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 'dart:io'; 6 | 7 | import 'package:dart_model/dart_model.dart'; 8 | 9 | import 'src/bootstrap.dart'; 10 | 11 | /// Builds macros. 12 | /// 13 | /// TODO(davidmorgan): add a way to clean up generated files and built output. 14 | class MacroBuilder { 15 | /// Builds an executable from user-written macro code. 16 | /// 17 | /// Each `QualifiedName` in [macroImplementations] must point to a class that 18 | /// implements `Macro` from `package:macro`. 19 | /// 20 | /// The [packageConfig] must include the macros and all their deps. 21 | /// 22 | /// TODO(davidmorgan): figure out builder lifecycle: is it one builder per 23 | /// host, one per workspace, one per build? 24 | /// TODO(davidmorgan): replace `File` packageConfig with a concept of version 25 | /// solve and workspace. 26 | /// TODO(davidmorgan): support for multi-root workspaces. 27 | /// TODO(davidmorgan): support (or decide not to support) in-memory overlay 28 | /// filesystems. 29 | Future build( 30 | Uri packageConfig, 31 | Iterable macroImplementations, 32 | ) async { 33 | final script = createBootstrap(macroImplementations.toList()); 34 | 35 | return await MacroBuild(packageConfig, script).build(); 36 | } 37 | } 38 | 39 | /// A bundle of one or more macros that's ready to execute. 40 | class BuiltMacroBundle { 41 | // TODO(davidmorgan): other formats besides executable. 42 | final String executablePath; 43 | 44 | BuiltMacroBundle(this.executablePath); 45 | } 46 | 47 | /// A single build. 48 | /// 49 | /// TODO(davidmorgan): split to interface+implementations as we add different 50 | /// ways to build. 51 | class MacroBuild { 52 | final Uri packageConfig; 53 | final String script; 54 | final Directory workspace = Directory.systemTemp.createTempSync( 55 | 'macro_builder', 56 | ); 57 | 58 | /// Creates a build for [script] with [packageConfig], which must have all 59 | /// the needed deps. 60 | MacroBuild(this.packageConfig, this.script); 61 | 62 | /// Runs the build. 63 | /// 64 | /// Throws on failure to build. 65 | Future build() async { 66 | final scriptFile = File.fromUri(workspace.uri.resolve('bin/main.dart')); 67 | await scriptFile.create(recursive: true); 68 | await scriptFile.writeAsString(script.toString()); 69 | 70 | final targetPackageConfig = File.fromUri( 71 | workspace.uri.resolve('.dart_tool/package_config.json'), 72 | ); 73 | targetPackageConfig.parent.createSync(recursive: true); 74 | targetPackageConfig.writeAsStringSync( 75 | _makePackageConfigAbsolute(packageConfig), 76 | ); 77 | 78 | // See package:analyzer/src/summary2/kernel_compilation_service.dart for an 79 | // example of compiling macros using the frontend server. 80 | // 81 | // For now just use the command line. 82 | 83 | final result = Process.runSync( 84 | // TODO(davidmorgan): this is wrong if run from an AOT-compiled 85 | // executable. 86 | Platform.resolvedExecutable, 87 | ['compile', 'exe', 'bin/main.dart', '--output=bin/main.exe'], 88 | workingDirectory: workspace.path, 89 | ); 90 | if (result.exitCode != 0) { 91 | throw StateError('Compile failed: ${result.stderr}'); 92 | } 93 | 94 | return BuiltMacroBundle( 95 | File.fromUri(scriptFile.parent.uri.resolve('main.exe')).path, 96 | ); 97 | } 98 | 99 | /// Returns the contents of [packageConfig] with relative paths replaced to 100 | /// absolute paths, so the pubspec will work from any location. 101 | String _makePackageConfigAbsolute(Uri packageConfig) { 102 | final file = File.fromUri(packageConfig); 103 | final root = file.parent.parent.absolute.uri; 104 | return file.readAsStringSync().replaceAll( 105 | '"rootUri": "../', 106 | '"rootUri": "$root', 107 | ); 108 | } 109 | } 110 | -------------------------------------------------------------------------------- /pkgs/_macro_builder/lib/src/bootstrap.dart: -------------------------------------------------------------------------------- 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/dart_model.dart'; 6 | 7 | /// Creates the entrypoint script for [macros]. 8 | String createBootstrap(List macros) { 9 | final script = StringBuffer(); 10 | for (var i = 0; i != macros.length; ++i) { 11 | final macro = macros[i]; 12 | // TODO(davidmorgan): pick non-clashing prefixes. 13 | script.writeln("import '${macro.uri}' as m$i;"); 14 | } 15 | script.write(''' 16 | import 'dart:convert' as convert; 17 | 18 | import 'package:_macro_client/macro_client.dart' as macro_client; 19 | import 'package:macro_service/macro_service.dart' as macro_service; 20 | 21 | void main(List arguments) { 22 | macro_client.MacroClient.run( 23 | endpoint: macro_service.HostEndpoint.fromJson( 24 | convert.json.decode(arguments[0])), 25 | macros: ['''); 26 | for (var i = 0; i != macros.length; ++i) { 27 | final macro = macros[i]; 28 | script.write('m$i.${macro.name}()'); 29 | if (i != macros.length - 1) script.write(', '); 30 | } 31 | script.writeln(']);'); 32 | script.writeln('}'); 33 | return script.toString(); 34 | } 35 | -------------------------------------------------------------------------------- /pkgs/_macro_builder/mono_pkg.yaml: -------------------------------------------------------------------------------- 1 | sdk: 2 | - pubspec 3 | - dev 4 | 5 | stages: 6 | - analyze_and_format: 7 | - analyze: --fatal-infos . 8 | - format: 9 | sdk: 10 | - dev 11 | - unit_test: 12 | - test: --test-randomize-ordering-seed=random 13 | os: 14 | - linux 15 | - windows 16 | -------------------------------------------------------------------------------- /pkgs/_macro_builder/pubspec.yaml: -------------------------------------------------------------------------------- 1 | name: _macro_builder 2 | publish-to: none 3 | description: >- 4 | Builds macros. 5 | repository: https://github.com/dart-lang/macros/tree/master/pkgs/_macro_builder 6 | resolution: workspace 7 | 8 | environment: 9 | sdk: ^3.7.0-157.0.dev 10 | 11 | dependencies: 12 | dart_model: any 13 | 14 | dev_dependencies: 15 | dart_flutter_team_lints: ^3.0.0 16 | test: ^1.25.0 17 | -------------------------------------------------------------------------------- /pkgs/_macro_builder/test/macro_builder_test.dart: -------------------------------------------------------------------------------- 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 'dart:io'; 6 | import 'dart:isolate'; 7 | 8 | import 'package:_macro_builder/macro_builder.dart'; 9 | import 'package:_macro_builder/src/bootstrap.dart'; 10 | import 'package:dart_model/dart_model.dart'; 11 | import 'package:test/test.dart'; 12 | 13 | void main() { 14 | group(MacroBuilder, () { 15 | test('bootstrap matches golden', () async { 16 | final script = createBootstrap([ 17 | QualifiedName( 18 | uri: 'package:_test_macros/declare_x_macro.dart', 19 | name: 'DeclareX', 20 | ), 21 | QualifiedName( 22 | uri: 'package:_test_macros/declare_y_macro.dart', 23 | name: 'DeclareY', 24 | ), 25 | QualifiedName( 26 | uri: 'package:_more_macros/other_macro.dart', 27 | name: 'OtherMacroImplementation', 28 | ), 29 | ]); 30 | 31 | expect(script, ''' 32 | import 'package:_test_macros/declare_x_macro.dart' as m0; 33 | import 'package:_test_macros/declare_y_macro.dart' as m1; 34 | import 'package:_more_macros/other_macro.dart' as m2; 35 | import 'dart:convert' as convert; 36 | 37 | import 'package:_macro_client/macro_client.dart' as macro_client; 38 | import 'package:macro_service/macro_service.dart' as macro_service; 39 | 40 | void main(List arguments) { 41 | macro_client.MacroClient.run( 42 | endpoint: macro_service.HostEndpoint.fromJson( 43 | convert.json.decode(arguments[0])), 44 | macros: [m0.DeclareX(), m1.DeclareY(), m2.OtherMacroImplementation()]); 45 | } 46 | '''); 47 | }); 48 | 49 | test('builds macros', () async { 50 | final builder = MacroBuilder(); 51 | 52 | final bundle = await builder.build(Isolate.packageConfigSync!, [ 53 | QualifiedName( 54 | uri: 'package:_test_macros/declare_x_macro.dart', 55 | name: 'DeclareXImplementation', 56 | ), 57 | ]); 58 | 59 | expect(File(bundle.executablePath).existsSync(), true); 60 | }); 61 | }); 62 | } 63 | -------------------------------------------------------------------------------- /pkgs/_macro_client/analysis_options.yaml: -------------------------------------------------------------------------------- 1 | include: package:dart_flutter_team_lints/analysis_options.yaml 2 | -------------------------------------------------------------------------------- /pkgs/_macro_client/lib/src/execute_macro.dart: -------------------------------------------------------------------------------- 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 'dart:async'; 6 | 7 | import 'package:dart_model/dart_model.dart'; 8 | import 'package:macro/macro.dart'; 9 | import 'package:macro_service/macro_service.dart'; 10 | 11 | import '../macro_client.dart'; 12 | import 'builder_impls.dart'; 13 | 14 | /// Runs [macro] in the types phase and returns an [AugmentResponse]. 15 | Future executeTypesMacro( 16 | Macro macro, 17 | Host host, 18 | AugmentRequest request, 19 | ) async { 20 | final target = request.target; 21 | final model = request.model; 22 | final interface = model.uris[target.uri]!.scopes[target.name]!; 23 | 24 | switch ((interface.properties, macro)) { 25 | case (Properties(isClass: true), ClassTypesMacro macro): 26 | final builder = ClassTypesBuilderImpl(interface, host); 27 | await macro.buildTypesForClass(builder); 28 | return builder.response; 29 | case (_, LibraryTypesMacro()): 30 | case (_, ConstructorTypesMacro()): 31 | case (_, MethodTypesMacro()): 32 | case (_, FunctionTypesMacro()): 33 | case (_, FieldTypesMacro()): 34 | case (_, VariableTypesMacro()): 35 | case (_, EnumTypesMacro()): 36 | case (_, ExtensionTypesMacro()): 37 | case (_, ExtensionTypeTypesMacro()): 38 | case (_, MixinTypesMacro()): 39 | case (_, EnumValueTypesMacro()): 40 | case (_, TypeAliasTypesMacro()): 41 | throw UnimplementedError('Unimplemented macro target'); 42 | default: 43 | throw UnsupportedError( 44 | 'Unsupported macro type or invalid target:\n' 45 | 'macro: $macro\ntarget: $target', 46 | ); 47 | } 48 | } 49 | 50 | /// Runs [macro] in the declarations phase and returns an [AugmentResponse]. 51 | Future executeDeclarationsMacro( 52 | Macro macro, 53 | Host host, 54 | AugmentRequest request, 55 | ) async { 56 | final target = request.target; 57 | final model = request.model; 58 | final interface = model.uris[target.uri]!.scopes[target.name]!; 59 | 60 | switch ((interface.properties, macro)) { 61 | case (Properties(isClass: true), ClassDeclarationsMacro macro): 62 | final builder = ClassDeclarationsBuilderImpl(interface, host); 63 | await macro.buildDeclarationsForClass(builder); 64 | return builder.response; 65 | case (_, LibraryDeclarationsMacro()): 66 | case (_, EnumDeclarationsMacro()): 67 | case (_, ExtensionDeclarationsMacro()): 68 | case (_, ExtensionTypeDeclarationsMacro()): 69 | case (_, MixinDeclarationsMacro()): 70 | case (_, EnumValueDeclarationsMacro()): 71 | case (_, ConstructorDeclarationsMacro()): 72 | case (_, MethodDeclarationsMacro()): 73 | case (_, FieldDeclarationsMacro()): 74 | case (_, FunctionDeclarationsMacro()): 75 | case (_, VariableDeclarationsMacro()): 76 | case (_, TypeAliasDeclarationsMacro()): 77 | throw UnimplementedError('Unimplemented macro target'); 78 | default: 79 | throw UnsupportedError( 80 | 'Unsupported macro type or invalid target:\n' 81 | 'macro: $macro\ntarget: $target', 82 | ); 83 | } 84 | } 85 | 86 | /// Runs [macro] in the definitions phase and returns an [AugmentResponse]. 87 | Future executeDefinitionsMacro( 88 | Macro macro, 89 | Host host, 90 | AugmentRequest request, 91 | ) async { 92 | final target = request.target; 93 | final model = request.model; 94 | final interface = model.uris[target.uri]!.scopes[target.name]!; 95 | 96 | switch ((interface.properties, macro)) { 97 | case (Properties(isClass: true), ClassDefinitionsMacro macro): 98 | final builder = ClassDefinitionsBuilderImpl(interface, host); 99 | await macro.buildDefinitionsForClass(builder); 100 | return builder.response; 101 | case (_, LibraryDefinitionsMacro()): 102 | case (_, EnumDefinitionsMacro()): 103 | case (_, ExtensionDefinitionsMacro()): 104 | case (_, ExtensionTypeDefinitionsMacro()): 105 | case (_, MixinDefinitionsMacro()): 106 | case (_, EnumValueDefinitionsMacro()): 107 | case (_, ConstructorDefinitionsMacro()): 108 | case (_, MethodDefinitionsMacro()): 109 | case (_, FieldDefinitionsMacro()): 110 | case (_, FunctionDefinitionsMacro()): 111 | case (_, VariableDefinitionsMacro()): 112 | throw UnimplementedError('Unimplemented macro target'); 113 | default: 114 | throw UnsupportedError( 115 | 'Unsupported macro type or invalid target:\n' 116 | 'macro: $macro\ntarget: $target', 117 | ); 118 | } 119 | } 120 | -------------------------------------------------------------------------------- /pkgs/_macro_client/mono_pkg.yaml: -------------------------------------------------------------------------------- 1 | sdk: 2 | - pubspec 3 | - dev 4 | 5 | stages: 6 | - analyze_and_format: 7 | - analyze: --fatal-infos . 8 | - format: 9 | sdk: 10 | - dev 11 | - unit_test: 12 | - test: --test-randomize-ordering-seed=random 13 | os: 14 | - linux 15 | - windows 16 | -------------------------------------------------------------------------------- /pkgs/_macro_client/pubspec.yaml: -------------------------------------------------------------------------------- 1 | name: _macro_client 2 | publish-to: none 3 | description: >- 4 | Connects user macro code to a macro host. 5 | repository: https://github.com/dart-lang/macros/tree/master/pkgs/_macro_client 6 | resolution: workspace 7 | 8 | environment: 9 | sdk: ^3.7.0-157.0.dev 10 | 11 | dependencies: 12 | dart_model: any 13 | macro: any 14 | macro_service: any 15 | 16 | dev_dependencies: 17 | _test_macros: any 18 | async: ^2.11.0 19 | dart_flutter_team_lints: ^3.0.0 20 | test: ^1.25.0 21 | -------------------------------------------------------------------------------- /pkgs/_macro_host/analysis_options.yaml: -------------------------------------------------------------------------------- 1 | include: package:dart_flutter_team_lints/analysis_options.yaml 2 | -------------------------------------------------------------------------------- /pkgs/_macro_host/lib/macro_host.dart: -------------------------------------------------------------------------------- 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 | export 'src/macro_host.dart' show MacroHost, QueryService; 6 | -------------------------------------------------------------------------------- /pkgs/_macro_host/lib/src/macro_cache.dart: -------------------------------------------------------------------------------- 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:crypto/crypto.dart'; 6 | import 'package:dart_model/dart_model.dart'; 7 | import 'package:macro_service/macro_service.dart'; 8 | 9 | import 'macro_host.dart'; 10 | 11 | class MacroResultsCache { 12 | final _cache = <_MacroResultsCacheKey, _MacroResultsCacheValue>{}; 13 | 14 | final QueryService queryService; 15 | 16 | MacroResultsCache(this.queryService); 17 | 18 | /// Adds an entry into the cache. 19 | /// 20 | /// The cache keys are a combined value of the [annotation], and [request]. 21 | /// 22 | /// The caceh values are based on each query in [queryResults] and a digest of 23 | /// the [Model] response for that query. 24 | void cache({ 25 | required QualifiedName annotation, 26 | required AugmentRequest request, 27 | required Iterable<({Query query, Model response})> queryResults, 28 | required AugmentResponse response, 29 | }) { 30 | _cache[( 31 | annotation: annotation.asRecord, 32 | target: request.target.asRecord, 33 | phase: request.phase, 34 | )] = ( 35 | queries: queryResults.map((q) => q.query), 36 | resultsDigest: 37 | queryResults 38 | .skip(1) 39 | .fold( 40 | queryResults.first.response, 41 | (model, next) => model.mergeWith(next.response), 42 | ) 43 | .digest, 44 | response: response, 45 | ); 46 | } 47 | 48 | /// Returns a cached result for the given augmentation request, if there is 49 | /// one and it is still valid. 50 | /// 51 | /// Otherwise, invalidates the cache for this request and returns `null`. 52 | Future cachedResult( 53 | QualifiedName annotation, 54 | AugmentRequest request, 55 | ) async { 56 | final cacheKey = ( 57 | annotation: annotation.asRecord, 58 | target: request.target.asRecord, 59 | phase: request.phase, 60 | ); 61 | final cached = _cache[cacheKey]; 62 | if (cached == null) return null; 63 | 64 | final queryResults = await Scope.query.run( 65 | () => Future.wait( 66 | cached.queries.map( 67 | (query) => queryService.handle(QueryRequest(query: query)), 68 | ), 69 | ), 70 | ); 71 | final newResultsDigest = 72 | queryResults 73 | .skip(1) 74 | .fold( 75 | queryResults.first.model, 76 | (model, next) => model.mergeWith(next.model), 77 | ) 78 | .digest; 79 | if (newResultsDigest != cached.resultsDigest) { 80 | _cache.remove(cacheKey); 81 | return null; 82 | } 83 | return cached.response; 84 | } 85 | } 86 | 87 | typedef _MacroResultsCacheKey = 88 | ({QualifiedNameRecord annotation, QualifiedNameRecord target, int phase}); 89 | 90 | typedef _MacroResultsCacheValue = 91 | ({ 92 | /// All queries done by a macro in a given phase. 93 | Iterable queries, 94 | 95 | /// The [Digest] of the merged model from all query responses. 96 | Digest resultsDigest, 97 | 98 | /// The macro augmentation response that was cached. 99 | AugmentResponse response, 100 | }); 101 | 102 | typedef QualifiedNameRecord = 103 | (String uri, String? scope, String name, bool? isStatic); 104 | 105 | extension on QualifiedName { 106 | QualifiedNameRecord get asRecord => (uri, scope, name, isStatic); 107 | } 108 | -------------------------------------------------------------------------------- /pkgs/_macro_host/lib/src/package_config.dart: -------------------------------------------------------------------------------- 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 'dart:io'; 6 | 7 | import 'package:dart_model/dart_model.dart'; 8 | import 'package:package_config/package_config.dart'; 9 | 10 | /// Reads a package config to determine information about macros. 11 | class MacroPackageConfig { 12 | final PackageConfig packageConfig; 13 | final Uri uri; 14 | 15 | MacroPackageConfig({required this.uri, required this.packageConfig}); 16 | 17 | factory MacroPackageConfig.readFromUri(Uri uri) => MacroPackageConfig( 18 | uri: uri, 19 | packageConfig: PackageConfig.parseBytes( 20 | File.fromUri(uri).readAsBytesSync(), 21 | uri, 22 | ), 23 | ); 24 | 25 | /// Checks whether [name] is a macro annotation. 26 | /// 27 | /// If so, returns the qualified name of the macro implementation. 28 | /// 29 | /// If not, returns `null`. 30 | /// 31 | /// This is a placeholder implementation until `language/3728` is 32 | /// implemented. It expects macros to be marked by a comment in the 33 | /// annotation package `pubspec.yaml` that looks like this: 34 | /// 35 | /// ``` 36 | /// # macro 37 | /// ``` 38 | /// 39 | /// For example: 40 | /// 41 | /// ``` 42 | /// # macro lib/declare_x_macro.dart#DeclareX package:_test_macros/declare_x_macro.dart#DeclareXImplementation 43 | /// ``` 44 | QualifiedName? lookupMacroImplementation(QualifiedName name) { 45 | var packageName = name.uri; 46 | if (packageName.startsWith('dart:') || 47 | packageName.startsWith('org-dartlang-sdk:')) { 48 | return null; 49 | } 50 | // TODO(davidmorgan): error handling when lookup fails. 51 | if (packageName.startsWith('file:')) { 52 | packageName = 53 | packageConfig.toPackageUri(Uri.parse(packageName)).toString(); 54 | } 55 | final libraryPathAndName = 56 | 'lib/${packageName.substring(packageName.indexOf('/') + 1)}#${name.name}'; 57 | if (packageName.startsWith('package:') && packageName.contains('/')) { 58 | packageName = packageName.substring('package:'.length); 59 | packageName = packageName.substring(0, packageName.indexOf('/')); 60 | } else { 61 | // TODO(davidmorgan): support macros outside lib dirs. 62 | throw ArgumentError('Name must start "package:" and have a path: $name'); 63 | } 64 | 65 | final matchingPackage = packageConfig[packageName]; 66 | if (matchingPackage == null) { 67 | throw StateError('Package "$packageName" not found in package config.'); 68 | } 69 | 70 | // TODO(language/3728): read macro annotation identifiers from package 71 | // config. Until then, check the pubsec, to simulate what that feature will 72 | // do. 73 | final packageUri = matchingPackage.root; 74 | final pubspecUri = packageUri.resolve('pubspec.yaml'); 75 | final lines = File.fromUri(pubspecUri).readAsLinesSync(); 76 | 77 | final implsByLibraryQualifiedName = {}; 78 | for (final line in lines) { 79 | if (!line.startsWith('# macro ')) continue; 80 | final items = line.split(' '); 81 | // The rest of the line should be the library qualified name of the 82 | // annotation then the fully qualified name of the implementation. 83 | implsByLibraryQualifiedName[items[2]] = QualifiedName.parse(items[3]); 84 | } 85 | 86 | return implsByLibraryQualifiedName[libraryPathAndName]; 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /pkgs/_macro_host/mono_pkg.yaml: -------------------------------------------------------------------------------- 1 | sdk: 2 | - pubspec 3 | - dev 4 | 5 | stages: 6 | - analyze_and_format: 7 | - analyze: --fatal-infos . 8 | - format: 9 | sdk: 10 | - dev 11 | - unit_test: 12 | - test: --test-randomize-ordering-seed=random 13 | os: 14 | - linux 15 | - windows 16 | -------------------------------------------------------------------------------- /pkgs/_macro_host/pubspec.yaml: -------------------------------------------------------------------------------- 1 | name: _macro_host 2 | publish-to: none 3 | description: >- 4 | Hosts macros. 5 | repository: https://github.com/dart-lang/macros/tree/master/pkgs/_macro_host 6 | resolution: workspace 7 | 8 | environment: 9 | sdk: ^3.7.0-157.0.dev 10 | 11 | dependencies: 12 | _macro_builder: any 13 | _macro_runner: any 14 | _macro_server: any 15 | crypto: ^3.0.6 16 | dart_model: any 17 | macro_service: any 18 | package_config: ^2.1.0 19 | 20 | dev_dependencies: 21 | _test_macros: any 22 | dart_flutter_team_lints: ^3.0.0 23 | test: ^1.25.0 24 | -------------------------------------------------------------------------------- /pkgs/_macro_host/test/package_config_test.dart: -------------------------------------------------------------------------------- 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 'dart:io'; 6 | import 'dart:isolate'; 7 | 8 | import 'package:_macro_host/src/package_config.dart'; 9 | import 'package:dart_model/dart_model.dart'; 10 | import 'package:test/test.dart'; 11 | 12 | void main() { 13 | group(MacroPackageConfig, () { 14 | test('can look up macro implementations from package URIs', () async { 15 | final packageConfig = MacroPackageConfig.readFromUri( 16 | Isolate.packageConfigSync!, 17 | ); 18 | 19 | expect( 20 | packageConfig 21 | .lookupMacroImplementation( 22 | QualifiedName( 23 | uri: 'package:_test_macros/declare_x_macro.dart', 24 | name: 'DeclareX', 25 | ), 26 | )! 27 | .asString, 28 | 'package:_test_macros/declare_x_macro.dart#DeclareXImplementation', 29 | ); 30 | }); 31 | 32 | test('can look up macro implementations from file URIs', () async { 33 | final packageConfig = MacroPackageConfig.readFromUri( 34 | Isolate.packageConfigSync!, 35 | ); 36 | 37 | final sourceFileUri = Directory.current.uri.resolve( 38 | '../_test_macros/lib/declare_x_macro.dart', 39 | ); 40 | 41 | expect( 42 | packageConfig 43 | .lookupMacroImplementation( 44 | QualifiedName(uri: '$sourceFileUri', name: 'DeclareX'), 45 | )! 46 | .asString, 47 | 'package:_test_macros/declare_x_macro.dart#DeclareXImplementation', 48 | ); 49 | }); 50 | }); 51 | } 52 | -------------------------------------------------------------------------------- /pkgs/_macro_runner/analysis_options.yaml: -------------------------------------------------------------------------------- 1 | include: package:dart_flutter_team_lints/analysis_options.yaml 2 | -------------------------------------------------------------------------------- /pkgs/_macro_runner/lib/macro_runner.dart: -------------------------------------------------------------------------------- 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 'dart:convert'; 6 | import 'dart:io'; 7 | 8 | import 'package:_macro_builder/macro_builder.dart'; 9 | import 'package:macro_service/macro_service.dart'; 10 | 11 | /// Runs macros. 12 | /// 13 | /// TODO(davidmorgan): support shutdown/cleanup. 14 | class MacroRunner { 15 | /// Starts [macroBundle] connected to [endpoint]. 16 | void start({ 17 | required BuiltMacroBundle macroBundle, 18 | required HostEndpoint endpoint, 19 | }) { 20 | Process.run(macroBundle.executablePath, [json.encode(endpoint)]).then(( 21 | result, 22 | ) { 23 | if (result.exitCode != 0) { 24 | print('Macro process exited with error: ${result.stderr}'); 25 | } 26 | }); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /pkgs/_macro_runner/mono_pkg.yaml: -------------------------------------------------------------------------------- 1 | sdk: 2 | - pubspec 3 | - dev 4 | 5 | stages: 6 | - analyze_and_format: 7 | - analyze: --fatal-infos . 8 | - format: 9 | sdk: 10 | - dev 11 | - unit_test: 12 | - test: --test-randomize-ordering-seed=random 13 | os: 14 | - linux 15 | - windows 16 | -------------------------------------------------------------------------------- /pkgs/_macro_runner/pubspec.yaml: -------------------------------------------------------------------------------- 1 | name: _macro_runner 2 | publish-to: none 3 | description: >- 4 | Runs macros. 5 | repository: https://github.com/dart-lang/macros/tree/master/pkgs/_macro_runner 6 | resolution: workspace 7 | 8 | environment: 9 | sdk: ^3.7.0-157.0.dev 10 | 11 | dependencies: 12 | _macro_builder: any 13 | macro_service: any 14 | 15 | dev_dependencies: 16 | dart_flutter_team_lints: ^3.0.0 17 | dart_model: any 18 | test: ^1.25.0 19 | -------------------------------------------------------------------------------- /pkgs/_macro_runner/test/macro_runner_test.dart: -------------------------------------------------------------------------------- 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 'dart:io'; 6 | import 'dart:isolate'; 7 | 8 | import 'package:_macro_builder/macro_builder.dart'; 9 | import 'package:_macro_runner/macro_runner.dart'; 10 | import 'package:dart_model/dart_model.dart'; 11 | import 'package:macro_service/macro_service.dart'; 12 | import 'package:test/test.dart'; 13 | 14 | void main() { 15 | for (final protocol in [ 16 | Protocol(encoding: ProtocolEncoding.json), 17 | Protocol(encoding: ProtocolEncoding.binary), 18 | ]) { 19 | group('MacroRunner with ${protocol.encoding}', () { 20 | test('runs macros', () async { 21 | final builder = MacroBuilder(); 22 | final bundle = await builder.build(Isolate.packageConfigSync!, [ 23 | QualifiedName( 24 | uri: 'package:_test_macros/declare_x_macro.dart', 25 | name: 'DeclareXImplementation', 26 | ), 27 | ]); 28 | 29 | final serverSocket = await ServerSocket.bind('localhost', 0); 30 | addTearDown(serverSocket.close); 31 | 32 | final runner = MacroRunner(); 33 | runner.start( 34 | macroBundle: bundle, 35 | endpoint: HostEndpoint(port: serverSocket.port), 36 | ); 37 | 38 | expect( 39 | serverSocket.first.timeout(const Duration(seconds: 10)), 40 | completes, 41 | ); 42 | }); 43 | }); 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /pkgs/_macro_server/analysis_options.yaml: -------------------------------------------------------------------------------- 1 | include: package:dart_flutter_team_lints/analysis_options.yaml 2 | -------------------------------------------------------------------------------- /pkgs/_macro_server/mono_pkg.yaml: -------------------------------------------------------------------------------- 1 | sdk: 2 | - pubspec 3 | - dev 4 | 5 | stages: 6 | - analyze_and_format: 7 | - analyze: --fatal-infos . 8 | - format: 9 | sdk: 10 | - dev 11 | - unit_test: 12 | - test: --test-randomize-ordering-seed=random 13 | os: 14 | - linux 15 | - windows 16 | -------------------------------------------------------------------------------- /pkgs/_macro_server/pubspec.yaml: -------------------------------------------------------------------------------- 1 | name: _macro_server 2 | publish-to: none 3 | description: >- 4 | Serves a `macro_service`. 5 | repository: https://github.com/dart-lang/macros/tree/master/pkgs/_macro_server 6 | resolution: workspace 7 | 8 | environment: 9 | sdk: ^3.7.0-157.0.dev 10 | 11 | dependencies: 12 | dart_model: any 13 | macro_service: any 14 | 15 | dev_dependencies: 16 | _macro_client: any 17 | _test_macros: any 18 | dart_flutter_team_lints: ^3.0.0 19 | test: ^1.25.0 20 | -------------------------------------------------------------------------------- /pkgs/_macro_tool/README.md: -------------------------------------------------------------------------------- 1 | A tool for running and benchmarking with macros. 2 | 3 | Specify a list of commands in addition to options: 4 | 5 | apply: runs macros, outputs to disk 6 | patch_for_analyzer: fixes macro output for the analyzer 7 | patch_for_cfe: fixes macro output for the CFE 8 | run: runs the specified script 9 | revert: reverts patches to files 10 | benchmark_apply: benchmarks applying macros 11 | benchmark_analyze: benchmarks analyzing macros 12 | bust_caches: modifies files like the benchmarks do 13 | watch: loops watching for changes to the specified script and applying 14 | 15 | ## Examples 16 | 17 | All examples are run from the root of this repo. 18 | 19 | Benchmarks require that you first create sources to benchmark with: 20 | 21 | ```bash 22 | dart run benchmark_generator large 16 BuiltValue JsonCodable 23 | ``` 24 | 25 | Benchmark running macros: 26 | 27 | ```bash 28 | dart run _macro_tool \ 29 | --workspace=goldens/foo/lib/generated/large \ 30 | benchmark_apply 31 | ``` 32 | 33 | Benchmark analysis on macro output without running macros: 34 | 35 | ```bash 36 | dart run _macro_tool \ 37 | --workspace=goldens/foo/lib/generated/large \ 38 | apply patch_for_analyzer benchmark_analyze revert 39 | ``` 40 | 41 | Run a script with macros: 42 | 43 | ```bash 44 | dart run _macro_tool \ 45 | --workspace=goldens/foo \ 46 | --script=goldens/foo/lib/json_codable_test.dart \ 47 | apply patch_for_cfe run revert 48 | ``` 49 | 50 | Watch for changes to a script, applying macros when it changes, writing the 51 | output to disk and reporting how long it took: 52 | 53 | ```bash 54 | dart run _macro_tool \ 55 | --workspace=goldens/foo \ 56 | --script=goldens/foo/lib/json_codable_test.dart \ 57 | watch 58 | ``` 59 | 60 | Clean up output after using `watch`: 61 | 62 | ```bash 63 | dart run _macro_tool \ 64 | --workspace=goldens/foo \ 65 | --script=goldens/foo/lib/json_codable_test.dart \ 66 | revert 67 | ``` 68 | -------------------------------------------------------------------------------- /pkgs/_macro_tool/bin/_macro_tool.dart: -------------------------------------------------------------------------------- 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 'dart:io'; 6 | 7 | import 'package:_macro_tool/main.dart' as macro_tool; 8 | 9 | Future main(List arguments) async { 10 | exit(await macro_tool.main(arguments)); 11 | } 12 | -------------------------------------------------------------------------------- /pkgs/_macro_tool/lib/analyzer_macro_runner.dart: -------------------------------------------------------------------------------- 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:_analyzer_macros/macro_implementation.dart'; 6 | import 'package:analyzer/dart/analysis/analysis_context.dart'; 7 | import 'package:analyzer/dart/analysis/analysis_context_collection.dart'; 8 | import 'package:analyzer/dart/analysis/results.dart' hide FileResult; 9 | import 'package:analyzer/diagnostic/diagnostic.dart'; 10 | import 'package:analyzer/src/summary2/macro_injected_impl.dart' 11 | as injected_analyzer; 12 | import 'package:macro_service/macro_service.dart'; 13 | 14 | import 'macro_runner.dart'; 15 | import 'source_file.dart'; 16 | 17 | class AnalyzerMacroRunner implements MacroRunner { 18 | final String workspacePath; 19 | final String packageConfigPath; 20 | 21 | @override 22 | final List sourceFiles; 23 | 24 | late final AnalysisContext analysisContext; 25 | AnalyzerMacroImplementation? analyzerMacroImplementation; 26 | 27 | AnalyzerMacroRunner({ 28 | required this.workspacePath, 29 | required this.packageConfigPath, 30 | }) : sourceFiles = SourceFile.findDartInWorkspace(workspacePath) { 31 | final contextCollection = AnalysisContextCollection( 32 | includedPaths: [workspacePath], 33 | ); 34 | analysisContext = contextCollection.contexts.single; 35 | } 36 | 37 | void notifyChange(String sourcePath) { 38 | analysisContext.changeFile(sourcePath); 39 | } 40 | 41 | @override 42 | Future run({bool injectImplementation = true}) async { 43 | if (injectImplementation) { 44 | analyzerMacroImplementation ??= await AnalyzerMacroImplementation.start( 45 | protocol: Protocol( 46 | encoding: ProtocolEncoding.binary, 47 | version: ProtocolVersion.macros1, 48 | ), 49 | packageConfig: Uri.file(packageConfigPath), 50 | ); 51 | injected_analyzer.macroImplementation = analyzerMacroImplementation; 52 | } else { 53 | injected_analyzer.macroImplementation = null; 54 | } 55 | 56 | final fileResults = []; 57 | final stopwatch = Stopwatch()..start(); 58 | await analysisContext.applyPendingFileChanges(); 59 | 60 | Duration? firstDuration; 61 | for (final sourceFile in sourceFiles) { 62 | ResolvedLibraryResult resolvedLibrary = 63 | (await analysisContext.currentSession.getResolvedLibrary(sourceFile)) 64 | as ResolvedLibraryResult; 65 | 66 | final errors = 67 | ((await analysisContext.currentSession.getErrors(sourceFile)) 68 | as ErrorsResult) 69 | .errors 70 | .where((e) => e.severity == Severity.error) 71 | .map((e) => e.toString()) 72 | .toList(); 73 | 74 | final augmentationUnits = 75 | resolvedLibrary.units.where((u) => u.isMacroPart).toList(); 76 | final output = augmentationUnits.singleOrNull?.content; 77 | 78 | fileResults.add( 79 | FileResult(sourceFile: sourceFile, output: output, errors: errors), 80 | ); 81 | firstDuration ??= stopwatch.elapsed; 82 | } 83 | 84 | return WorkspaceResult( 85 | fileResults: fileResults, 86 | firstResultAfter: firstDuration!, 87 | lastResultAfter: stopwatch.elapsed, 88 | ); 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /pkgs/_macro_tool/lib/cfe_macro_runner.dart: -------------------------------------------------------------------------------- 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 'dart:io'; 6 | 7 | import 'package:_cfe_macros/macro_implementation.dart'; 8 | import 'package:front_end/src/macros/macro_injected_impl.dart' as injected_cfe; 9 | import 'package:frontend_server/compute_kernel.dart'; 10 | import 'package:macro_service/macro_service.dart'; 11 | import 'package:path/path.dart' as p; 12 | 13 | import 'macro_runner.dart'; 14 | import 'source_file.dart'; 15 | 16 | class CfeMacroRunner implements MacroRunner { 17 | final String workspacePath; 18 | final String packageConfigPath; 19 | 20 | @override 21 | final List sourceFiles; 22 | 23 | CfeMacroImplementation? cfeMacroImplementation; 24 | 25 | CfeMacroRunner({required this.workspacePath, required this.packageConfigPath}) 26 | : sourceFiles = SourceFile.findDartInWorkspace(workspacePath); 27 | 28 | void notifyChange(String sourcePath) { 29 | // No incremental compile. 30 | } 31 | 32 | File get _productPlatformDill { 33 | // TODO(davidmorgan): this dill comes from the Dart SDK running the test, 34 | // but `package:frontend_server` and `package:front_end` are used as a 35 | // library, so we will see version skew breakage. Find a better way. 36 | final result = File( 37 | p.canonicalize( 38 | '${Platform.resolvedExecutable}/../../' 39 | 'lib/_internal/vm_platform_strong_product.dill', 40 | ), 41 | ); 42 | if (!result.existsSync()) { 43 | throw StateError('Failed to find platform dill: $result'); 44 | } 45 | return result; 46 | } 47 | 48 | @override 49 | Future run({bool injectImplementation = true}) async { 50 | if (injectImplementation) { 51 | cfeMacroImplementation ??= await CfeMacroImplementation.start( 52 | protocol: Protocol( 53 | encoding: ProtocolEncoding.json, 54 | version: ProtocolVersion.macros1, 55 | ), 56 | packageConfig: Uri.file(packageConfigPath), 57 | ); 58 | injected_cfe.macroImplementation = cfeMacroImplementation; 59 | } else { 60 | injected_cfe.macroImplementation = null; 61 | } 62 | 63 | final fileResults = []; 64 | final stopwatch = Stopwatch()..start(); 65 | 66 | // Don't directly use the compiler output: for consistency with the analyzer 67 | // codepath, run from the resulting source. 68 | // TODO(davidmorgan): maybe offer both as options? Not clear yet. 69 | final outputFile = File('/dev/null'); 70 | 71 | final packagesUri = Uri.file(packageConfigPath); 72 | 73 | final computeKernelResult = await computeKernel([ 74 | '--enable-experiment=macros', 75 | '--no-summary', 76 | '--no-summary-only', 77 | '--target=vm', 78 | '--dart-sdk-summary=${_productPlatformDill.uri}', 79 | '--output=${outputFile.path}', 80 | for (final sourceFile in sourceFiles) '--source=${Uri.file(sourceFile)}', 81 | '--packages-file=$packagesUri', 82 | // TODO(davidmorgan): this is so we can pull the generated 83 | // augmentation source out of incremental compiler state; find a less 84 | // hacky way. 85 | '--use-incremental-compiler', 86 | ]); 87 | 88 | final sources = 89 | computeKernelResult 90 | .previousState! 91 | .incrementalCompiler! 92 | .context 93 | .uriToSource; 94 | 95 | for (final sourceFile in sourceFiles) { 96 | final macroSource = 97 | sources[Uri.file(sourceFile.path).replace(scheme: 'dart-macro+file')]; 98 | if (macroSource != null) { 99 | fileResults.add( 100 | FileResult( 101 | sourceFile: sourceFile, 102 | output: macroSource.text, 103 | errors: computeKernelResult.succeeded ? [] : ['compile failed'], 104 | ), 105 | ); 106 | } 107 | } 108 | 109 | return WorkspaceResult( 110 | fileResults: fileResults, 111 | firstResultAfter: stopwatch.elapsed, 112 | lastResultAfter: stopwatch.elapsed, 113 | ); 114 | } 115 | } 116 | -------------------------------------------------------------------------------- /pkgs/_macro_tool/lib/macro_runner.dart: -------------------------------------------------------------------------------- 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 'source_file.dart'; 6 | 7 | /// Runs macros. 8 | abstract interface class MacroRunner { 9 | /// Source files in the workspace. 10 | List get sourceFiles; 11 | 12 | /// Runs macros in all files in the workspace, [sourceFiles]. 13 | /// 14 | /// Returns augmentations and errors for each file. 15 | /// 16 | /// [injectImplementation] controls whether the `dart_model` macro 17 | /// implementation is injected. Otherwise, the v1 implementation is used. 18 | Future run({bool injectImplementation = true}); 19 | 20 | /// Notifies the host that a file changed. 21 | /// 22 | /// Call this on changed files then call [run] again for an incremental run. 23 | void notifyChange(String sourcePath); 24 | } 25 | 26 | /// [MacroRunner] result for the whole workspace. 27 | class WorkspaceResult { 28 | /// Results per file. 29 | List fileResults; 30 | 31 | /// Time taken to produce the first file result. 32 | Duration firstResultAfter; 33 | 34 | /// Time taken to produce all results. 35 | Duration lastResultAfter; 36 | 37 | WorkspaceResult({ 38 | required this.fileResults, 39 | required this.firstResultAfter, 40 | required this.lastResultAfter, 41 | }); 42 | 43 | /// Errors from all [FileResult]s. 44 | List get allErrors => [ 45 | for (final result in fileResults) ...result.errors, 46 | ]; 47 | } 48 | 49 | /// [MacroRunner] result for one file. 50 | class FileResult { 51 | /// The file. 52 | final SourceFile sourceFile; 53 | 54 | /// Macro augmentation output, or `null` if there was no output. 55 | final String? output; 56 | 57 | /// Errors for this file. 58 | final List errors; 59 | 60 | FileResult({ 61 | required this.sourceFile, 62 | required this.output, 63 | required this.errors, 64 | }); 65 | } 66 | -------------------------------------------------------------------------------- /pkgs/_macro_tool/lib/main.dart: -------------------------------------------------------------------------------- 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 'dart:isolate'; 6 | 7 | import 'package:args/args.dart'; 8 | import 'package:path/path.dart' as p; 9 | 10 | import 'analyzer_macro_runner.dart'; 11 | import 'cfe_macro_runner.dart'; 12 | import 'macro_tool.dart'; 13 | 14 | final argParser = 15 | ArgParser() 16 | ..addOption( 17 | 'host', 18 | defaultsTo: 'analyzer', 19 | help: 'The macro host: "analyzer" or "cfe".', 20 | ) 21 | ..addOption('workspace', help: 'Path to workspace.') 22 | ..addOption('packageConfig', help: 'Path to package config.') 23 | ..addOption('script', help: 'Path to script.') 24 | ..addOption( 25 | 'benchmark-iterations', 26 | defaultsTo: '5', 27 | help: 'Benchmark iterations.', 28 | ); 29 | 30 | Future main(List arguments) async { 31 | final args = argParser.parse(arguments); 32 | final commands = args.rest; 33 | 34 | final host = HostOption.forString(args['host'] as String?); 35 | final workspace = args['workspace'] as String?; 36 | final packageConfig = 37 | args['packageConfig'] as String? ?? Isolate.packageConfigSync!.path; 38 | final script = args['script'] as String?; 39 | 40 | if (host == null || workspace == null || commands.isEmpty) { 41 | print(''' 42 | Runs a Dart script with `dart_model` macros. 43 | 44 | Usage: after the options, pass a list of commands. See `README.md` for 45 | commands and examples. 46 | 47 | ${argParser.usage}'''); 48 | return 1; 49 | } 50 | 51 | final canonicalizedPackageConfig = p.canonicalize(packageConfig); 52 | final canonicalizedWorkspace = p.canonicalize(workspace); 53 | 54 | final tool = MacroTool( 55 | macroRunner: 56 | host == HostOption.analyzer 57 | ? AnalyzerMacroRunner( 58 | packageConfigPath: canonicalizedPackageConfig, 59 | workspacePath: canonicalizedWorkspace, 60 | ) 61 | : CfeMacroRunner( 62 | packageConfigPath: canonicalizedPackageConfig, 63 | workspacePath: canonicalizedWorkspace, 64 | ), 65 | packageConfigPath: canonicalizedPackageConfig, 66 | workspacePath: canonicalizedWorkspace, 67 | benchmarkIterations: int.parse(args['benchmark-iterations']), 68 | scriptPath: script == null ? null : p.canonicalize(script), 69 | ); 70 | 71 | var exitCode = 0; 72 | for (final command in commands) { 73 | switch (command) { 74 | case 'apply': 75 | await tool.apply(); 76 | case 'patch_for_analyzer': 77 | tool.patchForAnalyzer(); 78 | case 'patch_for_cfe': 79 | tool.patchForCfe(); 80 | case 'run': 81 | exitCode = await tool.run(); 82 | case 'revert': 83 | tool.revert(); 84 | case 'benchmark_apply': 85 | await tool.benchmarkApply(); 86 | case 'benchmark_analyze': 87 | await tool.benchmarkAnalyze(); 88 | case 'bust_caches': 89 | tool.bustCaches(); 90 | case 'watch': 91 | await tool.watch(); 92 | default: 93 | print('Unknown command: $command'); 94 | return 1; 95 | } 96 | } 97 | 98 | return exitCode; 99 | } 100 | 101 | enum HostOption { 102 | analyzer, 103 | cfe; 104 | 105 | static HostOption? forString(String? option) => switch (option) { 106 | 'analyzer' => HostOption.analyzer, 107 | 'cfe' => HostOption.cfe, 108 | _ => throw ArgumentError('Not a valid host: $option'), 109 | }; 110 | } 111 | -------------------------------------------------------------------------------- /pkgs/_macro_tool/mono_pkg.yaml: -------------------------------------------------------------------------------- 1 | sdk: 2 | - pubspec 3 | - dev 4 | 5 | stages: 6 | - analyze_and_format: 7 | - analyze: --fatal-infos . 8 | - format: 9 | sdk: 10 | - dev 11 | - unit_test: 12 | # Some tests write to test/package_under_test, so limit concurrency. 13 | - test: --test-randomize-ordering-seed=random --concurrency=1 14 | os: 15 | - linux 16 | - windows 17 | -------------------------------------------------------------------------------- /pkgs/_macro_tool/pubspec.yaml: -------------------------------------------------------------------------------- 1 | name: _macro_tool 2 | publish-to: none 3 | description: >- 4 | CLI for running code with macros applied. 5 | resolution: workspace 6 | environment: 7 | sdk: ^3.7.0-157.0.dev 8 | 9 | dependencies: 10 | _analyzer_macros: any 11 | 12 | dev_dependencies: 13 | dart_flutter_team_lints: ^3.0.0 14 | test: ^1.25.7 15 | -------------------------------------------------------------------------------- /pkgs/_macro_tool/test/macro_runner_test.dart: -------------------------------------------------------------------------------- 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 'dart:io'; 6 | import 'dart:isolate'; 7 | 8 | import 'package:_macro_tool/analyzer_macro_runner.dart'; 9 | import 'package:_macro_tool/cfe_macro_runner.dart'; 10 | import 'package:test/test.dart'; 11 | 12 | void main() { 13 | final packageConfigPath = Isolate.packageConfigSync!.toFilePath(); 14 | final workspacePath = 15 | Directory.fromUri(Uri.parse('./test/package_under_test')).absolute.path; 16 | 17 | group('AnalyzerMacroRunner', () { 18 | test('runs macros', () async { 19 | final macroRunner = AnalyzerMacroRunner( 20 | packageConfigPath: packageConfigPath, 21 | workspacePath: workspacePath, 22 | ); 23 | final result = await macroRunner.run(); 24 | 25 | // Two files that produce macro output, no errors. 26 | expect(result.fileResults.length, 2); 27 | expect(result.allErrors, isEmpty); 28 | expect(result.fileResults.map((r) => r.output), everyElement(isNotNull)); 29 | }); 30 | }); 31 | 32 | group('CfeMacroRunner', () { 33 | test('runs macros', () async { 34 | final macroRunner = CfeMacroRunner( 35 | packageConfigPath: packageConfigPath, 36 | workspacePath: workspacePath, 37 | ); 38 | final result = await macroRunner.run(); 39 | 40 | // Two files that produce macro output, no errors. 41 | expect(result.fileResults.length, 2); 42 | expect(result.allErrors, isEmpty); 43 | expect(result.fileResults.map((r) => r.output), everyElement(isNotNull)); 44 | }); 45 | }); 46 | } 47 | -------------------------------------------------------------------------------- /pkgs/_macro_tool/test/macro_tool_test.dart: -------------------------------------------------------------------------------- 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:_macro_tool/main.dart' as macro_tool; 6 | import 'package:test/test.dart'; 7 | 8 | void main() { 9 | tearDown(() async { 10 | await macro_tool.main([ 11 | '--workspace=test/package_under_test', 12 | '--packageConfig=../../.dart_tool/package_config.json', 13 | 'revert', 14 | ]); 15 | }); 16 | 17 | test('apply macros with analyzer then run', () async { 18 | expect( 19 | await macro_tool.main([ 20 | '--workspace=test/package_under_test', 21 | '--packageConfig=../../.dart_tool/package_config.json', 22 | '--script=test/package_under_test/lib/apply_declare_x.dart', 23 | 'apply', 24 | 'patch_for_cfe', 25 | 'run', 26 | ]), 27 | // The script exit code is the macro-generated value: 3. 28 | 3, 29 | ); 30 | }); 31 | 32 | test('apply macros with CFE then run', () async { 33 | expect( 34 | await macro_tool.main([ 35 | '--workspace=test/package_under_test', 36 | '--packageConfig=../../.dart_tool/package_config.json', 37 | '--script=test/package_under_test/lib/apply_declare_x.dart', 38 | '--host=cfe', 39 | 'apply', 40 | 'patch_for_cfe', 41 | 'run', 42 | ]), 43 | // The script exit code is the macro-generated value: 3. 44 | 3, 45 | ); 46 | }); 47 | } 48 | -------------------------------------------------------------------------------- /pkgs/_macro_tool/test/package_under_test/analysis_options.yaml: -------------------------------------------------------------------------------- 1 | analyzer: 2 | enable-experiment: 3 | - macros 4 | -------------------------------------------------------------------------------- /pkgs/_macro_tool/test/package_under_test/lib/apply_declare_x.dart: -------------------------------------------------------------------------------- 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 'dart:io'; 6 | 7 | import 'package:_test_macros/declare_x_macro.dart'; 8 | 9 | @DeclareX() 10 | class ClassWithMacroApplied { 11 | int CACHEBUSTER = 1; 12 | } 13 | 14 | void main() { 15 | final code = ((ClassWithMacroApplied() as dynamic).x); 16 | print(code); 17 | exit(code); 18 | } 19 | -------------------------------------------------------------------------------- /pkgs/_macro_tool/test/package_under_test/lib/apply_query_class.dart: -------------------------------------------------------------------------------- 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:_test_macros/query_class.dart'; 6 | 7 | @QueryClass() 8 | abstract class ClassWithMacroApplied { 9 | abstract final int x; 10 | } 11 | -------------------------------------------------------------------------------- /pkgs/_macro_tool/test/package_under_test/pubspec.yaml: -------------------------------------------------------------------------------- 1 | name: package_under_test_macro_tool 2 | publish-to: none 3 | resolution: workspace 4 | 5 | environment: 6 | sdk: ^3.7.0-157.0.dev 7 | 8 | dependencies: 9 | _test_macros: any 10 | -------------------------------------------------------------------------------- /pkgs/_test_macros/analysis_options.yaml: -------------------------------------------------------------------------------- 1 | include: package:dart_flutter_team_lints/analysis_options.yaml 2 | -------------------------------------------------------------------------------- /pkgs/_test_macros/lib/declare_x_macro.dart: -------------------------------------------------------------------------------- 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/dart_model.dart'; 6 | import 'package:macro/macro.dart'; 7 | import 'package:macro_service/macro_service.dart'; 8 | 9 | import 'templating.dart'; 10 | 11 | /// Adds a getter `int get x` to the class. 12 | class DeclareX { 13 | const DeclareX(); 14 | } 15 | 16 | class DeclareXImplementation implements ClassDeclarationsMacro { 17 | // TODO(davidmorgan): this should be injected by the bootstrap script. 18 | @override 19 | MacroDescription get description => MacroDescription( 20 | annotation: QualifiedName( 21 | uri: 'package:_test_macros/declare_x_macro.dart', 22 | name: 'DeclareX', 23 | ), 24 | runsInPhases: [2], 25 | ); 26 | 27 | @override 28 | void buildDeclarationsForClass(ClassDeclarationsBuilder builder) { 29 | builder.declareInType( 30 | Augmentation(code: expandTemplate('int get x => 3;')), 31 | ); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /pkgs/_test_macros/lib/literal_params.dart: -------------------------------------------------------------------------------- 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/dart_model.dart'; 6 | // ignore: implementation_imports 7 | import 'package:dart_model/src/macro_metadata.g.dart'; 8 | import 'package:macro/macro.dart'; 9 | import 'package:macro_service/macro_service.dart'; 10 | 11 | import 'templating.dart'; 12 | 13 | /// Covers macro metadata cases where the params will always be written as 14 | /// literals in the annotation. 15 | /// 16 | /// Outputs comments with evaluation results. 17 | /// 18 | /// Throws if the annotation has something other than supported literals. 19 | /// TODO(davidmorgan): support diagnostics, make failures diagnostics. 20 | class LiteralParams { 21 | final int? anInt; 22 | final num? aNum; 23 | final double? aDouble; 24 | final String? aString; 25 | final Object? anObject; 26 | final List? ints; 27 | final List? nums; 28 | final List? doubles; 29 | final List? strings; 30 | final List? objects; 31 | 32 | const LiteralParams({ 33 | required this.anInt, 34 | this.aNum, 35 | this.aDouble, 36 | this.aString, 37 | this.anObject, 38 | this.ints, 39 | this.nums, 40 | this.doubles, 41 | this.strings, 42 | this.objects, 43 | }); 44 | } 45 | 46 | class LiteralParamsImplementation implements ClassDeclarationsMacro { 47 | // TODO(davidmorgan): this should be injected by the bootstrap script. 48 | @override 49 | MacroDescription get description => MacroDescription( 50 | annotation: QualifiedName( 51 | uri: 'package:_test_macros/literal_params.dart', 52 | name: 'LiteralParams', 53 | ), 54 | runsInPhases: [2], 55 | ); 56 | 57 | @override 58 | Future buildDeclarationsForClass( 59 | ClassDeclarationsBuilder builder, 60 | ) async { 61 | // TODO(davidmorgan): need a way to find the correct annotation, this just 62 | // uses the first. 63 | final annotation = 64 | builder 65 | .target 66 | .metadataAnnotations 67 | .first 68 | .expression 69 | .asConstructorInvocation; 70 | 71 | final namedArguments = { 72 | for (final argument in annotation.arguments) 73 | if (argument.type == ArgumentType.namedArgument) 74 | argument.asNamedArgument.name: 75 | argument.asNamedArgument.expression.evaluate, 76 | }; 77 | 78 | builder.declareInType( 79 | Augmentation( 80 | code: expandTemplate( 81 | [ 82 | for (final entry in namedArguments.entries) 83 | ' // ${entry.key}: ${entry.value}, ${entry.value.runtimeType}', 84 | ].join('\n'), 85 | ), 86 | ), 87 | ); 88 | } 89 | } 90 | 91 | // TODO(davidmorgan): common code for this in `dart_model` so macros don't 92 | // all have to write expression evaluation code. 93 | extension ExpressionExtension on Expression { 94 | Object get evaluate => switch (type) { 95 | ExpressionType.integerLiteral => int.parse(asIntegerLiteral.text), 96 | ExpressionType.doubleLiteral => asDoubleLiteral.value, 97 | ExpressionType.stringLiteral => asStringLiteral.evaluate, 98 | ExpressionType.booleanLiteral => asBooleanLiteral.value, 99 | ExpressionType.listLiteral => 100 | asListLiteral.elements.map((e) => e.evaluate).toList(), 101 | // TODO(davidmorgan): need the type name to do something useful here, 102 | // for now just return the JSON. 103 | ExpressionType.constructorInvocation => asConstructorInvocation.toString(), 104 | // TODO(davidmorgan): need to follow references to do something useful 105 | // here, for now just return the JSON. 106 | ExpressionType.staticGet => asStaticGet.toString(), 107 | _ => 108 | throw UnsupportedError( 109 | 'Not supported in @LiteralParams annotation: $this', 110 | ), 111 | }; 112 | } 113 | 114 | extension ElementExtension on Element { 115 | Object get evaluate => switch (type) { 116 | ElementType.expressionElement => asExpressionElement.expression.evaluate, 117 | _ => 118 | throw UnsupportedError( 119 | 'Not supported in @LiteralParams annotation: $this', 120 | ), 121 | }; 122 | } 123 | 124 | extension StringLiteralExtension on StringLiteral { 125 | Object get evaluate { 126 | if (parts.length != 1) { 127 | throw UnsupportedError( 128 | 'Not supported in @LiteralParams annotation: $this', 129 | ); 130 | } 131 | final part = parts.single; 132 | return switch (part.type) { 133 | StringLiteralPartType.stringPart => part.asStringPart.text, 134 | _ => 135 | throw UnsupportedError( 136 | 'Not supported in @LiteralParams annotation: $this', 137 | ), 138 | }; 139 | } 140 | } 141 | -------------------------------------------------------------------------------- /pkgs/_test_macros/lib/query_class.dart: -------------------------------------------------------------------------------- 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 'dart:convert'; 6 | 7 | import 'package:dart_model/dart_model.dart'; 8 | import 'package:macro/macro.dart'; 9 | import 'package:macro_service/macro_service.dart'; 10 | 11 | import 'templating.dart'; 12 | 13 | /// Applies a macro which sends an empty query, and outputs an augmentation 14 | /// that is the query result as a comment. 15 | class QueryClass { 16 | const QueryClass(); 17 | } 18 | 19 | class QueryClassImplementation implements ClassDefinitionsMacro { 20 | // TODO(davidmorgan): this should be injected by the bootstrap script. 21 | @override 22 | MacroDescription get description => MacroDescription( 23 | annotation: QualifiedName( 24 | uri: 'package:_test_macros/query_class.dart', 25 | name: 'QueryClass', 26 | ), 27 | runsInPhases: [3], 28 | ); 29 | 30 | @override 31 | Future buildDefinitionsForClass(ClassDefinitionsBuilder builder) async { 32 | final qualifiedName = builder.model.qualifiedNameOf(builder.target.node)!; 33 | final result = await builder.query(Query(target: qualifiedName)); 34 | builder.augment( 35 | docCommentsAndMetadata: Augmentation( 36 | code: expandTemplate('// ${json.encode(result)}'), 37 | ), 38 | ); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /pkgs/_test_macros/lib/templating.dart: -------------------------------------------------------------------------------- 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/dart_model.dart'; 6 | 7 | // TODO(davidmorgan): figure out where this should go. 8 | extension TemplatingExtension on QualifiedName { 9 | String get code => '{{$uri#$name}}'; 10 | } 11 | 12 | Augmentation augmentation(String template) => 13 | Augmentation(code: expandTemplate(template)); 14 | 15 | /// Converts [template] to a mix of `Identifier` and `String`. 16 | /// 17 | /// References of the form `{{uri#name}}` become [QualifiedName] wrapped in 18 | /// [Code.qualifiedName], everything else becomes `String`. 19 | /// 20 | /// TODO(davidmorgan): figure out where this should go. 21 | List expandTemplate(String template) { 22 | final result = []; 23 | var index = 0; 24 | while (index < template.length) { 25 | final start = template.indexOf('{{', index); 26 | if (start == -1) { 27 | result.add(Code.string(template.substring(index))); 28 | break; 29 | } 30 | result.add(Code.string(template.substring(index, start))); 31 | final end = template.indexOf('}}', start); 32 | if (end == -1) { 33 | throw ArgumentError('Unmatched opening brace: $template'); 34 | } 35 | final name = template.substring(start + 2, end); 36 | result.add(Code.qualifiedName(QualifiedName.parse(name))); 37 | index = end + 2; 38 | } 39 | return result; 40 | } 41 | -------------------------------------------------------------------------------- /pkgs/_test_macros/mono_pkg.yaml: -------------------------------------------------------------------------------- 1 | sdk: 2 | - pubspec 3 | - dev 4 | 5 | stages: 6 | - analyze_and_format: 7 | - analyze: --fatal-infos . 8 | - format: 9 | sdk: 10 | - dev 11 | -------------------------------------------------------------------------------- /pkgs/_test_macros/pubspec.yaml: -------------------------------------------------------------------------------- 1 | name: _test_macros 2 | publish-to: none 3 | description: >- 4 | Some test macros. 5 | repository: https://github.com/dart-lang/macros/tree/master/pkgs/_test_macros 6 | resolution: workspace 7 | 8 | environment: 9 | sdk: ^3.7.0-157.0.dev 10 | 11 | dependencies: 12 | dart_model: any 13 | macro: any 14 | macro_service: any 15 | 16 | dev_dependencies: 17 | dart_flutter_team_lints: ^3.0.0 18 | test: ^1.25.0 19 | 20 | # TODO(language/3728): use the real feature when there is one. 21 | # macro lib/built_value.dart#BuiltValue package:_test_macros/built_value.dart#BuiltValueImplementation 22 | # macro lib/built_value.dart#BuiltValueBuilder package:_test_macros/built_value.dart#BuiltValueBuilderImplementation 23 | # macro lib/declare_x_macro.dart#DeclareX package:_test_macros/declare_x_macro.dart#DeclareXImplementation 24 | # macro lib/json_codable.dart#JsonCodable package:_test_macros/json_codable.dart#JsonCodableImplementation 25 | # macro lib/query_class.dart#QueryClass package:_test_macros/query_class.dart#QueryClassImplementation 26 | # macro lib/literal_params.dart#LiteralParams package:_test_macros/literal_params.dart#LiteralParamsImplementation 27 | -------------------------------------------------------------------------------- /pkgs/dart_model/analysis_options.yaml: -------------------------------------------------------------------------------- 1 | include: package:dart_flutter_team_lints/analysis_options.yaml 2 | 3 | analyzer: 4 | errors: 5 | # TODO(davidmorgan): generated code can exceed 80 chars. 6 | lines_longer_than_80_chars: ignore 7 | -------------------------------------------------------------------------------- /pkgs/dart_model/benchmark/builder_maps_builder_wire_benchmark.dart: -------------------------------------------------------------------------------- 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 'dart:typed_data'; 6 | 7 | import 'package:dart_model/src/json_buffer/json_buffer_builder.dart'; 8 | 9 | import 'serialization_benchmark.dart'; 10 | 11 | JsonBufferBuilder? runningBuffer; 12 | 13 | /// Benchmark accumulating data directly into a [JsonBufferBuilder]. 14 | class BuilderMapsBuilderWireBenchmark extends SerializationBenchmark { 15 | @override 16 | Uint8List serialize(Map data) { 17 | assert(data == runningBuffer!.map); 18 | return runningBuffer!.serialize(); 19 | } 20 | 21 | @override 22 | Map createData() { 23 | final buffer = runningBuffer = JsonBufferBuilder(); 24 | final map = buffer.map; 25 | 26 | for (final key in mapKeys) { 27 | final intKey = int.parse(key); 28 | final members = buffer.createGrowableMap(); 29 | map[key] = Interface( 30 | members: members, 31 | properties: Properties( 32 | isAbstract: (intKey & 1) == 1, 33 | isClass: (intKey & 2) == 2, 34 | isGetter: (intKey & 4) == 4, 35 | isField: (intKey & 8) == 8, 36 | isMethod: (intKey & 16) == 16, 37 | isStatic: (intKey & 32) == 32, 38 | ), 39 | ); 40 | for (final memberName in makeMemberNames(intKey)) { 41 | members[memberName] = _makeMember(memberName); 42 | } 43 | } 44 | 45 | return buffer.map; 46 | } 47 | 48 | @override 49 | Map deserialize(Uint8List serialized) => 50 | JsonBufferBuilder.deserialize(serialized).map; 51 | 52 | Member _makeMember(String key) { 53 | final intKey = key.length; 54 | return Member( 55 | properties: Properties( 56 | isAbstract: (intKey & 1) == 1, 57 | isClass: (intKey & 2) == 2, 58 | isGetter: (intKey & 4) == 4, 59 | isField: const [true, false, null][intKey % 3], 60 | isMethod: (intKey & 16) == 16, 61 | isStatic: (intKey & 32) == 32, 62 | ), 63 | ); 64 | } 65 | } 66 | 67 | /// An interface. 68 | extension type Interface.fromJson(Map node) { 69 | static TypedMapSchema schema = TypedMapSchema({ 70 | 'members': Type.growableMapPointer, 71 | 'properties': Type.typedMapPointer, 72 | }); 73 | 74 | Interface({Map? members, Properties? properties}) 75 | : this.fromJson(runningBuffer!.createTypedMap(schema, members, properties)); 76 | 77 | /// Map of members by name. 78 | Map get members => (node['members'] as Map).cast(); 79 | 80 | /// The properties of this interface. 81 | Properties get properties => node['properties'] as Properties; 82 | } 83 | 84 | extension type Member.fromJson(Map node) { 85 | static TypedMapSchema schema = TypedMapSchema({ 86 | 'properties': Type.typedMapPointer, 87 | }); 88 | 89 | Member({Properties? properties}) 90 | : this.fromJson(runningBuffer!.createTypedMap(schema, properties)); 91 | 92 | /// The properties of this member. 93 | Properties get properties => node['properties'] as Properties; 94 | } 95 | 96 | /// Set of boolean properties. 97 | extension type Properties.fromJson(Map node) { 98 | static TypedMapSchema schema = TypedMapSchema({ 99 | 'isAbstract': Type.boolean, 100 | 'isClass': Type.boolean, 101 | 'isGetter': Type.boolean, 102 | 'isField': Type.boolean, 103 | 'isMethod': Type.boolean, 104 | 'isStatic': Type.boolean, 105 | }); 106 | 107 | Properties({ 108 | bool? isAbstract, 109 | bool? isClass, 110 | bool? isGetter, 111 | bool? isField, 112 | bool? isMethod, 113 | bool? isStatic, 114 | }) : this.fromJson( 115 | runningBuffer!.createTypedMap( 116 | schema, 117 | isAbstract, 118 | isClass, 119 | isGetter, 120 | isField, 121 | isMethod, 122 | isStatic, 123 | ), 124 | ); 125 | 126 | /// Whether the entity is abstract, meaning it has no definition. 127 | bool get isAbstract => node['isAbstract'] as bool; 128 | 129 | /// Whether the entity is a class. 130 | bool get isClass => node['isClass'] as bool; 131 | 132 | /// Whether the entity is a getter. 133 | bool get isGetter => node['isGetter'] as bool; 134 | 135 | /// Whether the entity is a field. 136 | bool get isField => node['isField'] as bool; 137 | 138 | /// Whether the entity is a method. 139 | bool get isMethod => node['isMethod'] as bool; 140 | 141 | /// Whether the entity is static. 142 | bool get isStatic => node['isStatic'] as bool; 143 | } 144 | -------------------------------------------------------------------------------- /pkgs/dart_model/benchmark/builder_maps_json_wire_benchmark.dart: -------------------------------------------------------------------------------- 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 'dart:convert'; 6 | import 'dart:typed_data'; 7 | 8 | import 'builder_maps_builder_wire_benchmark.dart'; 9 | 10 | /// Benchmark accumulating data directly into a `JsonBufferBuilder` then 11 | /// serializing it to JSON. 12 | class BuilderMapsJsonWireBenchmark extends BuilderMapsBuilderWireBenchmark { 13 | @override 14 | Uint8List serialize(Map data) => 15 | json.fuse(utf8).encode(data) as Uint8List; 16 | 17 | @override 18 | Map deserialize(Uint8List serialized) => 19 | json.fuse(utf8).decode(serialized) as Map; 20 | } 21 | -------------------------------------------------------------------------------- /pkgs/dart_model/benchmark/lazy_maps_buffer_wire_benchmark.dart: -------------------------------------------------------------------------------- 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 'dart:typed_data'; 6 | 7 | import 'json_buffer.dart'; 8 | import 'serialization_benchmark.dart'; 9 | 10 | /// Benchmark accumulating data into a [JsonBuffer] via [LazyMap]. 11 | class LazyMapsBufferWireBenchmark extends SerializationBenchmark { 12 | @override 13 | Uint8List serialize(Map data) => 14 | JsonBuffer(data).serialize(); 15 | 16 | @override 17 | LazyMap createData() { 18 | return LazyMap(mapKeys, (key) { 19 | final intKey = int.parse(key); 20 | return Interface( 21 | members: LazyMap(makeMemberNames(intKey), _makeMember).cast(), 22 | properties: Properties( 23 | isAbstract: (intKey & 1) == 1, 24 | isClass: (intKey & 2) == 2, 25 | isGetter: (intKey & 4) == 4, 26 | isField: (intKey & 8) == 8, 27 | isMethod: (intKey & 16) == 16, 28 | isStatic: (intKey & 32) == 32, 29 | ), 30 | ); 31 | }); 32 | } 33 | 34 | Member _makeMember(String key) { 35 | final intKey = key.length; 36 | return Member( 37 | properties: Properties( 38 | isAbstract: (intKey & 1) == 1, 39 | isClass: (intKey & 2) == 2, 40 | isGetter: (intKey & 4) == 4, 41 | isField: const [true, false, null][intKey % 3], 42 | isMethod: (intKey & 16) == 16, 43 | isStatic: (intKey & 32) == 32, 44 | ), 45 | ); 46 | } 47 | 48 | @override 49 | Map deserialize(Uint8List serialized) => 50 | JsonBuffer.deserialize(serialized).asMap; 51 | } 52 | 53 | /// An interface. 54 | extension type Interface.fromJson(Map node) { 55 | Interface({Map? members, Properties? properties}) 56 | : this.fromJson( 57 | LazyMap( 58 | [ 59 | if (members != null) 'members', 60 | if (properties != null) 'properties', 61 | ], 62 | (key) => switch (key) { 63 | 'members' => members, 64 | 'properties' => properties, 65 | _ => null, 66 | }, 67 | ), 68 | ); 69 | 70 | /// Map of members by name. 71 | Map get members => (node['members'] as Map).cast(); 72 | 73 | /// The properties of this interface. 74 | Properties get properties => node['properties'] as Properties; 75 | } 76 | 77 | extension type Member.fromJson(Map node) { 78 | Member({Properties? properties}) 79 | : this.fromJson( 80 | LazyMap( 81 | [if (properties != null) 'properties'], 82 | (key) => switch (key) { 83 | 'properties' => properties, 84 | _ => null, 85 | }, 86 | ), 87 | ); 88 | 89 | /// The properties of this member. 90 | Properties get properties => node['properties'] as Properties; 91 | } 92 | 93 | /// Set of boolean properties. 94 | extension type Properties.fromJson(Map node) { 95 | Properties({ 96 | bool? isAbstract, 97 | bool? isClass, 98 | bool? isGetter, 99 | bool? isField, 100 | bool? isMethod, 101 | bool? isStatic, 102 | }) : this.fromJson( 103 | LazyMap( 104 | [ 105 | if (isAbstract != null) 'isAbstract', 106 | if (isClass != null) 'isClass', 107 | if (isGetter != null) 'isGetter', 108 | if (isField != null) 'isField', 109 | if (isMethod != null) 'isMethod', 110 | if (isStatic != null) 'isStatic', 111 | ], 112 | (key) => switch (key) { 113 | 'isAbstract' => isAbstract, 114 | 'isClass' => isClass, 115 | 'isGetter' => isGetter, 116 | 'isField' => isField, 117 | 'isMethod' => isMethod, 118 | 'isStatic' => isStatic, 119 | _ => null, 120 | }, 121 | ), 122 | ); 123 | 124 | /// Whether the entity is abstract, meaning it has no definition. 125 | bool get isAbstract => node['isAbstract'] as bool; 126 | 127 | /// Whether the entity is a class. 128 | bool get isClass => node['isClass'] as bool; 129 | 130 | /// Whether the entity is a getter. 131 | bool get isGetter => node['isGetter'] as bool; 132 | 133 | /// Whether the entity is a field. 134 | bool get isField => node['isField'] as bool; 135 | 136 | /// Whether the entity is a method. 137 | bool get isMethod => node['isMethod'] as bool; 138 | 139 | /// Whether the entity is static. 140 | bool get isStatic => node['isStatic'] as bool; 141 | } 142 | -------------------------------------------------------------------------------- /pkgs/dart_model/benchmark/lazy_maps_json_wire_benchmark.dart: -------------------------------------------------------------------------------- 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 'dart:convert'; 6 | import 'dart:typed_data'; 7 | 8 | import 'lazy_maps_buffer_wire_benchmark.dart'; 9 | 10 | /// Benchmark accumulating data into a `JsonBuffer` via `LazyMap` then 11 | /// serializing it to JSON. 12 | class LazyMapsJsonWireBenchmark extends LazyMapsBufferWireBenchmark { 13 | @override 14 | Uint8List serialize(Map data) => 15 | json.fuse(utf8).encode(data) as Uint8List; 16 | 17 | @override 18 | Map deserialize(Uint8List serialized) => 19 | json.fuse(utf8).decode(serialized) as Map; 20 | } 21 | -------------------------------------------------------------------------------- /pkgs/dart_model/benchmark/main.dart: -------------------------------------------------------------------------------- 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 'dart:math'; 6 | 7 | import 'package:collection/collection.dart'; 8 | 9 | import 'builder_maps_builder_wire_benchmark.dart'; 10 | import 'builder_maps_json_wire_benchmark.dart'; 11 | import 'lazy_maps_buffer_wire_benchmark.dart'; 12 | import 'lazy_maps_json_wire_benchmark.dart'; 13 | import 'lazy_wrappers_buffer_wire_benchmark.dart'; 14 | import 'lazy_wrappers_buffer_wire_benchmark.dart' as wrapped; 15 | import 'regular_dart_classes.dart'; 16 | import 'regular_dart_classes.dart' as regular; 17 | import 'sdk_maps_buffer_wire_benchmark.dart'; 18 | import 'sdk_maps_builder_wire_benchmark.dart'; 19 | import 'sdk_maps_json_wire_benchmark.dart'; 20 | import 'serialization_benchmark.dart'; 21 | 22 | void main() { 23 | final serializationBenchmarks = [ 24 | SdkMapsJsonWireBenchmark(), 25 | SdkMapsBufferWireBenchmark(), 26 | SdkMapsBuilderWireBenchmark(), 27 | LazyMapsJsonWireBenchmark(), 28 | LazyMapsBufferWireBenchmark(), 29 | LazyWrappersBufferWireBenchmark(), 30 | BuilderMapsJsonWireBenchmark(), 31 | BuilderMapsBuilderWireBenchmark(), 32 | RegularClassesBufferWireBenchmark(), 33 | ]; 34 | 35 | for (var i = 0; i != 3; ++i) { 36 | // Collects the total measurements from all phases, per benchmark. 37 | final totals = { 38 | for (var benchmark in serializationBenchmarks) benchmark: 0, 39 | }; 40 | 41 | for (var stage in BenchmarkStage.values) { 42 | var padding = 0; 43 | for (final benchmark in serializationBenchmarks) { 44 | benchmark.stage = stage; 45 | padding = max(padding, benchmark.name.length); 46 | } 47 | 48 | for (final benchmark in serializationBenchmarks) { 49 | final measure = benchmark.measure().toMilliseconds; 50 | totals[benchmark] = totals[benchmark]! + measure; 51 | 52 | var buffer = 53 | StringBuffer(benchmark.name.padLeft(padding + 1)) 54 | ..write(': ') 55 | ..write('${measure}ms'.padLeft(6)); 56 | 57 | switch (stage) { 58 | case BenchmarkStage.serialize: 59 | final paddedBytes = '${benchmark.bytes}'.padLeft(7); 60 | buffer.write(', $paddedBytes bytes'); 61 | case BenchmarkStage.process: 62 | buffer.write(', hash ${benchmark.hashResult}'); 63 | default: 64 | } 65 | print(buffer.toString()); 66 | } 67 | } 68 | 69 | // Add up the totals and print them. 70 | { 71 | var padding = 0; 72 | String name(SerializationBenchmark benchmark) => 73 | '${benchmark.runtimeType}-total'; 74 | 75 | for (final benchmark in serializationBenchmarks) { 76 | padding = max(padding, name(benchmark).length); 77 | } 78 | for (var benchmark in serializationBenchmarks) { 79 | var buffer = 80 | StringBuffer(name(benchmark).padLeft(padding + 1)) 81 | ..write(':') 82 | ..write('${totals[benchmark]}ms'.padLeft(7)); 83 | print(buffer.toString()); 84 | } 85 | } 86 | 87 | print(''); 88 | 89 | print('validating benchmark results (this is slow)'); 90 | for (final benchmark in serializationBenchmarks.skip(1)) { 91 | var deserialized = benchmark.deserialized; 92 | // Need to unwrap these to compare them as raw maps. 93 | if (deserialized is Map) { 94 | deserialized = deserialized.map( 95 | (k, v) => MapEntry(k, v.toJson()), 96 | ); 97 | } else if (deserialized is Map) { 98 | deserialized = deserialized.map( 99 | (k, v) => MapEntry(k, v.toJson()), 100 | ); 101 | } 102 | if (!const DeepCollectionEquality().equals( 103 | deserialized, 104 | serializationBenchmarks.first.deserialized, 105 | )) { 106 | throw StateError( 107 | 'Validation failed for ${benchmark.name}, deserialized does not match.', 108 | ); 109 | } 110 | } 111 | print('\nDeserialized output validated.\n'); 112 | } 113 | } 114 | 115 | extension DoubleExtension on double { 116 | int get toMilliseconds => (this / 1000).round(); 117 | } 118 | -------------------------------------------------------------------------------- /pkgs/dart_model/benchmark/sdk_maps_buffer_wire_benchmark.dart: -------------------------------------------------------------------------------- 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 'dart:typed_data'; 6 | 7 | import 'json_buffer.dart'; 8 | import 'sdk_maps_json_wire_benchmark.dart'; 9 | 10 | /// Benchmark accumulating data into SDK maps then serializing it via 11 | /// [JsonBuffer]. 12 | class SdkMapsBufferWireBenchmark extends SdkMapsJsonWireBenchmark { 13 | @override 14 | Uint8List serialize(Map data) => 15 | JsonBuffer(data).serialize(); 16 | 17 | @override 18 | Map deserialize(Uint8List serialized) => 19 | JsonBuffer.deserialize(serialized).asMap; 20 | } 21 | -------------------------------------------------------------------------------- /pkgs/dart_model/benchmark/sdk_maps_builder_wire_benchmark.dart: -------------------------------------------------------------------------------- 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 'dart:typed_data'; 6 | 7 | import 'package:dart_model/src/json_buffer/json_buffer_builder.dart'; 8 | 9 | import 'sdk_maps_json_wire_benchmark.dart'; 10 | 11 | /// Benchmark accumulating data into SDK maps then serializing it via 12 | /// [JsonBufferBuilder]. 13 | class SdkMapsBuilderWireBenchmark extends SdkMapsJsonWireBenchmark { 14 | @override 15 | Uint8List serialize(Map data) { 16 | final builder = JsonBufferBuilder(); 17 | builder.map.addAll(data); 18 | return builder.serialize(); 19 | } 20 | 21 | @override 22 | Map deserialize(Uint8List serialized) => 23 | JsonBufferBuilder.deserialize(serialized).map; 24 | } 25 | -------------------------------------------------------------------------------- /pkgs/dart_model/benchmark/sdk_maps_json_wire_benchmark.dart: -------------------------------------------------------------------------------- 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 'dart:convert'; 6 | import 'dart:typed_data'; 7 | 8 | import 'serialization_benchmark.dart'; 9 | 10 | /// Benchmark accumulating data into SDK maps then serializing it to JSON. 11 | class SdkMapsJsonWireBenchmark extends SerializationBenchmark { 12 | @override 13 | Uint8List serialize(Map data) => 14 | json.fuse(utf8).encode(data) as Uint8List; 15 | 16 | @override 17 | Map createData() { 18 | return Map.fromIterable( 19 | mapKeys, 20 | value: (key) { 21 | final intKey = int.parse(key as String); 22 | return Interface( 23 | members: 24 | Map.fromIterable( 25 | makeMemberNames(intKey), 26 | value: (k) => _makeMember(k as String), 27 | ).cast(), 28 | properties: Properties( 29 | isAbstract: (intKey & 1) == 1, 30 | isClass: (intKey & 2) == 2, 31 | isGetter: (intKey & 4) == 4, 32 | isField: (intKey & 8) == 8, 33 | isMethod: (intKey & 16) == 16, 34 | isStatic: (intKey & 32) == 32, 35 | ), 36 | ); 37 | }, 38 | ); 39 | } 40 | 41 | Member _makeMember(String key) { 42 | final intKey = key.length; 43 | return Member( 44 | properties: Properties( 45 | isAbstract: (intKey & 1) == 1, 46 | isClass: (intKey & 2) == 2, 47 | isGetter: (intKey & 4) == 4, 48 | isField: const [true, false, null][intKey % 3], 49 | isMethod: (intKey & 16) == 16, 50 | isStatic: (intKey & 32) == 32, 51 | ), 52 | ); 53 | } 54 | 55 | @override 56 | Map deserialize(Uint8List serialized) { 57 | return json.fuse(utf8).decode(serialized) as Map; 58 | } 59 | } 60 | 61 | /// An interface. 62 | extension type Interface.fromJson(Map node) { 63 | Interface({Map? members, Properties? properties}) 64 | : this.fromJson({ 65 | if (members != null) 'members': members, 66 | if (properties != null) 'properties': properties, 67 | }); 68 | 69 | /// Map of members by name. 70 | Map get members => (node['members'] as Map).cast(); 71 | 72 | /// The properties of this interface. 73 | Properties get properties => node['properties'] as Properties; 74 | } 75 | 76 | extension type Member.fromJson(Map node) { 77 | Member({Properties? properties}) 78 | : this.fromJson({if (properties != null) 'properties': properties}); 79 | 80 | /// The properties of this member. 81 | Properties get properties => node['properties'] as Properties; 82 | } 83 | 84 | /// Set of boolean properties. 85 | extension type Properties.fromJson(Map node) { 86 | Properties({ 87 | bool? isAbstract, 88 | bool? isClass, 89 | bool? isGetter, 90 | bool? isField, 91 | bool? isMethod, 92 | bool? isStatic, 93 | }) : this.fromJson({ 94 | if (isAbstract != null) 'isAbstract': isAbstract, 95 | if (isClass != null) 'isClass': isClass, 96 | if (isGetter != null) 'isGetter': isGetter, 97 | if (isField != null) 'isField': isField, 98 | if (isMethod != null) 'isMethod': isMethod, 99 | if (isStatic != null) 'isStatic': isStatic, 100 | }); 101 | 102 | /// Whether the entity is abstract, meaning it has no definition. 103 | bool get isAbstract => node['isAbstract'] as bool; 104 | 105 | /// Whether the entity is a class. 106 | bool get isClass => node['isClass'] as bool; 107 | 108 | /// Whether the entity is a getter. 109 | bool get isGetter => node['isGetter'] as bool; 110 | 111 | /// Whether the entity is a field. 112 | bool get isField => node['isField'] as bool; 113 | 114 | /// Whether the entity is a method. 115 | bool get isMethod => node['isMethod'] as bool; 116 | 117 | /// Whether the entity is static. 118 | bool get isStatic => node['isStatic'] as bool; 119 | } 120 | -------------------------------------------------------------------------------- /pkgs/dart_model/benchmark/serialization_benchmark.dart: -------------------------------------------------------------------------------- 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 'dart:typed_data'; 6 | 7 | import 'package:benchmark_harness/benchmark_harness.dart'; 8 | 9 | import 'lazy_wrappers_buffer_wire_benchmark.dart' as wrapped; 10 | 11 | const mapSize = 10000; 12 | final mapKeys = List.generate(mapSize, (i) => i.toString()); 13 | 14 | /// Benchmark serializing `dart_model` data. 15 | abstract class SerializationBenchmark extends BenchmarkBase { 16 | late BenchmarkStage stage; 17 | 18 | /// The result of [createData]; passed to [serialize] later. 19 | late Map _data; 20 | 21 | /// The result of [serialize]; used to report the size and passed to 22 | /// [deserialize] later. 23 | late Uint8List _serialized; 24 | 25 | /// The result of [deserialize]; used to check correctness and passed to 26 | /// [deepHash] later. 27 | late Map deserialized; 28 | 29 | /// The result of [deepHash]; result should be identical from all 30 | /// implementations. 31 | late int hashResult; 32 | 33 | SerializationBenchmark() : super(''); 34 | 35 | /// The length of the serialized bytes, only valid to call after running 36 | /// [BenchmarkStage.serialize]. 37 | int get bytes => _serialized.lengthInBytes; 38 | 39 | @override 40 | String get name => '$runtimeType-${stage.name}'; 41 | 42 | @override 43 | void run() { 44 | switch (stage) { 45 | case BenchmarkStage.create: 46 | _data = createData(); 47 | case BenchmarkStage.serialize: 48 | _serialized = serialize(_data); 49 | case BenchmarkStage.deserialize: 50 | deserialized = deserialize(_serialized); 51 | case BenchmarkStage.process: 52 | hashResult = deepHash(deserialized); 53 | } 54 | } 55 | 56 | /// Used to measure [BenchmarkStage.create], sets [_data] to the result. 57 | Map createData(); 58 | 59 | /// Used to measure [BenchmarkStage.serialize], called with [data], sets 60 | /// [_serialized] to the result. 61 | Uint8List serialize(Map data); 62 | 63 | /// Used to measure [BenchmarkStage.deserialize], called with 64 | /// [_serialized], sets [deserialized] to the result. 65 | Map deserialize(Uint8List data); 66 | 67 | /// Used to measure [BenchmarkStage.process], called with 68 | /// [_serialized], sets [deserialized] to the result. 69 | /// 70 | /// Default implementation works only for JSON style maps. 71 | int deepHash(Map deserialized) { 72 | var result = 0; 73 | for (final entry in deserialized.entries) { 74 | result ^= entry.key.hashCode; 75 | final value = entry.value; 76 | result ^= switch (value) { 77 | Map() => deepHash(value), 78 | Hashable() => value.deepHash, 79 | wrapped.Serializable() => deepHash(value.toJson()), 80 | String() || int() || bool() => value.hashCode, 81 | _ => 82 | throw StateError( 83 | 'Unrecognized JSON value $value, ' 84 | 'custom hash function needed?', 85 | ), 86 | }; 87 | } 88 | return result; 89 | } 90 | 91 | List makeMemberNames(int key) { 92 | final length = key % 10; 93 | return List.generate( 94 | // "key % 2999" so some member names are reused. 95 | length, 96 | (i) => 'interface${key % 2999}member$i', 97 | ); 98 | } 99 | } 100 | 101 | enum BenchmarkStage { create, serialize, deserialize, process } 102 | 103 | /// Interface for computing a hash, when the underlying object isn't a Map. 104 | abstract interface class Hashable { 105 | int get deepHash; 106 | } 107 | -------------------------------------------------------------------------------- /pkgs/dart_model/lib/dart_model.dart: -------------------------------------------------------------------------------- 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 | export 'src/dart_model.dart'; 6 | export 'src/lazy_merged_map.dart' show MergeModels; 7 | export 'src/scopes.dart'; 8 | export 'src/type.dart'; 9 | export 'src/type_system.dart'; 10 | -------------------------------------------------------------------------------- /pkgs/dart_model/lib/serialization.dart: -------------------------------------------------------------------------------- 1 | import 'dart:typed_data'; 2 | 3 | import 'src/json_buffer/json_buffer_builder.dart'; 4 | import 'src/scopes.dart'; 5 | 6 | extension SerializeExtension on Map { 7 | Uint8List serializeToBinary() => Scope.serializeToBinary(this); 8 | } 9 | 10 | extension DeserializeExtension on Uint8List { 11 | Map deserializeFromBinary() => 12 | JsonBufferBuilder.deserialize(this).map; 13 | } 14 | -------------------------------------------------------------------------------- /pkgs/dart_model/lib/src/deep_cast_map.dart: -------------------------------------------------------------------------------- 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 'dart:collection'; 6 | 7 | extension DeepCastMap on Map { 8 | /// A lazy deep cast map for `this`, where the values are deeply cast using 9 | /// the provided [castValue] function, and they keys are normally cast. 10 | Map deepCast(V Function(SV) castValue) => 11 | _DeepCastMap(this, castValue); 12 | } 13 | 14 | /// Like a `CastMap`, except it can perform deep casts on values using a 15 | /// provided conversion function. 16 | class _DeepCastMap extends MapBase { 17 | final Map _source; 18 | 19 | final V Function(SV) _castValue; 20 | 21 | _DeepCastMap(this._source, this._castValue); 22 | 23 | @override 24 | bool containsValue(Object? value) => _source.containsValue(value); 25 | 26 | @override 27 | bool containsKey(Object? key) => _source.containsKey(key); 28 | 29 | @override 30 | V? operator [](Object? key) { 31 | final value = _source[key]; 32 | if (value == null) return null; 33 | return _castValue(value); 34 | } 35 | 36 | @override 37 | void operator []=(K key, V value) { 38 | _source[key as SK] = value as SV; 39 | } 40 | 41 | @override 42 | V putIfAbsent(K key, V Function() ifAbsent) => 43 | _castValue(_source.putIfAbsent(key as SK, () => ifAbsent() as SV)); 44 | 45 | @override 46 | V? remove(Object? key) { 47 | final removed = _source.remove(key); 48 | if (removed == null) return null; 49 | return _castValue(removed); 50 | } 51 | 52 | @override 53 | void clear() { 54 | _source.clear(); 55 | } 56 | 57 | @override 58 | void forEach(void Function(K key, V value) f) { 59 | _source.forEach((SK key, SV value) { 60 | f(key as K, _castValue(value)); 61 | }); 62 | } 63 | 64 | @override 65 | Iterable get keys => _source.keys.cast(); 66 | 67 | @override 68 | Iterable get values => _source.values.map(_castValue); 69 | 70 | @override 71 | int get length => _source.length; 72 | 73 | @override 74 | bool get isEmpty => _source.isEmpty; 75 | 76 | @override 77 | bool get isNotEmpty => _source.isNotEmpty; 78 | 79 | @override 80 | V update(K key, V Function(V value) update, {V Function()? ifAbsent}) { 81 | return _castValue( 82 | _source.update( 83 | key as SK, 84 | (SV value) => update(_castValue(value)) as SV, 85 | ifAbsent: (ifAbsent == null) ? null : () => ifAbsent() as SV, 86 | ), 87 | ); 88 | } 89 | 90 | @override 91 | void updateAll(V Function(K key, V value) update) { 92 | _source.updateAll( 93 | (SK key, SV value) => update(key as K, _castValue(value)) as SV, 94 | ); 95 | } 96 | 97 | @override 98 | Iterable> get entries { 99 | return _source.entries.map>( 100 | (MapEntry e) => MapEntry(e.key as K, _castValue(e.value)), 101 | ); 102 | } 103 | 104 | @override 105 | void addEntries(Iterable> entries) { 106 | for (var entry in entries) { 107 | _source[entry.key as SK] = entry.value as SV; 108 | } 109 | } 110 | 111 | @override 112 | void removeWhere(bool Function(K key, V value) test) { 113 | _source.removeWhere( 114 | (SK key, SV value) => test(key as K, _castValue(value)), 115 | ); 116 | } 117 | } 118 | -------------------------------------------------------------------------------- /pkgs/dart_model/lib/src/json_buffer/closed_list.dart: -------------------------------------------------------------------------------- 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 | part of 'json_buffer_builder.dart'; 6 | 7 | /// Methods for writing and reading "closed lists". 8 | /// 9 | /// A "closed list" is a `List` in a byte buffer that has all values 10 | /// known at the time of writing. 11 | extension ClosedLists on JsonBufferBuilder { 12 | // Layout: [length, ...entries] 13 | // 14 | // Each entry is: [key (pointer to string), value type, value]. 15 | // 16 | // Values are stored with `_writeAny`. 17 | static const _valueSize = _typeSize + _pointerSize; 18 | 19 | /// Adds a `List` to the buffer, returns the [_Pointer] to it. 20 | /// 21 | /// The `List` should be small and already evaluated, making it fast to 22 | /// iterate. 23 | _Pointer _addClosedList(List list) { 24 | _explanations?.push('addClosedList $list'); 25 | 26 | final length = list.length; 27 | final pointer = _reserve(_lengthSize + length * _valueSize); 28 | 29 | _writeLength(pointer, length); 30 | 31 | var entryPointer = pointer + _lengthSize; 32 | for (final value in list) { 33 | _writeAny(entryPointer, value); 34 | entryPointer += _valueSize; 35 | } 36 | 37 | _explanations?.pop(); 38 | return pointer; 39 | } 40 | 41 | /// Returns the [_ClosedList] at [pointer]. 42 | List _readClosedList(_Pointer pointer) { 43 | return _ClosedList(this, pointer); 44 | } 45 | } 46 | 47 | class _ClosedList with ListMixin { 48 | final JsonBufferBuilder _buffer; 49 | final _Pointer _pointer; 50 | @override 51 | final int length; 52 | 53 | _ClosedList(this._buffer, this._pointer) 54 | : length = _buffer._readLength(_pointer); 55 | 56 | @override 57 | Object? operator [](int index) { 58 | RangeError.checkValidIndex(index, this); 59 | final pointer = _pointer + _lengthSize + index * ClosedLists._valueSize; 60 | return _buffer._readAny(pointer); 61 | } 62 | 63 | @override 64 | void operator []=(int index, Object? value) { 65 | throw UnsupportedError('This JsonBufferBuilder list is read-only.'); 66 | } 67 | 68 | @override 69 | void add(Object? key) { 70 | throw UnsupportedError('This JsonBufferBuilder list is read-only.'); 71 | } 72 | 73 | @override 74 | set length(int length) { 75 | throw UnsupportedError('This JsonBufferBuilder list is read-only.'); 76 | } 77 | 78 | void buildDigest(ByteConversionSink byteSink) { 79 | var iterator = _ClosedListPointerIterator(_buffer, _pointer, length); 80 | while (iterator.moveNext()) { 81 | _buffer._buildDigest(iterator.current, byteSink); 82 | } 83 | } 84 | } 85 | 86 | /// `Iterator` that reads a "closed list" in a [JsonBufferBuilder]. 87 | class _ClosedListIterator implements Iterator { 88 | final JsonBufferBuilder _buffer; 89 | final _Pointer _last; 90 | _Pointer _pointer; 91 | 92 | _ClosedListIterator(this._buffer, _Pointer pointer, int length) 93 | : _last = pointer + _lengthSize + length * ClosedLists._valueSize, 94 | // Subtract because `moveNext` is called before reading. 95 | _pointer = pointer + _lengthSize - ClosedLists._valueSize; 96 | 97 | @override 98 | T get current => _buffer._readAny(_pointer) as T; 99 | 100 | @override 101 | bool moveNext() { 102 | if (_pointer == _last) return false; 103 | if (_pointer > _last) throw StateError('Moved past _last!'); 104 | _pointer += ClosedLists._valueSize; 105 | return _pointer != _last; 106 | } 107 | } 108 | 109 | class _ClosedListPointerIterator extends _ClosedListIterator<_Pointer> { 110 | _ClosedListPointerIterator(super.buffer, super.pointer, super.length); 111 | 112 | @override 113 | _Pointer get current => _pointer; 114 | } 115 | -------------------------------------------------------------------------------- /pkgs/dart_model/lib/src/json_buffer/explanations.dart: -------------------------------------------------------------------------------- 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 | part of 'json_buffer_builder.dart'; 6 | 7 | /// Tracks why each byte in a [JsonBufferBuilder] was written, and does 8 | /// additional checks. 9 | class _Explanations { 10 | final Map _explanationsByPointer = {}; 11 | final List _explanationsStack = []; 12 | 13 | static const _allowOverwriteTag = ' [allowOverwrite]'; 14 | 15 | /// Log what is being written. 16 | /// 17 | /// At the start of a method writing to the buffer, "push" details of the 18 | /// write. Then, call "explain" on every write to the buffer. The details 19 | /// will be logged. Finally, call "pop" before returning. 20 | void push(String explanation) { 21 | if (explanation.length > 40) { 22 | explanation = '${explanation.substring(0, 37)}...'; 23 | } 24 | _explanationsStack.add(explanation); 25 | } 26 | 27 | /// Call at the end of a write method to "pop" from the explanations stack. 28 | void pop() { 29 | _explanationsStack.removeLast(); 30 | } 31 | 32 | /// Associate the current explanation stack with [pointer]. 33 | /// 34 | /// Set [allowOverwrite] if multiple writes to this location are allowed. 35 | /// Otherwise, this method throws on multiple writes. 36 | void explain(_Pointer pointer, {bool allowOverwrite = false}) { 37 | if (_explanationsStack.isNotEmpty) { 38 | final explanation = 39 | _explanationsStack.join(', ') + 40 | (allowOverwrite ? _allowOverwriteTag : ''); 41 | final oldExplanation = _explanationsByPointer[pointer]; 42 | if (oldExplanation != null) { 43 | if (!allowOverwrite || !oldExplanation.contains(_allowOverwriteTag)) { 44 | throw StateError( 45 | 'Second write to $pointer!\n' 46 | ' Old explanation: ${_explanationsByPointer[pointer]}\n' 47 | ' New explanation: $explanation', 48 | ); 49 | } 50 | } 51 | _explanationsByPointer[pointer] = explanation; 52 | } 53 | } 54 | 55 | /// Associate the current explanation stack with all bytes [from] until [to]. 56 | void explainRange(_Pointer from, _Pointer to) { 57 | for (var pointer = from; pointer != to; ++pointer) { 58 | explain(pointer); 59 | } 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /pkgs/dart_model/lib/src/json_buffer/iterables.dart: -------------------------------------------------------------------------------- 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 | part of 'json_buffer_builder.dart'; 6 | 7 | /// More efficient implementations than `MapMixin` for maps with efficient 8 | /// `entries` and `length`. 9 | mixin _EntryMapMixin on Map { 10 | // `MapMixin` iterates keys then looks up each value. 11 | // 12 | // Instead, iterate `entries`. 13 | @override 14 | void forEach(void Function(K key, V value) action) { 15 | for (final entry in entries) { 16 | action(entry.key, entry.value); 17 | } 18 | } 19 | 20 | // `MapMixin` uses `keys.isEmpty`. 21 | @override 22 | bool get isEmpty => length == 0; 23 | 24 | // `MapMixin` uses `keys.isNotEmpty`. 25 | @override 26 | bool get isNotEmpty => length != 0; 27 | } 28 | 29 | /// An [Iterable] that uses the supplied function to create an [Iterator]. 30 | /// 31 | /// [length] is also passed in, as the default implementation is very slow. 32 | class _IteratorFunctionIterable extends Iterable { 33 | final Iterator Function() _function; 34 | @override 35 | final int length; 36 | 37 | _IteratorFunctionIterable(this._function, {required this.length}); 38 | 39 | @override 40 | Iterator get iterator => _function(); 41 | } 42 | 43 | /// A `Map` in a `JsonBufferBuilder`. 44 | abstract interface class MapInBuffer { 45 | /// The buffer backing this `Map`. 46 | JsonBufferBuilder get buffer; 47 | 48 | /// The `Map` that contains this value, or `null` if this value has not been 49 | /// added to a `Map` or is itself the root `Map`. 50 | Map? get parent; 51 | 52 | /// A pointer to the start of this object in [buffer]. 53 | int get pointer; 54 | } 55 | -------------------------------------------------------------------------------- /pkgs/dart_model/lib/src/json_buffer/type.dart: -------------------------------------------------------------------------------- 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 | part of 'json_buffer_builder.dart'; 6 | 7 | /// Bytes to store a `Type`. 8 | const int _typeSize = 1; 9 | 10 | /// Bytes to stare a `Pointer`. 11 | const int _pointerSize = 4; 12 | 13 | /// Bytes to store the length of a collection. 14 | const int _lengthSize = 4; 15 | 16 | /// Initial size of the buffer. 17 | /// 18 | /// TODO(davidmorgan): this matters for performance; optimize. 19 | const int _initialBufferSize = 32; 20 | 21 | /// A pointer into the buffer. 22 | typedef _Pointer = int; 23 | 24 | /// The type of a value in the buffer. 25 | enum Type { 26 | nil(false), 27 | type(false), 28 | pointer(true), 29 | uint32(false), 30 | float64(false), 31 | boolean(false), 32 | anyPointer(false), // This is actually a type followed by a pointer. 33 | stringPointer(true), 34 | closedListPointer(true), 35 | closedMapPointer(true), 36 | growableMapPointer(true), 37 | typedMapPointer(true); 38 | 39 | /// Whether this object is always stored as a raw pointer. 40 | final bool isPointer; 41 | 42 | const Type(this.isPointer); 43 | 44 | /// Returns the [Type] of [value], or throws if it is not a supported type. 45 | /// 46 | /// [builder] is used to check maps to see if they are already in the current 47 | /// `JsonBufferBuilder` or need to be copied. 48 | static Type _of(Object? value, JsonBufferBuilder builder) { 49 | switch (value) { 50 | case Null(): 51 | return Type.nil; 52 | case String(): 53 | return Type.stringPointer; 54 | case int(): 55 | return Type.uint32; 56 | case double(): 57 | return Type.float64; 58 | case bool(): 59 | return Type.boolean; 60 | case Type(): 61 | return Type.type; 62 | case List(): 63 | return Type.closedListPointer; 64 | case _TypedMap(): 65 | return builder == value.buffer 66 | ? Type.typedMapPointer 67 | : Type.closedMapPointer; 68 | case _GrowableMap(): 69 | return builder == value.buffer 70 | ? Type.growableMapPointer 71 | : Type.closedMapPointer; 72 | case Map(): 73 | return Type.closedMapPointer; 74 | } 75 | throw UnsupportedError( 76 | 'Unsupported type: ${value.runtimeType}, value: $value', 77 | ); 78 | } 79 | 80 | /// The size in bytes of value of this type. 81 | int get _sizeInBytes { 82 | switch (this) { 83 | case nil: 84 | return 0; 85 | case type: 86 | case boolean: 87 | return 1; 88 | case uint32: 89 | case pointer: 90 | case closedListPointer: 91 | case stringPointer: 92 | case closedMapPointer: 93 | case growableMapPointer: 94 | case typedMapPointer: 95 | return 4; 96 | case anyPointer: 97 | return 5; 98 | case float64: 99 | return 8; 100 | } 101 | } 102 | } 103 | -------------------------------------------------------------------------------- /pkgs/dart_model/lib/src/lazy_merged_map.dart: -------------------------------------------------------------------------------- 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 'dart:collection'; 6 | 7 | import 'dart_model.dart'; 8 | 9 | /// An implementation of a lazy merged json [Map] view over two [Map]s. 10 | /// 11 | /// The intended use case is for merging JSON payloads together into a single 12 | /// payload, where their structure is the same. 13 | /// 14 | /// If both maps have the same key present, the logic for the values of those 15 | /// shared keys goes as follows: 16 | /// 17 | /// - If both values are `Map`, a nested [LazyMergedMapView] 18 | /// is returned. 19 | /// - Else if they are equal values, the value from [left] is returned. 20 | /// - Else a [StateError] is thrown. 21 | /// 22 | /// Nested [List]s are not specifically handled at this time and must be equal. 23 | /// 24 | /// The [keys] getter will de-duplicate the keys. 25 | class LazyMergedMapView extends MapBase { 26 | final Map left; 27 | final Map right; 28 | 29 | LazyMergedMapView(this.left, this.right); 30 | 31 | @override 32 | Object? operator [](Object? key) { 33 | // TODO: Can we do better? These lookups can each be linear for buffer maps. 34 | var leftValue = left[key]; 35 | var rightValue = right[key]; 36 | if (leftValue != null) { 37 | if (rightValue != null) { 38 | if (leftValue is Map && 39 | rightValue is Map) { 40 | return LazyMergedMapView(leftValue, rightValue); 41 | } 42 | if (leftValue is List && rightValue is List) { 43 | if (leftValue.length != rightValue.length) { 44 | throw StateError( 45 | 'Cannot merge lists of different lengths, ' 46 | 'got $leftValue and $rightValue', 47 | ); 48 | } 49 | // TODO: Something better for lists, it isn't clear how to merge them. 50 | return leftValue; 51 | } else if (leftValue != rightValue) { 52 | throw StateError( 53 | 'Cannot merge maps with different values, and ' 54 | '$leftValue != $rightValue', 55 | ); 56 | } 57 | return leftValue; 58 | } 59 | return leftValue; 60 | } else if (rightValue != null) { 61 | return rightValue; 62 | } 63 | return null; 64 | } 65 | 66 | @override 67 | void operator []=(String key, Object? value) => 68 | throw UnsupportedError('Merged maps are read only'); 69 | 70 | @override 71 | bool operator ==(Object other) => 72 | other is LazyMergedMapView && other.left == left && other.right == right; 73 | 74 | @override 75 | int get hashCode => Object.hash(left, right); 76 | 77 | @override 78 | void clear() => throw UnsupportedError('Merged maps are read only'); 79 | 80 | @override 81 | Iterable get keys sync* { 82 | var seen = {}; 83 | for (var key in left.keys.followedBy(right.keys)) { 84 | if (seen.add(key)) yield key; 85 | } 86 | } 87 | 88 | @override 89 | Object? remove(Object? key) => 90 | throw UnsupportedError('Merged maps are read only'); 91 | } 92 | 93 | extension AllMaps on Map { 94 | /// All the maps merged into this map, recursively expanded. 95 | Iterable> get expand sync* { 96 | if (this case final LazyMergedMapView self) { 97 | yield* self.left.expand; 98 | yield* self.right.expand; 99 | } else { 100 | yield this; 101 | } 102 | } 103 | 104 | /// All the maps merged into this map, recursively expanded, including the 105 | /// merged map objects themselves. 106 | Iterable> get expandWithMerged sync* { 107 | if (this case final LazyMergedMapView self) { 108 | yield self; 109 | yield* self.left.expand; 110 | yield* self.right.expand; 111 | } else { 112 | yield this; 113 | } 114 | } 115 | } 116 | 117 | extension MergeModels on Model { 118 | /// Creates a lazy merged view of `this` with [other]. 119 | Model mergeWith(Model other) => 120 | Model.fromJson(LazyMergedMapView(node, other.node)); 121 | } 122 | -------------------------------------------------------------------------------- /pkgs/dart_model/mono_pkg.yaml: -------------------------------------------------------------------------------- 1 | sdk: 2 | - pubspec 3 | - dev 4 | 5 | stages: 6 | - analyze_and_format: 7 | - analyze: --fatal-infos . 8 | - format: 9 | sdk: 10 | - dev 11 | - unit_test: 12 | - command: dart -Ddebug_json_buffer=true test --test-randomize-ordering-seed=random -c source 13 | os: 14 | - linux 15 | - windows 16 | -------------------------------------------------------------------------------- /pkgs/dart_model/pubspec.yaml: -------------------------------------------------------------------------------- 1 | name: dart_model 2 | version: 0.0.1-wip 3 | description: >- 4 | Data model for information about Dart code, queries about Dart code and 5 | augmentations to Dart code. Serializable with a versioned JSON schema for 6 | use by macros, generators and other tools. 7 | repository: https://github.com/dart-lang/macros/tree/master/pkgs/dart_model 8 | resolution: workspace 9 | 10 | environment: 11 | sdk: ^3.7.0-157.0.dev 12 | 13 | dependencies: 14 | collection: ^1.19.0 15 | convert: ^3.1.2 16 | crypto: ^3.0.6 17 | 18 | dev_dependencies: 19 | benchmark_harness: ^2.2.2 20 | dart_flutter_team_lints: ^3.0.0 21 | test: ^1.25.0 22 | -------------------------------------------------------------------------------- /pkgs/dart_model/test/deep_cast_test.dart: -------------------------------------------------------------------------------- 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/src/deep_cast_map.dart'; 6 | import 'package:test/test.dart'; 7 | 8 | void main() { 9 | test('can perform deep casts on maps', () { 10 | final initial = { 11 | 'x': [1, 2, 3], 12 | }; 13 | expect(initial, isNot(isA>>())); 14 | final typed = initial.deepCast>( 15 | (v) => (v as List).cast(), 16 | ); 17 | expect(typed, isA>>()); 18 | expect(typed['x']!, isA>()); 19 | expect(typed['x']!, [1, 2, 3]); 20 | }); 21 | 22 | test('can perform really deep casts on maps', () { 23 | final initial = { 24 | 'x': { 25 | 'y': [1, 2, 3], 26 | }, 27 | }; 28 | expect(initial, isNot(isA>>>())); 29 | 30 | final typed = initial.deepCast>>( 31 | (v) => (v as Map).deepCast>((v) => (v as List).cast()), 32 | ); 33 | expect(typed, isA>>>()); 34 | 35 | expect(initial['x'], isNot(isA>>())); 36 | final x = typed['x']!; 37 | expect(x, isA>>()); 38 | 39 | expect((initial['x'] as Map)['y'], isNot(isA>())); 40 | final y = x['y']!; 41 | expect(y, isA>()); 42 | expect(y, [1, 2, 3]); 43 | }); 44 | } 45 | -------------------------------------------------------------------------------- /pkgs/dart_model/test/json_buffer/closed_list_test.dart: -------------------------------------------------------------------------------- 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/src/json_buffer/json_buffer_builder.dart'; 6 | import 'package:test/test.dart'; 7 | 8 | void main() { 9 | group('ClosedList', () { 10 | late JsonBufferBuilder builder; 11 | 12 | setUp(() { 13 | builder = JsonBufferBuilder(); 14 | }); 15 | 16 | tearDown(() { 17 | printOnFailure('Try: dart -Ddebug_json_buffer=true test -c source'); 18 | printOnFailure(builder.toString()); 19 | }); 20 | 21 | test('simple write and read', () { 22 | final value = ['a', 1, 'b', 2]; 23 | builder.map['value'] = value; 24 | 25 | final deserializedValue = builder.map['value'] as List; 26 | expect(deserializedValue.runtimeType.toString(), '_ClosedList'); 27 | expect(deserializedValue, value); 28 | }); 29 | 30 | test('write and read with nested maps and lists', () { 31 | final value = [ 32 | 'a', 33 | null, 34 | 'bb', 35 | [], 36 | {}, 37 | [ 38 | 'ccc', 39 | {'a': 3}, 40 | ], 41 | ]; 42 | builder.map['value'] = value; 43 | final deserializedValue = builder.map['value'] as List; 44 | expect(deserializedValue.runtimeType.toString(), '_ClosedList'); 45 | expect(deserializedValue, value); 46 | }); 47 | }); 48 | } 49 | -------------------------------------------------------------------------------- /pkgs/dart_model/test/json_buffer/closed_map_test.dart: -------------------------------------------------------------------------------- 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/src/json_buffer/json_buffer_builder.dart'; 6 | import 'package:test/test.dart'; 7 | 8 | import 'testing.dart'; 9 | 10 | void main() { 11 | group('ClosedMap', () { 12 | late JsonBufferBuilder builder; 13 | 14 | setUp(() { 15 | builder = JsonBufferBuilder(); 16 | }); 17 | 18 | tearDown(() { 19 | printOnFailure('Try: dart -Ddebug_json_buffer=true test -c source'); 20 | printOnFailure(builder.toString()); 21 | }); 22 | 23 | test('equality and hashing is by identity', () { 24 | builder.map['a'] = {}; 25 | final closedMap1 = builder.map['a'] as Map; 26 | builder.map['b'] = {}; 27 | final closedMap2 = builder.map['b'] as Map; 28 | 29 | // Different maps with the same contents are not equal, have different hash codes. 30 | // Can't use default `expect` equality check as it special-cases `Map`. 31 | expect(closedMap1 == closedMap2, false); 32 | expect(closedMap1.hashCode, isNot(closedMap2.hashCode)); 33 | 34 | // Map is equal to a reference to itself, has same hash code. 35 | builder.map['a'] = closedMap1; 36 | final closedMap1Reference = builder.map['a'] as Map; 37 | expect(closedMap1 == closedMap1Reference, true); 38 | expect(closedMap1.hashCode, closedMap1Reference.hashCode); 39 | }); 40 | 41 | test('simple write and read', () { 42 | final value = {'a': 1, 'b': 2}; 43 | builder.map['value'] = value; 44 | 45 | final deserializedValue = builder.map['value'] as Map; 46 | expect(deserializedValue.runtimeType.toString(), '_ClosedMap'); 47 | expectFullyEquivalentMaps(deserializedValue, value); 48 | }); 49 | 50 | test('write and read with nested lists and maps', () { 51 | final value = { 52 | 'a': null, 53 | 'a0': [], 54 | 'a00': {}, 55 | 'bb': { 56 | 'ccc': {'a': 3}, 57 | }, 58 | 'cc': [ 59 | {'a': 'b'}, 60 | ['a', 'b'], 61 | ], 62 | }; 63 | builder.map['value'] = value; 64 | final deserializedValue = builder.map['value'] as Map; 65 | expect(deserializedValue.runtimeType.toString(), '_ClosedMap'); 66 | expectFullyEquivalentMaps(deserializedValue, value); 67 | }); 68 | 69 | test('typed maps created in the same buffer are stored as typed maps', () { 70 | final typedMap = builder.createTypedMap(TypedMapSchema({})); 71 | final value = {'a': typedMap}; 72 | builder.map['value'] = value; 73 | final deserializedValue = builder.map['value'] as Map; 74 | expect(deserializedValue['a'].runtimeType.toString(), '_TypedMap'); 75 | }); 76 | 77 | test( 78 | 'typed maps created in a different buffer are copied to closed maps', 79 | () { 80 | final typedMap = JsonBufferBuilder().createTypedMap(TypedMapSchema({})); 81 | final value = {'a': typedMap}; 82 | builder.map['value'] = value; 83 | final deserializedValue = builder.map['value'] as Map; 84 | expect(deserializedValue['a'].runtimeType.toString(), '_ClosedMap'); 85 | }, 86 | ); 87 | 88 | test('growable maps created in the same buffer are stored as growable ' 89 | 'maps', () { 90 | final growableMap = builder.createGrowableMap(); 91 | final value = {'a': growableMap}; 92 | builder.map['value'] = value; 93 | final deserializedValue = builder.map['value'] as Map; 94 | expect( 95 | deserializedValue['a'].runtimeType.toString(), 96 | '_GrowableMap', 97 | ); 98 | }); 99 | 100 | test('growable maps created in a different buffer are copied', () { 101 | final growableMap = JsonBufferBuilder().createGrowableMap(); 102 | final value = {'a': growableMap}; 103 | builder.map['value'] = value; 104 | final deserializedValue = builder.map['value'] as Map; 105 | expect(deserializedValue['a'].runtimeType.toString(), '_ClosedMap'); 106 | }); 107 | }); 108 | } 109 | -------------------------------------------------------------------------------- /pkgs/dart_model/test/json_buffer/growable_map_test.dart: -------------------------------------------------------------------------------- 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/src/json_buffer/json_buffer_builder.dart'; 6 | import 'package:test/test.dart'; 7 | 8 | import 'testing.dart'; 9 | 10 | void main() { 11 | group('GrowableMap', () { 12 | late JsonBufferBuilder builder; 13 | 14 | setUp(() { 15 | builder = JsonBufferBuilder(); 16 | }); 17 | 18 | tearDown(() { 19 | printOnFailure('Try: dart -Ddebug_json_buffer=true test -c source'); 20 | printOnFailure(builder.toString()); 21 | }); 22 | 23 | test('equality and hashing is by identity', () { 24 | final growableMap1 = builder.createGrowableMap(); 25 | final growableMap2 = builder.createGrowableMap(); 26 | 27 | // Different maps with the same contents are not equal, have different hash codes. 28 | // Can't use default `expect` equality check as it special-cases `Map`. 29 | expect(growableMap1 == growableMap2, false); 30 | expect(growableMap1.hashCode, isNot(growableMap2.hashCode)); 31 | 32 | // Map is equal to a reference to itself, has same hash code. 33 | builder.map['a'] = growableMap1; 34 | final growableMap1Reference = builder.map['a'] as Map; 35 | expect(growableMap1 == growableMap1Reference, true); 36 | expect(growableMap1.hashCode, growableMap1Reference.hashCode); 37 | }); 38 | 39 | test('can be written and read if empty', () { 40 | final value = builder.createGrowableMap(); 41 | builder.map['value'] = value; 42 | 43 | final deserializedValue = builder.map['value'] as Map; 44 | expect(deserializedValue.runtimeType.toString(), '_GrowableMap'); 45 | expectFullyEquivalentMaps(deserializedValue, value); 46 | }); 47 | 48 | test('can be written and read with one value', () { 49 | final value = builder.createGrowableMap(); 50 | value['a'] = 1; 51 | builder.map['value'] = value; 52 | 53 | final deserializedValue = builder.map['value'] as Map; 54 | expect(deserializedValue.runtimeType.toString(), '_GrowableMap'); 55 | expectFullyEquivalentMaps(deserializedValue, value); 56 | }); 57 | 58 | test('can be written and read', () { 59 | final value = builder.createGrowableMap(); 60 | value['a'] = 1; 61 | value['b'] = 2; 62 | builder.map['value'] = value; 63 | 64 | final deserializedValue = builder.map['value'] as Map; 65 | expect(deserializedValue.runtimeType.toString(), '_GrowableMap'); 66 | expectFullyEquivalentMaps(deserializedValue, value); 67 | }); 68 | 69 | test('can be written and read with nested maps', () { 70 | final map1 = builder.createGrowableMap(); 71 | final map2 = builder.createGrowableMap(); 72 | map2['A'] = 1; 73 | map1['a'] = 1; 74 | map1['b'] = 2; 75 | map1['c'] = map2; 76 | // Add to map2 after it was added to map1, checks that what is added 77 | // is a reference not a copy. 78 | map2['B'] = 2; 79 | map2['C'] = {'x': 1, 'y': 2}; 80 | map2['D'] = [1, 2, 3]; 81 | map2['E'] = []; 82 | map2['F'] = {}; 83 | 84 | builder.map['value'] = map1; 85 | 86 | final deserializedValue = builder.map['value'] as Map; 87 | expect(deserializedValue.runtimeType.toString(), '_GrowableMap'); 88 | expectFullyEquivalentMaps(deserializedValue, { 89 | 'a': 1, 90 | 'b': 2, 91 | 'c': { 92 | 'A': 1, 93 | 'B': 2, 94 | 'C': {'x': 1, 'y': 2}, 95 | 'D': [1, 2, 3], 96 | 'E': [], 97 | 'F': {}, 98 | }, 99 | }); 100 | }); 101 | }); 102 | } 103 | -------------------------------------------------------------------------------- /pkgs/dart_model/test/json_buffer/testing.dart: -------------------------------------------------------------------------------- 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:test/test.dart'; 6 | 7 | /// Expects that [map] and [expected] are equal. 8 | /// 9 | /// Does not assume that `map` correctly implements `Map`, instead compares via 10 | /// all methods. 11 | /// 12 | /// Does additional checks on `map`. 13 | void expectFullyEquivalentMaps( 14 | Map map, 15 | Map expected, 16 | ) { 17 | expect(map.entries.map((e) => e.key), expected.entries.map((e) => e.key)); 18 | expect(map.entries.map((e) => e.value), expected.entries.map((e) => e.value)); 19 | expect(map.isEmpty, expected.isEmpty); 20 | expect(map.isNotEmpty, expected.isNotEmpty); 21 | expect(map.keys, expected.keys); 22 | expect(map.length, expected.length); 23 | expect(map.values, expected.values); 24 | 25 | for (final key in map.keys) { 26 | expect(map.containsKey(key), isTrue); 27 | } 28 | expect(map.containsKey(Object()), isFalse); 29 | 30 | for (final value in map.values) { 31 | if (value is Map || value is Iterable) { 32 | // Collections do not implement deep `operator==`, so this is expected to 33 | // fail. 34 | continue; 35 | } 36 | expect(map.containsValue(value), isTrue, reason: value.toString()); 37 | } 38 | expect(map.containsValue(Object()), isFalse); 39 | } 40 | -------------------------------------------------------------------------------- /pkgs/dart_model/test/lazy_merged_map_test.dart: -------------------------------------------------------------------------------- 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/dart_model.dart'; 6 | import 'package:dart_model/src/lazy_merged_map.dart'; 7 | import 'package:test/test.dart' hide test; 8 | import 'package:test/test.dart' as t show test; 9 | 10 | void main() { 11 | test('Can merge models with different libraries', () async { 12 | final libA = Library(); 13 | final libB = Library(); 14 | final a = Model()..uris['package:a/a.dart'] = libA; 15 | final b = Model()..uris['package:b/b.dart'] = libB; 16 | final c = a.mergeWith(b); 17 | expect(c.uris['package:a/a.dart'], libA); 18 | expect(c.uris['package:b/b.dart'], libB); 19 | }); 20 | 21 | test( 22 | 'Can merge models with different scopes from the same library', 23 | () async { 24 | final interfaceA = Interface(); 25 | final interfaceB = Interface(); 26 | final a = 27 | Model() 28 | ..uris['package:a/a.dart'] = (Library()..scopes['A'] = interfaceA); 29 | final b = 30 | Model() 31 | ..uris['package:a/a.dart'] = (Library()..scopes['B'] = interfaceB); 32 | final c = a.mergeWith(b); 33 | expect(c.uris['package:a/a.dart'], isA()); 34 | expect(c.uris['package:a/a.dart']!.scopes['A'], interfaceA); 35 | expect(c.uris['package:a/a.dart']!.scopes['B'], interfaceB); 36 | }, 37 | ); 38 | 39 | test( 40 | 'Can merge models with the same interface but different properties', 41 | () async { 42 | final interfaceA1 = Interface(properties: Properties(isClass: true)); 43 | final interfaceA2 = Interface(properties: Properties(isAbstract: true)); 44 | final a = 45 | Model() 46 | ..uris['package:a/a.dart'] = (Library()..scopes['A'] = interfaceA1); 47 | final b = 48 | Model() 49 | ..uris['package:a/a.dart'] = (Library()..scopes['A'] = interfaceA2); 50 | final c = a.mergeWith(b); 51 | final properties = c.uris['package:a/a.dart']!.scopes['A']!.properties; 52 | expect(properties, isA()); 53 | expect(properties.isClass, true); 54 | expect(properties.isAbstract, true); 55 | // Not set 56 | expect(() => properties.isConstructor, throwsA(isA())); 57 | }, 58 | ); 59 | 60 | test('Errors if maps have same the key with different values', () async { 61 | final interfaceA1 = Interface(properties: Properties(isClass: true)); 62 | final interfaceA2 = Interface(properties: Properties(isClass: false)); 63 | final a = 64 | Model() 65 | ..uris['package:a/a.dart'] = (Library()..scopes['A'] = interfaceA1); 66 | final b = 67 | Model() 68 | ..uris['package:a/a.dart'] = (Library()..scopes['A'] = interfaceA2); 69 | final c = a.mergeWith(b); 70 | expect( 71 | () => c.uris['package:a/a.dart']!.scopes['A']!.properties.isClass, 72 | throwsA(isA()), 73 | ); 74 | }); 75 | } 76 | 77 | void test(String description, void Function() body) => 78 | t.test(description, () => Scope.query.run(body)); 79 | -------------------------------------------------------------------------------- /pkgs/dart_model/test/scopes_test.dart: -------------------------------------------------------------------------------- 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/dart_model.dart'; 6 | import 'package:dart_model/src/json_buffer/json_buffer_builder.dart'; 7 | import 'package:test/test.dart'; 8 | 9 | void main() { 10 | group('Scope', () { 11 | for (final scope in [Scope.none, Scope.macro, Scope.query]) { 12 | test('create maps and serialize work in $scope', () { 13 | expect( 14 | scope.run(() => Scope.createMap(TypedMapSchema({}))), 15 | {}, 16 | ); 17 | expect(scope.run(Scope.createGrowableMap), {}); 18 | expect( 19 | scope.run(() => Scope.serializeToBinary({})), 20 | JsonBufferBuilder().serialize(), 21 | ); 22 | }); 23 | } 24 | 25 | test('macro and query scopes cannot be nested', () { 26 | expect( 27 | () => Scope.macro.run(() => Scope.macro.run(() {})), 28 | throwsStateError, 29 | ); 30 | expect( 31 | () => Scope.macro.run(() => Scope.query.run(() {})), 32 | throwsStateError, 33 | ); 34 | expect( 35 | () => Scope.query.run(() => Scope.macro.run(() {})), 36 | throwsStateError, 37 | ); 38 | expect( 39 | () => Scope.query.run(() => Scope.query.run(() {})), 40 | throwsStateError, 41 | ); 42 | }); 43 | 44 | test('none scope can be nested in macro or query scopes', () { 45 | Scope.macro.run(() => Scope.none.run(() {})); 46 | Scope.query.run(() => Scope.none.run(() {})); 47 | }); 48 | 49 | test('addModel merges the model with the previous', () { 50 | late Model initial; 51 | late Model firstQuery; 52 | Scope.query.run(() { 53 | initial = Model()..uris['a'] = (Library()..scopes['A'] = Interface()); 54 | firstQuery = 55 | Model()..uris['a'] = (Library()..scopes['B'] = Interface()); 56 | }); 57 | Scope.macro.run(() { 58 | MacroScope.current.addModel(initial); 59 | expect(MacroScope.current.model.uris['a']!.scopes['A'], isNot(null)); 60 | expect(MacroScope.current.model.uris['a']!.scopes['B'], null); 61 | MacroScope.current.addModel(firstQuery); 62 | expect(MacroScope.current.model.uris['a']!.scopes['A'], isNot(null)); 63 | expect(MacroScope.current.model.uris['a']!.scopes['B'], isNot(null)); 64 | }); 65 | }); 66 | }); 67 | } 68 | -------------------------------------------------------------------------------- /pkgs/dart_model/test/type_test.dart: -------------------------------------------------------------------------------- 1 | import 'package:dart_model/dart_model.dart'; 2 | import 'package:test/test.dart'; 3 | 4 | void main() { 5 | void setupTypeHierarchyModel() { 6 | final hierarchy = TypeHierarchy(); 7 | 8 | void registerCoreType(String name, [String? extending]) { 9 | hierarchy.named['dart:core#$name'] = TypeHierarchyEntry( 10 | self: NamedTypeDesc( 11 | name: QualifiedName(name: name, uri: 'dart:core'), 12 | instantiation: [], 13 | ), 14 | typeParameters: [], 15 | supertypes: [ 16 | if (extending != null) 17 | NamedTypeDesc( 18 | name: QualifiedName(name: extending, uri: 'dart:core'), 19 | instantiation: [], 20 | ), 21 | ], 22 | ); 23 | } 24 | 25 | registerCoreType('Object'); 26 | registerCoreType('String', 'Object'); 27 | MacroScope.current.addModel(Model(types: hierarchy)); 28 | } 29 | 30 | StaticTypeDesc coreType(String name) { 31 | return StaticTypeDesc.namedTypeDesc( 32 | NamedTypeDesc( 33 | name: QualifiedName(name: name, uri: 'dart:core'), 34 | instantiation: [], 35 | ), 36 | ); 37 | } 38 | 39 | test('isSubtype uses resolved model', () { 40 | Scope.macro.run(() { 41 | setupTypeHierarchyModel(); 42 | 43 | final object = StaticType(coreType('Object')); 44 | final string = StaticType(coreType('String')); 45 | 46 | expect(string.isSubtypeOf(object), isTrue); 47 | expect(object.isSubtypeOf(string), isFalse); 48 | }); 49 | }); 50 | 51 | test('isEqualTo uses resolved model', () { 52 | Scope.macro.run(() { 53 | setupTypeHierarchyModel(); 54 | 55 | final object = StaticType(coreType('Object')); 56 | final string = StaticType(coreType('String')); 57 | 58 | expect(string.isEqualTo(string), isTrue); 59 | expect(object.isEqualTo(string), isFalse); 60 | }); 61 | }); 62 | } 63 | -------------------------------------------------------------------------------- /pkgs/macro/analysis_options.yaml: -------------------------------------------------------------------------------- 1 | include: package:dart_flutter_team_lints/analysis_options.yaml 2 | -------------------------------------------------------------------------------- /pkgs/macro/lib/macro.dart: -------------------------------------------------------------------------------- 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 | export 'src/builders.dart'; 6 | export 'src/macro.dart'; 7 | -------------------------------------------------------------------------------- /pkgs/macro/mono_pkg.yaml: -------------------------------------------------------------------------------- 1 | sdk: 2 | - pubspec 3 | - dev 4 | 5 | stages: 6 | - analyze_and_format: 7 | - analyze: --fatal-infos . 8 | - format: 9 | sdk: 10 | - dev 11 | -------------------------------------------------------------------------------- /pkgs/macro/pubspec.yaml: -------------------------------------------------------------------------------- 1 | name: macro 2 | version: 0.0.1-wip 3 | description: >- 4 | For implementing a macro. 5 | repository: https://github.com/dart-lang/macros/tree/master/pkgs/macro 6 | resolution: workspace 7 | 8 | environment: 9 | sdk: ^3.7.0-157.0.dev 10 | 11 | dependencies: 12 | collection: ^1.19.0 13 | dart_model: ^0.0.1-wip 14 | macro_service: ^0.0.1-wip 15 | 16 | dev_dependencies: 17 | dart_flutter_team_lints: ^3.0.0 18 | test: ^1.25.0 19 | -------------------------------------------------------------------------------- /pkgs/macro_service/analysis_options.yaml: -------------------------------------------------------------------------------- 1 | include: package:dart_flutter_team_lints/analysis_options.yaml 2 | 3 | analyzer: 4 | errors: 5 | # TODO(davidmorgan): generated code can exceed 80 chars. 6 | lines_longer_than_80_chars: ignore 7 | -------------------------------------------------------------------------------- /pkgs/macro_service/lib/macro_service.dart: -------------------------------------------------------------------------------- 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 | export 'src/handshake.g.dart'; 6 | export 'src/macro_service.dart'; 7 | export 'src/macro_service.g.dart'; 8 | -------------------------------------------------------------------------------- /pkgs/macro_service/lib/src/handshake.g.dart: -------------------------------------------------------------------------------- 1 | // This file is generated. To make changes edit tool/dart_model_generator 2 | // then run from the repo root: dart tool/dart_model_generator/bin/main.dart 3 | 4 | // ignore: implementation_imports,unused_import,prefer_relative_imports 5 | import 'package:dart_model/src/deep_cast_map.dart'; 6 | // ignore: implementation_imports,unused_import,prefer_relative_imports 7 | import 'package:dart_model/src/json_buffer/json_buffer_builder.dart'; 8 | // ignore: implementation_imports,unused_import,prefer_relative_imports 9 | import 'package:dart_model/src/scopes.dart'; 10 | 11 | /// Request to pick a protocol. 12 | extension type HandshakeRequest.fromJson(Map node) 13 | implements Object { 14 | HandshakeRequest({List? protocols}) 15 | : this.fromJson({if (protocols != null) 'protocols': protocols}); 16 | 17 | /// Supported protocols. 18 | List get protocols => (node['protocols'] as List).cast(); 19 | } 20 | 21 | /// The picked protocol, or `null` if no requested protocol is supported. 22 | extension type HandshakeResponse.fromJson(Map node) 23 | implements Object { 24 | HandshakeResponse({Protocol? protocol}) 25 | : this.fromJson({if (protocol != null) 'protocol': protocol}); 26 | 27 | /// Supported protocol. 28 | Protocol? get protocol => node['protocol'] as Protocol?; 29 | } 30 | 31 | /// The macro to host protocol version and encoding. TODO(davidmorgan): add the version. 32 | extension type Protocol.fromJson(Map node) implements Object { 33 | Protocol({ProtocolEncoding? encoding, ProtocolVersion? version}) 34 | : this.fromJson({ 35 | if (encoding != null) 'encoding': encoding, 36 | if (version != null) 'version': version, 37 | }); 38 | 39 | /// The initial protocol for any `host<->macro` connection. 40 | static Protocol handshakeProtocol = Protocol( 41 | encoding: ProtocolEncoding.json, 42 | version: ProtocolVersion.handshake, 43 | ); 44 | 45 | /// The wire format: json or binary. 46 | ProtocolEncoding get encoding => node['encoding'] as ProtocolEncoding; 47 | 48 | /// The protocol version, a name and number. 49 | ProtocolVersion get version => node['version'] as ProtocolVersion; 50 | } 51 | 52 | /// The wire encoding used. 53 | extension type const ProtocolEncoding.fromJson(String string) 54 | implements Object { 55 | static const ProtocolEncoding json = ProtocolEncoding.fromJson('json'); 56 | static const ProtocolEncoding binary = ProtocolEncoding.fromJson('binary'); 57 | } 58 | 59 | /// The protocol version. 60 | extension type const ProtocolVersion.fromJson(String string) implements Object { 61 | static const ProtocolVersion handshake = ProtocolVersion.fromJson( 62 | 'handshake', 63 | ); 64 | static const ProtocolVersion macros1 = ProtocolVersion.fromJson('macros1'); 65 | } 66 | -------------------------------------------------------------------------------- /pkgs/macro_service/lib/src/macro_service.dart: -------------------------------------------------------------------------------- 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 'dart:async'; 6 | import 'dart:convert'; 7 | import 'dart:typed_data'; 8 | 9 | import 'package:dart_model/serialization.dart'; 10 | 11 | import 'handshake.g.dart'; 12 | import 'macro_service.g.dart'; 13 | import 'message_grouper.dart'; 14 | 15 | /// Service provided by the host to the macro. 16 | abstract interface class HostService { 17 | /// Handles [request]. 18 | /// 19 | /// Returns `null` if the request is of a type not handled by this service 20 | /// instance. 21 | Future handle(MacroRequest request); 22 | } 23 | 24 | /// Service provided by the macro to the host. 25 | abstract interface class MacroService { 26 | /// Handles [request]. 27 | Future handle(HostRequest request); 28 | } 29 | 30 | /// Shared implementation of auto incrementing 32 bit IDs. 31 | /// 32 | /// These roll back to 0 once it is greater than 2^32. 33 | /// 34 | /// These are only unique to the process which generates the request, 35 | /// so for instance the host and macro services may generate conflicting ids 36 | /// and that is allowed. 37 | int get nextRequestId { 38 | final next = _nextRequestId++; 39 | if (_nextRequestId > 0x7fffffff) { 40 | _nextRequestId = 0; 41 | } 42 | return next; 43 | } 44 | 45 | int _nextRequestId = 0; 46 | 47 | final _jsonConverter = json.fuse(utf8); 48 | 49 | extension ProtocolExtension on Protocol { 50 | /// Serializes [node] and sends it to [sink]. 51 | void send(void Function(Uint8List) sink, Map node) { 52 | switch (encoding) { 53 | case ProtocolEncoding.json: 54 | sink(_jsonConverter.encode(node) as Uint8List); 55 | sink(_utf8Newline); 56 | case ProtocolEncoding.binary: 57 | // Four byte message length followed by message. 58 | // TODO(davidmorgan): variable length int encoding probably makes more 59 | // sense than fixed four bytes. 60 | final binary = node.serializeToBinary(); 61 | final length = binary.length; 62 | sink( 63 | Uint8List.fromList([ 64 | (length >> 24) & 255, 65 | (length >> 16) & 255, 66 | (length >> 8) & 255, 67 | length & 255, 68 | ]), 69 | ); 70 | sink(binary); 71 | default: 72 | throw StateError('Unsupported protocol: $this.'); 73 | } 74 | } 75 | 76 | /// Deserializes [stream] to JSON objects. 77 | /// 78 | /// The data on `stream` can be arbitrarily split to `Uint8List` instances, 79 | /// it does not have to be one list per message. 80 | Stream> decode(Stream stream) { 81 | switch (encoding) { 82 | case ProtocolEncoding.json: 83 | return const Utf8Decoder() 84 | .bind(stream) 85 | .transform(const LineSplitter()) 86 | .map((line) => json.decode(line) as Map); 87 | 88 | case ProtocolEncoding.binary: 89 | return MessageGrouper( 90 | stream, 91 | ).messageStream.map((message) => message.deserializeFromBinary()); 92 | default: 93 | throw StateError('Unsupported protocol: $this'); 94 | } 95 | } 96 | 97 | bool equals(Protocol other) => 98 | encoding == other.encoding && version == other.version; 99 | } 100 | 101 | final _utf8Newline = const Utf8Encoder().convert('\n'); 102 | -------------------------------------------------------------------------------- /pkgs/macro_service/lib/src/message_grouper.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2022, 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 'dart:async'; 6 | import 'dart:math' as math; 7 | import 'dart:typed_data'; 8 | 9 | /// Collects messages from an input stream of bytes. 10 | /// 11 | /// Each message should start with a 32 bit big endian uint indicating its size, 12 | /// followed by that many bytes. 13 | class MessageGrouper { 14 | /// The input bytes stream subscription. 15 | late final StreamSubscription _inputStreamSubscription; 16 | 17 | /// The buffer to store the length bytes in. 18 | final _FixedBuffer _lengthBuffer = _FixedBuffer(4); 19 | 20 | /// If reading raw data, buffer for the data. 21 | _FixedBuffer? _messageBuffer; 22 | 23 | late final StreamController _messageStreamController = 24 | StreamController( 25 | onCancel: () { 26 | _inputStreamSubscription.cancel(); 27 | }, 28 | ); 29 | 30 | Stream get messageStream => _messageStreamController.stream; 31 | 32 | MessageGrouper(Stream> inputStream) { 33 | _inputStreamSubscription = inputStream.listen(_handleBytes, onDone: cancel); 34 | } 35 | 36 | /// Stop listening to the input stream for further updates, and close the 37 | /// output stream. 38 | void cancel() { 39 | _inputStreamSubscription.cancel(); 40 | _messageStreamController.close(); 41 | } 42 | 43 | void _handleBytes(List bytes, [int offset = 0]) { 44 | final messageBuffer = _messageBuffer; 45 | if (messageBuffer == null) { 46 | while (offset < bytes.length && !_lengthBuffer.isReady) { 47 | _lengthBuffer.addByte(bytes[offset++]); 48 | } 49 | if (_lengthBuffer.isReady) { 50 | final length = 51 | _lengthBuffer[0] << 24 | 52 | _lengthBuffer[1] << 16 | 53 | _lengthBuffer[2] << 8 | 54 | _lengthBuffer[3]; 55 | // Reset the length reading state. 56 | _lengthBuffer.reset(); 57 | // Switch to the message payload reading state. 58 | _messageBuffer = _FixedBuffer(length); 59 | _handleBytes(bytes, offset); 60 | } else { 61 | // Continue reading the length. 62 | return; 63 | } 64 | } else { 65 | // Read the data from `bytes`. 66 | offset += messageBuffer.addBytes(bytes, offset); 67 | 68 | // If we completed a message, add it to the output stream. 69 | if (messageBuffer.isReady) { 70 | _messageStreamController.add(messageBuffer.bytes); 71 | // Switch to the length reading state. 72 | _messageBuffer = null; 73 | _handleBytes(bytes, offset); 74 | } 75 | } 76 | } 77 | } 78 | 79 | /// A buffer of fixed length. 80 | class _FixedBuffer { 81 | final Uint8List bytes; 82 | 83 | /// The offset in [bytes]. 84 | int _offset = 0; 85 | 86 | _FixedBuffer(int length) : bytes = Uint8List(length); 87 | 88 | /// Return `true` when the required number of bytes added. 89 | bool get isReady => _offset == bytes.length; 90 | 91 | int operator [](int index) => bytes[index]; 92 | 93 | void addByte(int byte) { 94 | bytes[_offset++] = byte; 95 | } 96 | 97 | /// Consume at most as many bytes from [source] as required by fill [bytes]. 98 | /// Return the number of consumed bytes. 99 | int addBytes(List source, int offset) { 100 | int toConsume = math.min(source.length - offset, bytes.length - _offset); 101 | bytes.setRange(_offset, _offset + toConsume, source, offset); 102 | _offset += toConsume; 103 | return toConsume; 104 | } 105 | 106 | /// Reset the number of added bytes to zero. 107 | void reset() { 108 | _offset = 0; 109 | } 110 | } 111 | -------------------------------------------------------------------------------- /pkgs/macro_service/mono_pkg.yaml: -------------------------------------------------------------------------------- 1 | sdk: 2 | - pubspec 3 | - dev 4 | 5 | stages: 6 | - analyze_and_format: 7 | - analyze: --fatal-infos . 8 | - format: 9 | sdk: 10 | - dev 11 | - unit_test: 12 | - test: --test-randomize-ordering-seed=random 13 | os: 14 | - linux 15 | - windows 16 | -------------------------------------------------------------------------------- /pkgs/macro_service/pubspec.yaml: -------------------------------------------------------------------------------- 1 | name: macro_service 2 | version: 0.0.1-wip 3 | description: >- 4 | Macro communication with the macro host. 5 | repository: https://github.com/dart-lang/macros/tree/master/pkgs/macro_service 6 | resolution: workspace 7 | 8 | environment: 9 | sdk: ^3.7.0-157.0.dev 10 | 11 | dependencies: 12 | async: ^2.11.0 13 | dart_model: any 14 | 15 | dev_dependencies: 16 | dart_flutter_team_lints: ^3.0.0 17 | test: ^1.25.0 18 | -------------------------------------------------------------------------------- /pkgs/macro_service/test/protocol_test.dart: -------------------------------------------------------------------------------- 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 'dart:async'; 6 | import 'dart:math' show min; 7 | import 'dart:typed_data'; 8 | 9 | import 'package:macro_service/macro_service.dart'; 10 | import 'package:test/test.dart'; 11 | 12 | void main() { 13 | for (final protocol in [ 14 | Protocol(encoding: ProtocolEncoding.json), 15 | Protocol(encoding: ProtocolEncoding.binary), 16 | ]) { 17 | group('Protocol using ${protocol.encoding}', () { 18 | test('can round trip JSON data', () async { 19 | final json = { 20 | 'string': 'string', 21 | 'int': 7, 22 | 'boolean': true, 23 | 'map': {'key': 'value'}, 24 | 'list': [1, 'two', 3], 25 | }; 26 | 27 | final receivedData = []; 28 | void sink(Uint8List data) => receivedData.addAll(data); 29 | protocol.send(sink, json); 30 | 31 | final streamController = StreamController(); 32 | final decodedStream = protocol.decode(streamController.stream); 33 | streamController.add(Uint8List.fromList(receivedData)); 34 | final message = await decodedStream.first; 35 | 36 | expect(message, json); 37 | }); 38 | 39 | test('can handle multiple arbitrarily split messages', () async { 40 | final json = { 41 | 'string': 'string', 42 | 'int': 7, 43 | 'boolean': true, 44 | 'map': {'key': 'value'}, 45 | 'list': [1, 'two', 3], 46 | }; 47 | 48 | final receivedData = []; 49 | void sink(Uint8List data) => receivedData.addAll(data); 50 | for (var i = 0; i != 1000; ++i) { 51 | protocol.send(sink, json); 52 | } 53 | 54 | final streamController = StreamController(); 55 | final decodedStream = protocol.decode(streamController.stream); 56 | 57 | // Split into chunks with size 1, 2, 3, ... until done. 58 | var sizeToTake = 1; 59 | while (receivedData.isNotEmpty) { 60 | final take = min(sizeToTake++, receivedData.length); 61 | final takenBytes = Uint8List.fromList( 62 | receivedData.take(take).toList(), 63 | ); 64 | receivedData.removeRange(0, take); 65 | streamController.add(takenBytes); 66 | } 67 | unawaited(streamController.close()); 68 | 69 | final messages = await decodedStream.toList(); 70 | expect(messages.length, 1000); 71 | 72 | for (final message in messages) { 73 | expect(message, json); 74 | } 75 | }); 76 | }); 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /pubspec.yaml: -------------------------------------------------------------------------------- 1 | name: macros_workspace 2 | environment: 3 | sdk: ^3.7.0-157.0.dev 4 | dev_dependencies: 5 | dart_flutter_team_lints: ^3.1.0 6 | publish_to: none 7 | workspace: 8 | - goldens/foo 9 | - pkgs/_analyzer_cfe_macros 10 | - pkgs/_analyzer_macros 11 | - pkgs/_analyzer_macros/test/package_under_test 12 | - pkgs/_cfe_macros 13 | - pkgs/_cfe_macros/test/package_under_test 14 | - pkgs/_macro_builder 15 | - pkgs/_macro_client 16 | - pkgs/_macro_host 17 | - pkgs/_macro_runner 18 | - pkgs/_macro_server 19 | - pkgs/_macro_tool 20 | - pkgs/_macro_tool/test/package_under_test 21 | - pkgs/_test_macros 22 | - pkgs/dart_model 23 | - pkgs/macro 24 | - pkgs/macro_service 25 | - tool/benchmark_generator 26 | - tool/dart_model_generator 27 | 28 | # Update by running: tool/set_sdk_version 29 | dependency_overrides: 30 | _fe_analyzer_shared: 31 | git: 32 | url: https://github.com/dart-lang/sdk.git 33 | path: pkg/_fe_analyzer_shared 34 | ref: 3.7.0-157.0.dev 35 | _js_interop_checks: 36 | git: 37 | url: https://github.com/dart-lang/sdk.git 38 | path: pkg/_js_interop_checks 39 | ref: 3.7.0-157.0.dev 40 | _macros: 41 | git: 42 | url: https://github.com/dart-lang/sdk.git 43 | path: pkg/_macros 44 | ref: 3.7.0-157.0.dev 45 | analysis_server: 46 | git: 47 | url: https://github.com/dart-lang/sdk.git 48 | path: pkg/analysis_server 49 | ref: 3.7.0-157.0.dev 50 | analyzer: 51 | git: 52 | url: https://github.com/dart-lang/sdk.git 53 | path: pkg/analyzer 54 | ref: 3.7.0-157.0.dev 55 | analyzer_plugin: 56 | git: 57 | url: https://github.com/dart-lang/sdk.git 58 | path: pkg/analyzer_plugin 59 | ref: 3.7.0-157.0.dev 60 | analyzer_utilities: 61 | git: 62 | url: https://github.com/dart-lang/sdk.git 63 | path: pkg/analyzer_utilities 64 | ref: 3.7.0-157.0.dev 65 | build_integration: 66 | git: 67 | url: https://github.com/dart-lang/sdk.git 68 | path: pkg/build_integration 69 | ref: 3.7.0-157.0.dev 70 | compiler: 71 | git: 72 | url: https://github.com/dart-lang/sdk.git 73 | path: pkg/compiler 74 | ref: 3.7.0-157.0.dev 75 | dart_style: 76 | git: 77 | url: https://github.com/dart-lang/dart_style.git 78 | ref: dc13a2f8e667825980cbc1a06ed645620f9bed70 # Not an SDK ref! 79 | dart2js_info: 80 | git: 81 | url: https://github.com/dart-lang/sdk.git 82 | path: pkg/dart2js_info 83 | ref: 3.7.0-157.0.dev 84 | dart2wasm: 85 | git: 86 | url: https://github.com/dart-lang/sdk.git 87 | path: pkg/dart2wasm 88 | ref: 3.7.0-157.0.dev 89 | dev_compiler: 90 | git: 91 | url: https://github.com/dart-lang/sdk.git 92 | path: pkg/dev_compiler 93 | ref: 3.7.0-157.0.dev 94 | front_end: 95 | git: 96 | url: https://github.com/dart-lang/sdk.git 97 | path: pkg/front_end 98 | ref: 3.7.0-157.0.dev 99 | frontend_server: 100 | git: 101 | url: https://github.com/dart-lang/sdk.git 102 | path: pkg/frontend_server 103 | ref: 3.7.0-157.0.dev 104 | heap_snapshot: 105 | git: 106 | url: https://github.com/dart-lang/sdk.git 107 | path: pkg/heap_snapshot 108 | ref: 3.7.0-157.0.dev 109 | js_ast: 110 | git: 111 | url: https://github.com/dart-lang/sdk.git 112 | path: pkg/js_ast 113 | ref: 3.7.0-157.0.dev 114 | js_runtime: 115 | git: 116 | url: https://github.com/dart-lang/sdk.git 117 | path: pkg/js_runtime 118 | ref: 3.7.0-157.0.dev 119 | js_shared: 120 | git: 121 | url: https://github.com/dart-lang/sdk.git 122 | path: pkg/js_shared 123 | ref: 3.7.0-157.0.dev 124 | kernel: 125 | git: 126 | url: https://github.com/dart-lang/sdk.git 127 | path: pkg/kernel 128 | ref: 3.7.0-157.0.dev 129 | analysis_server_plugin: 130 | git: 131 | url: https://github.com/dart-lang/sdk.git 132 | path: pkg/analysis_server_plugin 133 | ref: 3.7.0-157.0.dev 134 | language_server_protocol: 135 | git: 136 | url: https://github.com/dart-lang/sdk.git 137 | path: third_party/pkg/language_server_protocol 138 | ref: 3.7.0-157.0.dev 139 | linter: 140 | git: 141 | url: https://github.com/dart-lang/sdk.git 142 | path: pkg/linter 143 | ref: 3.7.0-157.0.dev 144 | mmap: 145 | git: 146 | url: https://github.com/dart-lang/sdk.git 147 | path: pkg/mmap 148 | ref: 3.7.0-157.0.dev 149 | telemetry: 150 | git: 151 | url: https://github.com/dart-lang/sdk.git 152 | path: pkg/telemetry 153 | ref: 3.7.0-157.0.dev 154 | vm: 155 | git: 156 | url: https://github.com/dart-lang/sdk.git 157 | path: pkg/vm 158 | ref: 3.7.0-157.0.dev 159 | vm_service: 160 | git: 161 | url: https://github.com/dart-lang/sdk.git 162 | path: pkg/vm_service 163 | ref: 3.7.0-157.0.dev 164 | wasm_builder: 165 | git: 166 | url: https://github.com/dart-lang/sdk.git 167 | path: pkg/wasm_builder 168 | ref: 3.7.0-157.0.dev 169 | -------------------------------------------------------------------------------- /schemas/handshake.schema.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://json-schema.org/draft/2020-12/schema", 3 | "oneOf": [ 4 | { 5 | "$ref": "#/$defs/HandshakeRequest" 6 | }, 7 | { 8 | "$ref": "#/$defs/HandshakeResponse" 9 | } 10 | ], 11 | "$defs": { 12 | "HandshakeRequest": { 13 | "type": "object", 14 | "description": "Request to pick a protocol.", 15 | "properties": { 16 | "protocols": { 17 | "type": "array", 18 | "description": "Supported protocols.", 19 | "items": { 20 | "$ref": "#/$defs/Protocol" 21 | } 22 | } 23 | } 24 | }, 25 | "HandshakeResponse": { 26 | "type": "object", 27 | "description": "The picked protocol, or `null` if no requested protocol is supported.", 28 | "properties": { 29 | "protocol": { 30 | "$comment": "Supported protocol.", 31 | "$ref": "#/$defs/Protocol" 32 | } 33 | } 34 | }, 35 | "Protocol": { 36 | "type": "object", 37 | "description": "The macro to host protocol version and encoding. TODO(davidmorgan): add the version.", 38 | "properties": { 39 | "encoding": { 40 | "$comment": "The wire format: json or binary.", 41 | "$ref": "#/$defs/ProtocolEncoding" 42 | }, 43 | "version": { 44 | "$comment": "The protocol version, a name and number.", 45 | "$ref": "#/$defs/ProtocolVersion" 46 | } 47 | } 48 | }, 49 | "ProtocolEncoding": { 50 | "type": "string", 51 | "description": "The wire encoding used." 52 | }, 53 | "ProtocolVersion": { 54 | "type": "string", 55 | "description": "The protocol version." 56 | } 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /tool/benchmark_generator/README.md: -------------------------------------------------------------------------------- 1 | # Benchmark Generator 2 | 3 | Generates code that uses macros, for benchmarking. 4 | 5 | Example use, from the root of this repo: 6 | 7 | ``` 8 | dart run benchmark_generator large 64 BuiltValue JsonCodable 9 | dart run _macro_tool \ 10 | --workspace=goldens/foo \ 11 | --packageConfig=.dart_tool/package_config.json \ 12 | --script=goldens/foo/lib/generated/large/a0.dart \ 13 | --host=analyzer watch 14 | ``` 15 | 16 | then change `goldens/foo/lib/generated/large/a0.dart` to see the refresh time. 17 | -------------------------------------------------------------------------------- /tool/benchmark_generator/bin/benchmark_generator.dart: -------------------------------------------------------------------------------- 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 'dart:io'; 6 | 7 | import 'package:benchmark_generator/input_generator.dart'; 8 | import 'package:benchmark_generator/workspace.dart'; 9 | import 'package:dart_model/dart_model.dart'; 10 | 11 | Future main(List arguments) async { 12 | if (arguments.length < 3) { 13 | print(''' 14 | Creates packages to benchmark macro performance. 15 | 16 | Available macro names: BuiltValue, JsonCodable 17 | 18 | Usage: 19 | 20 | dart run benchmark_generator <# libraries> [additional macro names] 21 | '''); 22 | exit(1); 23 | } 24 | 25 | final workspaceName = arguments[0]; 26 | final libraryCount = int.parse(arguments[1]); 27 | 28 | final macroNames = arguments.skip(2).toList(); 29 | final macros = [ 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 | ]; 43 | 44 | final workspace = Workspace(workspaceName); 45 | print('Creating under: ${workspace.directory.path}'); 46 | final inputGenerator = ClassesAndFieldsInputGenerator( 47 | macros: macros, 48 | fieldsPerClass: 100, 49 | classesPerLibrary: 10, 50 | librariesPerCycle: libraryCount, 51 | ); 52 | inputGenerator.generate(workspace); 53 | } 54 | -------------------------------------------------------------------------------- /tool/benchmark_generator/lib/input_generator.dart: -------------------------------------------------------------------------------- 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 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 json); 69 | external Map 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 | } 80 | -------------------------------------------------------------------------------- /tool/benchmark_generator/lib/workspace.dart: -------------------------------------------------------------------------------- 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 'dart:io'; 6 | import 'dart:isolate'; 7 | 8 | class Workspace { 9 | final String name; 10 | 11 | Directory get directory => Directory.fromUri( 12 | Isolate.packageConfigSync!.resolve('../goldens/foo/lib/generated/$name'), 13 | ); 14 | 15 | Workspace(this.name) { 16 | if (directory.existsSync()) directory.deleteSync(recursive: true); 17 | directory.createSync(recursive: true); 18 | } 19 | 20 | void write(String path, {required String source}) { 21 | final file = File.fromUri(directory.uri.resolve('$path')); 22 | file.parent.createSync(recursive: true); 23 | file.writeAsStringSync(source); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /tool/benchmark_generator/mono_pkg.yaml: -------------------------------------------------------------------------------- 1 | sdk: 2 | - pubspec 3 | - dev 4 | 5 | stages: 6 | - analyze_and_format: 7 | - analyze: --fatal-infos . 8 | - format: 9 | sdk: 10 | - dev 11 | -------------------------------------------------------------------------------- /tool/benchmark_generator/pubspec.yaml: -------------------------------------------------------------------------------- 1 | name: benchmark_generator 2 | publish-to: none 3 | resolution: workspace 4 | environment: 5 | sdk: ^3.7.0-157.0.dev 6 | 7 | dev_dependencies: 8 | dart_flutter_team_lints: ^3.0.0 9 | test: ^1.25.7 10 | -------------------------------------------------------------------------------- /tool/ci.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # Created with package:mono_repo v6.6.2 3 | 4 | # Support built in commands on windows out of the box. 5 | 6 | # When it is a flutter repo (check the pubspec.yaml for "sdk: flutter") 7 | # then "flutter pub" is called instead of "dart pub". 8 | # This assumes that the Flutter SDK has been installed in a previous step. 9 | function pub() { 10 | if grep -Fq "sdk: flutter" "${PWD}/pubspec.yaml"; then 11 | command flutter pub "$@" 12 | else 13 | command dart pub "$@" 14 | fi 15 | } 16 | 17 | function format() { 18 | command dart format "$@" 19 | } 20 | 21 | # When it is a flutter repo (check the pubspec.yaml for "sdk: flutter") 22 | # then "flutter analyze" is called instead of "dart analyze". 23 | # This assumes that the Flutter SDK has been installed in a previous step. 24 | function analyze() { 25 | if grep -Fq "sdk: flutter" "${PWD}/pubspec.yaml"; then 26 | command flutter analyze "$@" 27 | else 28 | command dart analyze "$@" 29 | fi 30 | } 31 | 32 | if [[ -z ${PKGS} ]]; then 33 | echo -e '\033[31mPKGS environment variable must be set! - TERMINATING JOB\033[0m' 34 | exit 64 35 | fi 36 | 37 | if [[ "$#" == "0" ]]; then 38 | echo -e '\033[31mAt least one task argument must be provided! - TERMINATING JOB\033[0m' 39 | exit 64 40 | fi 41 | 42 | SUCCESS_COUNT=0 43 | declare -a FAILURES 44 | 45 | for PKG in ${PKGS}; do 46 | echo -e "\033[1mPKG: ${PKG}\033[22m" 47 | EXIT_CODE=0 48 | pushd "${PKG}" >/dev/null || EXIT_CODE=$? 49 | 50 | if [[ ${EXIT_CODE} -ne 0 ]]; then 51 | echo -e "\033[31mPKG: '${PKG}' does not exist - TERMINATING JOB\033[0m" 52 | exit 64 53 | fi 54 | 55 | dart pub upgrade || EXIT_CODE=$? 56 | 57 | if [[ ${EXIT_CODE} -ne 0 ]]; then 58 | echo -e "\033[31mPKG: ${PKG}; 'dart pub upgrade' - FAILED (${EXIT_CODE})\033[0m" 59 | FAILURES+=("${PKG}; 'dart pub upgrade'") 60 | else 61 | for TASK in "$@"; do 62 | EXIT_CODE=0 63 | echo 64 | echo -e "\033[1mPKG: ${PKG}; TASK: ${TASK}\033[22m" 65 | case ${TASK} in 66 | analyze) 67 | echo 'dart analyze --fatal-infos .' 68 | dart analyze --fatal-infos . || EXIT_CODE=$? 69 | ;; 70 | command_0) 71 | echo '../../tool/run_e2e_tests.sh' 72 | ../../tool/run_e2e_tests.sh || EXIT_CODE=$? 73 | ;; 74 | command_1) 75 | echo 'dart -Ddebug_json_buffer=true test --test-randomize-ordering-seed=random -c source' 76 | dart -Ddebug_json_buffer=true test --test-randomize-ordering-seed=random -c source || EXIT_CODE=$? 77 | ;; 78 | format) 79 | echo 'dart format --output=none --set-exit-if-changed .' 80 | dart format --output=none --set-exit-if-changed . || EXIT_CODE=$? 81 | ;; 82 | test_0) 83 | echo 'dart test --test-randomize-ordering-seed=random' 84 | dart test --test-randomize-ordering-seed=random || EXIT_CODE=$? 85 | ;; 86 | test_1) 87 | echo 'dart test --test-randomize-ordering-seed=random --concurrency=1' 88 | dart test --test-randomize-ordering-seed=random --concurrency=1 || EXIT_CODE=$? 89 | ;; 90 | *) 91 | echo -e "\033[31mUnknown TASK '${TASK}' - TERMINATING JOB\033[0m" 92 | exit 64 93 | ;; 94 | esac 95 | 96 | if [[ ${EXIT_CODE} -ne 0 ]]; then 97 | echo -e "\033[31mPKG: ${PKG}; TASK: ${TASK} - FAILED (${EXIT_CODE})\033[0m" 98 | FAILURES+=("${PKG}; TASK: ${TASK}") 99 | else 100 | echo -e "\033[32mPKG: ${PKG}; TASK: ${TASK} - SUCCEEDED\033[0m" 101 | SUCCESS_COUNT=$((SUCCESS_COUNT + 1)) 102 | fi 103 | 104 | done 105 | fi 106 | 107 | echo 108 | echo -e "\033[32mSUCCESS COUNT: ${SUCCESS_COUNT}\033[0m" 109 | 110 | if [ ${#FAILURES[@]} -ne 0 ]; then 111 | echo -e "\033[31mFAILURES: ${#FAILURES[@]}\033[0m" 112 | for i in "${FAILURES[@]}"; do 113 | echo -e "\033[31m $i\033[0m" 114 | done 115 | fi 116 | 117 | popd >/dev/null || exit 70 118 | echo 119 | done 120 | 121 | if [ ${#FAILURES[@]} -ne 0 ]; then 122 | exit 1 123 | fi 124 | -------------------------------------------------------------------------------- /tool/dart_model_generator/analysis_options.yaml: -------------------------------------------------------------------------------- 1 | include: package:dart_flutter_team_lints/analysis_options.yaml 2 | -------------------------------------------------------------------------------- /tool/dart_model_generator/bin/main.dart: -------------------------------------------------------------------------------- 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:generate_dart_model/definitions.dart'; 6 | 7 | void main() { 8 | for (final generationResult in schemas.generate()) { 9 | generationResult.write(); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /tool/dart_model_generator/mono_pkg.yaml: -------------------------------------------------------------------------------- 1 | sdk: 2 | - pubspec 3 | - dev 4 | 5 | stages: 6 | - analyze_and_format: 7 | - analyze: --fatal-infos . 8 | - format: 9 | sdk: 10 | - dev 11 | - unit_test: 12 | - test: --test-randomize-ordering-seed=random 13 | os: 14 | - linux 15 | - windows 16 | -------------------------------------------------------------------------------- /tool/dart_model_generator/pubspec.yaml: -------------------------------------------------------------------------------- 1 | name: generate_dart_model 2 | publish-to: none 3 | resolution: workspace 4 | environment: 5 | sdk: ^3.7.0-157.0.dev 6 | 7 | dependencies: 8 | dart_model: any 9 | dart_style: any 10 | json_schema: ^5.1.7 11 | 12 | dev_dependencies: 13 | dart_flutter_team_lints: ^3.0.0 14 | test: ^1.25.7 15 | -------------------------------------------------------------------------------- /tool/dart_model_generator/test/generated_output_test.dart: -------------------------------------------------------------------------------- 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 'dart:io'; 6 | 7 | import 'package:generate_dart_model/definitions.dart'; 8 | import 'package:test/test.dart'; 9 | 10 | void main() { 11 | for (final generationResult in schemas.generate()) { 12 | test('${generationResult.path} is up to date', () { 13 | final expected = generationResult.content; 14 | final actual = File('../../${generationResult.path}').readAsStringSync(); 15 | // TODO: On windows we get carriage returns, which makes this fail 16 | // without ignoring white space. In theory this shouldn't happen. 17 | expect( 18 | actual, 19 | equalsIgnoringWhitespace(expected), 20 | reason: ''' 21 | Output is not up to date. Please run 22 | 23 | dart tool/dart_model_generator/bin/main.dart 24 | 25 | in repo root. 26 | ''', 27 | ); 28 | }); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /tool/generate_converter: -------------------------------------------------------------------------------- 1 | #!/bin/bash -- 2 | # 3 | # Generates converter -->dart_model from `_fe_analyzer_shared` code. 4 | # 5 | # TODO(davidmorgan): this is intended to be single use code: delete if we don't 6 | # need it again, convert to Dart if we do. 7 | # 8 | # mkdir /tmp/inputs 9 | # cp ~/git/dart-sdk/sdk/pkg/_fe_analyzer_shared/lib/src/metadata/* /tmp/inputs 10 | # rm /tmp/inputs/proto.dart /tmp/inputs/parser.dart /tmp/inputs/scope.dart /tmp/inputs/evaluate.dart 11 | # tool/generate_converter /tmp/inputs 12 | 13 | path="$1" 14 | source=$(find $path -name \*.dart | xargs cat) 15 | IFS=$'\n' 16 | 17 | # Unions. 18 | for union in $( 19 | echo "$source" \ 20 | | grep 'sealed class' \ 21 | | sed -e s'#sealed class ##' \ 22 | | sed -e 's# {##' \ 23 | | sort); do 24 | echo "dart_model.$union? convertTo$union(Object? object) => switch (object) {" 25 | for type in $(echo "$source" \ 26 | | grep "extends $union" \ 27 | | sed -e 's#abstract ##' \ 28 | | sed -e 's#class ##' \ 29 | | sed -e 's# extends .*##'); do 30 | lower_first_type=${type,} 31 | echo " front_end.$type o => dart_model.$union.$lower_first_type(convert(o)!)," 32 | done 33 | echo "null => null," 34 | echo "_ => throw ArgumentError(object)," 35 | echo "};"; 36 | done 37 | 38 | echo "T? convert(Object? object) => switch (object) {" 39 | 40 | # Enums. 41 | for type in $(echo "$source" \ 42 | | grep -E '^enum [A-Z]' \ 43 | | sed -e 's#enum ##' \ 44 | | sed -e 's# .*##' \ 45 | | sort); do 46 | echo "front_end.$type o => o.name as T," 47 | done 48 | 49 | # Classes. 50 | for type in $(echo "$source" \ 51 | | grep -E '^[a-z ]*class ' \ 52 | | grep -v 'sealed' \ 53 | | sed -e 's#abstract ##' \ 54 | | sed -e 's#class ##' \ 55 | | sed -e 's# extends .*##' \ 56 | | sed -e 's# {##' \ 57 | | sort); do 58 | echo "front_end.$type o => dart_model.$type(" 59 | for line in $(echo "$source" \ 60 | | grep -A1000000 "class $type extends" \ 61 | | tail -n +2); do 62 | if echo "$line" | grep -q ' final'; then 63 | field_name=$(echo $line | sed -e 's# final ##' | sed -e 's#.* ##' | sed -e 's#;##') 64 | if test "$field_name" == "unresolved"; then 65 | continue 66 | fi 67 | field_type=$(echo $line | sed -e 's# final ##' | sed -e 's# .*##') 68 | if echo "$field_type" | grep -E -q '^(Argument|Element|Expression|RecordField|Reference|StringLiteralPart)$'; then 69 | echo " $field_name: convertTo$field_type(o.$field_name)," 70 | else 71 | echo " $field_name: convert(o.$field_name)," 72 | fi 73 | elif echo "$line" | grep -q ' get '; then 74 | field_name=$(echo $line | sed -e 's#.* get ##' | sed -e 's#;##') 75 | if test "$field_name" == "unresolved"; then 76 | continue 77 | fi 78 | field_type=$(echo $line | sed -e 's# ##' | sed -e 's# get .*##') 79 | if echo "$field_type" | grep -E -q '^(Argument|Element|Expression|RecordField|Reference|StringLiteralPart)$'; then 80 | echo " $field_name: convertTo$field_type(o.$field_name)," 81 | else 82 | echo " $field_name: convert(o.$field_name)," 83 | fi 84 | else 85 | break 86 | fi 87 | done 88 | echo ") as T," 89 | done 90 | echo "String o => o as T," 91 | echo "int o => o as T," 92 | echo "bool o => o as T," 93 | echo "double o => o as T," 94 | echo "List o => o.map((i) => convert>(i)!).toList() as T," 95 | echo "null => null," 96 | echo "_ => throw ArgumentError(object)," 97 | echo "};" 98 | -------------------------------------------------------------------------------- /tool/generate_definitions: -------------------------------------------------------------------------------- 1 | #!/bin/bash -- 2 | # 3 | # Generates dart_model definitions from `_fe_analyzer_shared` code. 4 | # 5 | # TODO(davidmorgan): this is intended to be single use code: delete if we don't 6 | # need it again, convert to Dart if we do. 7 | # 8 | # mkdir /tmp/inputs 9 | # cp ~/git/dart-sdk/sdk/pkg/_fe_analyzer_shared/lib/src/metadata/* /tmp/inputs 10 | # rm /tmp/inputs/proto.dart /tmp/inputs/parser.dart /tmp/inputs/scope.dart /tmp/inputs/evaluate.dart 11 | # tool/generate_definitions /tmp/inputs 12 | 13 | path="$1" 14 | source=$(find $path -name \*.dart | xargs cat) 15 | IFS=$'\n' 16 | 17 | echo "final definitions = [" 18 | 19 | # Unions. 20 | for union in $( 21 | echo "$source" \ 22 | | grep 'sealed class' \ 23 | | sed -e s'#sealed class ##' \ 24 | | sed -e 's# {##' \ 25 | | sort); do 26 | echo "Definition.union('$union'," 27 | echo " createInBuffer: true," 28 | echo " description: ''," 29 | echo " types: [" 30 | for type in $(echo "$source" \ 31 | | grep "extends $union" \ 32 | | sed -e 's#abstract ##' \ 33 | | sed -e 's#class ##' \ 34 | | sed -e 's# extends .*##'); do 35 | echo " '$type'," 36 | done 37 | echo " ]," 38 | echo " properties: []," 39 | echo "),"; 40 | done 41 | 42 | # Enums. 43 | for type in $(echo "$source" \ 44 | | grep -E '^enum [A-Z]' \ 45 | | sed -e 's#enum ##' \ 46 | | sed -e 's# .*##' \ 47 | | sort); do 48 | echo "Definition.\$enum('$type'," 49 | echo " description: ''," 50 | echo " values: [" 51 | for line in $(echo "$source" \ 52 | | grep -A1000000 "enum $type " \ 53 | | tail -n +2); do 54 | if echo "$line" | grep -q -e ';' -e '}'; then 55 | break 56 | else 57 | value=$(echo $line | sed -e 's# ##' | sed -e 's#(.*##' | sed -e 's#,##') 58 | echo " '$value'," 59 | fi 60 | done 61 | echo " ]," 62 | echo ")," 63 | done 64 | 65 | # Classes. 66 | for type in $(echo "$source" \ 67 | | grep -E '^[a-z ]*class ' \ 68 | | grep -v 'sealed' \ 69 | | sed -e 's#abstract ##' \ 70 | | sed -e 's#class ##' \ 71 | | sed -e 's# extends .*##' \ 72 | | sed -e 's# {##' \ 73 | | sort); do 74 | echo "Definition.clazz('$type'," 75 | echo " createInBuffer: true," 76 | echo " description: ''," 77 | echo " properties: [" 78 | for line in $(echo "$source" \ 79 | | grep -A1000000 "class $type extends" \ 80 | | tail -n +2); do 81 | if echo "$line" | grep -q ' final'; then 82 | field_name=$(echo $line | sed -e 's# final ##' | sed -e 's#.* ##' | sed -e 's#;##') 83 | if test "$field_name" == "unresolved"; then 84 | continue 85 | fi 86 | field_type=$(echo $line | sed -e 's# final ##' | sed -e 's# .*##') 87 | if echo "$field_type" | grep -F -q '?'; then 88 | field_type=$(echo "$field_type" | sed -e 's#?##') 89 | echo " Property('$field_name', type: '$field_type', description: '', nullable: true)," 90 | else 91 | echo " Property('$field_name', type: '$field_type', description: '')," 92 | fi 93 | elif echo "$line" | grep -q ' get '; then 94 | field_name=$(echo $line | sed -e 's#.* get ##' | sed -e 's#;##') 95 | if test "$field_name" == "unresolved"; then 96 | continue 97 | fi 98 | field_type=$(echo $line | sed -e 's# ##' | sed -e 's# get .*##') 99 | if echo "$field_type" | grep -F -q '?'; then 100 | field_type=$(echo "$field_type" | sed -e 's#?##') 101 | echo " Property('$field_name', type: '$field_type', description: '', nullable: true)," 102 | else 103 | echo " Property('$field_name', type: '$field_type', description: '')," 104 | fi 105 | else 106 | break 107 | fi 108 | done 109 | echo " ]," 110 | echo ")," 111 | done 112 | echo "];" 113 | -------------------------------------------------------------------------------- /tool/run_e2e_tests.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # 3 | # Runs tests under `goldens/foo` with macros applied. 4 | 5 | # All paths below are relative to this script, change to script directory. 6 | cd $(dirname ${BASH_SOURCE[0]}) 7 | 8 | echo "Testing goldens/foo/*_test.dart..." 9 | 10 | for test_file in $(cd ../goldens/foo/lib/; find . -name \*_test.dart); do 11 | echo "Testing $test_file..." 12 | if dart run _macro_tool \ 13 | --workspace=../goldens/foo \ 14 | --script=../goldens/foo/lib/${test_file} \ 15 | apply patch_for_cfe run revert; then 16 | echo "PASS" 17 | else 18 | echo "FAIL" 19 | # Fail fast: fail on first test failure. 20 | exit 1 21 | fi 22 | done 23 | -------------------------------------------------------------------------------- /tool/set_sdk_ref: -------------------------------------------------------------------------------- 1 | #!/bin/bash -- 2 | # 3 | # Updates ref to SDK packages in pubspec.yaml. 4 | # 5 | # Refs with a comment on the same line, starting `#`, are not updated: this is 6 | # for the `dart_style` ref which is to another repo. 7 | # 8 | # If it's a dev tag, also updates SDK version. 9 | 10 | REF=$1 11 | 12 | if echo $REF | grep -Eq 'dev$'; then 13 | sed -i -e "s#^ sdk: .*# sdk: ^$REF#" $(find . -name pubspec.yaml) 14 | else 15 | if echo $REF | grep -Eqv '^[a-z0-9][a-z0-9-]*$'; then 16 | echo "Doesn't look like a git ref? $REF" 17 | echo "Usage: tool/set_sdk_version " 18 | exit 1 19 | fi 20 | 21 | echo "Got a ref, not a dev tag: updating deps but not SDK version." 22 | fi 23 | 24 | sed -i -e "s#^ ref: [^#]*\$# ref: $REF#" pubspec.yaml 25 | --------------------------------------------------------------------------------