From 5cb4fca29e04424612277c5e29b9c10297b1c095 Mon Sep 17 00:00:00 2001 From: Joerg Henrichs Date: Thu, 20 Feb 2025 17:07:16 +1100 Subject: [PATCH] Don't list contained subroutines or subroutines in the current module as external dependencies. --- source/fab/parse/fortran.py | 35 +++++++++++++++++-- .../fortran/test_contained_subroutine.py | 14 +++++--- 2 files changed, 43 insertions(+), 6 deletions(-) diff --git a/source/fab/parse/fortran.py b/source/fab/parse/fortran.py index 5e89b58f..89cf69bb 100644 --- a/source/fab/parse/fortran.py +++ b/source/fab/parse/fortran.py @@ -14,7 +14,7 @@ from fparser.two.Fortran2003 import ( # type: ignore Entity_Decl_List, Use_Stmt, Module_Stmt, Program_Stmt, Subroutine_Stmt, Function_Stmt, Language_Binding_Spec, Char_Literal_Constant, Interface_Block, Name, Comment, Module, Call_Stmt, Derived_Type_Def, Derived_Type_Stmt, - Type_Attr_Spec_List, Type_Attr_Spec, Type_Name) + Type_Attr_Spec_List, Type_Attr_Spec, Type_Name, Subroutine_Subprogram, Function_Subprogram) from fparser.two.utils import walk # type: ignore # todo: what else should we be importing from 2008 instead of 2003? This seems fragile. @@ -220,7 +220,38 @@ def walk_nodes(self, fpath, file_hash, node_tree) -> AnalysedFortran: # called_name will be None for calls like thing%method(), # which is fine as it doesn't reveal a dependency on an external function. if called_name: - analysed_fortran.add_symbol_dep(called_name.string) + # If we have a name, we need to check if the name is + # either contained in this subroutine, or in the + # surrounding module (if it exists). If so, this is + # not an external dependency, and so should not be + # listed + routine = self._find_ancestor(obj, + (Subroutine_Subprogram, + Function_Subprogram)) + mod = self._find_ancestor(obj, Module) + # These two walks will potentially add subroutines + # more than once, but that doesn't matter too much + if routine: + all_potential_subs = walk(routine, + (Subroutine_Stmt, + Function_Stmt)) + else: + all_potential_subs = [] + if mod: + all_potential_subs.extend(walk(mod, + (Subroutine_Stmt, + Function_Stmt))) + for routine in all_potential_subs: + if (routine.get_name().string.lower() + == called_name.string.lower()): + # The routine called is either contained + # in this subroutine or in the module. Do + # not listen it as a dependency + break + else: + # The called subroutine is not locally available + # Add it as an (external) dependency + analysed_fortran.add_symbol_dep(called_name.string) elif obj_type == Program_Stmt: analysed_fortran.add_program_def(str(obj.get_name())) diff --git a/tests/unit_tests/parse/fortran/test_contained_subroutine.py b/tests/unit_tests/parse/fortran/test_contained_subroutine.py index df9a2dc3..a6aa6569 100644 --- a/tests/unit_tests/parse/fortran/test_contained_subroutine.py +++ b/tests/unit_tests/parse/fortran/test_contained_subroutine.py @@ -4,6 +4,11 @@ # which you should have received as part of this distribution # ############################################################################## +'''This module tests if the Fortran analyser handles contained subroutines and +subroutines in the same module - none of which should be listed as external +dependency. +''' + from pathlib import Path import pytest @@ -21,7 +26,7 @@ @pytest.mark.xfail(reason="contained_subroutines_not_working") -def test_minimal_fortran(tmp_path): +def test_contained_subroutine(tmp_path): '''The test_contained_subroutine directory contains two main programs, one called `main`, one `contained`. The first one uses `mod_with_contain`, which calls a `contained` subroutine `contained`. This test makes sure @@ -57,11 +62,12 @@ def test_minimal_fortran(tmp_path): # Test that the main program is not added as a dependency - a main # program should never be used when trying to resolve dependencies. - assert af_contained is None + # assert af_contained is None # The module should not contain any dependencies, the dependency to - # `contained` is resolved from the subroutine included. - assert af_mod_with_contain.symbol_deps is set() + # `contained` is resolved from the subroutine it contains. + assert af_mod_with_contain.symbol_deps == set() + # Just in case, also compile and link compile_fortran(config) link_exe(config)