From dfeb077d3fa97fbb9295869806f08578b257b35a Mon Sep 17 00:00:00 2001 From: David Morgan Date: Tue, 5 Aug 2025 12:31:44 +0200 Subject: [PATCH 1/2] Deprecated TypeChecker.fromRuntime, add TypeChecker.typeNamed as recommended replacement. --- source_gen/CHANGELOG.md | 9 +++ .../lib/src/generator_for_annotation.dart | 15 +++- source_gen/lib/src/type_checker.dart | 51 +++++++++++++ source_gen/pubspec.yaml | 2 +- source_gen/test/constants_test.dart | 6 +- .../test/external_only_type_checker_test.dart | 4 +- source_gen/test/type_checker_test.dart | 74 ++++++++++++++++++- 7 files changed, 153 insertions(+), 8 deletions(-) diff --git a/source_gen/CHANGELOG.md b/source_gen/CHANGELOG.md index b63675c8..07e444a6 100644 --- a/source_gen/CHANGELOG.md +++ b/source_gen/CHANGELOG.md @@ -1,3 +1,12 @@ +## 3.1.0-wip + +- Prepare to stop using `dart:mirrors`: deprecate `TypeChecker.fromRuntime`. + It will be removed in version `4.0.0`. Add `TypeChecker.typeNamed` which is + the recommended replacement. +- Prepare to stop using `dart:mirrors`: add `inPackage` and `inSdk` parameters + to `GenerateForAnnotation`. These will start being used in version `4.0.0` + when it switches to `TypeChecker.typeNamed`. + ## 3.0.0 - **Breaking Change**: use the new `element2` APIs in `analyzer`. Builders that diff --git a/source_gen/lib/src/generator_for_annotation.dart b/source_gen/lib/src/generator_for_annotation.dart index 7b41f133..ef977fdb 100644 --- a/source_gen/lib/src/generator_for_annotation.dart +++ b/source_gen/lib/src/generator_for_annotation.dart @@ -42,12 +42,25 @@ import 'type_checker.dart'; /// may be helpful to check which elements have a given annotation. abstract class GeneratorForAnnotation extends Generator { final bool throwOnUnresolved; + final String? inPackage; + final bool? inSdk; /// By default, this generator will throw if it encounters unresolved /// annotations. You can override this by setting [throwOnUnresolved] to /// `false`. - const GeneratorForAnnotation({this.throwOnUnresolved = true}); + /// + /// With `source_gen` 4.0.0 this class will stop using mirrors for matching + /// annotations and will fall back to comparing the name of `T`. Pass + /// [inPackage] and [inSdk] to tighten the check; see [TypeChecker#typeNamed]. + /// To use a custom annotation check, override [typeChecker]. + const GeneratorForAnnotation({ + this.throwOnUnresolved = true, + this.inPackage, + this.inSdk, + }); + // This will switch to `typeNamed` in 4.0.0. + // ignore: deprecated_member_use_from_same_package TypeChecker get typeChecker => TypeChecker.fromRuntime(T); @override diff --git a/source_gen/lib/src/type_checker.dart b/source_gen/lib/src/type_checker.dart index 8b6617c0..9acb7035 100644 --- a/source_gen/lib/src/type_checker.dart +++ b/source_gen/lib/src/type_checker.dart @@ -35,8 +35,27 @@ abstract class TypeChecker { /// Create a new [TypeChecker] backed by a runtime [type]. /// /// This implementation uses `dart:mirrors` (runtime reflection). + @Deprecated(''' +Will be removed in 4.0.0 to drop `dart:mirrors` dependency. + +Recommended: replace `fromRuntime(Foo)` with +`typeNamed(Foo, inPackage: 'foo_package')`. This is a slighly weaker check than +`fromRuntime(Foo)` as it matches any annotation named `Foo` in +`package:foo_package`. + +If you need an exact match, use `fromUrl`.''') const factory TypeChecker.fromRuntime(Type type) = _MirrorTypeChecker; + /// Create a new [TypeChecker] for types matching the name of [type]. + /// + /// Optionally, also pass [inPackage] to restrict to a specific package by + /// name. Set [inSdk] if it's a `dart` package. + const factory TypeChecker.typeNamed( + Type type, { + String? inPackage, + bool? inSdk, + }) = _NameTypeChecker; + /// Create a new [TypeChecker] backed by a static [type]. const factory TypeChecker.fromStatic(DartType type) = _LibraryTypeChecker; @@ -269,6 +288,38 @@ class _MirrorTypeChecker extends TypeChecker { String toString() => _computed.toString(); } +// Checks a runtime type name and optional package against a static type. +class _NameTypeChecker extends TypeChecker { + final Type _type; + + final String? _inPackage; + final bool _inSdk; + + const _NameTypeChecker(this._type, {String? inPackage, bool? inSdk}) + : _inPackage = inPackage, + _inSdk = inSdk ?? false, + super._(); + + String get _typeName { + final result = _type.toString(); + return result.contains('<') + ? result.substring(0, result.indexOf('<')) + : result; + } + + @override + bool isExactly(Element2 element) { + final uri = element.library2!.uri; + return element.name3 == _typeName && + (_inPackage == null || + (((uri.scheme == 'dart') == _inSdk) && + uri.pathSegments.first == _inPackage)); + } + + @override + String toString() => _inPackage == null ? '$_type' : '$_inPackage#$_type'; +} + // Checks a runtime type against an Uri and Symbol. class _UriTypeChecker extends TypeChecker { final String _url; diff --git a/source_gen/pubspec.yaml b/source_gen/pubspec.yaml index ae3eb26a..2f671051 100644 --- a/source_gen/pubspec.yaml +++ b/source_gen/pubspec.yaml @@ -1,5 +1,5 @@ name: source_gen -version: 3.0.0 +version: 3.1.0-wip description: >- Source code generation builders and utilities for the Dart build system repository: https://github.com/dart-lang/source_gen/tree/master/source_gen diff --git a/source_gen/test/constants_test.dart b/source_gen/test/constants_test.dart index f9196b56..cc963167 100644 --- a/source_gen/test/constants_test.dart +++ b/source_gen/test/constants_test.dart @@ -199,7 +199,11 @@ void main() { test('should compare using TypeChecker', () { final $deprecated = constants[8]; - const check = TypeChecker.fromRuntime(Deprecated); + const check = TypeChecker.typeNamed( + Deprecated, + inPackage: 'core', + inSdk: true, + ); expect($deprecated.instanceOf(check), isTrue, reason: '$deprecated'); }); }); diff --git a/source_gen/test/external_only_type_checker_test.dart b/source_gen/test/external_only_type_checker_test.dart index 7499856e..ce5f60a1 100644 --- a/source_gen/test/external_only_type_checker_test.dart +++ b/source_gen/test/external_only_type_checker_test.dart @@ -76,7 +76,9 @@ void main() { 'TypeChecker.forRuntime', () { commonTests( - checkNonPublic: () => const TypeChecker.fromRuntime(NonPublic), + checkNonPublic: + () => + const TypeChecker.typeNamed(NonPublic, inPackage: 'source_gen'), ); }, onPlatform: const { diff --git a/source_gen/test/type_checker_test.dart b/source_gen/test/type_checker_test.dart index f5307b63..bc30c8ee 100644 --- a/source_gen/test/type_checker_test.dart +++ b/source_gen/test/type_checker_test.dart @@ -335,15 +335,77 @@ void main() { group('TypeChecker.forRuntime', () { commonTests( + // ignore: deprecated_member_use_from_same_package checkIterable: () => const TypeChecker.fromRuntime(Iterable), + // ignore: deprecated_member_use_from_same_package checkEnum: () => const TypeChecker.fromRuntime(Enum), + // ignore: deprecated_member_use_from_same_package checkEnumMixin: () => const TypeChecker.fromRuntime(MyEnumMixin), + // ignore: deprecated_member_use_from_same_package checkMap: () => const TypeChecker.fromRuntime(Map), + // ignore: deprecated_member_use_from_same_package checkMapMixin: () => const TypeChecker.fromRuntime(MyMapMixin), + // ignore: deprecated_member_use_from_same_package checkHashMap: () => const TypeChecker.fromRuntime(HashMap), + // ignore: deprecated_member_use_from_same_package checkGenerator: () => const TypeChecker.fromRuntime(Generator), + + checkGeneratorForAnnotation: + // ignore: deprecated_member_use_from_same_packages + () => const TypeChecker.typeNamed( + GeneratorForAnnotation, + inPackage: 'source_gen', + ), + ); + }); + + group('TypeChecker.typeNamed without package', () { + commonTests( + checkIterable: () => const TypeChecker.typeNamed(Iterable), + checkEnum: () => const TypeChecker.typeNamed(Enum), + checkEnumMixin: () => const TypeChecker.typeNamed(MyEnumMixin), + checkMap: () => const TypeChecker.typeNamed(Map), + checkMapMixin: () => const TypeChecker.typeNamed(MyMapMixin), + checkHashMap: () => const TypeChecker.typeNamed(HashMap), + checkGenerator: () => const TypeChecker.typeNamed(Generator), checkGeneratorForAnnotation: - () => const TypeChecker.fromRuntime(GeneratorForAnnotation), + () => const TypeChecker.typeNamed(GeneratorForAnnotation), + ); + }); + + group('TypeChecker.typeNamed with package', () { + commonTests( + checkIterable: + () => const TypeChecker.typeNamed( + Iterable, + inPackage: 'core', + inSdk: true, + ), + checkEnum: + () => + const TypeChecker.typeNamed(Enum, inPackage: 'core', inSdk: true), + checkEnumMixin: + () => + const TypeChecker.typeNamed(MyEnumMixin, inPackage: 'source_gen'), + checkMap: + () => + const TypeChecker.typeNamed(Map, inPackage: 'core', inSdk: true), + checkMapMixin: + () => + const TypeChecker.typeNamed(MyMapMixin, inPackage: 'source_gen'), + checkHashMap: + () => const TypeChecker.typeNamed( + HashMap, + inPackage: 'collection', + inSdk: true, + ), + checkGenerator: + () => const TypeChecker.typeNamed(Generator, inPackage: 'source_gen'), + checkGeneratorForAnnotation: + () => const TypeChecker.typeNamed( + GeneratorForAnnotation, + inPackage: 'source_gen', + ), ); }); @@ -393,7 +455,11 @@ void main() { class X {} ''', (resolver) async => (await resolver.findLibraryByName('_test'))!); final classX = library.getClass2('X')!; - const $deprecated = TypeChecker.fromRuntime(Deprecated); + const $deprecated = TypeChecker.typeNamed( + Deprecated, + inPackage: 'core', + inSdk: true, + ); expect( () => $deprecated.annotationsOf(classX), @@ -412,8 +478,8 @@ void main() { test('should check multiple checkers', () { const listOrMap = TypeChecker.any([ - TypeChecker.fromRuntime(List), - TypeChecker.fromRuntime(Map), + TypeChecker.typeNamed(List, inPackage: 'core', inSdk: true), + TypeChecker.typeNamed(Map, inPackage: 'core', inSdk: true), ]); expect(listOrMap.isExactlyType(staticMap), isTrue); }); From a9f545008ed3b85b1ea5f3cf7c34d08bca79813c Mon Sep 17 00:00:00 2001 From: David Morgan Date: Wed, 6 Aug 2025 10:37:08 +0200 Subject: [PATCH 2/2] Address review comments. --- source_gen/lib/src/generator_for_annotation.dart | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/source_gen/lib/src/generator_for_annotation.dart b/source_gen/lib/src/generator_for_annotation.dart index ef977fdb..6e582f57 100644 --- a/source_gen/lib/src/generator_for_annotation.dart +++ b/source_gen/lib/src/generator_for_annotation.dart @@ -42,7 +42,15 @@ import 'type_checker.dart'; /// may be helpful to check which elements have a given annotation. abstract class GeneratorForAnnotation extends Generator { final bool throwOnUnresolved; + + /// Annotation package for [TypeChecker.typeNamed]. + /// + /// Currently unused, will be used from `source_gen` 4.0.0. final String? inPackage; + + /// Annotation package type for [TypeChecker.typeNamed]. + /// + /// Currently unused, will be used from `source_gen` 4.0.0. final bool? inSdk; /// By default, this generator will throw if it encounters unresolved @@ -51,7 +59,7 @@ abstract class GeneratorForAnnotation extends Generator { /// /// With `source_gen` 4.0.0 this class will stop using mirrors for matching /// annotations and will fall back to comparing the name of `T`. Pass - /// [inPackage] and [inSdk] to tighten the check; see [TypeChecker#typeNamed]. + /// [inPackage] and [inSdk] to tighten the check; see [TypeChecker.typeNamed]. /// To use a custom annotation check, override [typeChecker]. const GeneratorForAnnotation({ this.throwOnUnresolved = true,