Skip to content

fix(py-lsp): resolve calls through aliased imports#1049

Open
DeusData wants to merge 1 commit into
mainfrom
fix/988-py-alias-import
Open

fix(py-lsp): resolve calls through aliased imports#1049
DeusData wants to merge 1 commit into
mainfrom
fix/988-py-alias-import

Conversation

@DeusData

Copy link
Copy Markdown
Owner

Closes #988

Bug (worse than reported)

The issue reports lsp=MISS with registry fallback covering it. On current main with a minimal two-file fixture, from m import f as g; g(x) produces no CALLS edge at all — the registry fallback doesn't cover the minimal shape either. Two stacked defects:

  1. Binding: py_lsp_bind_imports detects from-style imports by the local name matching the module-path tail. An alias never matches (local=g, qn=m.f), so g was bound MODULE(m.f) — a call on a module → MISS.
  2. Join: even with the binding fixed, cbm_pipeline_find_lsp_resolution compares the bare tail of the resolved QN (f) against the textual callee (g) and dropped the resolution — verified by local instrumentation of resolve_file_calls (the reporter's exact observation point).

Fix

  1. A dotted module path whose tail differs from the local name is an aliased binding → bind NAMED(qn); phase 6 upgrades it to the registered function/class (from X import Y as Z) or module (import a.b as z). Single-segment import m as mm stays MODULE (untouched).
  2. When the identifier-callee NAMED branch resolves a name whose textual form differs from the QN tail, emit strategy lsp_import_alias with the textual name stashed in reason, and gate that strategy in the join's reason list — the exact pattern already used for lsp_dict_dispatch/lsp_func_ptr/lsp_destructor. The parallel path's hash index misses the alias key by design and falls through to the same gated linear scan.

Reproduce-first

contract_edge_python_aliased_import_call_resolves_issue988 pins all four import forms: aliased from-import (RED on main: alias=0 plain=1 modalias=1 dotalias=1), plain from-import, single-segment module alias, and dotted module alias — the last two guard the MODULE→NAMED flip against regressing module-attribute chains. The unit test pylsp_import_from_aliased is tightened from "NAMED or MODULE acceptable" to NAMED-only (the tolerance this issue proved wrong).

Verification

Full local suite 5999 passed, 1 skipped (main + the new test), zero regressions; lint-ci clean.

@DeusData DeusData added this to the 0.9.1-rc milestone Jul 12, 2026
@DeusData DeusData enabled auto-merge July 12, 2026 12:51
from m import f as g; g() produced no CALLS edge at all. Two stacked
defects: (1) py_lsp_bind_imports keyed from-style detection on the
local name matching the module-path tail - an alias never matches, so
g was bound MODULE(m.f) and the call missed; (2) even with the binding
fixed, the pass join compares the bare tail of the resolved QN (f)
against the textual callee (g) and dropped the resolution.

Fix: (1) a dotted module path whose tail differs from the local name
is an aliased binding - bind NAMED(qn) and let phase 6 upgrade it to
the registered function/class (from-import alias) or module (import
a.b as z); (2) when the identifier-callee NAMED branch resolves a name
whose textual form differs from the QN tail, emit strategy
lsp_import_alias with the textual name stashed in reason, and gate
that strategy in the join's reason list - the exact pattern already
used by lsp_dict_dispatch and lsp_func_ptr. The parallel path's hash
index misses the alias key and falls through to the same gated linear
scan.

The contract test pins all four import forms (aliased from-import,
plain from-import, single-segment module alias, dotted module alias)
so the flip from MODULE to NAMED binding cannot regress the module-
alias attribute chains.

Closes #988

Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
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.

Python LSP does not resolve import ... as ... bindings — lsp=MISS for aliased call sites

1 participant