Skip to content

Commit cf56cfe

Browse files
kevmoomatanlurey
authored andcommitted
isAssignable, isSuper fix (#183)
* TypeChecker: Add test for generic types * Clarify the behavior of isSuper and isAssignable and fixe related issue due to bug in pkg/analyzer dart-lang/sdk#29767 * oops
1 parent cf9bd92 commit cf56cfe

File tree

3 files changed

+83
-10
lines changed

3 files changed

+83
-10
lines changed

lib/generators/json_serializable_generator.dart

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -310,8 +310,9 @@ DartType _getImplementationType(DartType type, TypeChecker checker) {
310310
if (checker.isExactlyType(type)) return type;
311311

312312
if (type is InterfaceType) {
313-
var tests =
314-
type.interfaces.map((type) => _getImplementationType(type, checker));
313+
var tests = [type.interfaces, type.mixins]
314+
.expand((e) => e)
315+
.map((type) => _getImplementationType(type, checker));
315316
var match = _firstNotNull(tests);
316317

317318
if (match != null) return match;

lib/src/type_checker.dart

Lines changed: 49 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -52,13 +52,13 @@ abstract class TypeChecker {
5252
.map((a) => a.computeConstantValue())
5353
.where((a) => isExactlyType(a.type));
5454

55-
/// Returns `true` if representing the exact same class as or a superclass of
56-
/// [element]
55+
/// Returns `true` if the type of [element] can be assigned to the type
56+
/// represented by `this`.
5757
bool isAssignableFrom(Element element) =>
58-
isExactly(element) || isSuperOf(element);
58+
isExactly(element) || _getAllSupertypes(element).any(isExactlyType);
5959

60-
/// Returns `true` if representing the exact same type as or a supertype of
61-
/// [staticType].
60+
/// Returns `true` if [staticType] can be assigned to the type represented
61+
/// by `this`.
6262
bool isAssignableFromType(DartType staticType) =>
6363
isAssignableFrom(staticType.element);
6464

@@ -69,13 +69,55 @@ abstract class TypeChecker {
6969
bool isExactlyType(DartType staticType) => isExactly(staticType.element);
7070

7171
/// Returns `true` if representing a super class of [element].
72-
bool isSuperOf(Element element) =>
73-
element is ClassElement && element.allSupertypes.any(isExactlyType);
72+
///
73+
/// This check only takes into account the *extends* hierarchy. If you wish
74+
/// to check mixins and interfaces, use [isAssignableFrom].
75+
bool isSuperOf(Element element) {
76+
if (element is ClassElement) {
77+
var theSuper = element.supertype;
78+
79+
do {
80+
if (isExactlyType(theSuper)) {
81+
return true;
82+
}
83+
84+
theSuper = theSuper.superclass;
85+
} while (theSuper != null);
86+
}
87+
88+
return false;
89+
}
7490

7591
/// Returns `true` if representing a super type of [staticType].
92+
///
93+
/// This only takes into account the *extends* hierarchy. If you wish
94+
/// to check mixins and interfaces, use [isAssignableFromType].
7695
bool isSuperTypeOf(DartType staticType) => isSuperOf(staticType.element);
7796
}
7897

98+
//TODO(kevmoo) Remove when bug with `ClassElement.allSupertypes` is fixed
99+
// https://github.com/dart-lang/sdk/issues/29767
100+
Iterable<InterfaceType> _getAllSupertypes(Element element) sync* {
101+
if (element is ClassElement) {
102+
var processed = new Set<InterfaceType>();
103+
var toExplore = new List<InterfaceType>.from(element.allSupertypes);
104+
105+
while (toExplore.isNotEmpty) {
106+
var item = toExplore.removeLast();
107+
108+
if (processed.add(item)) {
109+
yield item;
110+
111+
// Now drill into nested superTypes - but make sure not to duplicate
112+
// any of them.
113+
toExplore.addAll(item.element.allSupertypes.where((e) {
114+
return !toExplore.contains(e) && !processed.contains(e);
115+
}));
116+
}
117+
}
118+
}
119+
}
120+
79121
Uri _normalizeUrl(Uri url) {
80122
switch (url.scheme) {
81123
case 'dart':

test/type_checker_test.dart

Lines changed: 31 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,11 @@ import 'package:test/test.dart';
1212

1313
void main() {
1414
// Resolved top-level types from dart:core and dart:collection.
15+
InterfaceType staticUri;
1516
DartType staticMap;
1617
DartType staticHashMap;
18+
DartType staticUnmodifiableListView;
19+
TypeChecker staticIterableChecker;
1720
TypeChecker staticMapChecker;
1821
TypeChecker staticHashMapChecker;
1922

@@ -29,12 +32,17 @@ void main() {
2932
''');
3033

3134
final core = resolver.getLibraryByName('dart.core');
35+
var staticIterable = core.getType('Iterable').type;
36+
staticIterableChecker = new TypeChecker.fromStatic(staticIterable);
37+
staticUri = core.getType('Uri').type;
3238
staticMap = core.getType('Map').type;
3339
staticMapChecker = new TypeChecker.fromStatic(staticMap);
3440

3541
final collection = resolver.getLibraryByName('dart.collection');
3642
staticHashMap = collection.getType('HashMap').type;
3743
staticHashMapChecker = new TypeChecker.fromStatic(staticHashMap);
44+
staticUnmodifiableListView =
45+
collection.getType('UnmodifiableListView').type;
3846

3947
final sourceGen =
4048
new LibraryReader(resolver.getLibraryByName('source_gen'));
@@ -48,11 +56,20 @@ void main() {
4856

4957
// Run a common set of type comparison checks with various implementations.
5058
void commonTests({
59+
@required TypeChecker checkIterable(),
5160
@required TypeChecker checkMap(),
5261
@required TypeChecker checkHashMap(),
5362
@required TypeChecker checkGenerator(),
5463
@required TypeChecker checkGeneratorForAnnotation(),
5564
}) {
65+
group('(Iterable)', () {
66+
test('should be assignable from dart:collection#UnmodifiableListView',
67+
() {
68+
expect(checkIterable().isAssignableFromType(staticUnmodifiableListView),
69+
true);
70+
});
71+
});
72+
5673
group('(Map)', () {
5774
test('should equal dart:core#Map', () {
5875
expect(checkMap().isExactlyType(staticMap), isTrue,
@@ -69,12 +86,22 @@ void main() {
6986
});
7087

7188
test('should be a super type of dart:collection#HashMap', () {
72-
expect(checkMap().isSuperTypeOf(staticHashMap), isTrue);
89+
expect(checkMap().isSuperTypeOf(staticHashMap), isFalse);
7390
});
7491

7592
test('should be assignable from dart:collection#HashMap', () {
7693
expect(checkMap().isAssignableFromType(staticHashMap), isTrue);
7794
});
95+
96+
// Ensure we're consistent WRT generic types
97+
test('should be assignable from Map<String, String>', () {
98+
// Using Uri.queryParamaters to get a Map<String, String>
99+
var stringStringMapType =
100+
staticUri.getGetter('queryParameters').returnType;
101+
102+
expect(checkMap().isAssignableFromType(stringStringMapType), isTrue);
103+
expect(checkMap().isExactlyType(stringStringMapType), isTrue);
104+
});
78105
});
79106

80107
group('(HashMap)', () {
@@ -122,6 +149,7 @@ void main() {
122149

123150
group('TypeChecker.forRuntime', () {
124151
commonTests(
152+
checkIterable: () => const TypeChecker.fromRuntime(Iterable),
125153
checkMap: () => const TypeChecker.fromRuntime(Map),
126154
checkHashMap: () => const TypeChecker.fromRuntime(HashMap),
127155
checkGenerator: () => const TypeChecker.fromRuntime(Generator),
@@ -131,6 +159,7 @@ void main() {
131159

132160
group('TypeChecker.forStatic', () {
133161
commonTests(
162+
checkIterable: () => staticIterableChecker,
134163
checkMap: () => staticMapChecker,
135164
checkHashMap: () => staticHashMapChecker,
136165
checkGenerator: () => staticGeneratorChecker,
@@ -139,6 +168,7 @@ void main() {
139168

140169
group('TypeChecker.fromUrl', () {
141170
commonTests(
171+
checkIterable: () => const TypeChecker.fromUrl('dart:core#Iterable'),
142172
checkMap: () => const TypeChecker.fromUrl('dart:core#Map'),
143173
checkHashMap: () =>
144174
const TypeChecker.fromUrl('dart:collection#HashMap'),

0 commit comments

Comments
 (0)