Skip to content

Commit 12d271a

Browse files
authored
Combine the two implementations of "library canonicalization" (#3781)
I was shocked to find that we had two mechanisms for determining an element's "canonical library": `ModelElement.canonicalLibrary` and a `PackageGraph._findCanonicalLibraryFor`. They were not quite the same implementation, but the differences turn out to be uninteresting; the two implementations can be combined. This change removes `PackageGraph._findCanonicalLibraryFor`. This implementation was only used in the package graph to find the canonical element for a given element. That code can now just use `ModelElement.canonicalLibrary`. It requires a fair bit of refactoring all over: * `PackageGraph.findCanonicalModelElementFor` previously accepted an analyzer Element, instead of a ModelElement, but almost all of the call sites used `foo.element` or some other expression that could easily be replaced with an access to a ModelElement instead of the correlated Element. * Document `ModelElement.definingLibrary`, `ModelElement.canonicalLibrary`, `ModelElement._searchForCanonicalLibrary`. * Simplify `ContainerMember.computeCanonicalEnclosingContainer` to stop unnecessarily special-casing extensions and extension types. * Simplify lookup component of `ModelElement._searchForCanonicalLibrary`. * Simplity `ContainerMember.referenceGrandparentOverrides` to not use `sync*`. * Move the top-level `findCanonicalFor` function out of `model_utils.dart` and into `inheriting_container.dart`, the only code that uses it anymore.
1 parent ca61935 commit 12d271a

8 files changed

+93
-105
lines changed

lib/src/element_type.dart

+1-2
Original file line numberDiff line numberDiff line change
@@ -301,8 +301,7 @@ abstract class DefinedElementType extends ElementType {
301301
@override
302302
bool get isPublic {
303303
var canonicalClass =
304-
packageGraph.findCanonicalModelElementFor(modelElement.element) ??
305-
modelElement;
304+
packageGraph.findCanonicalModelElementFor(modelElement) ?? modelElement;
306305
return canonicalClass.isPublic;
307306
}
308307

lib/src/generator/templates.runtime_renderers.dart

+1-1
Original file line numberDiff line numberDiff line change
@@ -3424,7 +3424,7 @@ class _Renderer_ContainerMember extends RendererBase<ContainerMember> {
34243424
renderVariable: (CT_ c, Property<CT_> self,
34253425
List<String> remainingNames) =>
34263426
self.renderSimpleVariable(
3427-
c, remainingNames, 'Iterable<Library>'),
3427+
c, remainingNames, 'List<Library>'),
34283428
renderIterable: (CT_ c, RendererBase<CT_> r,
34293429
List<MustachioNode> ast, StringSink sink) {
34303430
return c.referenceGrandparentOverrides.map((e) =>

lib/src/model/container_member.dart

+10-19
Original file line numberDiff line numberDiff line change
@@ -40,16 +40,8 @@ mixin ContainerMember on ModelElement {
4040
return canonicalEnclosingContainer;
4141
}();
4242

43-
Container? computeCanonicalEnclosingContainer() {
44-
final enclosingElement = this.enclosingElement;
45-
return switch (enclosingElement) {
46-
Extension() =>
47-
packageGraph.findCanonicalModelElementFor(enclosingElement.element),
48-
ExtensionType() =>
49-
packageGraph.findCanonicalModelElementFor(enclosingElement.element),
50-
_ => packageGraph.findCanonicalModelElementFor(element.enclosingElement),
51-
} as Container?;
52-
}
43+
Container? computeCanonicalEnclosingContainer() =>
44+
packageGraph.findCanonicalModelElementFor(enclosingElement) as Container?;
5345

5446
@override
5547
// TODO(jcollins-g): dart-lang/dartdoc#2693.
@@ -63,13 +55,12 @@ mixin ContainerMember on ModelElement {
6355
];
6456

6557
@override
66-
Iterable<Library> get referenceGrandparentOverrides sync* {
67-
// TODO(jcollins-g): split Field documentation up between accessors
68-
// and resolve the pieces with different scopes. dart-lang/dartdoc#2693.
69-
// Until then, just pretend we're handling this correctly.
70-
yield (documentationFrom.first as ModelElement).definingLibrary;
71-
// TODO(jcollins-g): Wean users off of depending on canonical library
72-
// resolution. dart-lang/dartdoc#2696
73-
if (canonicalLibrary != null) yield canonicalLibrary!;
74-
}
58+
List<Library> get referenceGrandparentOverrides =>
59+
// TODO(jcollins-g): split Field documentation up between accessors
60+
// and resolve the pieces with different scopes. dart-lang/dartdoc#2693.
61+
// Until then, just pretend we're handling this correctly.
62+
[
63+
(documentationFrom.first as ModelElement).definingLibrary,
64+
(packageGraph.findCanonicalModelElementFor(this) ?? this).library,
65+
];
7566
}

lib/src/model/inheritable.dart

+3-3
Original file line numberDiff line numberDiff line change
@@ -70,8 +70,8 @@ mixin Inheritable on ContainerMember {
7070
.memberByExample(this)
7171
.canonicalEnclosingContainer;
7272
}
73-
var canonicalContainer = packageGraph
74-
.findCanonicalModelElementFor(c.element) as Container?;
73+
var canonicalContainer =
74+
packageGraph.findCanonicalModelElementFor(c) as Container?;
7575
// TODO(jcollins-g): invert this lookup so traversal is recursive
7676
// starting from the ModelElement.
7777
if (canonicalContainer != null) {
@@ -100,7 +100,7 @@ mixin Inheritable on ContainerMember {
100100
}
101101
} else if (definingEnclosingContainer is! Extension) {
102102
// TODO(jcollins-g): factor out extension logic into [Extendable].
103-
return packageGraph.findCanonicalModelElementFor(element.enclosingElement)
103+
return packageGraph.findCanonicalModelElementFor(enclosingElement)
104104
as Container?;
105105
}
106106
return super.computeCanonicalEnclosingContainer();

lib/src/model/inheriting_container.dart

+12-2
Original file line numberDiff line numberDiff line change
@@ -405,18 +405,28 @@ abstract class InheritingContainer extends Container {
405405
} else {
406406
var implementers = packageGraph.implementers[implementer];
407407
if (implementers != null) {
408-
model_utils.findCanonicalFor(implementers).forEach(addToResult);
408+
_findCanonicalFor(implementers).forEach(addToResult);
409409
}
410410
}
411411
}
412412

413413
var immediateImplementers = packageGraph.implementers[this];
414414
if (immediateImplementers != null) {
415-
model_utils.findCanonicalFor(immediateImplementers).forEach(addToResult);
415+
_findCanonicalFor(immediateImplementers).forEach(addToResult);
416416
}
417417
return result.toList(growable: false)..sort(byName);
418418
}
419419

420+
/// Finds canonical classes for all classes in the iterable, if possible.
421+
/// If a canonical class can not be found, returns the original class.
422+
Iterable<InheritingContainer> _findCanonicalFor(
423+
Iterable<InheritingContainer> containers) {
424+
return containers.map((container) {
425+
var canonical = packageGraph.findCanonicalModelElementFor(container);
426+
return canonical as InheritingContainer? ?? container;
427+
});
428+
}
429+
420430
bool get hasPublicInterfaces => publicInterfaces.isNotEmpty;
421431

422432
List<InheritingContainer> get interfaceElements => [

lib/src/model/model_element.dart

+20-9
Original file line numberDiff line numberDiff line change
@@ -439,17 +439,23 @@ abstract class ModelElement
439439
ExtensionType() => enclosingElement,
440440
_ => null,
441441
};
442-
return packageGraph.findCanonicalModelElementFor(element,
442+
return packageGraph.findCanonicalModelElementFor(this,
443443
preferredClass: preferredClass);
444444
}();
445445

446446
bool get hasSourceHref => sourceHref.isNotEmpty;
447447

448448
late final String sourceHref = SourceLinker.fromElement(this).href();
449449

450+
/// The library in which [element] is declared.
451+
///
452+
/// This is different from [library] if this [ModelElement] is derived from
453+
/// a library that exports [element] from another library.
450454
Library get definingLibrary =>
451455
getModelForElement(element.library!) as Library;
452456

457+
/// A public, documented library which exports this [ModelElement], ideally in
458+
/// [definingLibrary]'s package.
453459
late final Library? canonicalLibrary = () {
454460
if (element.hasPrivateName) {
455461
// Privately named elements can never have a canonical library.
@@ -480,6 +486,8 @@ abstract class ModelElement
480486
return null;
481487
}();
482488

489+
/// Searches [PackageGraph.libraryExports] for a public, documented library
490+
/// which exports this [ModelElement], ideally in [definingLibrary]'s package.
483491
Library? _searchForCanonicalLibrary() {
484492
var thisAndExported = packageGraph.libraryExports[definingLibrary.element];
485493
if (thisAndExported == null) {
@@ -494,17 +502,20 @@ abstract class ModelElement
494502
topLevelElement.enclosingElement != null) {
495503
topLevelElement = topLevelElement.enclosingElement!;
496504
}
505+
var topLevelElementName = topLevelElement.name;
506+
if (topLevelElementName == null) {
507+
// Any member of an unnamed extension is not public, and has no
508+
// canonical library.
509+
return null;
510+
}
497511

498512
final candidateLibraries = thisAndExported
499513
.where((l) =>
500514
l.isPublic && l.package.documentedWhere != DocumentLocation.missing)
501515
.where((l) {
502-
var lookup =
503-
l.element.exportNamespace.definedNames[topLevelElement.name!];
504-
return switch (lookup) {
505-
PropertyAccessorElement() => topLevelElement == lookup.variable2,
506-
_ => topLevelElement == lookup,
507-
};
516+
var lookup = l.element.exportNamespace.definedNames[topLevelElementName];
517+
return topLevelElement ==
518+
(lookup is PropertyAccessorElement ? lookup.variable2 : lookup);
508519
}).toList(growable: true);
509520

510521
// Avoid claiming canonicalization for elements outside of this element's
@@ -612,8 +623,8 @@ abstract class ModelElement
612623

613624
bool get hasParameters => parameters.isNotEmpty;
614625

615-
/// If [canonicalLibrary] (or [canonicalEnclosingElement], for [Inheritable]
616-
/// subclasses) is null, this is null.
626+
/// If [canonicalLibrary] (or [Inheritable.canonicalEnclosingContainer], for
627+
/// [Inheritable] subclasses) is `null`, this is `null`.
617628
@override
618629
String? get href {
619630
if (!identical(canonicalModelElement, this)) {

lib/src/model/package_graph.dart

+46-59
Original file line numberDiff line numberDiff line change
@@ -708,121 +708,108 @@ class PackageGraph with CommentReferable, Nameable {
708708
return buffer.toString();
709709
}
710710

711-
final Map<Element, Library?> _canonicalLibraryFor = {};
712-
713-
/// Tries to find a top level library that references this element.
714-
Library? _findCanonicalLibraryFor(Element e) {
715-
assert(allLibrariesAdded);
716-
if (_canonicalLibraryFor.containsKey(e)) {
717-
return _canonicalLibraryFor[e];
718-
}
719-
_canonicalLibraryFor[e] = null;
720-
721-
var searchElement = switch (e) {
722-
PropertyAccessorElement() => e.variable2,
723-
GenericFunctionTypeElement() => e.enclosingElement,
724-
_ => e,
725-
};
726-
if (searchElement == null) return null;
727-
for (var library in publicLibraries) {
728-
var modelElements = library.modelElementsMap[searchElement];
729-
if (modelElements != null) {
730-
if (modelElements.any((element) => element.isCanonical)) {
731-
return _canonicalLibraryFor[e] = library;
732-
}
733-
}
734-
}
735-
return _canonicalLibraryFor[e];
736-
}
737-
738-
/// Tries to find a canonical [ModelElement] for this [e]. If we know this
739-
/// element is related to a particular class, pass a [preferredClass] to
740-
/// disambiguate.
711+
/// Tries to find a canonical [ModelElement] for this [modelElement]. If we
712+
/// know this element is related to a particular class, pass a
713+
/// [preferredClass] to disambiguate.
741714
///
742715
/// This doesn't know anything about [PackageGraph.inheritThrough] and
743716
/// probably shouldn't, so using it with [Inheritable]s without special casing
744717
/// is not advised.
745-
ModelElement? findCanonicalModelElementFor(Element? e,
718+
///
719+
/// This can return `null` in a few ways: if [modelElement] is `null`, or if
720+
/// it has no canonical library, or if a possible canonical model element has
721+
/// a `false` value for `isCanonical`.
722+
ModelElement? findCanonicalModelElementFor(ModelElement? modelElement,
746723
{Container? preferredClass}) {
747724
assert(allLibrariesAdded);
748-
if (e == null) return null;
725+
if (modelElement == null) return null;
726+
var element = modelElement.element;
749727
if (preferredClass != null) {
750728
var canonicalClass =
751-
findCanonicalModelElementFor(preferredClass.element) as Container?;
729+
findCanonicalModelElementFor(preferredClass) as Container?;
752730
if (canonicalClass != null) preferredClass = canonicalClass;
753731
}
754-
var lib = _findCanonicalLibraryFor(e);
732+
var lib = modelElement.canonicalLibrary;
755733
if (lib == null && preferredClass != null) {
756-
lib = _findCanonicalLibraryFor(preferredClass.element);
734+
lib = preferredClass.canonicalLibrary;
757735
}
758736
// For elements defined in extensions, they are canonical.
759-
var enclosingElement = e.enclosingElement;
737+
var enclosingElement = element.enclosingElement;
760738
if (enclosingElement is ExtensionElement) {
761739
lib ??= getModelForElement(enclosingElement.library) as Library?;
762740
// TODO(keertip): Find a better way to exclude members of extensions
763741
// when libraries are specified using the "--include" flag.
764742
if (lib != null && lib.isDocumented) {
765-
return getModelFor(e, lib);
743+
return getModelFor(element, lib);
766744
}
767745
}
768746
// TODO(jcollins-g): The data structures should be changed to eliminate
769747
// guesswork with member elements.
770-
var declaration = e.declaration;
771-
ModelElement? modelElement;
748+
var declaration = element.declaration;
749+
ModelElement? canonicalModelElement;
772750
if (declaration != null &&
773-
(e is ClassMemberElement || e is PropertyAccessorElement)) {
774-
e = declaration;
775-
modelElement = _findCanonicalModelElementForAmbiguous(e, lib,
751+
(element is ClassMemberElement || element is PropertyAccessorElement)) {
752+
modelElement = getModelForElement(declaration);
753+
element = modelElement.element;
754+
canonicalModelElement = _findCanonicalModelElementForAmbiguous(
755+
modelElement, lib,
776756
preferredClass: preferredClass as InheritingContainer?);
777757
} else {
778758
if (lib != null) {
779-
if (e is PropertyInducingElement) {
780-
var getter = e.getter != null ? getModelFor(e.getter!, lib) : null;
781-
var setter = e.setter != null ? getModelFor(e.setter!, lib) : null;
782-
modelElement = getModelForPropertyInducingElement(e, lib,
759+
if (element is PropertyInducingElement) {
760+
var getter =
761+
element.getter != null ? getModelFor(element.getter!, lib) : null;
762+
var setter =
763+
element.setter != null ? getModelFor(element.setter!, lib) : null;
764+
canonicalModelElement = getModelForPropertyInducingElement(
765+
element, lib,
783766
getter: getter as Accessor?, setter: setter as Accessor?);
784767
} else {
785-
modelElement = getModelFor(e, lib);
768+
canonicalModelElement = getModelFor(element, lib);
786769
}
787770
}
788-
assert(modelElement is! Inheritable);
789-
if (modelElement != null && !modelElement.isCanonical) {
790-
modelElement = null;
771+
assert(canonicalModelElement is! Inheritable);
772+
if (canonicalModelElement != null && !canonicalModelElement.isCanonical) {
773+
canonicalModelElement = null;
791774
}
792775
}
793776
// Prefer fields and top-level variables.
794-
if (e is PropertyAccessorElement && modelElement is Accessor) {
795-
modelElement = modelElement.enclosingCombo;
777+
if (element is PropertyAccessorElement &&
778+
canonicalModelElement is Accessor) {
779+
canonicalModelElement = canonicalModelElement.enclosingCombo;
796780
}
797-
return modelElement;
781+
return canonicalModelElement;
798782
}
799783

800-
ModelElement? _findCanonicalModelElementForAmbiguous(Element e, Library? lib,
784+
ModelElement? _findCanonicalModelElementForAmbiguous(
785+
ModelElement modelElement, Library? lib,
801786
{InheritingContainer? preferredClass}) {
787+
var element = modelElement.element;
802788
var candidates = <ModelElement>{};
803789
if (lib != null) {
804790
var constructedWithKey = allConstructedModelElements[
805-
ConstructedModelElementsKey(e, lib, null)];
791+
ConstructedModelElementsKey(element, lib, null)];
806792
if (constructedWithKey != null) {
807793
candidates.add(constructedWithKey);
808794
}
809795
var constructedWithKeyWithClass = allConstructedModelElements[
810-
ConstructedModelElementsKey(e, lib, preferredClass)];
796+
ConstructedModelElementsKey(element, lib, preferredClass)];
811797
if (constructedWithKeyWithClass != null) {
812798
candidates.add(constructedWithKeyWithClass);
813799
}
814800
if (candidates.isEmpty) {
815801
candidates = {
816-
...?allInheritableElements[InheritableElementsKey(e, lib)]
802+
...?allInheritableElements[InheritableElementsKey(element, lib)]
817803
?.where((me) => me.isCanonical),
818804
};
819805
}
820806
}
821807

822-
var canonicalClass = findCanonicalModelElementFor(e.enclosingElement);
808+
var canonicalClass =
809+
findCanonicalModelElementFor(modelElement.enclosingElement);
823810
if (canonicalClass is InheritingContainer) {
824811
candidates.addAll(canonicalClass.allCanonicalModelElements
825-
.where((m) => m.element == e));
812+
.where((m) => m.element == element));
826813
}
827814

828815
var matches = {...candidates.where((me) => me.isCanonical)};

lib/src/model_utils.dart

-10
Original file line numberDiff line numberDiff line change
@@ -52,16 +52,6 @@ Iterable<T> filterHasCanonical<T extends ModelElement>(
5252
return maybeHasCanonicalItems.where((me) => me.canonicalModelElement != null);
5353
}
5454

55-
/// Finds canonical classes for all classes in the iterable, if possible.
56-
/// If a canonical class can not be found, returns the original class.
57-
Iterable<InheritingContainer> findCanonicalFor(
58-
Iterable<InheritingContainer> containers) {
59-
return containers.map((c) =>
60-
c.packageGraph.findCanonicalModelElementFor(c.element)
61-
as InheritingContainer? ??
62-
c);
63-
}
64-
6555
extension ElementExtension on Element {
6656
bool get hasPrivateName {
6757
final name = this.name;

0 commit comments

Comments
 (0)