diff --git a/graphify/extractors/fortran.py b/graphify/extractors/fortran.py index acd4562f2..d7b55226c 100644 --- a/graphify/extractors/fortran.py +++ b/graphify/extractors/fortran.py @@ -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", diff --git a/tests/test_languages.py b/tests/test_languages.py index 64a6d4ec8..d463bc748 100644 --- a/tests/test_languages.py +++ b/tests/test_languages.py @@ -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."""