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>