fix(python): keep missing relative-import targets out of the checkout path

A relative import of a module with no file behind it (`from .absent
import x`) built its target from the attempted absolute path, so the id
became e.g. `c_users_<user>_..._pkg_absent_py`. The root-relative id
remap never rewrites it (there is no node to anchor on), so the checkout
location and the OS username ended up in graph.json and every clone got
a different id.

Use the dotted module name relative to the scan root instead
(`pkg_absent`), the id an unresolved absolute import of the same module
already gets, so `from .absent` and `from pkg.absent` now meet on one
node. An import that climbs above the scan root falls back to a `ref`
id. Imports that resolve, and attempted paths that exist, are unchanged.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01B8gwEpjajiWHVW3c9smuKf
(cherry picked from commit 8cdbb0ea4f)
This commit is contained in:
rohit-jsfreaky authored and safishamsi committed 2026-10-06 20:54:15 +01:00
1 parent ac467b0c00
commit 0083ee78e5
2 files changed
+54 -1

No files matched your search

+29 -1
View File
@@ -674,6 +674,23 @@ def _python_import_bindings(node, source: bytes) -> list[tuple[str, str]]:
return bindings
def _missing_relative_module_name(attempted: Path, root: Path, raw: str) -> str:
"""Dotted module name, relative to ``root``, of an unresolved relative import.
``attempted`` is the file the import would have named (``pkg/missing.py`` or
``pkg/__init__.py``). Falls back to ``ref:`` + the raw specifier when the
import climbs above the scan root, so the id never carries an absolute path.
"""
try:
rel = attempted.relative_to(root)
except ValueError:
return f"ref:{raw}"
parts = list(rel.with_suffix("").parts)
if parts and parts[-1] == "__init__":
parts.pop()
return ".".join(parts) if parts else f"ref:{raw}"
def _import_python(
node, source: bytes, file_nid: str, stem: str, edges: list, str_path: str,
scope_stack: list[str] | None = None, scan_root: Path | None = None,
@@ -749,13 +766,24 @@ def _import_python(
target_path = _resolve_python_module_path(
module_name, current_path, root, level=dots
)
missing = False
if target_path is None:
base = current_path.parent
for _ in range(dots - 1):
base = base.parent
rel = (module_name.replace(".", "/") + ".py") if module_name else "__init__.py"
target_path = base / rel
tgt_nid = _make_id(str(target_path))
missing = not target_path.is_file()
if missing:
# No file behind the import: minting the id from the
# attempted absolute path bakes the checkout location (and
# OS username) into the graph, and the root-relative id
# remap never rewrites it. Use the dotted module name
# relative to the scan root, the id an unresolved absolute
# import of the same module already gets.
tgt_nid = _make_id(_missing_relative_module_name(target_path, root, raw))
else:
tgt_nid = _make_id(str(target_path))
else:
# Use the shared scan-root-aware resolver for absolute imports.
# It stops at the corpus boundary and handles package roots the
+25
View File
@@ -147,6 +147,31 @@ def test_overdeep_relative_import_is_unresolved_not_fatal(tmp_path: Path):
assert _node_id(result, "ok()", "pkg/mod.py")
def _missing_import_targets(checkout: Path) -> set[str]:
_write(checkout / "pkg" / "__init__.py", "")
source = _write(
checkout / "pkg" / "app.py",
"from .absent import helper\n"
"from pkg.absent import other\n\n"
"def run():\n"
" return helper(), other()\n",
)
result = extract([source, checkout / "pkg" / "__init__.py"], cache_root=checkout)
return {e["target"] for e in result["edges"] if e["relation"] == "imports_from"}
def test_missing_relative_import_target_id_does_not_depend_on_the_checkout(tmp_path: Path):
"""A relative import of a module with no file behind it minted its target id
from the attempted absolute path (``..._pkg_absent_py``), which the
root-relative remap never rewrites: the checkout location and OS username
ended up in the graph. It now gets the dotted module name, the id an
unresolved absolute import of the same module already gets."""
first = _missing_import_targets(tmp_path / "clone_one")
second = _missing_import_targets(tmp_path / "elsewhere" / "clone_two")
assert first == second == {"pkg_absent"}
def test_ordinary_relative_import_still_resolves(tmp_path: Path):
target = _write(tmp_path / "pkg" / "sibling.py", "def helper():\n return 1\n")
source = _write(tmp_path / "pkg" / "mod.py", "from .sibling import helper\n")