Skip to content

Commit

Permalink
Replaced _has_ancestor_type with _find_ancestor (which also returns t…
Browse files Browse the repository at this point in the history
…he ancestor if it exists).
  • Loading branch information
hiker committed Feb 20, 2025
1 parent fb69409 commit be0e919
Show file tree
Hide file tree
Showing 3 changed files with 43 additions and 20 deletions.
24 changes: 21 additions & 3 deletions source/fab/parse/fortran.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@

from fab.build_config import BuildConfig
from fab.dep_tree import AnalysedDependent
from fab.parse.fortran_common import _has_ancestor_type, _typed_child, FortranAnalyserBase
from fab.parse.fortran_common import _typed_child, FortranAnalyserBase
from fab.util import file_checksum, string_checksum

logger = logging.getLogger(__name__)
Expand Down Expand Up @@ -186,6 +186,23 @@ def __init__(self,
self.ignore_mod_deps: Iterable[str] = list(ignore_mod_deps or [])
self.depends_on_comment_found = False

@staticmethod
def _find_ancestor(node, cls):
'''Checks if there is an ancestor in the tree that is of the given
type(s).
:param node: an fparser node.
:param cls: a type or list of types to be used with isinstance()
:return: The first node among the ancestors of the given node, or
None if no such ancestor exists.
'''
current = node
while current and not isinstance(current, cls):
current = current.parent
return current


def walk_nodes(self, fpath, file_hash, node_tree) -> AnalysedFortran:

# see what's in the tree
Expand Down Expand Up @@ -314,7 +331,7 @@ def _process_subroutine_or_function(self, analysed_file, fpath, obj):
bind_name = name.string.replace('"', '')

# importing a c function into fortran, i.e binding within an interface block
if _has_ancestor_type(obj, Interface_Block):
if self._find_ancestor(obj, Interface_Block):
# found a dependency on C
logger.debug(f"found function binding import '{bind_name}'")
analysed_file.add_symbol_dep(bind_name)
Expand All @@ -325,7 +342,8 @@ def _process_subroutine_or_function(self, analysed_file, fpath, obj):

# not bound, just record the presence of the fortran symbol
# we don't need to record stuff in modules (we think!)
elif not _has_ancestor_type(obj, Module) and not _has_ancestor_type(obj, Interface_Block):
elif (not self._find_ancestor(obj, Module) and
not self._find_ancestor(obj, Interface_Block)):
if isinstance(obj, Subroutine_Stmt):
analysed_file.add_symbol_def(str(obj.get_name()))
if isinstance(obj, Function_Stmt):
Expand Down
11 changes: 0 additions & 11 deletions source/fab/parse/fortran_common.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,17 +26,6 @@
logger = logging.getLogger(__name__)


def _has_ancestor_type(obj, obj_type):
# Recursively check if an object has an ancestor of the given type.
if not obj.parent:
return False

if isinstance(obj.parent, obj_type):
return True

return _has_ancestor_type(obj.parent, obj_type)


def _typed_child(parent, child_type: Type, must_exist=False):
# Look for a child of a certain type.
# Returns the child or None.
Expand Down
28 changes: 22 additions & 6 deletions tests/unit_tests/parse/fortran/test_has_ancestor_type.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,24 +3,40 @@
# For further details please refer to the file COPYRIGHT
# which you should have received as part of this distribution
# ##############################################################################
from fab.parse.fortran_common import _has_ancestor_type

'''Test the find_ancestor function.
'''

class Thing1(object):
from fab.parse.fortran import FortranAnalyser


class Thing1():
# pylint: disable=too-few-public-methods
'''A dummy class mirroring the fparser design - have a parent attribute.'''
def __init__(self, parent):
self.parent = parent


class Thing2(Thing1):
pass
# pylint: disable=too-few-public-methods
'''Dummy class for testing.'''


class Test_has_ancestor_type(object):
class TestFindAncestor():
'''Test the find_ancerstor functionality of the FortranAnalyser.'''

def test_true(self):
'''Test to successfully find the class'''
t2 = Thing2(None)
thing = Thing1(parent=Thing1(parent=t2))
# Test that it evaluates to true, and also that it is the right type
assert FortranAnalyser._find_ancestor(thing, Thing2)
assert FortranAnalyser._find_ancestor(thing, Thing2) is t2

thing = Thing1(parent=Thing1(parent=Thing2(None)))
assert _has_ancestor_type(thing, Thing2)
assert FortranAnalyser._find_ancestor(thing, (Thing1, Thing2))

def test_false(self):
'''Test if the class is not found.'''
thing = Thing1(parent=Thing1(parent=Thing1(None)))
assert not _has_ancestor_type(thing, Thing2)
assert not FortranAnalyser._find_ancestor(thing, Thing2)

0 comments on commit be0e919

Please sign in to comment.