Skip to content

Commit

Permalink
Display indirectly implemented types via extended and mixed-in types (#…
Browse files Browse the repository at this point in the history
  • Loading branch information
srawlins authored Aug 30, 2024
1 parent b47a0c2 commit 199c7b4
Show file tree
Hide file tree
Showing 4 changed files with 160 additions and 59 deletions.
58 changes: 36 additions & 22 deletions lib/src/model/inheriting_container.dart
Original file line number Diff line number Diff line change
Expand Up @@ -453,38 +453,52 @@ abstract class InheritingContainer extends Container {
}
}

for (var interface in directInterfaces) {
var interfaceElement = interface.modelElement;
void addFromSupertype(DefinedElementType supertype,
{required bool addSupertypes}) {
var superElement = supertype.modelElement;

/// Do not recurse if we can find an element here.
if (interfaceElement.canonicalModelElement != null) {
addInterfaceIfUnique(interface);
continue;
if (superElement.canonicalModelElement != null) {
if (addSupertypes) addInterfaceIfUnique(supertype);
return;
}
// Public types used to be unconditionally exposed here. However,
// if the packages are [DocumentLocation.missing] we generally treat types
// defined in them as actually defined in a documented package.
// That translates to them being defined here, but in 'src/' or similar,
// and so, are now always hidden.

// This type is not backed by a canonical Class; search
// the superchain and publicInterfaces of this interface to pretend
// as though the hidden class didn't exist and this class was declared
// directly referencing the canonical classes further up the chain.
if (interfaceElement is! InheritingContainer) {

// This type is not backed by a canonical Class; it is not documented.
// Search it's `superChain` and `publicInterfaces` to pretend that `this`
// container directly implements canonical classes further up the chain.

if (superElement is! InheritingContainer) {
assert(
false,
'Can not handle intermediate non-public interfaces created by '
'Cannot handle intermediate non-public interfaces created by '
"ModelElements that are not classes or mixins: '$fullyQualifiedName' "
"contains an interface '$interface', defined by '$interfaceElement'",
"contains a supertype '$supertype', defined by '$superElement'",
);
continue;
return;
}
var publicSuperChain = interfaceElement.superChain.wherePublic;
if (publicSuperChain.isNotEmpty) {
var publicSuperChain = superElement.superChain.wherePublic;
if (publicSuperChain.isNotEmpty && addSupertypes) {
addInterfaceIfUnique(publicSuperChain.first);
}
interfaceElement.publicInterfaces.forEach(addInterfaceIfUnique);
superElement.publicInterfaces.forEach(addInterfaceIfUnique);
}

for (var interface in directInterfaces) {
addFromSupertype(interface, addSupertypes: true);
}
for (var supertype in superChain) {
var interfaceElement = supertype.modelElement;

// Do not recurse if we can find an element here.
if (interfaceElement.canonicalModelElement != null) {
continue;
}
addFromSupertype(supertype, addSupertypes: false);
}
if (this case Class(:var mixedInTypes) || Enum(:var mixedInTypes)) {
for (var mixin in mixedInTypes) {
addFromSupertype(mixin, addSupertypes: false);
}
}
return interfaces;
}
Expand Down
77 changes: 69 additions & 8 deletions test/classes_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -25,9 +25,9 @@ class C {}
extension Ex on C {}
''');

var f = library.classes.named('C');
var c = library.classes.named('C');
expect(
f.potentiallyApplicableExtensionsSorted,
c.potentiallyApplicableExtensionsSorted,
contains(library.extensions.named('Ex')),
);
}
Expand All @@ -38,9 +38,9 @@ class C<T> {}
extension Ex on C<int> {}
''');

var f = library.classes.named('C');
var c = library.classes.named('C');
expect(
f.potentiallyApplicableExtensionsSorted,
c.potentiallyApplicableExtensionsSorted,
contains(library.extensions.named('Ex')),
);
}
Expand All @@ -52,9 +52,9 @@ class D extends C {}
extension Ex on C {}
''');

var f = library.classes.named('D');
var d = library.classes.named('D');
expect(
f.potentiallyApplicableExtensionsSorted,
d.potentiallyApplicableExtensionsSorted,
contains(library.extensions.named('Ex')),
);
}
Expand All @@ -66,9 +66,9 @@ class D<T> extends C<T> {}
extension Ex on C<int> {}
''');

var f = library.classes.named('D');
var d = library.classes.named('D');
expect(
f.potentiallyApplicableExtensionsSorted,
d.potentiallyApplicableExtensionsSorted,
contains(library.extensions.named('Ex')),
);
}
Expand Down Expand Up @@ -109,6 +109,67 @@ class C implements _B {}
expect(c.publicInterfaces.first.modelElement, library.classes.named('A'));
}

void test_publicInterfaces_indirectViaPrivate2() async {
var library = await bootPackageWithLibrary('''
class A {}
class _B implements A {}
class _C implements _B {}
class D implements _C {}
''');

var d = library.classes.named('D');
expect(d.publicInterfaces, hasLength(1));
expect(d.publicInterfaces.first.modelElement, library.classes.named('A'));
}

void test_publicInterfaces_indirectViaPrivateExtendedClass() async {
var library = await bootPackageWithLibrary('''
class A {}
class _B implements A {}
class C extends _B {}
''');

var c = library.classes.named('C');
expect(c.publicInterfaces, hasLength(1));
expect(c.publicInterfaces.first.modelElement, library.classes.named('A'));
}

void test_publicInterfaces_indirectViaPrivateExtendedClass2() async {
var library = await bootPackageWithLibrary('''
class A {}
class _B implements A {}
class _C extends _B {}
class D extends _C {}
''');

var c = library.classes.named('D');
expect(c.publicInterfaces, hasLength(1));
expect(c.publicInterfaces.first.modelElement, library.classes.named('A'));
}

void test_publicInterfaces_onlyExtendedClasses() async {
var library = await bootPackageWithLibrary('''
class A {}
class B extends A {}
class _C extends B {}
class D extends _C {}
''');

expect(library.classes.named('D').publicInterfaces, isEmpty);
}

void test_publicInterfaces_indirectViaPrivateMixedInMixin() async {
var library = await bootPackageWithLibrary('''
class A {}
mixin _M implements A {}
class C with _M {}
''');

var c = library.classes.named('C');
expect(c.publicInterfaces, hasLength(1));
expect(c.publicInterfaces.first.modelElement, library.classes.named('A'));
}

void test_publicInterfaces_indirectViaPrivate_multiply() async {
var library = await bootPackageWithLibrary('''
class A<T> {}
Expand Down
33 changes: 16 additions & 17 deletions test/src/utils.dart
Original file line number Diff line number Diff line change
Expand Up @@ -322,35 +322,34 @@ MatchingLinkResult referenceLookup(Warnable element, String codeRef) =>

/// Returns a matcher which compresses consecutive whitespace in [text] into a
/// single space.
Matcher matchesCompressed(String text) => matches(RegExp(text.replaceAll(
RegExp(r'\s\s+', multiLine: true),
' *',
)));

/// We can not use [ExperimentalFeature.releaseVersion] or even
/// [ExperimentalFeature.experimentalReleaseVersion] as these are set to `null`
/// even when partial analyzer implementations are available.
Matcher matchesCompressed(String text) => matches(RegExp(
text.replaceAll(RegExp(r'\s\s+', multiLine: true), r'\s*'),
));

// We can not use `ExperimentalFeature.releaseVersion` or even
// `ExperimentalFeature.experimentalReleaseVersion` as these are set to `null`
// even when partial analyzer implementations are available.
bool get namedArgumentsAnywhereAllowed =>
VersionRange(min: Version.parse('2.17.0-0'), includeMin: true)
.allows(platformVersion);

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

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

/// We can not use [ExperimentalFeature.releaseVersion] or even
/// [ExperimentalFeature.experimentalReleaseVersion] as these are set to `null`
/// even when partial analyzer implementations are available.
// We can not use `ExperimentalFeature.releaseVersion` or even
// `ExperimentalFeature.experimentalReleaseVersion` as these are set to `null`
// even when partial analyzer implementations are available.
bool get classModifiersAllowed =>
VersionRange(min: Version.parse('3.0.0-0.0-dev'), includeMin: true)
.allows(platformVersion);
Expand Down
51 changes: 39 additions & 12 deletions test/templates/class_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -46,12 +46,36 @@ class Foo implements _Foo {}
''');
var baseLines = readLines(['lib', 'Base-class.html']);

baseLines.expectMainContentContainsAllInOrder([
matches('<dt>Implementers</dt>'),
matches('<dd><ul class="comma-separated clazz-relationships">'),
matches('<li><a href="../lib/Foo-class.html">Foo</a></li>'),
matches('</ul></dd>'),
]);
expect(
baseLines.join('\n'),
matchesCompressed('''
<dt>Implementers</dt>
<dd><ul class="comma-separated clazz-relationships">
<li><a href="../lib/Foo-class.html">Foo</a></li>
</ul></dd>
'''),
);
}

void test_implementers_class_extends2() async {
await createPackageWithLibrary('''
abstract class A {}
abstract class B extends A {}
class _C extends B {}
class D extends _C {}
''');
var baseLines = readLines(['lib', 'A-class.html']);

expect(
baseLines.join('\n'),
// D should not be found; it is indirect via B.
matchesCompressed('''
<dt>Implementers</dt>
<dd><ul class="comma-separated clazz-relationships">
<li><a href="../lib/B-class.html">B</a></li>
</ul></dd>
'''),
);
}

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

baseLines.expectMainContentContainsAllInOrder([
matches('<dt>Implementers</dt>'),
matches('<dd><ul class="comma-separated clazz-relationships">'),
matches('<li><a href="../lib/Foo-class.html">Foo</a></li>'),
matches('</ul></dd>'),
]);
expect(
baseLines.join('\n'),
matchesCompressed(r'''
<dt>Implementers</dt>
<dd><ul class="comma-separated clazz-relationships">
<li><a href="../lib/Foo-class.html">Foo</a></li>
</ul></dd>
'''),
);
}

void test_implementers_class_implements_withInstantiatedType() async {
Expand Down

0 comments on commit 199c7b4

Please sign in to comment.