diff --git a/src/psyclone/psyir/nodes/call.py b/src/psyclone/psyir/nodes/call.py index 79e73d5e61..17724c7adb 100644 --- a/src/psyclone/psyir/nodes/call.py +++ b/src/psyclone/psyir/nodes/call.py @@ -487,12 +487,15 @@ def copy(self): return new_copy - def get_callees(self) -> List[Routine]: + def get_callees(self, load_external_files: bool = True) -> List[Routine]: ''' Searches for the implementation(s) of all potential target routines for this Call. It does *not* attempt to resolve static polymorphism by checking the argument types. + :param load_external_files: allow this method to load external files + to find the needed declarations. + :returns: the Routine(s) that this call targets. :raises NotImplementedError: if the routine is not found or a @@ -583,7 +586,9 @@ def get_callees(self) -> List[Routine]: else: target_name = cursor.name try: - container = csym.find_container_psyir(local_node=self) + container = csym.find_container_psyir( + local_node=self, + load_external_files=load_external_files) except SymbolError: raise NotImplementedError( f"RoutineSymbol '{rsym.name}' is imported from " @@ -828,6 +833,7 @@ def get_argument_map(self, routine: Routine) -> List[int]: def get_callee( self, use_first_callee_and_no_arg_check: bool = False + load_external_files: bool = True ) -> Tuple[Routine, List[int]]: ''' Searches for the implementation(s) of the target routine for this Call @@ -842,6 +848,8 @@ def get_callee( :param use_first_callee_and_no_arg_check: whether or not (the default) to just find the first potential callee without checking its arguments. + :param load_external_files: allow this method to load external files + to find the needed declarations. :returns: A tuple of two elements. The first element is the routine that this call targets. The second one a list of arguments @@ -849,7 +857,6 @@ def get_callee( :raises NotImplementedError: if the routine is not local and not found in any containers in scope at the call site. - ''' routine_list = self.get_callees() diff --git a/src/psyclone/psyir/symbols/containersymbol.py b/src/psyclone/psyir/symbols/containersymbol.py index d71a54c0a3..ae74792635 100644 --- a/src/psyclone/psyir/symbols/containersymbol.py +++ b/src/psyclone/psyir/symbols/containersymbol.py @@ -37,11 +37,15 @@ # ----------------------------------------------------------------------------- ''' This module contains the ContainerSymbol and its interfaces.''' +from typing import TYPE_CHECKING, Optional from psyclone.psyir.symbols.symbol import Symbol, SymbolError from psyclone.psyir.symbols.interfaces import SymbolInterface from psyclone.configuration import Config +if TYPE_CHECKING: # pragma: no cover + from psyclone.psyir.nodes import Container, Node + class ContainerSymbol(Symbol): ''' Symbol that represents a reference to a Container. The reference @@ -125,7 +129,11 @@ def copy(self): new_symbol.is_intrinsic = self.is_intrinsic return new_symbol - def find_container_psyir(self, local_node=None): + def find_container_psyir( + self, + local_node: Optional['Node'] = None, + load_external_files: bool = True + ) -> 'Container': ''' Searches for the Container that this Symbol refers to. If it is not available, use the interface to import the container. If `local_node` is supplied then the PSyIR tree below it is searched for @@ -134,6 +142,8 @@ def find_container_psyir(self, local_node=None): :param local_node: root of PSyIR sub-tree to include in search for the container. :type local_node: Optional[:py:class:`psyclone.psyir.nodes.Node`] + :param load_external_files: allow this method to load external files + to find the needed declarations. :returns: referenced container. :rtype: :py:class:`psyclone.psyir.nodes.Container` @@ -151,7 +161,8 @@ def find_container_psyir(self, local_node=None): self._reference = local return self._reference # We didn't find it so now attempt to import the container. - self._reference = self._interface.get_container(self._name) + self._reference = self._interface.get_container( + self._name, load_external_files) return self._reference def __str__(self): @@ -212,12 +223,18 @@ def is_intrinsic(self, value): class ContainerSymbolInterface(SymbolInterface): ''' Abstract implementation of the ContainerSymbol Interface ''' - @staticmethod - def get_container(name): + def __init__(self): + self._container_psyir = None + + def get_container(self, name: str, load_external_files: bool = True): ''' Abstract method to import an external container, the specific implementation depends on the language used. - :param str name: name of the external entity to be imported. + :param name: name of the external entity to be imported. + :param load_external_files: whether to search, parse and link an + external source file to populate the required container PSyIR + node (doing this operation in an already created PSyIR is + expensive, so explore using the RESOLVE_IMPORTS option first). :raises NotImplementedError: this is an abstract method. ''' @@ -227,12 +244,15 @@ def get_container(name): class FortranModuleInterface(ContainerSymbolInterface): ''' Implementation of ContainerSymbolInterface for Fortran modules ''' - @staticmethod - def get_container(name): + def get_container(self, name: str, load_external_files: bool = True): ''' Imports a Fortran module as a PSyIR Container (via the ModuleManager) and returns it. - :param str name: name of the module to be imported. + :param name: name of the module to be imported. + :param load_external_files: whether to search, parse and link an + external source file to populate the required container PSyIR + node (doing this operation in an already created PSyIR is + expensive, so explore using the RESOLVE_IMPORTS option first). :returns: container associated with the given name. :rtype: :py:class:`psyclone.psyir.nodes.Container` @@ -241,6 +261,15 @@ def get_container(name): import path. ''' + if not load_external_files: + if self._container_psyir is None: + raise SymbolError( + f"Module '{name}' has not been loaded and linked to the " + f"local PSyIR symbol. Use the RESOLVE_IMPORTS parameter " + f" in the psyclone script or the 'load_external_files=" + "True' in this method to attempt to do so.") + return self._container_psyir + # pylint: disable-next=import-outside-toplevel from psyclone.parse import ModuleManager mod_manager = ModuleManager.get() @@ -259,7 +288,8 @@ def get_container(name): raise SymbolError( f"Module '{name}' not found in any of the include_paths " f"directories {Config.get().include_paths}.") - return minfo.get_psyir() + self._container_psyir = minfo.get_psyir() + return self._container_psyir # For Sphinx AutoAPI documentation generation diff --git a/src/psyclone/tests/psyir/nodes/call_test.py b/src/psyclone/tests/psyir/nodes/call_test.py index 6387d36a66..c3310f9a15 100644 --- a/src/psyclone/tests/psyir/nodes/call_test.py +++ b/src/psyclone/tests/psyir/nodes/call_test.py @@ -1495,7 +1495,8 @@ def test_call_get_callees_resolved_not_found(fortran_reader, monkeypatch): " but the source defining that container could not be found. The " "module search path is set to [" in str(err.value)) monkeypatch.setattr(ContainerSymbol, "find_container_psyir", - lambda _1, local_node=None: None) + lambda _1, local_node=None, + load_external_files=False: None) with pytest.raises(NotImplementedError) as err: _ = call.get_callees() assert ("RoutineSymbol 'this_one' is imported from Container 'another_mod'" diff --git a/src/psyclone/tests/psyir/symbols/containersymbol_test.py b/src/psyclone/tests/psyir/symbols/containersymbol_test.py index c35db1b845..2b20113e5a 100644 --- a/src/psyclone/tests/psyir/symbols/containersymbol_test.py +++ b/src/psyclone/tests/psyir/symbols/containersymbol_test.py @@ -159,7 +159,7 @@ def test_containersymbol_resolve_external_container(monkeypatch): sym = ContainerSymbol("my_mod") monkeypatch.setattr(sym._interface, "get_container", - lambda x: "MockContainer") + lambda x, _: "MockContainer") # At the beginning the reference is never resolved (lazy evaluation) assert not sym._reference @@ -170,14 +170,14 @@ def test_containersymbol_resolve_external_container(monkeypatch): # Check that subsequent invocations do not update the container reference monkeypatch.setattr(sym._interface, "get_container", - staticmethod(lambda x: "OtherContainer")) + staticmethod(lambda x, _: "OtherContainer")) assert sym.find_container_psyir() == "MockContainer" def test_containersymbol_generic_interface(): '''Check ContainerSymbolInterface abstract methods ''' - abstractinterface = ContainerSymbolInterface + abstractinterface = ContainerSymbolInterface() with pytest.raises(NotImplementedError) as error: abstractinterface.get_container("name") @@ -188,13 +188,13 @@ def test_containersymbol_fortranmodule_interface(monkeypatch, tmpdir): '''Check that the FortranModuleInterface imports Fortran modules as containers or produces the appropriate errors''' - fminterface = FortranModuleInterface + fminterface = FortranModuleInterface() path = str(tmpdir) # Try with a non-existent module and no include path monkeypatch.setattr(Config.get(), "_include_paths", []) with pytest.raises(SymbolError) as error: - fminterface.get_container("fake_module") + fminterface.get_container("fake_module", True) assert ("Module 'fake_module' not found in any of the include_paths " "directories []." in str(error.value)) diff --git a/src/psyclone/tests/psyir/symbols/symbol_test.py b/src/psyclone/tests/psyir/symbols/symbol_test.py index 2e9c272d3b..d2baf5fb74 100644 --- a/src/psyclone/tests/psyir/symbols/symbol_test.py +++ b/src/psyclone/tests/psyir/symbols/symbol_test.py @@ -328,7 +328,7 @@ def test_get_external_symbol(monkeypatch): # Monkeypatch the container's FortranModuleInterface so that it always # appears to be unable to find the "some_mod" module - def fake_import(name): + def fake_import(name, load_external_files): raise SymbolError("Oh dear") monkeypatch.setattr(other_container._interface, "get_container", fake_import)