diff --git a/graphify/extract.py b/graphify/extract.py index 30388c1..96a7233 100644 --- a/graphify/extract.py +++ b/graphify/extract.py @@ -7507,6 +7507,19 @@ def extract_fortran(path: Path) -> dict: target_nid = _make_id(stem, callee) add_edge(scope_nid, target_nid, "calls", node.start_point[0] + 1, confidence="EXTRACTED", context="call") + # x = compute(args) — function invocations are `call_expression`, which + # shares Fortran's `name(...)` syntax with array indexing. Only emit a + # call edge when the callee resolves to a procedure defined in this file + # (an array variable produces no matching node), so array accesses can't + # fabricate spurious `calls` edges. + elif t == "call_expression": + name_node = next((c for c in node.children if c.type == "identifier"), None) + if name_node: + callee = _read_text(name_node, source).lower() + target_nid = _make_id(stem, callee) + if target_nid in seen_ids and target_nid != scope_nid: + add_edge(scope_nid, target_nid, "calls", node.start_point[0] + 1, + confidence="EXTRACTED", context="call") for child in node.children: walk_calls(child, scope_nid) diff --git a/tests/fixtures/sample.f90 b/tests/fixtures/sample.f90 index a78e18f..e409850 100644 --- a/tests/fixtures/sample.f90 +++ b/tests/fixtures/sample.f90 @@ -43,6 +43,19 @@ contains print *, "Area =", area end subroutine print_area + function double_val(x) result(y) + real, intent(in) :: x + real :: y + y = x * 2.0 + end function double_val + + subroutine report(radius) + real, intent(in) :: radius + real :: scaled + scaled = double_val(radius) + print *, scaled + end subroutine report + end module geometry diff --git a/tests/test_languages.py b/tests/test_languages.py index 0976b48..c082e2d 100644 --- a/tests/test_languages.py +++ b/tests/test_languages.py @@ -1531,6 +1531,24 @@ def test_fortran_finds_calls(): assert len(call_edges) >= 1 +def test_fortran_finds_function_call(): + """`y = f(x)` function invocations must emit a calls edge. + + Function calls are `call_expression` (not `subroutine_call`); that node was + never handled, so every function-to-function call was dropped. The callee is + resolved against defined procedures so array indexing (`arr(i)`) can't + fabricate a spurious edge. + """ + r = extract_fortran(FIXTURES / "sample.f90") + labels = {n["id"]: n["label"] for n in r["nodes"]} + found = any( + "report" in labels.get(e["source"], "") + and "double_val" in labels.get(e["target"], "") + for e in r["edges"] if e["relation"] == "calls" + ) + assert found, "report() should have a calls edge to double_val()" + + def test_fortran_case_insensitive_names(): r = extract_fortran(FIXTURES / "sample.f90") labels = [n["label"] for n in r["nodes"]]