Skip to content

feat: opt-in precise CALLS resolver for Python (cgh[lsp], jedi-backed)#73

Merged
joy-software merged 1 commit into
developfrom
feat/precise-calls
Jun 7, 2026
Merged

feat: opt-in precise CALLS resolver for Python (cgh[lsp], jedi-backed)#73
joy-software merged 1 commit into
developfrom
feat/precise-calls

Conversation

@joy-software

Copy link
Copy Markdown
Contributor

FEAT-14 (minimal proof of concept). Opt-in, Python-only precise call resolution that fixes the cross-file / same-name-collision gap the name matcher (BUG-2) can't.

Design

Rather than spawn a real LSP server (pyright/pylsp subprocess + JSON-RPC — heavy, fragile), this uses jedi, the same static-analysis engine python-lsp-server wraps. analysis/precise_calls.py is the seam where a true LSP backend could slot in later.

Gating (default behavior UNCHANGED)

Three guards, all required: precise_calls config flag on (default False; [codegraph].precise_calls or CGH_PRECISE_CALLS), jedi importable (the new lsp extra), and file is Python. Any other case falls back to the existing name-matched _resolve_calls exactly as today. jedi import is lazy + guarded.

What it does

For each Python call site, jedi.Script(...).goto(follow_imports=True) resolves the precise definition, mapped to the graph's {file}::{name} / {file}::{Class}.{method} id scheme; only callees inside the repo are kept. Handles the macOS symlink path mismatch and restores the recursion limit parso lowers on import.

Proof

test_precise_calls_beats_same_name_collision: a.py imports b.helper, defines a decoy helper(), calls b.helper() — the name matcher links to the decoy, the precise resolver correctly links to b.py::helper.

Test plan

  • test_precise_calls.py (5, importorskip jedi): cross-file fn, cross-file method, env override, collision, flag-off
  • full suite WITHOUT jedi: 393 passed, 8 skipped (default unchanged); WITH jedi: 397 passed
  • pyproject lsp extra + uv.lock; ruff + no-ai-tells clean

FEAT-14 proof of concept. Adds a jedi-backed resolver that does
goto-definition on every Python call site and maps each resolved
definition to the graph's Function id scheme, so cross-file call
edges are exact instead of name-matched. jedi is the same static
engine python-lsp-server wraps, which keeps the machinery far lighter
than spawning a real LSP subprocess while leaving resolve_calls_for_file
as the seam a true LSP backend could replace later.

Strictly opt-in and Python-only. The new precise_calls config flag
(off by default, CGH_PRECISE_CALLS env override) gates it, and the
resolver imports jedi lazily behind the new cgh[lsp] extra. With the
flag off or the extra absent, the indexer falls back to the existing
name-matched resolver and behavior is unchanged.

The resolver rebuilds target paths from the indexer's repo_root so ids
match stored Function nodes even when jedi resolves symlinks, restores
the recursion limit parso lowers on import, and caps call sites per
file. Tests cover cross-file resolution, the Class.method id form, the
env override, and the same-name collision the name matcher cannot get
right; all degrade to a skip when jedi is missing.
@joy-software joy-software merged commit 82fbaaa into develop Jun 7, 2026
8 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant