Repository navigation
fix(python): a class with PEP 695 type parameters reads its base list - #3111
Merged
Merged
Conversation
pythonBaseList, the resolver's reader of a Python base list behind module-global, self. and class-name dispatch, expected the ( of the list right after the class name. In class Repo[T](Base[T]): it found the [ of the type parameters, so the class seemed to have no bases there, though extraction gave it its extends edge: current = Repo() then current.run(), and Repo.run(x), reached nothing. The reader now skips a type parameter list between the name and the bases, with brackets balanced, so bounds like T: dict[str, int], *Ts and **P go whole, and a constraint tuple like [T: (int, str)] isn't taken for the base list. Either list can wrap over several lines. Anything else after the name and its parameters, such as the : of class NoBases[T]:, still means no base list. Validation: - The test file: 4 of 7 fail on main (the controls pass), 7 pass here, and the multiline, generic-base and type-argument suites still pass. - A fixture of ours on the built CLI, kernel on: Repo.run(None), a wrapped list, a constraint tuple and a module global reach no method on main and Base::run here. class NoBases[T] reaches neither run, and Typed[T](Base[User]) does not reach User.save. - A/B against main on 11 Python repos, 486,377 edges (requests, flask, litestar, mypy, sqlalchemy, polar's and django's packages, and four small apps that use PEP 695 bases): nothing changes. Neither do express and gin, whose full dumps are byte-identical. polar has 39 such classes, and none is reached by a call this reader decides. - Index cost on sqlalchemy, median of 3 interleaved: 23.4 s on main and here. Original PR #3105 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 written with PEP 695 type parameters, like
class Repo[T](Base[T]):, now reaches the methods its bases declare, through a module global and through the class name, as a class without type parameters already did.Lands #3105 by @JosefAschauer.
Root cause
pythonBaseList, the resolver's reader behind module-global,self.and class-name dispatch, expected the(of the base list right after the class name. Here it found the[of the type parameters, so the class seemed to have no bases. Extraction already gave these classes theirextendsedge.What came from the contributor, and what we changed
From #3105, unchanged: the reader change in
name-matcher.ts, which skips a balanced type parameter list, and the test file.Ours: the CHANGELOG bullet placed after the newest Python bullet, with the credit added.
Validation
Stage A: every changed line of 34250df read. Scanner clean: the only hits are
RegExp.exec, the test's temp-dir writes and its../srcimport.Reproduced on main with a fixture of ours, kernel on:
Repo.run(None), a wrapped list, a constraint tuple and a module global reach nothing. With this change they reachBase::run,class NoBases[T]reaches neitherrun, andTyped[T](Base[User])doesn't reachUser.save.Tests: 4 of 7 fail on main (the 3 controls pass), 7 pass here. The multiline, generic-base and type-argument suites still pass.
A/B against main, kernel on: no change.
server/polar@ 8dd9956django/@ a6a5d1ePEP 695 bases are still rare, so this mostly closes a gap rather than moving real graphs. polar has 39 such classes, and the four small apps were picked because they use them, but no call in these repos is decided by this reader on such a class. Controls have byte-identical full dumps.
Cost: sqlalchemy, median of 3 interleaved runs: 23.4 s on main and here.
Suite: 840 files (834 passed, 6 skipped), 9,887 tests passed, 0 failed, 87 skipped, on 303a881d, kernel on, 2 workers, Vitest 2. This head is that commit rebased onto main, which then moved the tests to Vitest 4 (#3110) and shares no source or test file with it. After
npm ci, its test file and the 4 other Python base suites pass on Vitest 4 (42 tests).🤖 Generated with Claude Code