fix(extract): add origin_file to cross-file stubs in the six dedicated extractors (#1515)

The #1462 same-label cross-file stub disambiguation (the origin_file key) only
existed in the generic extractor, so the six dedicated extractors — Julia,
Fortran, Go, Rust, PowerShell, ObjC — still collapsed same-named imported-type
stubs from different files into one conflated bare-id node (a false cross-package
link). Each now sets origin_file on its sourceless stub, identical to the generic
extractor; the generic _node_disambiguation_source_key consumes it, so two files
importing the same type stay distinct while source_file stays empty (the #1402
rewire onto a real definition is unchanged).

Ported from PR #1515 by @TPAteeq. Must ship with #1516: this widens origin_file to
six more languages, and #1516 is what strips it from graph.json. Verified: a 2-file
Go corpus now yields 2 distinct Widget stubs AND graph.json carries no origin_file.
Resolved a test-insertion conflict with #1516 by keeping both tests.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Mohammed Ateeq
2026-06-28 19:01:55 +01:00
committed by safishamsi
co-authored by Claude Opus 4.8
parent afa4aded2e
commit d177f04270
3 changed files with 29 additions and 0 deletions
+1
View File
@@ -22,6 +22,7 @@ Full release notes with details on each version: [GitHub Releases](https://githu
- Fix: imported type stubs with the same label no longer falsely merge across files when there is no project definition to rewire onto (#1462, thanks @jiangyq9). Two files that both `from pathlib import Path` and use `Path` as a type previously collapsed into one node; the referencing file is now kept as an internal disambiguator (`origin_file`) used only when splitting colliding ids, while `source_file` stays empty so a real project definition can still be rewired onto (the #1402 path is unaffected).
- Feat: resolve C# cross-file type references and extract `enum`/`struct`/`record` declarations (#1466, thanks @TheFedaikin). A new `_resolve_csharp_type_references` (the C# counterpart to the Java resolver) re-points dangling `inherits`/`implements`/`references` edges from no-source "shadow" stubs to their real definitions, disambiguating same-named types in different namespaces via the referencing file's `using` directives and enclosing namespace; ambiguous matches are refused rather than guessed. `enum`/`struct`/`record` types are now extracted as definitions so those references resolve too. Advances #1318 for C#.
- Fix: the Go AST extractor no longer creates phantom duplicate nodes for cross-file type references — the Go copy of `ensure_named_node` still used the older sourced-stub fallback; it now emits a sourceless stub like the other extractors, extending the #1402 fix to Go (#1500, thanks @TPAteeq).
- Fix: cross-file references to a same-named type now stay distinct across the six dedicated AST extractors (Go, Rust, Julia, Fortran, PowerShell, ObjC) instead of conflating into one shared node — #1462's `origin_file` stub-disambiguation had only been applied to the generic extractor; it now covers all seven.
## 0.8.50 (2026-06-27)
+6
View File
@@ -5967,6 +5967,7 @@ def extract_julia(path: Path) -> dict:
"file_type": "code",
"source_file": "",
"source_location": "",
"origin_file": str_path,
})
return nid
@@ -6263,6 +6264,7 @@ def extract_fortran(path: Path) -> dict:
"file_type": "code",
"source_file": "",
"source_location": "",
"origin_file": str_path,
})
return nid
@@ -6512,6 +6514,7 @@ def extract_go(path: Path) -> dict:
"file_type": "code",
"source_file": "",
"source_location": "",
"origin_file": str_path,
})
return nid
@@ -6866,6 +6869,7 @@ def extract_rust(path: Path) -> dict:
"file_type": "code",
"source_file": "",
"source_location": "",
"origin_file": str_path,
})
return nid
@@ -7169,6 +7173,7 @@ def extract_powershell(path: Path) -> dict:
"file_type": "code",
"source_file": "",
"source_location": "",
"origin_file": str_path,
})
return nid
@@ -9481,6 +9486,7 @@ def extract_objc(path: Path) -> dict:
"file_type": "code",
"source_file": "",
"source_location": "",
"origin_file": str_path,
})
return nid
+22
View File
@@ -201,6 +201,28 @@ def test_origin_file_is_not_serialized_into_extract_output(tmp_path):
assert len({node["id"] for node in path_nodes}) == 2
def test_go_imported_type_stubs_do_not_collide_across_source_files(tmp_path):
"""#1462 (dedicated extractors): the imported-type-stub disambiguation (the
``origin_file`` key) landed only in the generic extractor, so the six dedicated
extractors (Go, Rust, Julia, Fortran, PowerShell, ObjC) still collapsed same-label
cross-file stubs into one conflated bare-id node — a false cross-package link.
They must stay distinct per file while keeping ``source_file`` empty so the #1402
rewire still collapses them onto a real definition when one exists."""
first = tmp_path / "a/use_a.go"
second = tmp_path / "b/use_b.go"
first.parent.mkdir(parents=True)
second.parent.mkdir(parents=True)
first.write_text('package a\n\nimport "ext"\n\nfunc UseA(w ext.Widget) {}\n', encoding="utf-8")
second.write_text('package b\n\nimport "ext"\n\nfunc UseB(w ext.Widget) {}\n', encoding="utf-8")
result = extract([first, second], cache_root=tmp_path)
widget_nodes = [node for node in result["nodes"] if node["label"] == "Widget"]
assert len(widget_nodes) == 2
assert len({node["id"] for node in widget_nodes}) == 2
assert all(not node.get("source_file") for node in widget_nodes)
def test_extract_updates_raw_call_callers_after_duplicate_id_disambiguation(tmp_path):
first = tmp_path / "apps/api/Program.cs"
second = tmp_path / "tools/api/Program.cs"