Skip to content

Commit 9b932a2

Browse files
authored
Fix implementation chain with internal private classes (#2358)
Fix implementation chain with internal private classes
1 parent 86daa3a commit 9b932a2

File tree

4 files changed

+81
-17
lines changed

4 files changed

+81
-17
lines changed

lib/src/model/class.dart

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -166,6 +166,10 @@ class Class extends Container
166166
return '${package.baseHref}$filePath';
167167
}
168168

169+
/// Returns the [Class] with the library in which [element] is defined.
170+
Class get definingClass =>
171+
ModelElement.from(element, definingLibrary, packageGraph);
172+
169173
/// Returns all the "immediate" public implementors of this class.
170174
///
171175
/// If this class has a private implementor, then that is counted as a proxy

lib/src/model/package_graph.dart

Lines changed: 39 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
// BSD-style license that can be found in the LICENSE file.
44

55
import 'dart:async';
6+
import 'dart:collection';
67

78
import 'package:analyzer/dart/ast/ast.dart';
89
import 'package:analyzer/dart/element/element.dart';
@@ -79,7 +80,7 @@ class PackageGraph {
7980
for (var package in documentedPackages) {
8081
package.libraries.sort((a, b) => compareNatural(a.name, b.name));
8182
for (var library in package.libraries) {
82-
library.allClasses.forEach(_addToImplementors);
83+
_addToImplementors(library.allClasses);
8384
_extensions.addAll(library.extensions);
8485
}
8586
}
@@ -202,7 +203,9 @@ class PackageGraph {
202203
allInheritableElements = {};
203204

204205
/// A mapping of the list of classes which implement each class.
205-
final Map<Class, List<Class>> _implementors = {};
206+
final Map<Class, List<Class>> _implementors = LinkedHashMap(
207+
equals: (Class a, Class b) => a.definingClass == b.definingClass,
208+
hashCode: (Class class_) => class_.definingClass.hashCode);
206209

207210
/// A list of extensions that exist in the package graph.
208211
final List<Extension> _extensions = [];
@@ -576,26 +579,46 @@ class PackageGraph {
576579
return hrefMap;
577580
}
578581

579-
void _addToImplementors(Class class_) {
582+
void _addToImplementors(Iterable<Class> classes) {
580583
assert(!allImplementorsAdded);
581-
_implementors.putIfAbsent(class_, () => []);
582-
void checkAndAddClass(Class key) {
583-
_implementors.putIfAbsent(key, () => []);
584-
var list = _implementors[key];
585584

586-
if (!list.any((l) => l.element == class_.element)) {
587-
list.add(class_);
585+
// Private classes may not be included in [classes], but may still be
586+
// necessary links in the implementation chain. They are added here as they
587+
// are found, then processed after [classes].
588+
var privates = <Class>[];
589+
590+
void checkAndAddClass(Class implemented, Class implementor) {
591+
if (!implemented.isPublic) {
592+
privates.add(implemented);
593+
}
594+
implemented = implemented.canonicalModelElement ?? implemented;
595+
_implementors.putIfAbsent(implemented, () => []);
596+
var list = _implementors[implemented];
597+
// TODO(srawlins): This would be more efficient if we created a
598+
// SplayTreeSet keyed off of `.element`.
599+
if (!list.any((l) => l.element == implementor.element)) {
600+
list.add(implementor);
588601
}
589602
}
590603

591-
for (var type in class_.mixins) {
592-
checkAndAddClass(type.element);
593-
}
594-
if (class_.supertype != null) {
595-
checkAndAddClass(class_.supertype.element);
604+
void addImplementor(Class class_) {
605+
for (var type in class_.mixins) {
606+
checkAndAddClass(type.element, class_);
607+
}
608+
if (class_.supertype != null) {
609+
checkAndAddClass(class_.supertype.element, class_);
610+
}
611+
for (var type in class_.interfaces) {
612+
checkAndAddClass(type.element, class_);
613+
}
596614
}
597-
for (var type in class_.interfaces) {
598-
checkAndAddClass(type.element);
615+
616+
classes.forEach(addImplementor);
617+
618+
// [privates] may grow while processing; use a for loop, rather than a
619+
// for-each loop, to avoid concurrent modification errors.
620+
for (var i = 0; i < privates.length; i++) {
621+
addImplementor(privates[i]);
599622
}
600623
}
601624

test/end2end/model_test.dart

Lines changed: 28 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3582,7 +3582,7 @@ String topLevelFunction(int param1, bool param2, Cool coolBeans,
35823582
});
35833583

35843584
test('private classes do not break the implementor chain', () {
3585-
var Super1 = fakeLibrary.classes.firstWhere((c) => c.name == 'Super1');
3585+
var Super1 = fakeLibrary.classes.singleWhere((c) => c.name == 'Super1');
35863586
var publicImplementors = Super1.publicImplementors.map((i) => i.name);
35873587
expect(publicImplementors, hasLength(3));
35883588
// A direct implementor.
@@ -3593,6 +3593,33 @@ String topLevelFunction(int param1, bool param2, Cool coolBeans,
35933593
expect(publicImplementors, contains('Super6'));
35943594
});
35953595

3596+
test(
3597+
'private classes in internal libraries do not break the implementor chain',
3598+
() {
3599+
var GenericSuperProperty = fakeLibrary.classes
3600+
.singleWhere((c) => c.name == 'GenericSuperProperty');
3601+
var publicImplementors =
3602+
GenericSuperProperty.publicImplementors.map((i) => i.name);
3603+
expect(publicImplementors, hasLength(1));
3604+
// A direct implementor.
3605+
expect(publicImplementors, contains('GenericSuperValue'));
3606+
3607+
var GenericSuperValue =
3608+
fakeLibrary.classes.singleWhere((c) => c.name == 'GenericSuperValue');
3609+
publicImplementors =
3610+
GenericSuperValue.publicImplementors.map((i) => i.name);
3611+
expect(publicImplementors, hasLength(1));
3612+
// A direct implementor.
3613+
expect(publicImplementors, contains('GenericSuperNum'));
3614+
3615+
var GenericSuperNum =
3616+
fakeLibrary.classes.singleWhere((c) => c.name == 'GenericSuperNum');
3617+
publicImplementors =
3618+
GenericSuperNum.publicImplementors.map((i) => i.name);
3619+
expect(publicImplementors, hasLength(1));
3620+
expect(publicImplementors, contains('GenericSuperInt'));
3621+
});
3622+
35963623
test('the first class is Apple', () {
35973624
expect(apple.name, equals('Apple'));
35983625
});

testing/test_package/lib/src/tool.dart

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,3 +8,13 @@ abstract class PrivateLibraryToolUser {
88
/// {@end-tool}
99
void invokeToolPrivateLibrary();
1010
}
11+
12+
abstract class GenericSuperProperty<T> {}
13+
14+
abstract class GenericSuperValue<T> extends GenericSuperProperty<T> {}
15+
16+
class _GenericSuper<T> extends GenericSuperValue<T> {}
17+
18+
class GenericSuperNum<T extends num> extends _GenericSuper<T> {}
19+
20+
class GenericSuperInt extends GenericSuperNum<int> {}

0 commit comments

Comments
 (0)