Skip to content

Commit 6051dd2

Browse files
committed
Refactor special case for how canonical enclosing containers are calculated
There is some complicated logic in the calculations for determining a canonical enclosing container, when we find ourselves examining a "hidden" interface. The list of "hidden" interfaces, which we should not consider when determining a canonical enclosing container, is exactly one: `Interceptor`. But it's important that it stay buried. The logic was hard to read, so first, I split out a complicated if-condition into a few local variables. The logic also needs to track the "current" container in the inheritance list, and the "previous", and the "previous visible." This was complicated when using a for-in loop. I changed it to use a standard for loop with indices. Then I saw that we only ever find outself asking a container for it's "memberByExample" in the case when we're looking for one of Object's members (`toString`, `noSuchMethod`, `operator ==`, `hashCode`, and `runtimeType`. We can inline the logic to fetch a member by example. This allows us to delete a late field on every Container. Also, the package graph `_invisibleAnnotations` and `_inheritThrough` fields had very complicated initializers which I simplified with if-elements.
1 parent 73344a2 commit 6051dd2

File tree

4 files changed

+55
-67
lines changed

4 files changed

+55
-67
lines changed

lib/src/model/comment_referable.dart

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -223,14 +223,13 @@ extension on Scope {
223223
/// A set of utility methods for helping build
224224
/// [CommentReferable.referenceChildren] out of collections of other
225225
/// [CommentReferable]s.
226-
extension CommentReferableEntryGenerators on Iterable<CommentReferable> {
226+
extension CommentReferableEntryGenerators<T extends CommentReferable>
227+
on Iterable<T> {
227228
/// Creates reference entries for this Iterable.
228229
///
229230
/// If there is a conflict with [referable], the included [MapEntry] uses
230231
/// [referable]'s [CommentReferable.referenceName] as a prefix.
231-
Map<String, CommentReferable> explicitOnCollisionWith(
232-
CommentReferable referable) =>
233-
{
232+
Map<String, T> explicitOnCollisionWith(CommentReferable referable) => {
234233
for (var r in this)
235234
if (r.referenceName == referable.referenceName)
236235
'${referable.referenceName}.${r.referenceName}': r
@@ -239,13 +238,13 @@ extension CommentReferableEntryGenerators on Iterable<CommentReferable> {
239238
};
240239

241240
/// A mapping from each [CommentReferable]'s name to itself.
242-
Map<String, CommentReferable> get asMapByName => {
241+
Map<String, T> get asMapByName => {
243242
for (var r in this) r.referenceName: r,
244243
};
245244

246245
/// Returns all values not of this type.
247-
List<CommentReferable> whereNotType<T>() => [
246+
List<T> whereNotType<U>() => [
248247
for (var referable in this)
249-
if (referable is! T) referable,
248+
if (referable is! U) referable,
250249
];
251250
}

lib/src/model/container.dart

Lines changed: 0 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -156,33 +156,6 @@ abstract class Container extends ModelElement
156156
late final Set<Element> _allElements =
157157
allModelElements.map((e) => e.element).toSet();
158158

159-
late final Map<String, List<ModelElement>> _membersByName = () {
160-
var membersByName = <String, List<ModelElement>>{};
161-
for (var element in allModelElements) {
162-
membersByName.putIfAbsent(element.name, () => []).add(element);
163-
}
164-
return membersByName;
165-
}();
166-
167-
/// Given a [ModelElement] that is a member of some other class, returns
168-
/// the member of this class that has the same name and runtime type.
169-
///
170-
/// This enables object substitution for canonicalization, such as Interceptor
171-
/// for Object.
172-
T memberByExample<T extends ModelElement>(T example) {
173-
// [T] is insufficiently specific to disambiguate between different
174-
// subtypes of [Inheritable] or other mixins/implementations of
175-
// [ModelElement] via [Iterable.whereType].
176-
var possibleMembers = _membersByName[example.name]!
177-
.where((e) => e.runtimeType == example.runtimeType);
178-
if (example is Accessor) {
179-
possibleMembers = possibleMembers
180-
.where((e) => example.isGetter == (e as Accessor).isGetter);
181-
}
182-
assert(possibleMembers.length == 1);
183-
return possibleMembers.first as T;
184-
}
185-
186159
bool get hasPublicStaticFields => staticFields.any((e) => e.isPublic);
187160

188161
List<Field> get publicStaticFieldsSorted =>

lib/src/model/inheritable.dart

Lines changed: 35 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
import 'package:analyzer/dart/element/element.dart';
66
import 'package:collection/collection.dart' show IterableExtension;
77
import 'package:dartdoc/src/model/attribute.dart';
8+
import 'package:dartdoc/src/model/comment_referable.dart';
89
import 'package:dartdoc/src/model/model.dart';
910
import 'package:dartdoc/src/special_elements.dart';
1011

@@ -55,22 +56,40 @@ mixin Inheritable on ContainerMember {
5556
var searchElement = element.declaration;
5657
// TODO(jcollins-g): generate warning if an inherited element's definition
5758
// is in an intermediate non-canonical class in the inheritance chain?
58-
Container? previous;
59-
Container? previousNonSkippable;
6059
Container? found;
61-
for (var c in _inheritance.reversed) {
62-
// Filter out mixins.
63-
if (c.containsElement(searchElement)) {
64-
if ((packageGraph.inheritThrough.contains(previous) &&
65-
c != definingEnclosingContainer) ||
66-
(packageGraph.inheritThrough.contains(c) &&
67-
c == definingEnclosingContainer)) {
68-
return previousNonSkippable!
69-
.memberByExample(this)
70-
.canonicalEnclosingContainer;
60+
var reverseInheritance = _inheritance.reversed.toList();
61+
for (var i = 0; i < reverseInheritance.length; i++) {
62+
var container = reverseInheritance[i];
63+
if (container.containsElement(searchElement)) {
64+
var previousIsHiddenAndNotDefining = i > 0 &&
65+
_isHiddenInterface(reverseInheritance[i - 1]) &&
66+
container != definingEnclosingContainer;
67+
var thisIsHiddenAndDefining = _isHiddenInterface(container) &&
68+
container == definingEnclosingContainer;
69+
// If the previous container in the search is one of the "hidden"
70+
// interfaces, and it's not this member's defining container, OR if
71+
// this container in the search is one of the "hidden" interfaces,
72+
// and it is also this member's defining container, then we can just
73+
// immediately return the canonical enclosing container of the
74+
// overridden member in the previous, non-hidden container in the
75+
// inheritance.
76+
if (previousIsHiddenAndNotDefining || thisIsHiddenAndDefining) {
77+
var previousVisible = reverseInheritance
78+
.take(i)
79+
.lastWhere((e) => !_isHiddenInterface(e));
80+
var membersInPreviousVisible = previousVisible.allModelElements
81+
.where((e) => e.name == name)
82+
.whereType<Inheritable>()
83+
.whereNotType<Field>();
84+
assert(
85+
membersInPreviousVisible.length == 1,
86+
'found multiple members named "$name" in '
87+
'"${previousVisible.name}": '
88+
'${membersInPreviousVisible.toList()}');
89+
return membersInPreviousVisible.first.canonicalEnclosingContainer;
7190
}
72-
var canonicalContainer =
73-
packageGraph.findCanonicalModelElementFor(c) as Container?;
91+
var canonicalContainer = packageGraph
92+
.findCanonicalModelElementFor(container) as Container?;
7493
// TODO(jcollins-g): invert this lookup so traversal is recursive
7594
// starting from the ModelElement.
7695
if (canonicalContainer != null) {
@@ -80,10 +99,6 @@ mixin Inheritable on ContainerMember {
8099
break;
81100
}
82101
}
83-
previous = c;
84-
if (!packageGraph.inheritThrough.contains(c)) {
85-
previousNonSkippable = c;
86-
}
87102
}
88103
if (found != null) {
89104
return found;
@@ -96,6 +111,8 @@ mixin Inheritable on ContainerMember {
96111
return super.computeCanonicalEnclosingContainer();
97112
}
98113

114+
bool _isHiddenInterface(Container? c) => packageGraph.isHiddenInterface(c);
115+
99116
/// A roughly ordered list of this element's enclosing container's inheritance
100117
/// chain.
101118
///

lib/src/model/package_graph.dart

Lines changed: 14 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -706,29 +706,28 @@ class PackageGraph with CommentReferable, Nameable {
706706
?.linkedName ??
707707
'Object';
708708

709-
/// The set of [Class]es which should _not_ be presented as implementers.
709+
/// The set of [Class]es which should _not_ be considered the canonical
710+
/// enclosing container of any container member.
710711
///
711712
/// Add classes here if they are similar to Interceptor in that they are to be
712713
/// ignored even when they are the implementers of [Inheritable]s, and the
713714
/// class these inherit from should instead claim implementation.
714-
late final Set<Class> inheritThrough = () {
715-
var interceptorSpecialClass = specialClasses[SpecialClass.interceptor];
716-
if (interceptorSpecialClass == null) {
717-
return const <Class>{};
718-
}
715+
late final Set<Class> _inheritThrough = {
716+
if (specialClasses[SpecialClass.interceptor] case var interceptor?)
717+
interceptor,
718+
};
719719

720-
return {interceptorSpecialClass};
721-
}();
720+
/// Whether [c] is a "hidden" interface.
721+
///
722+
/// A hidden interface should never be considered the canonical enclosing
723+
/// container of a container member.
724+
bool isHiddenInterface(Container? c) => _inheritThrough.contains(c);
722725

723726
/// The set of [Class] objects that are similar to 'pragma' in that we should
724727
/// never count them as documentable annotations.
725-
late final Set<Class> _invisibleAnnotations = () {
726-
var pragmaSpecialClass = specialClasses[SpecialClass.pragma];
727-
if (pragmaSpecialClass == null) {
728-
return const <Class>{};
729-
}
730-
return {pragmaSpecialClass};
731-
}();
728+
late final Set<Class> _invisibleAnnotations = {
729+
if (specialClasses[SpecialClass.pragma] case var pragma?) pragma,
730+
};
732731

733732
bool isAnnotationVisible(Class class_) =>
734733
!_invisibleAnnotations.contains(class_);

0 commit comments

Comments
 (0)