Repository navigation
fix(python): a parameterized base links its class, like class Leaf(Base[str]) - #3101
Merged
Merged
Conversation
…se[str]) A Python class with a parameterized base had no extends edge. The grammar parses Base[str] in a base list as a subscript, and the base-list reader took only bare identifiers and attributes, so the base was skipped, on the wasm path and in the kernel alike. Everything that follows the class hierarchy missed such a class. The resolver's own text reader of a base list, pythonBases, also looked up Base[str] verbatim and split Pair[str, int] at its inner comma, so current = Leaf() then current.run() did not reach Base.run. A subscript base is now unwrapped to its value before the identifier or attribute check, in src/extraction/tree-sitter.ts and in the kernel's python.rs. pythonBases drops bracketed type arguments before splitting the list. Generic[T] and Protocol[T] stay outside the project like their plain forms; a computed base, a subscript of a subscript and keyword arguments link nothing. Validation: - The test file runs on both arms: red on main's source and kernel (12 of 16 fail, 6 per arm), green here (16 passed). - Kernel parity with this kernel on 3,155 Python files of flask, requests, mypy, litestar, sqlalchemy, polar and django: 0 diffs, and the same 5 files defer to wasm as on main. The kernel and wasm edge dumps of the repos run both ways are identical. - A/B against main on 7 Python repos, 457,948 edges: 1,492 added (901 extends, 555 calls, 36 references), 0 removed, 607 call sites moved, 223 kept their target with higher confidence. polar: 491 self.get_one_or_none() style calls move from the protocol stub RepositoryProtocol at 0.4 to RepositoryBase and its mixin at 0.9. litestar: 12 calls in GenericSyncMockRepository move from AbstractAsyncRepository to AbstractSyncRepository. sqlalchemy: 435 extends and 554 calls added, like t.insert() reaching TableClause.insert. We read 44 sampled changes and every small bucket. They read right, except 12 self.op() sites that move to the typing-only overload SQLCoreOperations.op and one Enum.copy that moves to TypeEngine.copy. - 3 of the 901 extends are wrong: sqlalchemy's Sequence[_T], Tuple[int, ...] and MutableSet[_T] from typing link to its own classes of those names. The plain form does this on main (28 class X(Enum) there), and none of the 3 brought a call edge. - Controls express and gin: full dumps byte-identical. - Index cost with PR #3001 on top, sqlalchemy, median of 3 interleaved: 23.7 s on main, 25.2 s here, with 1,143 more edges. Original PR #2998 by @JosefAschauer. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This was referenced Oct 30, 2026
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
A Python class with a parameterized base, like
class Leaf(Base[str]),class Repo(models.Base[int])orclass Both(Mixin, Base[str]), is now linked to that base, in extraction and in the kernel alike. A call through a module global holding such a class, likecurrent = Leaf()thencurrent.run(), now reaches the method the base declares.Lands #2998 by @JosefAschauer.
Root cause
The grammar parses
Base[str]in a base list as asubscript, and the base-list reader took only bare identifiers and attributes, so the base was skipped. The class had noextendsedge, and everything that follows the class hierarchy missed it. The resolver's own text reader,pythonBases, also looked upBase[str]verbatim and splitPair[str, int]at its inner comma.What came from the contributor, and what we changed
From #2998, unchanged: the
subscriptunwrap insrc/extraction/tree-sitter.tsand in the kernel'spython.rs, the bracket drop inpythonBases, and the test file.Ours:
CODEGRAPH_KERNEL_EXPECT=1), as the other kernel-gated tests do.Validation
Stage A: every changed line read, including the Rust. Scanner clean: the only hits are the test's temp-dir writes, its
CODEGRAPH_KERNEL*env toggles, which it restores, and its../srcimport. No binaries; the kernel was built here frompython.rs.Reproduced on main:
class Leaf(Base[str])has noextendsedge, on the wasm path and in the kernel.Tests: the test file runs on both arms. Red on main's source and kernel (12 of 16 fail, 6 per arm), green here (16 passed).
Kernel parity with this kernel, on 3,155 Python files of flask, requests, mypy, litestar, sqlalchemy, polar and django: 0 diffs, and the same 5 files defer to wasm as on main. The kernel and wasm edge dumps of flask, litestar, mypy and sqlalchemy are identical.
A/B against main, kernel on, every arm built from its own sources:
server/polar@ 8dd9956django/@ a6a5d1eBoth controls have byte-identical full dumps. polar and django are their package folders, to stay under the heavy-index gate; whole, they index 2,103 and 3,027 files.
Every edge class in the 7 Python repos, 457,948 edges on main:
extendsby importextendsby namecallsat 0.9t.insert()reachesTableClause.insertreferencesat 0.9calls0.4 → 0.9self.get_one_or_none()moves from the protocol stubRepositoryProtocoltoRepositoryBaseand its mixinscalls0.7 → 0.9GenericSyncMockRepositorymoves fromAbstractAsyncRepositorytoAbstractSyncRepository, and sqlalchemy'sInsert.extfromQuery.exttoHasSyntaxExtensions.extcallson a receiver, 0.65 or 0.8 → 0.9t.select()moves fromFunctionElement.selecttoFromClause.select, and polar'srepo.get_by_id()from another repository to the mixincalls0.9 → 0.4 or 0.7self.op()moves fromOperators.opto the typing-only overloadSQLCoreOperations.opEnum.copythat moves toTypeEngine.copy, a base further up the method resolution ordercalls, higher confidenceWe read 44 sampled changes and every bucket under 50.
Cost, with #3001 on top: sqlalchemy, median of 3 interleaved runs: 23.7 s on main, 25.2 s here, with 1,143 more edges.
Suite: 832 files (826 passed, 6 skipped), 9,803 tests passed, 0 failed, 87 skipped, on this exact commit, with this kernel staged, 4 workers.
Not in this PR
class X(Enum)withfrom enum import Enumlink to its ownsql/sqltypes.pyEnum, and litestar's 21 pydanticBaseModelclasses link to a docs example. This PR adds 3 more of that kind in sqlalchemy, becauseSequence[_T],Tuple[int, ...]andMutableSet[_T]fromtypingnow reach the name match like their plain forms do. None of the three brought a follow-on call edge. The fix belongs in its own PR, for plain and parameterized bases together.🤖 Generated with Claude Code