Repository navigation
fix(python-lsp): look attributes up along the C3 method resolution order - #2697
Merged
Merged
Conversation
The radarr diamond seen from an ordinary attribute: self.write_state() in RadarrCalendarEntity and e.write_state() on a RadarrCalendarEntity look the method up along the class's C3 order, which reaches CalendarEntity before the shared base Entity. Single inheritance as the control; sequential and parallel. Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
Ordinary attribute lookup walked the bases one at a time, each through its whole ancestry, so on a diamond self.m() and e.m() reached the shared base before a later base that overrides it. It now follows the class's C3 order, the order super() uses, after the class's own members; a hierarchy without a C3 order keeps the depth-first walk. Each class is linearized once per file: the orders live in a per-file, arena-allocated open-addressing cache keyed by class QN, which also replaces the per-lookup cache of the super() resolver. The registry is complete before the walk starts, so an entry never goes stale; keys and the class entry of an order are arena copies. Linearizing a class costs one merge of its bases' cached orders, so no lookup re-linearizes a deep hierarchy. Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
DeusData
added a commit
that referenced
this pull request
Oct 19, 2026
py_lsp.c: #2697 moved py_lookup_attribute below the C3 code (own members, C3 order, then the depth-first walk) and declares it up front; the trivial definition this branch still carried next to its module-member lookup is dropped, the module-member block is kept, and the C3 section takes main's header. Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What
#2696 makes
super()follow Python's C3 method resolution order. Ordinary attribute lookup had the samedefect: it walked the bases one at a time, each through its whole ancestry. So on a diamond,
self.m()orobj.m()reached the shared base before a later base that overrides it:(sentry's real hierarchy has more levels; it has this shape.)
py_lookup_attributenow asks the class's own members first and then follows the class's C3 order, thesame order
super()uses. A hierarchy without a C3 order keeps the depth-first walk.Each class is linearized once per file. The orders live in a per-file, arena-allocated open-addressing
cache keyed by class QN, which also replaces #2696's per-lookup cache. Linearizing a class merges its
bases' cached orders, so no lookup re-linearizes a deep hierarchy. The registry is complete before the walk
starts, so an entry never goes stale.
Stacked on #2696, whose branch (
fix/py-super-c3-mro) is this PR's base. No index-format change.Evidence
#2696's head vs this change, 26 Python repos (manifest v1), production builds, macOS arm64,
--repeat 2, every repeat byte-identical.Every other edge type is identical, apart from SEMANTICALLY_RELATED, which is derived from the CALLS
neighbourhoods. 1,417 of the removed CALLS are retargets on the same call line. The other 4 merge into an
edge the caller already had to the new target on another line. The TESTS changes follow from the CALLS.
No strategy is new: the edges stay
lsp_method/lsp_operator_dunder.Every retarget moves from the shared base that the depth-first walk reached through the first base, to
the class the C3 order puts first:
Fixtures.store_eventbecomesSnubaTestCase.store_event(761), andFixtures.create_projectbecomesSnubaTestCase.create_project(485).Fixturesis the common base ofboth test-case branches, so it follows
SnubaTestCasein every test class's MRO. The rest areSharedSnubaMixin.store_event, the metrics query builders'run_query, andDemoSafePermission.determine_access.CrawlerRunnerBase.crawlbecomesAsyncCrawlerRunner.crawl.engine.interfaces.Dialectstubs becomeDefaultDialect, andExternallyTraversible._copy_internalsbecomesHasCopyInternals._copy_internals.BasicbecomesExpr/AssocOp(doit,count_ops,could_extract_minus_sign), andBasic.simplifybecomesBooleanFunction.simplify.FlowHandler.async_show_form/async_create_entrybecomeConfigFlow's.Indexing time per pass is unchanged within machine noise. The repeats that ran slower were slower in
extraction as well, which this change does not touch.
Tests
pipeline_python_attribute_follows_c3_mro{,_parallel}: the radarr diamond from fix(python-lsp): resolve super() through the C3 method resolution order #2696, seen fromself.write_state()inside the class and frome.write_state()on an instance. Both reachCalendarEntity, never the sharedEntity, and a single-inheritance class is the control. Sequentialand parallel. RED without the fix (
ASSERT(self_c3)).py_lsp_scale, py_lsp_bench, lsp_resolution_probe, edge_types_probe, convergence_probe,
matrix_known_classes, parallel, incremental, integration) pass.