fix(python): bare class references produce references edges to classes (#1478) (#1493)

Python's class-as-value idioms (return SomeClass, x = SomeClass, registry
dicts, classes passed as arguments) produced no references edges, so
callers/impact on a Django/DRF serializer missed the views that consume it.
Three gates dropped them:

- return_statement was never dispatched by PYTHON_SPEC (kernel mirrored)
- the extraction gate (definedHere) collected function/method names only
- resolution accepted function/method targets only (matchFunctionRef +
  the function_ref import fast path)

Capture return_statement for Python (single expression; tuple returns not
descended), admit same-file CLASS names to the gate, and accept class
targets for Python bare identifiers — scoped to Python so the TS/JS KIND
FILTER contract is untouched. The docopt false-positive mechanism behind
the function-only rule (lowercase locals vs same-named methods) doesn't
transfer: methods stay excluded for bare ids, and the same-file/import
gate + unique-or-drop rules still apply.

Probed on django-rest-framework (~250 files): 559 new references→class
edges, 10/10 sampled genuine (serializer_class = AuthTokenSerializer, the
ModelSerializer field-mapping registry, aliases, ctor args, isinstance).
EXTRACTION_VERSION 24 → 25.

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Colby Mchenry
2026-08-01 01:36:07 -05:00
committed by GitHub
co-authored by Claude Fable 5
parent f2a5df34de
commit 38580e0b04
9 changed files with 171 additions and 11 deletions
+13 -6
View File
@@ -45,7 +45,7 @@ custom `visitNode` hooks like Scala's val/var handler) get a candidates-only
| C / ObjC | `argument_list` | `assignment_expression.right` | `initializer_pair.value` | `initializer_list`, `init_declarator.value` | `&fn` (`pointer_expression`), `@selector(...)` (ObjC) |
| C++ | **`&` forms only** in args/rhs/varinit | (same — explicit `&` only) | bare ids at FILE scope only | bare ids at FILE scope only | `&fn`, `&Cls::method` (resolved scoped to the class) |
| TS / JS (tsx/jsx) | `arguments` | `assignment_expression.right` | `pair.value` | `array`, `variable_declarator.value` | `this.method` (`member_expression`, class-scoped — see rule 3) |
| Python | `argument_list`, `keyword_argument.value` | `assignment.right` | `pair.value` | `list` | `self.method` (`attribute`) |
| Python | `argument_list`, `keyword_argument.value`, `return_statement` (#1478 — single expression only; tuple returns not descended) | `assignment.right` | `pair.value` | `list` | `self.method` (`attribute`) |
| Go | `argument_list` | `assignment_statement` / `short_var_declaration` (`expression_list`) | `keyed_element` | `literal_value`, `var_spec.value` | — |
| Rust | `arguments` | `assignment_expression.right` | `field_initializer.value` | `array_expression`, `static_item` / `let_declaration.value` | — |
| Java | `argument_list` | `assignment_expression.right` | — | `variable_declarator.value` | `method_reference` (`Cls::m`, `this::m`) — the only form |
@@ -76,11 +76,18 @@ custom `visitNode` hooks like Scala's val/var handler) get a candidates-only
`arena_ind_prev = arena_ind` (redis/jemalloc) each matched a unique
same-named function somewhere and produced wrong edges when `rhs`/`varinit`
were ungated.
3. **TS/JS/Python: bare ids resolve to `function` kind only.** A bare
identifier can never be a method value in these languages (methods need a
receiver — `this.m` / `self.m`), so allowing method targets soaked up
locals passed as arguments (`new Set(selectedPointsIndices)`;
docopt.py's `name`/`match` params — excalidraw/fmt A/B findings).
3. **TS/JS/Python: bare ids resolve to `function` kind only — plus `class`
for Python (#1478).** A bare identifier can never be a method value in
these languages (methods need a receiver — `this.m` / `self.m`), so
allowing method targets soaked up locals passed as arguments
(`new Set(selectedPointsIndices)`; docopt.py's `name`/`match` params —
excalidraw/fmt A/B findings). Python bare ids ALSO accept CLASS targets:
class-as-value is a core Python idiom (`return SomeSerializer`, registry
dicts, `admin.site.register(Model, Admin)`) with no type-annotation
recovery path, the gate additionally admits same-file CLASS names for
Python, and the docopt false-positive mechanism (lowercase locals vs
same-named methods) doesn't transfer to exact-name class matches. TS/JS
keep the class exclusion (the KIND FILTER contract).
TS/JS `this.X` values are captured as `this.`-PREFIXED candidates and
resolved CLASS-SCOPED (`resolveThisMemberFnRef` in
`src/resolution/index.ts`): the target must be a function/method whose