Skip to content

Commit 199c7b4

Browse files
authored
Display indirectly implemented types via extended and mixed-in types (#3855)
1 parent b47a0c2 commit 199c7b4

File tree

4 files changed

+160
-59
lines changed

4 files changed

+160
-59
lines changed

lib/src/model/inheriting_container.dart

Lines changed: 36 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -453,38 +453,52 @@ abstract class InheritingContainer extends Container {
453453
}
454454
}
455455

456-
for (var interface in directInterfaces) {
457-
var interfaceElement = interface.modelElement;
456+
void addFromSupertype(DefinedElementType supertype,
457+
{required bool addSupertypes}) {
458+
var superElement = supertype.modelElement;
458459

459460
/// Do not recurse if we can find an element here.
460-
if (interfaceElement.canonicalModelElement != null) {
461-
addInterfaceIfUnique(interface);
462-
continue;
461+
if (superElement.canonicalModelElement != null) {
462+
if (addSupertypes) addInterfaceIfUnique(supertype);
463+
return;
463464
}
464-
// Public types used to be unconditionally exposed here. However,
465-
// if the packages are [DocumentLocation.missing] we generally treat types
466-
// defined in them as actually defined in a documented package.
467-
// That translates to them being defined here, but in 'src/' or similar,
468-
// and so, are now always hidden.
469-
470-
// This type is not backed by a canonical Class; search
471-
// the superchain and publicInterfaces of this interface to pretend
472-
// as though the hidden class didn't exist and this class was declared
473-
// directly referencing the canonical classes further up the chain.
474-
if (interfaceElement is! InheritingContainer) {
465+
466+
// This type is not backed by a canonical Class; it is not documented.
467+
// Search it's `superChain` and `publicInterfaces` to pretend that `this`
468+
// container directly implements canonical classes further up the chain.
469+
470+
if (superElement is! InheritingContainer) {
475471
assert(
476472
false,
477-
'Can not handle intermediate non-public interfaces created by '
473+
'Cannot handle intermediate non-public interfaces created by '
478474
"ModelElements that are not classes or mixins: '$fullyQualifiedName' "
479-
"contains an interface '$interface', defined by '$interfaceElement'",
475+
"contains a supertype '$supertype', defined by '$superElement'",
480476
);
481-
continue;
477+
return;
482478
}
483-
var publicSuperChain = interfaceElement.superChain.wherePublic;
484-
if (publicSuperChain.isNotEmpty) {
479+
var publicSuperChain = superElement.superChain.wherePublic;
480+
if (publicSuperChain.isNotEmpty && addSupertypes) {
485481
addInterfaceIfUnique(publicSuperChain.first);
486482
}
487-
interfaceElement.publicInterfaces.forEach(addInterfaceIfUnique);
483+
superElement.publicInterfaces.forEach(addInterfaceIfUnique);
484+
}
485+
486+
for (var interface in directInterfaces) {
487+
addFromSupertype(interface, addSupertypes: true);
488+
}
489+
for (var supertype in superChain) {
490+
var interfaceElement = supertype.modelElement;
491+
492+
// Do not recurse if we can find an element here.
493+
if (interfaceElement.canonicalModelElement != null) {
494+
continue;
495+
}
496+
addFromSupertype(supertype, addSupertypes: false);
497+
}
498+
if (this case Class(:var mixedInTypes) || Enum(:var mixedInTypes)) {
499+
for (var mixin in mixedInTypes) {
500+
addFromSupertype(mixin, addSupertypes: false);
501+
}
488502
}
489503
return interfaces;
490504
}

test/classes_test.dart

Lines changed: 69 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -25,9 +25,9 @@ class C {}
2525
extension Ex on C {}
2626
''');
2727

28-
var f = library.classes.named('C');
28+
var c = library.classes.named('C');
2929
expect(
30-
f.potentiallyApplicableExtensionsSorted,
30+
c.potentiallyApplicableExtensionsSorted,
3131
contains(library.extensions.named('Ex')),
3232
);
3333
}
@@ -38,9 +38,9 @@ class C<T> {}
3838
extension Ex on C<int> {}
3939
''');
4040

41-
var f = library.classes.named('C');
41+
var c = library.classes.named('C');
4242
expect(
43-
f.potentiallyApplicableExtensionsSorted,
43+
c.potentiallyApplicableExtensionsSorted,
4444
contains(library.extensions.named('Ex')),
4545
);
4646
}
@@ -52,9 +52,9 @@ class D extends C {}
5252
extension Ex on C {}
5353
''');
5454

55-
var f = library.classes.named('D');
55+
var d = library.classes.named('D');
5656
expect(
57-
f.potentiallyApplicableExtensionsSorted,
57+
d.potentiallyApplicableExtensionsSorted,
5858
contains(library.extensions.named('Ex')),
5959
);
6060
}
@@ -66,9 +66,9 @@ class D<T> extends C<T> {}
6666
extension Ex on C<int> {}
6767
''');
6868

69-
var f = library.classes.named('D');
69+
var d = library.classes.named('D');
7070
expect(
71-
f.potentiallyApplicableExtensionsSorted,
71+
d.potentiallyApplicableExtensionsSorted,
7272
contains(library.extensions.named('Ex')),
7373
);
7474
}
@@ -109,6 +109,67 @@ class C implements _B {}
109109
expect(c.publicInterfaces.first.modelElement, library.classes.named('A'));
110110
}
111111

112+
void test_publicInterfaces_indirectViaPrivate2() async {
113+
var library = await bootPackageWithLibrary('''
114+
class A {}
115+
class _B implements A {}
116+
class _C implements _B {}
117+
class D implements _C {}
118+
''');
119+
120+
var d = library.classes.named('D');
121+
expect(d.publicInterfaces, hasLength(1));
122+
expect(d.publicInterfaces.first.modelElement, library.classes.named('A'));
123+
}
124+
125+
void test_publicInterfaces_indirectViaPrivateExtendedClass() async {
126+
var library = await bootPackageWithLibrary('''
127+
class A {}
128+
class _B implements A {}
129+
class C extends _B {}
130+
''');
131+
132+
var c = library.classes.named('C');
133+
expect(c.publicInterfaces, hasLength(1));
134+
expect(c.publicInterfaces.first.modelElement, library.classes.named('A'));
135+
}
136+
137+
void test_publicInterfaces_indirectViaPrivateExtendedClass2() async {
138+
var library = await bootPackageWithLibrary('''
139+
class A {}
140+
class _B implements A {}
141+
class _C extends _B {}
142+
class D extends _C {}
143+
''');
144+
145+
var c = library.classes.named('D');
146+
expect(c.publicInterfaces, hasLength(1));
147+
expect(c.publicInterfaces.first.modelElement, library.classes.named('A'));
148+
}
149+
150+
void test_publicInterfaces_onlyExtendedClasses() async {
151+
var library = await bootPackageWithLibrary('''
152+
class A {}
153+
class B extends A {}
154+
class _C extends B {}
155+
class D extends _C {}
156+
''');
157+
158+
expect(library.classes.named('D').publicInterfaces, isEmpty);
159+
}
160+
161+
void test_publicInterfaces_indirectViaPrivateMixedInMixin() async {
162+
var library = await bootPackageWithLibrary('''
163+
class A {}
164+
mixin _M implements A {}
165+
class C with _M {}
166+
''');
167+
168+
var c = library.classes.named('C');
169+
expect(c.publicInterfaces, hasLength(1));
170+
expect(c.publicInterfaces.first.modelElement, library.classes.named('A'));
171+
}
172+
112173
void test_publicInterfaces_indirectViaPrivate_multiply() async {
113174
var library = await bootPackageWithLibrary('''
114175
class A<T> {}

test/src/utils.dart

Lines changed: 16 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -322,35 +322,34 @@ MatchingLinkResult referenceLookup(Warnable element, String codeRef) =>
322322

323323
/// Returns a matcher which compresses consecutive whitespace in [text] into a
324324
/// single space.
325-
Matcher matchesCompressed(String text) => matches(RegExp(text.replaceAll(
326-
RegExp(r'\s\s+', multiLine: true),
327-
' *',
328-
)));
329-
330-
/// We can not use [ExperimentalFeature.releaseVersion] or even
331-
/// [ExperimentalFeature.experimentalReleaseVersion] as these are set to `null`
332-
/// even when partial analyzer implementations are available.
325+
Matcher matchesCompressed(String text) => matches(RegExp(
326+
text.replaceAll(RegExp(r'\s\s+', multiLine: true), r'\s*'),
327+
));
328+
329+
// We can not use `ExperimentalFeature.releaseVersion` or even
330+
// `ExperimentalFeature.experimentalReleaseVersion` as these are set to `null`
331+
// even when partial analyzer implementations are available.
333332
bool get namedArgumentsAnywhereAllowed =>
334333
VersionRange(min: Version.parse('2.17.0-0'), includeMin: true)
335334
.allows(platformVersion);
336335

337-
/// We can not use [ExperimentalFeature.releaseVersion] or even
338-
/// [ExperimentalFeature.experimentalReleaseVersion] as these are set to `null`
339-
/// even when partial analyzer implementations are available.
336+
// We can not use `ExperimentalFeature.releaseVersion` or even
337+
// `ExperimentalFeature.experimentalReleaseVersion` as these are set to `null`
338+
// even when partial analyzer implementations are available.
340339
bool get recordsAllowed =>
341340
VersionRange(min: Version.parse('2.19.0-0'), includeMin: true)
342341
.allows(platformVersion);
343342

344-
/// We can not use [ExperimentalFeature.releaseVersion] or even
345-
/// [ExperimentalFeature.experimentalReleaseVersion] as these are set to `null`
346-
/// even when partial analyzer implementations are available.
343+
// We can not use `ExperimentalFeature.releaseVersion` or even
344+
// `ExperimentalFeature.experimentalReleaseVersion` as these are set to `null`
345+
// even when partial analyzer implementations are available.
347346
bool get extensionTypesAllowed =>
348347
VersionRange(min: Version.parse('3.2.0-0.0-dev'), includeMin: true)
349348
.allows(platformVersion);
350349

351-
/// We can not use [ExperimentalFeature.releaseVersion] or even
352-
/// [ExperimentalFeature.experimentalReleaseVersion] as these are set to `null`
353-
/// even when partial analyzer implementations are available.
350+
// We can not use `ExperimentalFeature.releaseVersion` or even
351+
// `ExperimentalFeature.experimentalReleaseVersion` as these are set to `null`
352+
// even when partial analyzer implementations are available.
354353
bool get classModifiersAllowed =>
355354
VersionRange(min: Version.parse('3.0.0-0.0-dev'), includeMin: true)
356355
.allows(platformVersion);

test/templates/class_test.dart

Lines changed: 39 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -46,12 +46,36 @@ class Foo implements _Foo {}
4646
''');
4747
var baseLines = readLines(['lib', 'Base-class.html']);
4848

49-
baseLines.expectMainContentContainsAllInOrder([
50-
matches('<dt>Implementers</dt>'),
51-
matches('<dd><ul class="comma-separated clazz-relationships">'),
52-
matches('<li><a href="../lib/Foo-class.html">Foo</a></li>'),
53-
matches('</ul></dd>'),
54-
]);
49+
expect(
50+
baseLines.join('\n'),
51+
matchesCompressed('''
52+
<dt>Implementers</dt>
53+
<dd><ul class="comma-separated clazz-relationships">
54+
<li><a href="../lib/Foo-class.html">Foo</a></li>
55+
</ul></dd>
56+
'''),
57+
);
58+
}
59+
60+
void test_implementers_class_extends2() async {
61+
await createPackageWithLibrary('''
62+
abstract class A {}
63+
abstract class B extends A {}
64+
class _C extends B {}
65+
class D extends _C {}
66+
''');
67+
var baseLines = readLines(['lib', 'A-class.html']);
68+
69+
expect(
70+
baseLines.join('\n'),
71+
// D should not be found; it is indirect via B.
72+
matchesCompressed('''
73+
<dt>Implementers</dt>
74+
<dd><ul class="comma-separated clazz-relationships">
75+
<li><a href="../lib/B-class.html">B</a></li>
76+
</ul></dd>
77+
'''),
78+
);
5579
}
5680

5781
void test_implementers_class_implements_withGenericType() async {
@@ -61,12 +85,15 @@ class Foo<E> implements Base<E> {}
6185
''');
6286
var baseLines = readLines(['lib', 'Base-class.html']);
6387

64-
baseLines.expectMainContentContainsAllInOrder([
65-
matches('<dt>Implementers</dt>'),
66-
matches('<dd><ul class="comma-separated clazz-relationships">'),
67-
matches('<li><a href="../lib/Foo-class.html">Foo</a></li>'),
68-
matches('</ul></dd>'),
69-
]);
88+
expect(
89+
baseLines.join('\n'),
90+
matchesCompressed(r'''
91+
<dt>Implementers</dt>
92+
<dd><ul class="comma-separated clazz-relationships">
93+
<li><a href="../lib/Foo-class.html">Foo</a></li>
94+
</ul></dd>
95+
'''),
96+
);
7097
}
7198

7299
void test_implementers_class_implements_withInstantiatedType() async {

0 commit comments

Comments
 (0)