Skip to content

fix(python): suppress weak same-name CALLS for get/run/execute - #1386

Closed
Joseph-MingEn wants to merge 1 commit into
DeusData:mainfrom
Joseph-MingEn:fix/py-suppress-weak-generic-calls
Closed

fix(python): suppress weak same-name CALLS for get/run/execute#1386
Joseph-MingEn wants to merge 1 commit into
DeusData:mainfrom
Joseph-MingEn:fix/py-suppress-weak-generic-calls

Conversation

@Joseph-MingEn

Copy link
Copy Markdown
Contributor

Summary

  • Suppress weak short-name CALLS resolution for Python attribute calls and bare generic callees (get / run / execute), mirroring the existing TS/JS weak-method guard.
  • Flag Python attribute calls as is_method during extraction so prior_cp.get(...) cannot suffix_match onto unrelated Methods (e.g. _SessionRegistry.get).
  • Same guard drops bare run() Callable-parameter calls from binding to unrelated Methods (e.g. SatoriLive.run).

Motivation (Yui WP-B C2 / G2)

On a large Python repo, trace_path hop-1 showed fabricated CALLS:

  • router.submit_task_SessionRegistry.get via strategy=suffix_match / callee=prior_cp.get / conf 0.28
  • gate._run_with_heavy_slotSatoriLive.run via suffix_match / bare run() / conf 0.28

Strong strategies (import_map, same_module, lsp_*) are kept.

Test plan

  • ./build/c/test-runner registry (includes new python suppress unit tests)
  • ./build/c/test-runner pipeline (includes pipeline_python_suppresses_weak_generic_edges)
  • Maintainer CI green

Made with Cursor

Mirror TS/JS weak-method guards for Python attribute calls and bare
generic callees so suffix_match cannot bind prior_cp.get / run() onto
unrelated Methods (G2 / Yui WP-B C2).

Co-authored-by: Cursor <cursoragent@cursor.com>
@Joseph-MingEn
Joseph-MingEn requested a review from DeusData as a code owner July 31, 2026 16:33
@github-actions

Copy link
Copy Markdown

Thanks for opening this — it has been seen, and it is queued.

This note is automated, but it is not a brush-off: it exists so you know where your PR stands instead of having to guess from silence.

Current review status: working through a backlog. 0.9.1-rc.1 is out, so the release freeze that held reviews is over — but it left a large queue of open pull requests behind it, and we are reading through them oldest-first. The background is in discussion #1144.

What that means for this PR, concretely:

  • It will not be closed for inactivity. No stale bot touches pull requests here.
  • It may still sit a while before a human reads it. That is on us, not on you.
  • Older PRs are read first, so a recent one is not being skipped — it is behind a queue.

Things that will genuinely speed it up whenever review does happen:

  • Keep it rebased on main — the tree is moving quickly right now, and a conflicting branch cannot be reviewed as the diff you intended.
  • Get CI green, or say which failures you believe are pre-existing.
  • Keep the change to one claim. Bundled features and refactors get split before they get merged, which costs you a round trip.
  • Every commit needs a sign-off (git commit -s) — CI enforces DCO.

If this fixes a bug, a reproduction we can run is worth more than a description of the symptom.

Thanks for contributing, and sorry in advance for the wait.

@DeusData DeusData added bug Something isn't working parsing/quality Graph extraction bugs, false positives, missing edges priority/high Needs near-term maintainer attention; high-impact bug, regression, safety issue, or release blocker. labels Aug 3, 2026
@DeusData DeusData added this to the 0.9.1-rc milestone Aug 3, 2026
@DeusData

DeusData commented Aug 3, 2026

Copy link
Copy Markdown
Owner

Thank you for the focused Python same-name fallback suppression and the tests around generic method names. This is now routed as a high-priority parsing fix in 0.9.1-rc and linked to the remaining scope in #1355. This is triage rather than approval; our review queue is full, so the detailed review may take a little time, especially because the PR addresses only one part of the broader binding problem.

@DeusData

Copy link
Copy Markdown
Owner

The false-positive class is real (prior_cp.get resolving to an unrelated _SessionRegistry.get is exactly the #1276 family) and mirroring the TS/JS guard is the right instinct. Three things before this can merge: (1) mechanical — the branch conflicts, DCO and lint are red, and all three pr-smoke legs fail; rebase + sign-off + lint first. (2) The blanket is_method=true for every Python attribute call in extract_calls.c changes semantics for every other is_method consumer — narrow it or make the case. (3) The hardcoded {get,run,execute} blacklist plus blanket weak-member suppression is a recall cut on a priority language: our #1000 lesson is that graph-shape changes need corpus-scale evidence, so please include Django-scale before/after edge counts (we can help run them once the branch is green). Related work just landed you should build on: #1647 now suppresses cross-language suffix_match CALLS.

@DeusData

Copy link
Copy Markdown
Owner

Closing this one, and I want to be precise about why, because you identified a real defect class that nothing else in the codebase catches — and half of your diagnosis is being built now with credit to you.

What happened to each half

The member half is now covered. #1903 landed on main (5d96377a) doing the same thing your PR did for obj.method() calls: Python receivers that are not self/cls/super() nor import-rooted get is_method, enabling the existing weak-member guard against suffix_match/unique_name/field_type_hint/fuzzy. Your motivating case — prior_cp.get(...) with prior_cp a parameter — is covered identically.

The difference is exemptions. Your version flags every Python attribute call with none, which also drops self.inherited() resolving across files and helper.compute() / pkg.sub.fn() — Python's canonical cross-file call shape. Those are true edges, and it was the exemption list that made the difference, not the suppression predicate, which is byte-identical in both.

The bare-call half is genuinely yours, uncovered by anything, and is being implemented. After #1903:

def _run_with_heavy_slot(run):
    return run()          # still suffix_matches onto SatoriLive.run

#1903 structurally cannot catch this — there is no receiver, so the guard never fires. I checked whether anything else does: #1647's cross-language suffix guard only fires when caller and target languages differ, and both are Python here. So this class survives on main today, and you are the only person who found it.

The one change to your mechanism

The implementation keys on local binding rather than the {get, run, execute} list: suppress when the callee identifier is a parameter of the enclosing function, because a local binding shadows any project function and short-name resolution is therefore fabricated by construction.

The reasoning against the list is not that it is wrong today — it is that it is a claim about spellings rather than about whether the resolver knew anything, and it would age invisibly. Nothing fails when the generic-name distribution shifts; the graph just quietly loses different edges. Local binding covers handler(), callback(), fn() and every Callable-parameter shape without one, and it is decidable rather than empirical. That work carries Co-authored-by: credit to you.

Why this PR itself cannot be the vehicle

Practical, not a judgement on the work: it does not compile against current main (#1903 renamed cbm_tsjs_suppress_weak_method_match, which this calls), the branch is four weeks behind, and the commit carries no Signed-off-by trailer so DCO blocks it regardless. Rebasing across that to land a member half now redundant would be mostly discarded work.

Credit where it is due

You have two PRs here and both found real things. On #1371 you flagged the recall risk on your own PR, unprompted — that is rare and it is why the before/after numbers got run and it merged. Here you found a false-edge class that survives every guard currently on main. The diagnosis is yours even though the mechanism changed.

Thank you, genuinely. If you would rather carry the bare-call implementation yourself, say so and I will hand it straight back.

@DeusData DeusData closed this Aug 29, 2026
pcristin pushed a commit to pcristin/codebase-memory-mcp that referenced this pull request Sep 3, 2026
A Python `foo()` whose callee identifier is bound as a parameter of an
enclosing scope cannot be the module-level `foo` -- the parameter shadows it
for the whole body -- so resolving the call to a project Function/Method by a
weak short-name strategy fabricates the edge by construction:

    def _run_with_heavy_slot(run):
        return run()          # bound an unrelated SatoriLive.run

The receiver-aware weak-member guard (DeusData#1276) cannot see this class at all: a
bare call has no receiver, so is_method is false and the guard never fires.
This is the bare-call counterpart of python_receiver_is_exempt.

Keyed on the SCOPE FACT, not on the callee's spelling. A list of
generic-looking names (get / run / execute) asserts that certain spellings are
usually noise, which is a claim about corpus fashion rather than about what the
resolver knew, and it ages invisibly: nothing fails when the distribution
shifts, the graph just quietly loses different edges. A parameter binding is
decidable from this file's AST outright.

Parameters only, deliberately. A parameter is in scope for the entire body
regardless of position and Python forbids `global` on one, so no flow analysis
is needed. Local assignments are flow- and binding-form-sensitive (`for`,
`with as`, `except as`, `:=`, unpacking, plus global/nonlocal overrides); a
partial body scan would suppress the wrong edges invisibly -- the same failure
mode that rules out the name list. Enclosing scopes are walked to the file root
so a closure over an outer parameter counts.

Wired at both pass_calls.c and pass_parallel.c with an identical language gate:
a guard on one resolver only diverges the sequential and parallel paths. The
weak-strategy drop-list is now a single shared predicate used by both the member
guard and this one, so they cannot disagree about what "weak" means; a unit test
pins that agreement.

Tests pin both directions. The pipeline positive control is a cross-file bare
call with NO import, so it resolves by a weak strategy this guard could have
killed -- asserting a same_module edge would prove nothing, since no guard
touches same_module for any input. Verified by breaking the guard in both
directions: under-suppressing fails the negatives and the extraction flag;
over-suppressing fails the positives, so they are not vacuous. Disabling only
the pass_parallel.c gate fails only the >=50-file test and nothing else.

805 passed / 0 failed across pipeline registry parallel extraction complexity
lsp_resolution_probe.

The defect class was identified by Joseph-MingEn in DeusData#1386, which proposed a
name-keyed shape; the diagnosis is theirs.

Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
Co-authored-by: Joseph-MingEn <125283161+Joseph-MingEn@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something isn't working parsing/quality Graph extraction bugs, false positives, missing edges priority/high Needs near-term maintainer attention; high-impact bug, regression, safety issue, or release blocker.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants