Repository navigation
fix(python): a class-name call doesn't follow a base imported from outside the project - #3115
Merged
Merged
Conversation
…tside the project 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>
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 call on a class whose base comes from outside the project no longer links to a project class that only shares the base's name. With
class Color(Enum)afterfrom enum import Enum, orclass Color(enum.Enum),Color.name_of()no longer reaches a projectEnum.name_of.Follow-up to #3109, which fixed the
extendsedge and named this gap.Root cause
The reader behind class-name calls doesn't use the
extendsedge.declaredSupertypesgives the names in the base list, andfindInheritedClassMethodlooks each one up by name. So the import never counted.On sqlalchemy this produced a chain worth seeing.
Query(…, Generic[_T])hasGenericfromtyping, the walk landed on a test's localclass Generic, then on that test'sBase, and on toFunctionElement, soquery.select()linked toFunctionElement.select.Fix
The walk now skips a project class whose name the class head binds outside the project. It uses the check from #3109, moved to
name-matcher.tsso both callers share it: the import resolver already imports from there, not the reverse. The check reads the module throughcontext.resolveModuleFile, the seam name matching already has.A dotted base is bound through its first word:
enum.Enumthroughimport enum,e.Enumthroughimport enum as e, andcollections.abc.Mappingthroughimport collections.abc, which the check now counts as bindingcollections.Python only. TS, JS and the rest of the walk are unchanged.
Validation
Tests:
python-outside-bases.test.tsgains 4. A named import, a receiver named after the class, and dotted, aliased and deep bases reach nothing. A project base and a class in a file the import path names still reach their methods. Without the gate, 2 of the 4 fail. Taking out the dotted word or theimport a.bbinding fails the dotted one. 11 pass here.A/B against main, kernel on: 13 repos, 496,746 edges, 10 calls removed, 0 added, 0 moved, no
extendstouched.server/polar, djangodjango/, flask, requests, and the four small PEP 695 apps from #3105All 10 were read and all were wrong, each through
typing'sGenericinto a test's ownclass Generic:value.self_group()FunctionElement.self_groupquery.select()FunctionElement.selectquery.alias()FunctionElement.aliasquery.compile()DQLDMLClauseElement.compileCost: sqlalchemy, median of 3 interleaved runs: 27.8 s on main, 26.4 s here. That gap is noise, so nothing measurable.
Suite: 841 files (835 passed, 6 skipped), 9,890 tests passed, 2 expected failures, 0 failed, 87 skipped, on this exact commit, kernel on, 4 workers, Vitest 4.
🤖 Generated with Claude Code