Skip to content

Simplify how inheritance is computed. #4079

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Aug 4, 2025

Conversation

srawlins
Copy link
Member

The motivation here is that Inheritable.computeCanonicalEnclosingContainer is a complicated subroutine,
even after several refactorings. This is the code that decides where to link inherited members. So questions like "ah ha, List has a .isEmpty, but it inherits the docs from... Iterable.isEmpty." It's the wild west out there, with docs coming from indirect supertypes, interfaces, mixins, etc.

A big portion of the complexity comes from choosing an order in which to search the supertypes. We have to take a directed acyclic graph, and choose an order. Prior to this change, the ordering comes from each container class's implementation of get inheritanceChain. I think on paper this sounds like a good idea: let a Mixin compute its inheritance chain, let an Enum compute its inheritance chain. In practice, it spread complexity out too much, and a combined implementation is actually quite compact.

So in this change we remove InheritingContainer.inheritanceChain, the expandInheritanceChain extension getter, and Inheritable._inheritance. We replace all of this code with Inheritable._enclosingSuperTypes, which returns all of the various supertypes of the enclosing element, in a specific order. This getter performs a fairly simple walk up the inheritance tree (extended types, implemented types, mixed-in types, and superclass constraints).

The single call to Inheritable._inheritance is now a call to Inheritable._enclosingSuperTypes. computeCanonicalEnclosingContainer calls this in order to collect its set of candidates for the canonical enclosing container. _enclosingSuperTypes handles Object differently, which allows the impl of computeCanonicalEnclosingContainer to be much simplified, no longer concerning itself with any previous candidate.

This is very nearly a no-op. There is some change in the handling of Object and it's members. Because of this, we tweak the enum_test to no longer use --no-link-to-remote, so that the links to Object.hashCode and Enum.index continue to work.

I tested this with some manual verification of the Dart SDK docs, Flutter SDK docs, and the collection package docs.

srawlins added 2 commits July 24, 2025 15:17
The motivation here is that `Inheritable.computeCanonicalEnclosingContainer` is
a complicated subroutine,
[even](dart-lang@12d271a)
[after](dart-lang@d629e1e)
[several](dart-lang@dcc239a)
[refactorings](dart-lang@95f4208).
This is the code that decides where to link inherited members. So questions
like "ah ha, `List` has a `.isEmpty`, but it inherits the docs from...
`Iterable.isEmpty`." It's the wild west out there, with docs coming from
indirect supertypes, interfaces, mixins, etc.

A big portion of the complexity comes from choosing an order in which to search
the supertypes. We have to take a directed acyclic graph, and choose an order.
Prior to this change, the ordering comes from each container class's
implementation of `get inheritanceChain`. I think on paper this sounds like a
good idea: let a Mixin compute its inheritance chain, let an Enum compute its
inheritance chain. In practice, it spread complexity out too much, and a
combined implementation is actually quite compact.

So in this change we remove `InheritingContainer.inheritanceChain`, the
`expandInheritanceChain` extension getter, and `Inheritable._inheritance`. We
replace all of this code with `Inheritable._enclosingSuperTypes`, which returns
all of the various supertypes of the enclosing element, in a specific order.
This getter performs a fairly simple walk up the inheritance tree (extended
types, implemented types, mixed-in types, and superclass constraints).

The single call to `Inheritable._inheritance` is now a call to
`Inheritable._enclosingSuperTypes`. `computeCanonicalEnclosingContainer` calls
this in order to collect its set of candidates for the canonical enclosing
container. `_enclosingSuperTypes` handles `Object` differently, which allows
the impl of `computeCanonicalEnclosingContainer` to be much simplified, no
longer concerning itself with any _previous_ candidate.

This is very nearly a no-op. There is some change in the handling of `Object`
and it's members. Because of this, we tweak the `enum_test` to no longer use
`--no-link-to-remote`, so that the links to Object.hashCode and Enum.index
continue to work.
if (processed.contains(container)) return;
processed.add(container);
for (var interface in container.interfaceElements) {
addSupertype(interface);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I worry that using recursion here could lead to stackoverflows...
Perhaps better to manually maintain a stack of nodes being processed.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah I get what you mean. I think I tried that initially, and couldn't map this recursive algorithm into a loop with a stack. I think since it is a kind of depth-first walk, with the deepest containers in the hierarchy being added first. It would require a second mapping maybe, where you also track the containers that you will add to the stack.

In any case, it shouldn't be a problem because (a) the very first thing we do is check if container is in the processed set, and return quick if it is (so any illegal loops would not break this algorithm). And it's pretty unfathomable to have a straight-line hierarchy of more than... 100 containers? Even 10 is a wild stretch, much less 100 or 1,000 or 10,000.

@srawlins srawlins merged commit 414953e into dart-lang:main Aug 4, 2025
11 checks passed
@srawlins srawlins deleted the refactor-inheritable branch August 4, 2025 18:46
copybara-service bot pushed a commit to dart-lang/sdk that referenced this pull request Aug 4, 2025
…, web, webdriver

Revisions updated by `dart tools/rev_sdk_deps.dart`.

ai (https://github.com/dart-lang/ai/compare/7fbe88a..72a9283):
  72a9283  2025-07-31  Jacob MacDonald  add --exclude-tool option to exclude tools by name (dart-lang/ai#253)
  0dbbd8b  2025-07-30  Jacob MacDonald  only run in shell when on windows and running a .bat file (dart-lang/ai#252)
  0858b0b  2025-07-28  Jacob MacDonald  further prompt refinement, discourage use of temp ids and encourage getting the widget tree often (dart-lang/ai#249)
  912c1f3  2025-07-28  Jacob MacDonald  release dart_mcp v0.3.3 (dart-lang/ai#247)
  0f7cba8  2025-07-28  Jacob MacDonald  add analytics tracking for prompts (dart-lang/ai#246)

dartdoc (https://github.com/dart-lang/dartdoc/compare/882aea9..414953e):
  414953ed  2025-08-04  Sam Rawlins  Simplify how inheritance is computed. (dart-lang/dartdoc#4079)

ecosystem (https://github.com/dart-lang/ecosystem/compare/d5233c6..2fe3618):
  2fe3618  2025-08-01  dependabot[bot]  Bump the github-actions group with 3 updates (dart-lang/ecosystem#361)

protobuf (https://github.com/dart-lang/protobuf/compare/44ecd74..0b73b0d):
  0b73b0d  2025-07-30  Devon Carew  workspace, formatting, and proto version updates (google/protobuf.dart#1033)
  a664760  2025-07-30  Devon Carew  various CI workflow updates (google/protobuf.dart#1035)

test (https://github.com/dart-lang/test/compare/6aeb1e4..953e828):
  953e8282  2025-08-01  Nate Bosch  Drop executable arguments forwarding (dart-lang/test#2528)

tools (https://github.com/dart-lang/tools/compare/2a2a2d6..5e977d6):
  5e977d6f  2025-07-30  Jacob MacDonald  Fix mixtures of parentheses and spaces in windows command paths (dart-lang/tools#2138)
  607340ca  2025-07-29  Devon Carew  disable failing test (dart-lang/tools#2136)

vector_math (https://github.com/google/vector_math.dart/compare/13f185f..3939545):
  3939545  2025-08-04  Kevin Moore  Bump min SDK to 3.7, update dependencies, reformat (google/vector_math.dart#348)

web (https://github.com/dart-lang/web/compare/da1dd5d..1d5771b):
  1d5771b  2025-07-31  Nikechukwu  [interop] Add support for importing and exporting declarations, as well as multi-file output (dart-lang/web#418)

webdriver (https://github.com/google/webdriver.dart/compare/cfab787..595649d):
  595649d  2025-08-01  dependabot[bot]  Bump nanasess/setup-chromedriver (google/webdriver.dart#331)

Change-Id: Ia014bf6cafa9edcdaf453edda9cd5ecff516e16d
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/443625
Auto-Submit: Devon Carew <[email protected]>
Reviewed-by: Konstantin Shcheglov <[email protected]>
Commit-Queue: Konstantin Shcheglov <[email protected]>
@sigurdm
Copy link
Contributor

sigurdm commented Aug 5, 2025

And it's pretty unfathomable to have a straight-line hierarchy of more than... 100 containers?

Hard to imagine writing by hand - yes. But nothing in the spec prevents it, and our tools should be able to handle also bizarre generated code.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants