Skip to content

Commit dcc239a

Browse files
authored
Refactor special case for how canonical enclosing containers are calculated (#3925)
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 8003230 commit dcc239a

File tree

5 files changed

+55
-68
lines changed

5 files changed

+55
-68
lines changed

lib/src/generator/templates.runtime_renderers.dart

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16364,7 +16364,6 @@ const _invisibleGetters = {
1636416364
'hasFooterVersion',
1636516365
'hashCode',
1636616366
'implementers',
16367-
'inheritThrough',
1636816367
'inheritanceManager',
1636916368
'libraries',
1637016369
'libraryCount',

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

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

117+
bool _isHiddenInterface(Container? c) => packageGraph.isHiddenInterface(c);
118+
102119
/// A roughly ordered list of this element's enclosing container's inheritance
103120
/// chain.
104121
///

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)