Skip to content

Commit

Permalink
Deduplicate interfaces (#3848)
Browse files Browse the repository at this point in the history
  • Loading branch information
srawlins authored Aug 26, 2024
1 parent f7e8def commit e233b2b
Show file tree
Hide file tree
Showing 2 changed files with 90 additions and 3 deletions.
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.
}

0 comments on commit e233b2b

Please sign in to comment.