From d5fc2732847e2f17d4a850c6af27b15e4a9378a1 Mon Sep 17 00:00:00 2001 From: Matan Lurey Date: Tue, 4 Jul 2017 20:12:27 -0700 Subject: [PATCH 1/6] Add annotationsOfExact. --- CHANGELOG.md | 7 +++++++ lib/src/type_checker.dart | 26 +++++++++++++++++++------- pubspec.yaml | 2 +- test/type_checker_test.dart | 2 ++ 4 files changed, 29 insertions(+), 8 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 127e42f2..bd7803aa 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,10 @@ +## 0.6.0 + +* **Breaking change**: `TypeChecker#annotationsOf|firstAnnotationOf` now + returns annotations that are _assignable_ to the `TypeChecker`'s type. As a + result we've added `#annotationsOfExact|firstAnnotationOfExact` which has the + old behavior for precise checks. + ## 0.5.10+1 * Update minimum `analyzer` package to `0.29.10`. diff --git a/lib/src/type_checker.dart b/lib/src/type_checker.dart index 3c24864c..5e91470e 100644 --- a/lib/src/type_checker.dart +++ b/lib/src/type_checker.dart @@ -38,7 +38,7 @@ abstract class TypeChecker { /// package like in the `dart:` SDK. const factory TypeChecker.fromUrl(dynamic url) = _UriTypeChecker; - /// Returns the first constant annotating [element] that is this type. + /// Returns the first constant annotating [element] assignable to this type. /// /// Otherwise returns `null`. DartObject firstAnnotationOf(Element element) { @@ -49,18 +49,30 @@ abstract class TypeChecker { return results.isEmpty ? null : results.first; } - /// Returns every constant annotating [element] that is this type. + /// Returns the first constant annotating [element] that is exactly this type. + DartObject firstAnnotationOfExact(Element element) { + if (element.metadata.isEmpty) { + return null; + } + final results = annotationsOfExact(element); + return results.isEmpty ? null : results.first; + } + + /// Returns annotating constants on [element] assignable to this type. Iterable annotationsOf(Element element) => element.metadata + .map((a) => a.computeConstantValue()) + .where((a) => isAssignableFromType(a.type)); + + /// Returns annotating constants on [element] of exactly this type. + Iterable annotationsOfExact(Element element) => element.metadata .map((a) => a.computeConstantValue()) .where((a) => isExactlyType(a.type)); - /// Returns `true` if the type of [element] can be assigned to the type - /// represented by `this`. + /// Returns `true` if the type of [element] can be assigned to this type. bool isAssignableFrom(Element element) => isExactly(element) || _getAllSupertypes(element).any(isExactlyType); - /// Returns `true` if [staticType] can be assigned to the type represented - /// by `this`. + /// Returns `true` if [staticType] can be assigned to this type. bool isAssignableFromType(DartType staticType) => isAssignableFrom(staticType.element); @@ -97,7 +109,7 @@ abstract class TypeChecker { bool isSuperTypeOf(DartType staticType) => isSuperOf(staticType.element); } -//TODO(kevmoo) Remove when bug with `ClassElement.allSupertypes` is fixed +// TODO(kevmoo) Remove when bug with `ClassElement.allSupertypes` is fixed // https://github.com/dart-lang/sdk/issues/29767 Iterable _getAllSupertypes(Element element) sync* { if (element is ClassElement) { diff --git a/pubspec.yaml b/pubspec.yaml index 725a6e84..4e73c121 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,5 +1,5 @@ name: source_gen -version: 0.5.10+1 +version: 0.6.0-dev author: Dart Team description: Automated source code generation for Dart. homepage: https://github.com/dart-lang/source_gen diff --git a/test/type_checker_test.dart b/test/type_checker_test.dart index b13cd6c6..d5e6d8bc 100644 --- a/test/type_checker_test.dart +++ b/test/type_checker_test.dart @@ -2,6 +2,8 @@ // for details. All rights reserved. Use of this source code is governed by a // BSD-style license that can be found in the LICENSE file. +// Increase timeouts on this test which resolves source code and can be slow. +@Timeout.factor(2.0) import 'dart:collection'; import 'package:analyzer/dart/element/type.dart'; From 108c31af48253a800bd3b70176c277504742b08c Mon Sep 17 00:00:00 2001 From: Matan Lurey Date: Tue, 4 Jul 2017 20:25:37 -0700 Subject: [PATCH 2/6] Add type checks. --- lib/src/type_checker.dart | 18 ++++++++++++++---- test/type_checker_test.dart | 15 +++++++++++++++ 2 files changed, 29 insertions(+), 4 deletions(-) diff --git a/lib/src/type_checker.dart b/lib/src/type_checker.dart index 5e91470e..6c091b40 100644 --- a/lib/src/type_checker.dart +++ b/lib/src/type_checker.dart @@ -58,15 +58,25 @@ abstract class TypeChecker { return results.isEmpty ? null : results.first; } + DartObject _checkedConstantValue(ElementAnnotation annotation) { + final result = annotation.computeConstantValue(); + if (result == null) { + throw new StateError( + 'Could not resolve $annotation. An import or dependency may be ' + 'missing or invalid.'); + } + return result; + } + /// Returns annotating constants on [element] assignable to this type. Iterable annotationsOf(Element element) => element.metadata - .map((a) => a.computeConstantValue()) - .where((a) => isAssignableFromType(a.type)); + .map(_checkedConstantValue) + .where((a) => a?.type != null && isAssignableFromType(a.type)); /// Returns annotating constants on [element] of exactly this type. Iterable annotationsOfExact(Element element) => element.metadata - .map((a) => a.computeConstantValue()) - .where((a) => isExactlyType(a.type)); + .map(_checkedConstantValue) + .where((a) => a?.type != null && isExactlyType(a.type)); /// Returns `true` if the type of [element] can be assigned to this type. bool isAssignableFrom(Element element) => diff --git a/test/type_checker_test.dart b/test/type_checker_test.dart index d5e6d8bc..ae127b6c 100644 --- a/test/type_checker_test.dart +++ b/test/type_checker_test.dart @@ -179,4 +179,19 @@ void main() { checkGeneratorForAnnotation: () => const TypeChecker.fromUrl( 'package:source_gen/src/generator_for_annotation.dart#GeneratorForAnnotation')); }); + + test('should gracefully when something is not resolvable', () async { + final resolver = await resolveSource(r''' + library _test; + + @depracated // Intentionally mispelled. + class X {} + '''); + final lib = resolver.getLibraryByName('_test'); + final classX = lib.getType('X'); + final $deprecated = const TypeChecker.fromRuntime(Deprecated); + + expect(() => $deprecated.annotationsOf(classX), throwsStateError, + reason: 'deprecated was spelled wrong; no annotation can be resolved'); + }); } From 779215b3ed532e2686ee698b69d5aa0682ec6a36 Mon Sep 17 00:00:00 2001 From: Matan Lurey Date: Tue, 4 Jul 2017 20:27:32 -0700 Subject: [PATCH 3/6] Update CHANGELOG. --- CHANGELOG.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index bd7803aa..2a22470f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,10 @@ result we've added `#annotationsOfExact|firstAnnotationOfExact` which has the old behavior for precise checks. +* `TypeChecker#annotations...`-methods now throw a `StateError` if one or more + annotations on an element are not resolvable. This is usually a sign of a + mispelling, missing import, or missing dependency. + ## 0.5.10+1 * Update minimum `analyzer` package to `0.29.10`. From 977255deab97216dbf6ea6baa398cac59615baef Mon Sep 17 00:00:00 2001 From: Matan Lurey Date: Tue, 4 Jul 2017 20:37:01 -0700 Subject: [PATCH 4/6] Add TypeChecker.any. --- lib/src/type_checker.dart | 23 +++++++++++++++++++++++ test/type_checker_test.dart | 11 ++++++++++- 2 files changed, 33 insertions(+), 1 deletion(-) diff --git a/lib/src/type_checker.dart b/lib/src/type_checker.dart index 6c091b40..e1d4a8e5 100644 --- a/lib/src/type_checker.dart +++ b/lib/src/type_checker.dart @@ -14,6 +14,20 @@ import 'utils.dart'; abstract class TypeChecker { const TypeChecker._(); + /// Creates a new [TypeChecker] that delegates to other [checkers]. + /// + /// This implementation will return `true` for type checks if _any_ of the + /// provided type checkers return true, which is useful for deprecating an + /// API: + /// ```dart + /// const $Foo = const TypeChecker.fromRuntime(Foo); + /// const $Bar = const TypeChecker.fromRuntime(Bar); + /// + /// // Used until $Foo is deleted. + /// const $FooOrBar = const TypeChecker.forAny(const [$Foo, $Bar]); + /// ``` + const factory TypeChecker.any(Iterable checkers) = _AnyChecker; + /// Create a new [TypeChecker] backed by a runtime [type]. /// /// This implementation uses `dart:mirrors` (runtime reflection). @@ -209,3 +223,12 @@ class _UriTypeChecker extends TypeChecker { @override String toString() => '${uri}'; } + +class _AnyChecker extends TypeChecker { + final Iterable _checkers; + + const _AnyChecker(this._checkers) : super._(); + + @override + bool isExactly(Element element) => _checkers.any((c) => c.isExactly(element)); +} diff --git a/test/type_checker_test.dart b/test/type_checker_test.dart index ae127b6c..2d1426cd 100644 --- a/test/type_checker_test.dart +++ b/test/type_checker_test.dart @@ -6,6 +6,7 @@ @Timeout.factor(2.0) import 'dart:collection'; +import 'package:analyzer/dart/element/element.dart'; import 'package:analyzer/dart/element/type.dart'; import 'package:build_test/build_test.dart'; import 'package:meta/meta.dart'; @@ -180,7 +181,7 @@ void main() { 'package:source_gen/src/generator_for_annotation.dart#GeneratorForAnnotation')); }); - test('should gracefully when something is not resolvable', () async { + test('should fail gracefully when something is not resolvable', () async { final resolver = await resolveSource(r''' library _test; @@ -194,4 +195,12 @@ void main() { expect(() => $deprecated.annotationsOf(classX), throwsStateError, reason: 'deprecated was spelled wrong; no annotation can be resolved'); }); + + test('should check multiple checkers', () { + final listOrMap = const TypeChecker.any(const [ + const TypeChecker.fromRuntime(List), + const TypeChecker.fromRuntime(Map), + ]); + expect(listOrMap.isExactlyType(staticMap), isTrue); + }); } From fe9d9e3b5d0961e2ae1ecb7a83f6d0bc0f4a481e Mon Sep 17 00:00:00 2001 From: Matan Lurey Date: Tue, 4 Jul 2017 20:37:44 -0700 Subject: [PATCH 5/6] CHANGELOG. --- CHANGELOG.md | 3 +++ test/type_checker_test.dart | 1 - 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2a22470f..e574e3b4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,9 @@ * `TypeChecker#annotations...`-methods now throw a `StateError` if one or more annotations on an element are not resolvable. This is usually a sign of a mispelling, missing import, or missing dependency. + +* Added `TypeChecker.any`, which delegates to multiple other `TypeChecker` + implementations when making a type check. ## 0.5.10+1 diff --git a/test/type_checker_test.dart b/test/type_checker_test.dart index 2d1426cd..da5bd0d0 100644 --- a/test/type_checker_test.dart +++ b/test/type_checker_test.dart @@ -6,7 +6,6 @@ @Timeout.factor(2.0) import 'dart:collection'; -import 'package:analyzer/dart/element/element.dart'; import 'package:analyzer/dart/element/type.dart'; import 'package:build_test/build_test.dart'; import 'package:meta/meta.dart'; From 03361ea8b264dec4e07498d54312808df24e5775 Mon Sep 17 00:00:00 2001 From: Matan Lurey Date: Thu, 6 Jul 2017 09:32:09 -0700 Subject: [PATCH 6/6] . --- pubspec.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pubspec.yaml b/pubspec.yaml index 4e73c121..3a252fbd 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,5 +1,5 @@ name: source_gen -version: 0.6.0-dev +version: 0.6.0 author: Dart Team description: Automated source code generation for Dart. homepage: https://github.com/dart-lang/source_gen