fix(python): resolve bare class references to their classes - #186
Merged
Conversation
Python treats a class as a first-class value, so a DRF view selects its serializer with `return OrgSerializerFull`, a registry maps keys to classes, and a factory hands one back. None of those produced an edge, so `callers` on a serializer showed nothing and `impact` reported a hollow radius: the views that consume it were invisible. Three gates each dropped these references independently, and all three had to move together for one edge to survive: - The Python capture spec never dispatched `return_statement`, so a returned bare name was not even collected. Tuple returns stay uncollected on purpose — `return A, B` puts a single `expression_list` under the return, and `list` mode does not descend into it. - The extraction gate admitted only functions and methods defined in the file, so a same-file class name was discarded before resolution. - Resolution accepted only function targets for a bare identifier, at both the name matcher and the import fast path. Python now accepts class targets at both resolution sites through one shared predicate, so the two cannot drift apart. Methods stay excluded everywhere: a bare name has no receiver, and the lowercase-local collisions that rule guards against are unaffected by classes. Every other language keeps its existing kind filter untouched. `EXTRACTION_VERSION` moves 2 → 3, so existing indexes classify as outdated and re-extract to pick the new edges up. A dedicated `reference/golden/python/` fixture guards the semantics: six class-value shapes (return, assignment, registry pair, call argument, list literal, and an imported class returned as a value) and two negatives (a tuple return, and a bare method name). The existing mini corpus could not serve — its only Python file constructs and calls, never naming a class as a value. The imported case resolves through the name matcher's unique-name path, not the import fast path: `find_exported_symbol` requires an exported target and Python never marks one, upstream included. The import-side kind gate still mirrors upstream, and its test says plainly that it hand-builds a state real indexing cannot produce.
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
Batch 4 of the upstream v1.5.0 sync, porting
38580e0— the last portable itemof the round, and the only one that moves
reference/golden/. Batches 1-3shipped deliberately golden-neutral so this diff has exactly one possible cause.
Python treats a class as a first-class value, so a DRF view selects its
serializer with
return OrgSerializerFull, a registry maps keys to classes, anda factory hands one back. None of those produced an edge:
callerson aserializer showed nothing and
impactreported a hollow radius, with theconsuming views invisible.
Three gates dropped these references independently, and all three had to move
together for one edge to survive:
return_statement.discarding a same-file class name before resolution.
name matcher and the import fast path.
Python now accepts class targets at both resolution sites through one shared
predicate, so the two cannot drift apart. Methods stay excluded everywhere —
a bare name has no receiver, and the lowercase-local collisions that rule guards
against are unaffected by classes. Every other language keeps its existing kind
filter untouched.
EXTRACTION_VERSIONmoves 2 → 3 so existing indexesre-extract.
Tuple returns are a deliberate negative:
return A, Bputs a singleexpression_listunder the return, andlistmode does not descend into it —confirmed against
tree-sitter-python-0.25.0'snode-types.json, wherereturn_statementhas no fields and children[expression, expression_list].The golden fixture
The existing
minicorpus could not serve: its only Python file constructs(
Greeter(...)) and calls (greeter.greet(...)), never naming a class as avalue, and its
refs.jsonis empty. A dedicatedreference/golden/python/fixture guards eight auditable shapes — six positives(return, assignment RHS, registry pair, call argument, list literal, imported
class returned as a value) and two negatives (tuple return, bare method name).
The imported case resolves through the name matcher's unique-name path, not
the import fast path.
find_exported_symbolrequires an exported target andPython never marks one — measured zero
is_exportedPython nodes across bothfixtures, and upstream is identical (
python.tsimplements noisExported,import-resolver.ts:94doesif (!n.isExported) continue). So upstream's ownDRF assertion also comes from the name matcher. The import-side kind gate still
mirrors upstream, and its test states plainly that it hand-builds a state real
indexing cannot produce.
Verification
make cigreen;cargo test --workspace128 suites / 3127 passed / 0 failed(baseline
v0.42.3was 3121).names=[]; reverting the class gate →missing AliasClass: names=[]; stubbingthe shared predicate →
Python Class must be a function-ref target; revertingthe version bump →
left: 2, right: 3andleft: Building { built: 2 }, right: Outdated { built: 2 }.reference/golden/python/*artifacts, zero drift inmini/godot/ruby/cpp(
git diffcannot see new files andgit statuscollapses untracked dirs, sothe gate uses
git ls-files --othersfor the new half andgit diff --name-onlyfor the tracked half). Attribution: exactly sixclass-target
Referencesedges, each mapping to one named shape —choose_return → ReturnClass(:1),choose_alias → AliasClass(:5),choose_registry → RegistryClass(:9),choose_argument → ArgumentClass(:13),choose_list → ListClass(:17) at0.95 / function-ref, pluschoose_imported → ImportedClassat0.8;TupleA,TupleBandhandlerhave zero incoming edges;
refs.jsonholds exactly three unresolved rows.Reproducibility: regenerating into a fresh temp dir gave all five text
artifacts byte-identical, and the 38-statement schema set matches every
existing fixture.
callers OrgSerializerFullnow returnsget_serializer_classatviews.py:7,impactreports it among 3 affected symbols, both negatives hold, and aTypeScript
return TsSerializerproduces no function-ref edge — its onlyfile→class edge remains
imports / 0.9 / resolvedBy=import.Plan approved after two momus rounds; gates F1-F4 all APPROVE (F2 after four,
each round closing a stale statement rather than a behaviour defect).