PR #3109 stopped a base imported from a package linking to a project class of
its name, but only for the extends edge. The reader behind class-name calls
follows the base list as written: declaredSupertypes gives the names and
findInheritedClassMethod looks each up by name. So with class Color(Enum) after
from enum import Enum, Color.name_of() still reached a project Enum.name_of, as
did color.name_of() on a receiver named after the class. On sqlalchemy,
Query(..., Generic[_T]) with Generic from typing walked to a test's local
class Generic, then to that test's Base and on to FunctionElement, so
query.select() linked to FunctionElement.select.
The walk now skips a project class whose name the head binds outside the
project, with the check PR #3109 added, moved to name-matcher.ts so both can
call it (the import resolver already imports from there, not the reverse). A
dotted base is bound through its first word: enum.Enum through import enum,
e.Enum through import enum as e, collections.abc.Mapping through import
collections.abc, which the check now counts as binding collections.
Validation:
- python-outside-bases.test.ts gains the calls: named import, receiver, dotted,
aliased and deep bases reach nothing, and a project base and a class in a file
the import path names still reach their methods. Without the gate 2 of the 4
new tests fail, and taking out the dotted word or the import a.b binding fails
the dotted one. 11 pass here.
- A/B on 11 Python repos plus express and gin, 496,746 edges: 10 calls removed,
0 added, 0 moved, 0 extends touched. All 10 are in sqlalchemy and all were
wrong: 6 query.select(), query.alias() and query.compile() and 4
value.self_group(), each through typing's Generic into a test's own class
Generic and its Base.
- Controls express and gin unchanged.
- Index cost on sqlalchemy, median of 3 interleaved: 27.8 s on main, 26.4 s here,
which is noise.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A Python class whose base the file imports from a package, like
class Color(Enum) after from enum import Enum, linked to any project class
named Enum: the import found no project file, and the name match or a framework
resolver then took the first class of that name. sqlalchemy's enums all
extended its own sql/sqltypes.py Enum, and litestar's pydantic models extended
a BaseModel in a docs example. PR #2998 extended this to parameterized bases,
like Sequence[_T] from typing.
isBoundToOutOfRepoImport is the gate that drops such an edge for JS, TS and
Rust. It now answers for Python too: the name is outside when every import
binding it is absolute and resolveImportPath, which searches for a module the
way Python does, finds none. A class in a file the import path names stays,
because that search can miss a module on a path the program sets up itself, and
so does a class of the referring file, which may rebind the name. A name no
named import binds, like one from a star import, is left as it was.
Validation:
- New python-outside-bases.test.ts: the 7 outside bases fail on main, plain and
parameterized, and every case that must keep its edge passes both ways.
Green here (7 passed). Taking out each of the 4 guards fails exactly its own
test.
- A/B on 7 Python repos, 469,927 edges with the controls: 67 extends removed, 0
added, 0 moved. All 67 read against their imports and all were wrong:
sqlalchemy 31 (28 from enum, 3 from typing), litestar 21 (pydantic
BaseModel), polar 14 (11 fastapi APIRoute, 2 enum IntEnum, 1 opentelemetry
Meter), django 1 (ctypes Union).
- Controls express and gin unchanged.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>