fix(fortran): emit calls edges for function invocations
Function calls (`y = f(x)`) were silently dropped — only `subroutine_call` (`call sub(...)`) was handled in walk_calls. tree-sitter-fortran represents a function invocation as a `call_expression`, which had no branch, so every function-to-function call produced no edge. Handle `call_expression`. Because Fortran uses the same `name(...)` syntax for array indexing, the callee is resolved against procedures defined in the file (`target_nid in seen_ids`) before emitting — so array accesses like `arr(i)` cannot fabricate spurious `calls` edges. Adds a function + caller to the fixture and a regression test. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
committed by
safishamsi
co-authored by
Claude Opus 4.8
parent
f2ea6a6087
commit
b8f41c77eb
@@ -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)
|
||||
|
||||
|
||||
Vendored
+13
@@ -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
|
||||
|
||||
|
||||
|
||||
@@ -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"]]
|
||||
|
||||
Reference in New Issue
Block a user