Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 19 additions & 3 deletions lib/src/model/inheriting_container.dart
Original file line number Diff line number Diff line change
Expand Up @@ -436,13 +436,29 @@ abstract class InheritingContainer extends Container {
/// and so unlike other `public*` methods, is not a strict subset of
/// [directInterfaces] (the direct interfaces).
List<DefinedElementType> get publicInterfaces {
var interfaceElements = <InterfaceElement>{};
var interfaces = <DefinedElementType>[];

// Only interfaces with unique elements should be returned. Elements can
// implement an interface through multiple inheritance routes (e.g.
// `List<E>` implements `Iterable<E>` but also `_ListIterable<E>` which
// implements `EfficientLengthIterable<T>` which implements `Iterable<T>`),
// but there is no chance of type arguments differing, as that is illegal.
void addInterfaceIfUnique(DefinedElementType type) {
var firstPublicSuperElement = type.modelElement.element;
if (firstPublicSuperElement is InterfaceElement) {
if (interfaceElements.add(firstPublicSuperElement)) {
interfaces.add(type);
}
}
}

for (var interface in directInterfaces) {
var interfaceElement = interface.modelElement;

/// Do not recurse if we can find an element here.
if (interfaceElement.canonicalModelElement != null) {
interfaces.add(interface);
addInterfaceIfUnique(interface);
continue;
}
// Public types used to be unconditionally exposed here. However,
Expand All @@ -466,9 +482,9 @@ abstract class InheritingContainer extends Container {
}
var publicSuperChain = interfaceElement.superChain.wherePublic;
if (publicSuperChain.isNotEmpty) {
interfaces.add(publicSuperChain.first);
addInterfaceIfUnique(publicSuperChain.first);
}
interfaces.addAll(interfaceElement.publicInterfaces);
interfaceElement.publicInterfaces.forEach(addInterfaceIfUnique);
}
return interfaces;
}
Expand Down
71 changes: 71 additions & 0 deletions test/classes_test.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
// Copyright (c) 2024, the Dart project authors. Please see the AUTHORS file
// 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.

import 'package:test/test.dart';
import 'package:test_reflective_loader/test_reflective_loader.dart';

import 'dartdoc_test_base.dart';
import 'src/utils.dart';

void main() {
defineReflectiveSuite(() {
defineReflectiveTests(ClassesTest);
});
}

@reflectiveTest
class ClassesTest extends DartdocTestBase {
@override
String get libraryName => 'classes';

void test_publicInterfaces_direct() async {
var library = await bootPackageWithLibrary('''
class A {}
class B implements A {}
''');

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

void test_publicInterfaces_indirect() async {
var library = await bootPackageWithLibrary('''
class A {}
class B implements A {}
class C implements B {}
''');

var c = library.classes.named('C');
// Only `B` is shown, not indirect-through-public like `A`.
expect(c.publicInterfaces, hasLength(1));
expect(c.publicInterfaces.first.modelElement, library.classes.named('B'));
}

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

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> {}
class _B<U> implements A<U> {}
class C<T> implements A<T>, _B<T> {}
''');

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

// TODO(srawlins): Test everything else about classes.
}