From a21176ea7e7a141a0beefac1f5acc7881a2318ac Mon Sep 17 00:00:00 2001 From: thejesh Date: Mon, 13 Jul 2026 10:50:33 -0700 Subject: [PATCH] fix(cargo): honor `package = "..."` rename when resolving internal deps (#1858) --- graphify/cargo_introspect.py | 15 +++++- tests/test_cargo_introspect.py | 92 ++++++++++++++++++++++++++++++++++ 2 files changed, 105 insertions(+), 2 deletions(-) diff --git a/graphify/cargo_introspect.py b/graphify/cargo_introspect.py index 3bda96126..fa03ed309 100644 --- a/graphify/cargo_introspect.py +++ b/graphify/cargo_introspect.py @@ -78,8 +78,19 @@ def introspect_cargo(root: str | Path) -> dict[str, Any]: if not isinstance(dependencies, dict): continue source_file = manifest.relative_to(root_path).as_posix() - for dependency_name in sorted(dependencies): - target = crates.get(dependency_name) + for dep_key, spec in sorted(dependencies.items()): + # Cargo lets a dep table entry rename the crate via `package = "..."`: + # db = { path = "../storage", package = "internal-storage" } + # The key `db` is the name used in `use db::…;`; the actual crate + # published under `[package].name = "internal-storage"` is what + # `crates` is keyed by. Without honoring `package`, every renamed + # workspace-internal dep silently drops its edge (#1858). + real_name = dep_key + if isinstance(spec, dict): + pkg_override = spec.get("package") + if isinstance(pkg_override, str) and pkg_override: + real_name = pkg_override + target = crates.get(real_name) if target is None: continue edges.append( diff --git a/tests/test_cargo_introspect.py b/tests/test_cargo_introspect.py index e80d8c16a..9abf82655 100644 --- a/tests/test_cargo_introspect.py +++ b/tests/test_cargo_introspect.py @@ -363,3 +363,95 @@ def test_cargo_introspect_large_workspace_dependency_chain(tmp_path): "source_file": "crates/crate_198/Cargo.toml", "source_location": "L1", } in result["edges"] + + +def test_cargo_introspect_honors_package_rename_on_internal_dep(tmp_path): + """Renamed workspace-internal deps still produce a `crate_depends_on` edge (#1858). + + Cargo's `package = "..."` inside a dep table entry lets the key used in + `use db::…;` differ from the crate's real `[package].name`. Looking up + `crates` by the raw dep-table key misses the rename and silently drops + the edge. + """ + _write_manifest( + tmp_path / "Cargo.toml", + """ +[workspace] +members = ["app", "storage"] +""", + ) + app = tmp_path / "app" + storage = tmp_path / "storage" + app.mkdir() + storage.mkdir() + _write_manifest( + app / "Cargo.toml", + """ +[package] +name = "app" +version = "0.1.0" +edition = "2021" + +[dependencies] +db = { path = "../storage", package = "internal-storage" } +""", + ) + _write_manifest( + storage / "Cargo.toml", + """ +[package] +name = "internal-storage" +version = "0.1.0" +edition = "2021" +""", + ) + + result = introspect_cargo(tmp_path) + + node_ids = {node["id"] for node in result["nodes"]} + assert node_ids == {"crate:app", "crate:internal-storage"} + assert { + "source": "crate:app", + "target": "crate:internal-storage", + "relation": "crate_depends_on", + "context": "cargo_dependency", + "weight": 1.0, + "confidence": "EXTRACTED", + "source_file": "app/Cargo.toml", + "source_location": "L1", + } in result["edges"] + + +def test_cargo_introspect_package_rename_falls_through_when_unresolved(tmp_path): + """A rename pointing at an external (non-workspace) crate stays a no-op. + + Guards against the fix silently emitting edges to registry crates: the + resolved name still has to appear in `crates` (the workspace-internal + index) for an edge to be produced. + """ + _write_manifest( + tmp_path / "Cargo.toml", + """ +[workspace] +members = ["app"] +""", + ) + app = tmp_path / "app" + app.mkdir() + _write_manifest( + app / "Cargo.toml", + """ +[package] +name = "app" +version = "0.1.0" +edition = "2021" + +[dependencies] +tokio_rt = { version = "1", package = "tokio" } +""", + ) + + result = introspect_cargo(tmp_path) + + assert {node["id"] for node in result["nodes"]} == {"crate:app"} + assert result["edges"] == []