Skip to content

Commit e233b2b

Browse files
authored
Deduplicate interfaces (#3848)
1 parent f7e8def commit e233b2b

File tree

2 files changed

+90
-3
lines changed

2 files changed

+90
-3
lines changed

lib/src/model/inheriting_container.dart

Lines changed: 19 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -436,13 +436,29 @@ abstract class InheritingContainer extends Container {
436436
/// and so unlike other `public*` methods, is not a strict subset of
437437
/// [directInterfaces] (the direct interfaces).
438438
List<DefinedElementType> get publicInterfaces {
439+
var interfaceElements = <InterfaceElement>{};
439440
var interfaces = <DefinedElementType>[];
441+
442+
// Only interfaces with unique elements should be returned. Elements can
443+
// implement an interface through multiple inheritance routes (e.g.
444+
// `List<E>` implements `Iterable<E>` but also `_ListIterable<E>` which
445+
// implements `EfficientLengthIterable<T>` which implements `Iterable<T>`),
446+
// but there is no chance of type arguments differing, as that is illegal.
447+
void addInterfaceIfUnique(DefinedElementType type) {
448+
var firstPublicSuperElement = type.modelElement.element;
449+
if (firstPublicSuperElement is InterfaceElement) {
450+
if (interfaceElements.add(firstPublicSuperElement)) {
451+
interfaces.add(type);
452+
}
453+
}
454+
}
455+
440456
for (var interface in directInterfaces) {
441457
var interfaceElement = interface.modelElement;
442458

443459
/// Do not recurse if we can find an element here.
444460
if (interfaceElement.canonicalModelElement != null) {
445-
interfaces.add(interface);
461+
addInterfaceIfUnique(interface);
446462
continue;
447463
}
448464
// Public types used to be unconditionally exposed here. However,
@@ -466,9 +482,9 @@ abstract class InheritingContainer extends Container {
466482
}
467483
var publicSuperChain = interfaceElement.superChain.wherePublic;
468484
if (publicSuperChain.isNotEmpty) {
469-
interfaces.add(publicSuperChain.first);
485+
addInterfaceIfUnique(publicSuperChain.first);
470486
}
471-
interfaces.addAll(interfaceElement.publicInterfaces);
487+
interfaceElement.publicInterfaces.forEach(addInterfaceIfUnique);
472488
}
473489
return interfaces;
474490
}

test/classes_test.dart

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
// Copyright (c) 2024, the Dart project authors. Please see the AUTHORS file
2+
// for details. All rights reserved. Use of this source code is governed by a
3+
// BSD-style license that can be found in the LICENSE file.
4+
5+
import 'package:test/test.dart';
6+
import 'package:test_reflective_loader/test_reflective_loader.dart';
7+
8+
import 'dartdoc_test_base.dart';
9+
import 'src/utils.dart';
10+
11+
void main() {
12+
defineReflectiveSuite(() {
13+
defineReflectiveTests(ClassesTest);
14+
});
15+
}
16+
17+
@reflectiveTest
18+
class ClassesTest extends DartdocTestBase {
19+
@override
20+
String get libraryName => 'classes';
21+
22+
void test_publicInterfaces_direct() async {
23+
var library = await bootPackageWithLibrary('''
24+
class A {}
25+
class B implements A {}
26+
''');
27+
28+
var b = library.classes.named('B');
29+
expect(b.publicInterfaces, hasLength(1));
30+
expect(b.publicInterfaces.first.modelElement, library.classes.named('A'));
31+
}
32+
33+
void test_publicInterfaces_indirect() async {
34+
var library = await bootPackageWithLibrary('''
35+
class A {}
36+
class B implements A {}
37+
class C implements B {}
38+
''');
39+
40+
var c = library.classes.named('C');
41+
// Only `B` is shown, not indirect-through-public like `A`.
42+
expect(c.publicInterfaces, hasLength(1));
43+
expect(c.publicInterfaces.first.modelElement, library.classes.named('B'));
44+
}
45+
46+
void test_publicInterfaces_indirectViaPrivate() async {
47+
var library = await bootPackageWithLibrary('''
48+
class A {}
49+
class _B implements A {}
50+
class C implements _B {}
51+
''');
52+
53+
var c = library.classes.named('C');
54+
expect(c.publicInterfaces, hasLength(1));
55+
expect(c.publicInterfaces.first.modelElement, library.classes.named('A'));
56+
}
57+
58+
void test_publicInterfaces_indirectViaPrivate_multiply() async {
59+
var library = await bootPackageWithLibrary('''
60+
class A<T> {}
61+
class _B<U> implements A<U> {}
62+
class C<T> implements A<T>, _B<T> {}
63+
''');
64+
65+
var c = library.classes.named('C');
66+
expect(c.publicInterfaces, hasLength(1));
67+
expect(c.publicInterfaces.first.modelElement, library.classes.named('A'));
68+
}
69+
70+
// TODO(srawlins): Test everything else about classes.
71+
}

0 commit comments

Comments
 (0)