Skip to content

Commit 30b4df2

Browse files
authored
Connect interface types across private intermediate classes (#2495)
* First fix attempt * tweak and fix tests * add test
1 parent 619a128 commit 30b4df2

File tree

6 files changed

+98
-9
lines changed

6 files changed

+98
-9
lines changed

lib/src/model/class.dart

Lines changed: 42 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ class Class extends Container
4141
'Dartdoc 1.0.0')
4242
set supertype(DefinedElementType value) => _supertype = value;
4343

44-
final List<DefinedElementType> _interfaces;
44+
final List<DefinedElementType> _directInterfaces;
4545

4646
Class(ClassElement element, Library library, PackageGraph packageGraph)
4747
: _mixedInTypes = element.mixins
@@ -52,7 +52,7 @@ class Class extends Container
5252
_supertype = element.supertype?.element?.supertype == null
5353
? null
5454
: ElementType.from(element.supertype, library, packageGraph),
55-
_interfaces = element.interfaces
55+
_directInterfaces = element.interfaces
5656
.map<DefinedElementType>(
5757
(f) => ElementType.from(f, library, packageGraph))
5858
.toList(growable: false),
@@ -258,10 +258,46 @@ class Class extends Container
258258
Iterable<Field> get publicInheritedFields =>
259259
model_utils.filterNonPublic(inheritedFields);
260260

261-
List<DefinedElementType> get interfaces => _interfaces;
262-
263-
Iterable<DefinedElementType> get publicInterfaces =>
264-
model_utils.filterNonPublic(interfaces);
261+
/// Interfaces directly implemented by this class.
262+
List<DefinedElementType> get interfaces => _directInterfaces;
263+
264+
/// The public interfaces may include substitutions for intermediate
265+
/// private interfaces, and so unlike other public* methods, is not
266+
/// a strict subset of [interfaces].
267+
Iterable<DefinedElementType> get publicInterfaces sync* {
268+
for (var i in _directInterfaces) {
269+
/// Do not recurse if we can find an element here.
270+
if (i.element.canonicalModelElement != null) {
271+
yield i;
272+
continue;
273+
}
274+
// Public types used to be unconditionally exposed here. However,
275+
// if the packages are [DocumentLocation.missing] we generally treat types
276+
// defined in them as actually defined in a documented package.
277+
// That translates to them being defined here, but in 'src/' or similar,
278+
// and so, are now always hidden.
279+
280+
// This type is not backed by a canonical Class; search
281+
// the superchain and publicInterfaces of this interface to pretend
282+
// as though the hidden class didn't exist and this class was declared
283+
// directly referencing the canonical classes further up the chain.
284+
if (i.element is Class) {
285+
var hiddenClass = i.element as Class;
286+
if (hiddenClass.publicSuperChain.isNotEmpty) {
287+
yield hiddenClass.publicSuperChain.first;
288+
}
289+
yield* hiddenClass.publicInterfaces;
290+
} else {
291+
assert(
292+
false,
293+
'Can not handle intermediate non-public interfaces '
294+
'created by ModelElements that are not classes or mixins: '
295+
'${fullyQualifiedName} contains an interface {$i}, '
296+
'defined by ${i.element}');
297+
continue;
298+
}
299+
}
300+
}
265301

266302
bool get isAbstract => element.isAbstract;
267303

lib/src/model/package_graph.dart

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -618,6 +618,9 @@ class PackageGraph {
618618
for (var type in class_.interfaces) {
619619
checkAndAddClass(type.element, class_);
620620
}
621+
for (var type in class_.publicInterfaces) {
622+
checkAndAddClass(type.element, class_);
623+
}
621624
}
622625

623626
classes.forEach(addImplementor);

test/end2end/model_test.dart

Lines changed: 26 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1313,6 +1313,30 @@ void main() {
13131313
});
13141314

13151315
group('Class edge cases', () {
1316+
// This is distinct from inheritance in the language and the analyzer
1317+
// implementation.
1318+
test(
1319+
'Implementor chain is correctly rewritten through intermediate private classes',
1320+
() {
1321+
var implementorsLibrary = packageGraph.publicLibraries
1322+
.firstWhere((l) => l.name == 'implementors');
1323+
var ImplementerOfDeclaredPrivateClasses = implementorsLibrary.classes
1324+
.firstWhere((c) => c.name == 'ImplementerOfDeclaredPrivateClasses');
1325+
var ImplementerOfThings = implementorsLibrary.classes
1326+
.firstWhere((c) => c.name == 'ImplementerOfThings');
1327+
var ImplementBase = implementorsLibrary.classes
1328+
.firstWhere((c) => c.name == 'ImplementBase');
1329+
1330+
expect(ImplementerOfThings.publicInterfaces.first.element,
1331+
equals(ImplementBase));
1332+
expect(ImplementerOfDeclaredPrivateClasses.publicInterfaces.first.element,
1333+
equals(ImplementBase));
1334+
1335+
expect(ImplementBase.publicImplementors,
1336+
contains(ImplementerOfDeclaredPrivateClasses));
1337+
expect(ImplementBase.publicImplementors, contains(ImplementerOfThings));
1338+
});
1339+
13161340
test('Overrides from intermediate abstract classes are picked up correctly',
13171341
() {
13181342
var IntermediateAbstractSubclass = fakeLibrary.allClasses
@@ -3410,8 +3434,8 @@ String topLevelFunction(int param1, bool param2, Cool coolBeans,
34103434
ImplementsFutureVoid.linkedName,
34113435
equals(
34123436
'<a href="${HTMLBASE_PLACEHOLDER}fake/ImplementsFutureVoid-class.html">ImplementsFutureVoid</a>'));
3413-
var FutureVoid = ImplementsFutureVoid.publicInterfaces
3414-
.firstWhere((c) => c.name == 'Future');
3437+
var FutureVoid =
3438+
ImplementsFutureVoid.interfaces.firstWhere((c) => c.name == 'Future');
34153439
expect(
34163440
FutureVoid.linkedName,
34173441
equals(

test/src/utils.dart

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ import 'package:dartdoc/src/package_meta.dart';
1414
/// The number of public libraries in testing/test_package, minus 2 for
1515
/// the excluded libraries listed in the initializers for _testPackageGraphMemo
1616
/// and minus 1 for the <nodoc> tag in the 'excluded' library.
17-
const int kTestPackagePublicLibraries = 17;
17+
const int kTestPackagePublicLibraries = 18;
1818

1919
final _resourceProvider = pubPackageMetaProvider.resourceProvider;
2020
final _pathContext = _resourceProvider.pathContext;
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
// Copyright (c) 2021, 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+
// A library useful to check implementer chain edge cases.
6+
library implementors;
7+
8+
import 'src/intermediate_implements.dart';
9+
10+
abstract class ImplementBase {}
11+
12+
abstract class _APrivateThingToImplement implements ImplementBase {}
13+
14+
abstract class ImplementerOfThings implements IntermediateImplementer {}
15+
16+
abstract class ImplementerOfDeclaredPrivateClasses implements _APrivateThingToImplement {}
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
// Copyright (c) 2021, 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+
// A src-private library for intermediate classes to implement.
6+
library intermediate_implements;
7+
8+
import 'package:test_package/implementors.dart';
9+
10+
abstract class IntermediateImplementer implements ImplementBase {}

0 commit comments

Comments
 (0)