mirror of
https://github.com/safishamsi/graphify.git
synced 2026-10-14 16:09:26 +00:00
Add regression tests for the split impl block self call fix
Covers the issue's own shape (impl Foo split across two files, a
self call in one block reaching a method defined in the other) and a
same file negative control proving the pre existing bare name
resolution is unaffected.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
(cherry picked from commit 2d161af639)
This commit is contained in:
1 parent
0a92b56bd1
commit
d77dcb9a67
1 file changed
+86
@@ -0,0 +1,86 @@
|
||||
"""Rust cross-file `self.method()` resolution (#2234).
|
||||
|
||||
The shared cross-file call pass drops every member call (a bare method name
|
||||
like ``log`` has no import evidence and collides with any top-level function
|
||||
named ``log`` in the corpus, #543/#1219). Every other member-call-heavy
|
||||
language has a dedicated recovery pass behind that guard; Rust had none, so
|
||||
`self.apply_block()` never produced a `calls` edge unless the caller and the
|
||||
method happened to live in the same file.
|
||||
|
||||
`self.method()` inside `impl Foo { .. }` types the receiver as `Foo`
|
||||
syntactically, no inference needed -- these tests exercise that resolution,
|
||||
including the case Rust makes routine: an `impl Foo` block split across many
|
||||
files, each minting its own graph node for `Foo`.
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
from pathlib import Path
|
||||
|
||||
from graphify.extract import extract
|
||||
|
||||
|
||||
def _calls(tmp_path: Path, files: dict[str, str]):
|
||||
paths = []
|
||||
for name, body in files.items():
|
||||
path = tmp_path / name
|
||||
path.parent.mkdir(parents=True, exist_ok=True)
|
||||
path.write_text(body, encoding="utf-8")
|
||||
paths.append(path)
|
||||
result = extract(paths, cache_root=tmp_path / "graphify-out")
|
||||
calls = {
|
||||
(edge["source"], edge["target"]): edge
|
||||
for edge in result["edges"]
|
||||
if edge.get("relation") == "calls"
|
||||
}
|
||||
return calls, result
|
||||
|
||||
|
||||
def _find(result: dict, label: str, id_contains: str) -> str:
|
||||
return next(
|
||||
node["id"]
|
||||
for node in result["nodes"]
|
||||
if node.get("label") == label and id_contains in node["id"]
|
||||
)
|
||||
|
||||
|
||||
def test_self_call_resolves_across_files_to_a_split_impl_block(tmp_path: Path):
|
||||
"""The issue's own shape: `impl Foo` split across two files, a `self.`
|
||||
call in one block reaching a method defined in the other."""
|
||||
calls, result = _calls(tmp_path, {
|
||||
"state.rs": (
|
||||
"pub struct BlockchainState { height: u64 }\n"
|
||||
),
|
||||
"apply.rs": (
|
||||
"use crate::state::BlockchainState;\n"
|
||||
"impl BlockchainState {\n"
|
||||
" pub fn apply_block(&self) -> u64 { self.height }\n"
|
||||
"}\n"
|
||||
),
|
||||
"rollback.rs": (
|
||||
"use crate::state::BlockchainState;\n"
|
||||
"impl BlockchainState {\n"
|
||||
" pub fn rollback_one_block(&self) -> u64 { self.apply_block() }\n"
|
||||
"}\n"
|
||||
),
|
||||
})
|
||||
caller = _find(result, ".rollback_one_block()", "rollback")
|
||||
callee = _find(result, ".apply_block()", "apply")
|
||||
assert (caller, callee) in calls
|
||||
assert calls[(caller, callee)]["confidence"] == "EXTRACTED"
|
||||
|
||||
|
||||
def test_self_call_same_file_control_is_unaffected(tmp_path: Path):
|
||||
"""Negative control: the pre-existing same-file bare-name resolution
|
||||
(which never needed this pass) must keep working exactly as before."""
|
||||
calls, result = _calls(tmp_path, {
|
||||
"lib.rs": (
|
||||
"struct Widget { n: u32 }\n"
|
||||
"impl Widget {\n"
|
||||
" fn get(&self) -> u32 { self.n }\n"
|
||||
" fn show(&self) -> u32 { self.get() }\n"
|
||||
"}\n"
|
||||
),
|
||||
})
|
||||
caller = _find(result, ".show()", "widget")
|
||||
callee = _find(result, ".get()", "widget")
|
||||
assert (caller, callee) in calls
|
||||
Reference in new issue
Block a user