Repository navigation
fix(python): a base list wrapped over several lines is read whole - #3103
Merged
Merged
Conversation
Both of the resolver's readers of a Python base list stopped early. pythonBases, behind module-global and self. dispatch, read only the class line. declaredSupertypes, behind class-name calls, read three lines and needed the closing parenthesis on them. black and ruff wrap a long list one base per line, so class Wrapped(Mixin, Base) written that way seemed to have no bases: current = Wrapped() then current.run(), and Wrapped.run(x), reached nothing a base declares. Extraction already read the whole list. A new pythonBaseList reads from the class line to the list's closing parenthesis, counting nested parentheses, within the class's extent, and leaves out comments, so a ) in one doesn't end the list. Both readers use it. declaredSupertypes still drops type arguments first, the composition the author posted on PR #3001, and keeps its metaclass= and factory rewrites. Validation: - The test file: 7 of 9 fail without this change (the two one-line controls pass), 9 pass here. Putting the type-argument drop out of the merged case 'python' fails the new composition case and the two of PR #3083. - Wrapped.run(None) on a wrapped base list reaches Base::run with the kernel and with wasm, as do a list over five lines, a decorated class, and a wrapped Base[Optional[User]]. - A/B on top of PR #2998, 7 Python repos, 459,440 edges: 118 added (102 calls, 16 references), 0 removed, 959 call sites moved, 113 kept their target with higher confidence. requests, flask, litestar, mypy and django do not change. polar: 942 XRepository.from_session(session) calls move from a sibling repository's from_session at 0.65 to RepositoryBase.from_session at 0.85. sqlalchemy: wrapped heads like class Table( and class ColumnElement( now reach what they inherit: t.select() reaches FromClause.select. Its 17 moved sites read right; 15 of them land on the typing-only SQLCoreOperations overload of the same ColumnOperators method. - Controls express and gin: full dumps byte-identical. - No kernel change. Index cost with PR #2998, sqlalchemy, median of 3 interleaved: 23.7 s on main, 25.2 s here. Original PR #3001 by @JosefAschauer. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.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.
Summary
In Python, a class whose base list is wrapped over several lines, as black and ruff write a long one, now reaches the methods its bases declare, through a module global like
current = Wrapped()thencurrent.run()and through the class name likeWrapped.run(x), as a one-line list already did.Lands #3001 by @JosefAschauer.
Root cause
Both of the resolver's readers of a base list stopped early.
pythonBases, behind module-global andself.dispatch, read only theclassline.declaredSupertypes, behind class-name calls, read three lines and needed the closing parenthesis on them. Extraction already read the whole list, so the class had itsextendsedges and only these readers missed them.What came from the contributor, and what we changed
From #3001:
pythonBaseList, both readers using it, and the test file.Ours:
case 'python'ofdeclaredSupertypesconflicted with fix(python): a base's type argument is no supertype, so Repo.save() is not User.save #3083 (fix(python): a base's type argument is no supertype, so Repo.save() is not User.save #3000). It's resolved with the composition the author posted on fix(python): a base list wrapped over several lines is read whole #3001: read the list whole, then drop the type arguments.Base[User]still reachesBase::runand doesn't reachUser::save.readFilefallback inpythonBaseListsplits on/\r?\n/, as the rest of the file does. Withsplit('\n')a CRLF line keeps its\r, and/#.*$/then leaves a comment in.\ns, moved next to the other Python bullets, with the credit added.Validation
Stage A: every changed line read, on 17e2f5b and its commit 5d50558. Scanner clean: the only hits are
RegExp.exec, the test's temp-dir writes and its../srcimport.The gap from the review on #3001 is closed:
Wrapped.run(None)on a wrapped base list reachesBase::run, with the kernel and with wasm, and so do a list over five lines, a decorated class and a wrappedBase[Optional[User]]. On main none of these has an edge.Tests: 7 of 9 fail without this change (the two one-line controls pass), 9 pass here. Taking the type-argument drop out of the merged
case 'python'fails the new composition case and two of #3083's tests.A/B on top of #2998, kernel on:
server/polar@ 8dd9956django/@ a6a5d1eBoth controls have byte-identical full dumps. polar and django are their package folders.
Every edge class, 459,440 edges before:
calls0.65 → 0.85XRepository.from_session(session)moves from a sibling repository'sfrom_sessiontoRepositoryBase.from_session. One in sqlalchemy:ColumnElement._gen_cache_keycallson a receiverSQLCoreOperationsoverload of the sameColumnOperatorsmethod, whichColumnElementreaches first now that its wrapped list is read. Onetable.alias()moves toFromClause.aliascallsclass Table(andclass ColumnElement(, now reach what they inherit:t.select()reachesFromClause.selectreferencescalls, higher confidenceWe read all 17 moved sqlalchemy sites, 12 of polar's, and the added list. No kernel change, so no parity run beyond #2998's: this PR reads the same kernel output.
Cost, with #2998: sqlalchemy, median of 3 interleaved runs: 23.7 s on main, 25.2 s here.
Suite: 835 files (829 passed, 6 skipped), 9,830 tests passed, 0 failed, 87 skipped, on this exact commit, kernel on, 4 workers.
Not in this PR
class Repo[T](Base[T]):, still has no base list for these readers, as before.#or)inside a string in a base list is read as code, so such a rare list is read in part or not at all, as before.🤖 Generated with Claude Code