Skip to content

Commit e856173

Browse files
authored
Add better errors to TypeChecker. (#317)
* Add better errors to TypeChecker. * Add bug. * Fix tests. * Only dartfmt in dev. * Fix test and travis. * Address feedback.
1 parent aa3e8d1 commit e856173

6 files changed

+134
-32
lines changed

.travis.yml

+1-2
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,7 @@ language: dart
22

33
dart:
44
- dev
5-
# Flutter Alpha @ v0.0.23
6-
- 2.0.0-dev.19.0
5+
76
dart_task:
87
- test
98
- dartfmt

CHANGELOG.md

+6
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,9 @@
1+
## 0.7.6
2+
3+
* `TypeChecker` now throws an `UnresolvedAnnotationException` with a more
4+
detailed exception body (and properties useful for further debugging) instead
5+
of `Could not resolve @null`.
6+
17
## 0.7.5+1
28

39
* `LibraryBuilder` and `PartBuilder` now have a more readable `toString()`,

lib/source_gen.dart

+1-1
Original file line numberDiff line numberDiff line change
@@ -11,5 +11,5 @@ export 'src/generator.dart';
1111
export 'src/generator_for_annotation.dart';
1212
export 'src/library.dart' show AnnotatedElement, LibraryReader;
1313
export 'src/span_for_element.dart' show spanForElement;
14-
export 'src/type_checker.dart' show TypeChecker;
14+
export 'src/type_checker.dart' show TypeChecker, UnresolvedAnnotationException;
1515
export 'src/utils.dart' show typeNameOf;

lib/src/type_checker.dart

+104-22
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,15 @@
22
// for details. All rights reserved. Use of this source code is governed by a
33
// BSD-style license that can be found in the LICENSE file.
44

5-
import 'dart:mirrors';
5+
import 'dart:mirrors' hide SourceLocation;
66

77
import 'package:analyzer/dart/constant/value.dart';
88
import 'package:analyzer/dart/element/element.dart';
99
import 'package:analyzer/dart/element/type.dart';
10+
// TODO(https://github.com/dart-lang/sdk/issues/32454):
11+
// ignore: implementation_imports
12+
import 'package:analyzer/src/dart/element/element.dart';
13+
import 'package:source_span/source_span.dart';
1014

1115
import 'utils.dart';
1216

@@ -74,7 +78,8 @@ abstract class TypeChecker {
7478

7579
/// Returns the first constant annotating [element] that is exactly this type.
7680
///
77-
/// Throws on unresolved annotations unless [throwOnUnresolved] is `false`.
81+
/// Throws [UnresolvedAnnotationException] on unresolved annotations unless
82+
/// [throwOnUnresolved] is explicitly set to `false` (default is `true`).
7883
DartObject firstAnnotationOfExact(Element element, {bool throwOnUnresolved}) {
7984
if (element.metadata.isEmpty) {
8085
return null;
@@ -86,42 +91,70 @@ abstract class TypeChecker {
8691

8792
/// Returns if a constant annotating [element] is exactly this type.
8893
///
89-
/// Throws on unresolved annotations unless [throwOnUnresolved] is `false`.
94+
/// Throws [UnresolvedAnnotationException] on unresolved annotations unless
95+
/// [throwOnUnresolved] is explicitly set to `false` (default is `true`).
9096
bool hasAnnotationOfExact(Element element, {bool throwOnUnresolved}) =>
9197
firstAnnotationOfExact(element, throwOnUnresolved: throwOnUnresolved) !=
9298
null;
9399

94-
DartObject _computeConstantValue(ElementAnnotation annotation,
95-
{bool throwOnUnresolved}) {
100+
DartObject _computeConstantValue(
101+
Element element,
102+
int annotationIndex, {
103+
bool throwOnUnresolved,
104+
}) {
96105
throwOnUnresolved ??= true;
106+
final annotation = element.metadata[annotationIndex];
97107
final result = annotation.computeConstantValue();
98108
if (result == null && throwOnUnresolved) {
99-
throw new StateError(
100-
'Could not resolve $annotation. An import or dependency may be '
101-
'missing or invalid.');
109+
throw new UnresolvedAnnotationException._from(element, annotationIndex);
102110
}
103111
return result;
104112
}
105113

106114
/// Returns annotating constants on [element] assignable to this type.
107115
///
108-
/// Throws on unresolved annotations unless [throwOnUnresolved] is `false`.
109-
Iterable<DartObject> annotationsOf(Element element,
110-
{bool throwOnUnresolved}) =>
111-
element.metadata
112-
.map((annotation) => _computeConstantValue(annotation,
113-
throwOnUnresolved: throwOnUnresolved))
114-
.where((a) => a?.type != null && isAssignableFromType(a.type));
116+
/// Throws [UnresolvedAnnotationException] on unresolved annotations unless
117+
/// [throwOnUnresolved] is explicitly set to `false` (default is `true`).
118+
Iterable<DartObject> annotationsOf(
119+
Element element, {
120+
bool throwOnUnresolved,
121+
}) =>
122+
_annotationsWhere(
123+
element,
124+
isAssignableFromType,
125+
throwOnUnresolved: throwOnUnresolved,
126+
);
127+
128+
Iterable<DartObject> _annotationsWhere(
129+
Element element,
130+
bool Function(DartType) predicate, {
131+
bool throwOnUnresolved,
132+
}) sync* {
133+
for (var i = 0; i < element.metadata.length; i++) {
134+
final value = _computeConstantValue(
135+
element,
136+
i,
137+
throwOnUnresolved: throwOnUnresolved,
138+
);
139+
if (value?.type != null && predicate(value.type)) {
140+
yield value;
141+
}
142+
}
143+
}
115144

116145
/// Returns annotating constants on [element] of exactly this type.
117146
///
118-
/// Throws on unresolved annotations unless [throwOnUnresolved] is `false`.
119-
Iterable<DartObject> annotationsOfExact(Element element,
120-
{bool throwOnUnresolved}) =>
121-
element.metadata
122-
.map((annotation) => _computeConstantValue(annotation,
123-
throwOnUnresolved: throwOnUnresolved))
124-
.where((a) => a?.type != null && isExactlyType(a.type));
147+
/// Throws [UnresolvedAnnotationException] on unresolved annotations unless
148+
/// [throwOnUnresolved] is explicitly set to `false` (default is `true`).
149+
Iterable<DartObject> annotationsOfExact(
150+
Element element, {
151+
bool throwOnUnresolved,
152+
}) =>
153+
_annotationsWhere(
154+
element,
155+
isExactlyType,
156+
throwOnUnresolved: throwOnUnresolved,
157+
);
125158

126159
/// Returns `true` if the type of [element] can be assigned to this type.
127160
bool isAssignableFrom(Element element) =>
@@ -242,3 +275,52 @@ class _AnyChecker extends TypeChecker {
242275
@override
243276
bool isExactly(Element element) => _checkers.any((c) => c.isExactly(element));
244277
}
278+
279+
/// Exception thrown when [TypeChecker] fails to resolve a metadata annotation.
280+
///
281+
/// Methods such as [TypeChecker.firstAnnotationOf] may throw this exception
282+
/// when one or more annotations are not resolvable. This is usually a sign that
283+
/// something was misspelled, an import is missing, or a dependency was not
284+
/// defined (for build systems such as Bazel).
285+
class UnresolvedAnnotationException implements Exception {
286+
/// Element that was annotated with something we could not resolve.
287+
final Element annotatedElement;
288+
289+
/// Source span of the annotation that was not resolved.
290+
final SourceSpan annotationSource;
291+
292+
// TODO: Remove internal API once ElementAnnotation has source information.
293+
// https://github.com/dart-lang/sdk/issues/32454
294+
static SourceSpan _getSourceSpanFrom(ElementAnnotation annotation) {
295+
final internals = annotation as ElementAnnotationImpl;
296+
final astNode = internals.annotationAst;
297+
final contents = annotation.source.contents.data;
298+
final start = astNode.offset;
299+
final end = start + astNode.length;
300+
return new SourceSpan(
301+
new SourceLocation(start, sourceUrl: annotation.source.uri),
302+
new SourceLocation(end, sourceUrl: annotation.source.uri),
303+
contents.substring(start, end),
304+
);
305+
}
306+
307+
/// Creates an exception from an annotation ([annotationIndex]) that was not
308+
/// resolvable while traversing [Element.metadata] on [annotatedElement].
309+
factory UnresolvedAnnotationException._from(
310+
Element annotatedElement,
311+
int annotationIndex,
312+
) {
313+
final annotation = annotatedElement.metadata[annotationIndex];
314+
final sourceSpan = _getSourceSpanFrom(annotation);
315+
return new UnresolvedAnnotationException._(annotatedElement, sourceSpan);
316+
}
317+
318+
const UnresolvedAnnotationException._(
319+
this.annotatedElement,
320+
this.annotationSource,
321+
);
322+
323+
@override
324+
String toString() => annotationSource
325+
.message('Could not resolve annotation for $annotatedElement');
326+
}

pubspec.yaml

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
name: source_gen
2-
version: 0.7.6-dev
2+
version: 0.7.6
33
author: Dart Team <misc@dartlang.org>
44
description: Automated source code generation for Dart.
55
homepage: https://github.com/dart-lang/source_gen

test/type_checker_test.dart

+21-6
Original file line numberDiff line numberDiff line change
@@ -195,8 +195,16 @@ void main() {
195195
final classX = library.getType('X');
196196
final $deprecated = const TypeChecker.fromRuntime(Deprecated);
197197

198-
expect(() => $deprecated.annotationsOf(classX), throwsStateError,
199-
reason: 'deprecated was spelled wrong; no annotation can be resolved');
198+
expect(
199+
() => $deprecated.annotationsOf(classX),
200+
throwsA(allOf(
201+
const isInstanceOf<UnresolvedAnnotationException>(),
202+
predicate((e) => e
203+
.toString()
204+
.contains('Could not resolve annotation for class X')),
205+
predicate((e) => e.toString().contains('@depracated')))),
206+
reason: 'deprecated was spelled wrong; no annotation can be resolved',
207+
);
200208
});
201209

202210
test('should check multiple checkers', () {
@@ -313,10 +321,14 @@ void main() {
313321
});
314322

315323
test('should throw by default', () {
316-
expect(() => $A.firstAnnotationOf($ExampleOfA), throwsStateError);
317-
expect(() => $A.annotationsOf($ExampleOfA), throwsStateError);
318-
expect(() => $A.firstAnnotationOfExact($ExampleOfA), throwsStateError);
319-
expect(() => $A.annotationsOfExact($ExampleOfA), throwsStateError);
324+
expect(() => $A.firstAnnotationOf($ExampleOfA),
325+
throwsUnresolvedAnnotationException);
326+
expect(() => $A.annotationsOf($ExampleOfA),
327+
throwsUnresolvedAnnotationException);
328+
expect(() => $A.firstAnnotationOfExact($ExampleOfA),
329+
throwsUnresolvedAnnotationException);
330+
expect(() => $A.annotationsOfExact($ExampleOfA),
331+
throwsUnresolvedAnnotationException);
320332
});
321333

322334
test('should not throw if `throwOnUnresolved` == false', () {
@@ -342,3 +354,6 @@ void main() {
342354
});
343355
});
344356
}
357+
358+
final throwsUnresolvedAnnotationException =
359+
throwsA(const isInstanceOf<UnresolvedAnnotationException>());

0 commit comments

Comments
 (0)