revert(fortran): drop re-implementation of #4141 in favor of PR #4142

My #4141 fix used a one-off calls/generic_dispatch relation; PR #4142
(@rajatnagda45) implements the same fix with the codebase-standard
dispatches_to relation and earns author-preserving credit. Reverting the
Fortran hunk here so #4142 can be cherry-picked; the Julia fixes from the
same original commit are unaffected.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
safishamsiandClaude Opus 4.8 committed 2026-10-10 22:10:34 +01:00
1 parent 3f8260fcc3
commit a009ce7131
2 files changed
-111

No files matched your search

-39
View File
@@ -132,11 +132,6 @@ def extract_fortran(path: Path) -> dict:
# internal procedures have their nodes.
type_bound_procs: list[tuple[str, str, int]] = []
# (generic_nid, specific_procedure_name, line) for every `module procedure`
# listed under a named generic interface. Resolved after the walk, like
# type-bound procedures, so a call to the generic name links to each specific.
generic_procs: list[tuple[str, str, int]] = []
def _fortran_name(stmt_node) -> str | None:
"""Extract name from a *_statement node. Fortran is case-insensitive; lowercase."""
for child in stmt_node.children:
@@ -361,32 +356,6 @@ def extract_fortran(path: Path) -> dict:
add_edge(scope_nid, imp_nid, "imports", line, context="use")
return
if t == "interface":
# A NAMED generic interface (`interface area; module procedure
# area_circle, area_square; end interface area`) introduces a callable
# generic name that overloads the listed specific procedures. Without a
# node for the generic name, a call to it dangled. Unnamed and
# operator/assignment interfaces carry no `name` child, so they are left
# alone here; their explicit interface-body procedures are still walked.
stmt = next((c for c in node.children if c.type == "interface_statement"), None)
name = _fortran_name(stmt) if stmt else None
if name:
nid = _make_id(stem, name)
line = node.start_point[0] + 1
add_node(nid, f"{name}()", line)
add_edge(scope_nid, nid, "defines", line)
for pstmt in node.children:
if pstmt.type != "procedure_statement":
continue
for mn in pstmt.children:
if mn.type == "method_name":
generic_procs.append(
(nid, _read_text(mn, source).lower(),
pstmt.start_point[0] + 1))
for child in node.children:
walk(child, scope_nid)
return
for child in node.children:
walk(child, scope_nid)
@@ -401,14 +370,6 @@ def extract_fortran(path: Path) -> dict:
if tgt != type_nid:
add_edge(type_nid, tgt, "method", line, context="type_bound_procedure")
# Link each named generic interface to the specific procedures it overloads,
# now that the module's procedures have nodes. An out-of-module specific
# resolves to a sourceless stub (same treatment as type-bound procedures).
for generic_nid, proc_name, line in generic_procs:
tgt = ensure_named_node(proc_name, line)
if tgt != generic_nid:
add_edge(generic_nid, tgt, "calls", line, context="generic_dispatch")
_stmt_headers = {
"subroutine_statement", "function_statement",
"program_statement", "module_statement",
-72
View File
@@ -2677,78 +2677,6 @@ def test_fortran_type_bound_procedures_link_to_the_type(tmp_path):
assert ("circle", "scale") in methods
def test_fortran_named_generic_interface_is_extracted(tmp_path):
"""A named generic interface introduces a callable generic name that overloads
the listed specific procedures. The `interface` node had no walk branch, so the
generic name was dropped and a call to it dangled (#4141)."""
src = tmp_path / "shapes.f90"
src.write_text(
"module shapes\n"
" implicit none\n"
" interface area\n"
" module procedure area_circle, area_square\n"
" end interface area\n"
"contains\n"
" real function area_circle(r)\n"
" real :: r\n"
" area_circle = 3.14 * r * r\n"
" end function area_circle\n"
" real function area_square(s)\n"
" real :: s\n"
" area_square = s * s\n"
" end function area_square\n"
" subroutine report(x)\n"
" real :: x, a\n"
" a = area(x)\n"
" end subroutine report\n"
"end module shapes\n",
encoding="utf-8",
)
r = extract_fortran(src)
assert "error" not in r
assert "area" in [n["label"].rstrip("()") for n in r["nodes"]], "generic interface node dropped"
# the call to the generic name now resolves instead of dangling
assert ("report", "area") in _edge_labels(r, "calls")
# the generic dispatches to each listed specific procedure
dispatch = _edge_labels(r, "calls", "generic_dispatch")
assert ("area", "area_circle") in dispatch
assert ("area", "area_square") in dispatch
node_ids = {n["id"] for n in r["nodes"]}
for e in r["edges"]:
assert e["source"] in node_ids and e["target"] in node_ids, e
def test_fortran_operator_and_unnamed_interfaces_make_no_generic_node(tmp_path):
"""An `operator(+)`/`assignment(=)` interface and an unnamed interface carry no
generic name, so they must not mint a generic node; an unnamed interface body's
explicit procedure declaration is still extracted (no regression to #4141)."""
src = tmp_path / "ops.f90"
src.write_text(
"module m\n"
" implicit none\n"
" interface operator(+)\n"
" module procedure add_vec\n"
" end interface\n"
" interface\n"
" subroutine ext(x)\n"
" real :: x\n"
" end subroutine ext\n"
" end interface\n"
"contains\n"
" function add_vec(a, b) result(c)\n"
" real :: a, b, c\n"
" c = a + b\n"
" end function add_vec\n"
"end module m\n",
encoding="utf-8",
)
r = extract_fortran(src)
assert "error" not in r
labels = {n["label"].rstrip("()") for n in r["nodes"]}
assert "ext" in labels, "unnamed-interface body procedure dropped"
assert not ({"+", "operator", "assignment", ""} & labels), "spurious generic node for operator/unnamed interface"
def test_fortran_type_bound_procedure_from_other_module_is_sourceless(tmp_path):
"""A binding to a procedure implemented in another module resolves to a
sourceless stub the corpus rewire can collapse, never a dangling edge."""