Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 8 additions & 2 deletions source_gen/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
## 3.0.1-wip

## 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`.
- Allow `analyzer: '>=7.4.0 <9.0.0'`.

## 3.0.0
Expand Down
23 changes: 22 additions & 1 deletion source_gen/lib/src/generator_for_annotation.dart
Original file line number Diff line number Diff line change
Expand Up @@ -43,11 +43,32 @@ import 'type_checker.dart';
abstract class GeneratorForAnnotation<T> 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
/// 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
Expand Down
51 changes: 51 additions & 0 deletions source_gen/lib/src/type_checker.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down Expand Up @@ -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;
Expand Down
2 changes: 1 addition & 1 deletion source_gen/pubspec.yaml
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
name: source_gen
version: 3.0.1-wip
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
Expand Down
6 changes: 5 additions & 1 deletion source_gen/test/constants_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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');
});
});
Expand Down
4 changes: 3 additions & 1 deletion source_gen/test/external_only_type_checker_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,9 @@ void main() {
'TypeChecker.forRuntime',
() {
commonTests(
checkNonPublic: () => const TypeChecker.fromRuntime(NonPublic),
checkNonPublic:
() =>
const TypeChecker.typeNamed(NonPublic, inPackage: 'source_gen'),
);
},
onPlatform: const {
Expand Down
74 changes: 70 additions & 4 deletions source_gen/test/type_checker_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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',
),
);
});

Expand Down Expand Up @@ -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),
Expand All @@ -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);
});
Expand Down
Loading