From 0d01e0b6dfbb9da0ab8c1bd79c0c26812a8e49ed Mon Sep 17 00:00:00 2001 From: Sergi Siso Date: Mon, 29 Sep 2025 09:57:16 +0100 Subject: [PATCH 1/2] #3162 Add option to control loading of external files --- src/psyclone/psyir/nodes/call.py | 9 +++-- src/psyclone/psyir/symbols/containersymbol.py | 38 ++++++++++++++----- src/psyclone/tests/psyir/nodes/call_test.py | 3 +- .../psyir/symbols/containersymbol_test.py | 10 ++--- .../tests/psyir/symbols/symbol_test.py | 2 +- 5 files changed, 43 insertions(+), 19 deletions(-) diff --git a/src/psyclone/psyir/nodes/call.py b/src/psyclone/psyir/nodes/call.py index 1bc159a14a..ee1390e77a 100644 --- a/src/psyclone/psyir/nodes/call.py +++ b/src/psyclone/psyir/nodes/call.py @@ -480,7 +480,7 @@ def copy(self): return new_copy - def get_callees(self): + def get_callees(self, load_external_files=True): ''' Searches for the implementation(s) of all potential target routines for this Call without resolving static polymorphism by checking the @@ -578,7 +578,9 @@ def get_callees(self): 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 " @@ -777,6 +779,7 @@ def _get_argument_routine_match(self, routine: Routine): def get_callee( self, check_matching_arguments: bool = True, + load_external_files: bool = True ): ''' Searches for the implementation(s) of the target routine for this Call @@ -803,7 +806,7 @@ def get_callee( in any containers in scope at the call site. ''' - routine_list = self.get_callees() + routine_list = self.get_callees(load_external_files) err_info_list = [] diff --git a/src/psyclone/psyir/symbols/containersymbol.py b/src/psyclone/psyir/symbols/containersymbol.py index d71a54c0a3..6185ad7be5 100644 --- a/src/psyclone/psyir/symbols/containersymbol.py +++ b/src/psyclone/psyir/symbols/containersymbol.py @@ -125,7 +125,7 @@ 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=None, load_external_files=True): ''' 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 @@ -151,7 +151,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 +213,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 +234,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 +251,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 +278,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 455df7d456..089bf9ab32 100644 --- a/src/psyclone/tests/psyir/nodes/call_test.py +++ b/src/psyclone/tests/psyir/nodes/call_test.py @@ -1489,7 +1489,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 4a78fe85b1..58151d1c70 100644 --- a/src/psyclone/tests/psyir/symbols/symbol_test.py +++ b/src/psyclone/tests/psyir/symbols/symbol_test.py @@ -321,7 +321,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) From 85d27b188afacd1c1129bc4f9178563e1bc64b49 Mon Sep 17 00:00:00 2001 From: Sergi Siso Date: Wed, 1 Oct 2025 10:32:38 +0100 Subject: [PATCH 2/2] Update method docstrings --- src/psyclone/psyir/nodes/call.py | 6 +++++- src/psyclone/psyir/symbols/containersymbol.py | 12 +++++++++++- 2 files changed, 16 insertions(+), 2 deletions(-) diff --git a/src/psyclone/psyir/nodes/call.py b/src/psyclone/psyir/nodes/call.py index 8c55e16f19..8948f0552c 100644 --- a/src/psyclone/psyir/nodes/call.py +++ b/src/psyclone/psyir/nodes/call.py @@ -486,6 +486,9 @@ def get_callees(self, load_external_files: bool = True) -> List[Routine]: for this Call without resolving 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 @@ -793,7 +796,8 @@ def get_callee( the very first implementation of the matching routine will be returned (even if the argument type check failed). The argument types and number of arguments might therefore mismatch! - :type ret_arg_match_list: bool + :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 diff --git a/src/psyclone/psyir/symbols/containersymbol.py b/src/psyclone/psyir/symbols/containersymbol.py index 6185ad7be5..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, load_external_files=True): + 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, load_external_files=True): :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`