From 38bfc611fbb6d9fb89d25e79305f4245caff0cc3 Mon Sep 17 00:00:00 2001 From: LonelyCat124 <3043914+LonelyCat124@users.noreply.github.com.> Date: Tue, 4 Aug 2026 15:53:53 +0100 Subject: [PATCH 01/11] Make statement abstract with the new abstract methods --- .../common/psylayer/global_reduction.py | 19 ++++++ src/psyclone/psyGen.py | 36 +++++++++++ src/psyclone/psyir/nodes/call.py | 31 ++++++++-- src/psyclone/psyir/nodes/codeblock.py | 20 ++++++ src/psyclone/psyir/nodes/directive.py | 21 +++++++ src/psyclone/psyir/nodes/if_block.py | 21 +++++++ src/psyclone/psyir/nodes/loop.py | 21 +++++++ src/psyclone/psyir/nodes/psy_data_node.py | 20 ++++++ src/psyclone/psyir/nodes/return_stmt.py | 21 +++++++ src/psyclone/psyir/nodes/routine.py | 20 ++++++ src/psyclone/psyir/nodes/statement.py | 24 +++++++- src/psyclone/psyir/nodes/while_loop.py | 21 +++++++ .../tests/psyir/backend/debug_writer_test.py | 6 ++ .../tests/psyir/backend/visitor_test.py | 6 ++ src/psyclone/tests/psyir/nodes/node_test.py | 61 ++++++++++--------- .../tests/psyir/nodes/psy_data_node_test.py | 10 +-- .../tests/psyir/nodes/schedule_test.py | 6 +- .../transformations/inline_trans_test.py | 7 ++- .../transformations/transformations_test.py | 9 +-- .../tests/test_files/dummy_statement.py | 51 ++++++++++++++++ 20 files changed, 383 insertions(+), 48 deletions(-) create mode 100644 src/psyclone/tests/test_files/dummy_statement.py diff --git a/src/psyclone/domain/common/psylayer/global_reduction.py b/src/psyclone/domain/common/psylayer/global_reduction.py index c8bde83d35..43adf9182a 100644 --- a/src/psyclone/domain/common/psylayer/global_reduction.py +++ b/src/psyclone/domain/common/psylayer/global_reduction.py @@ -40,6 +40,7 @@ ''' This module provides the GlobalReduction base class. ''' import copy +from typing import List from psyclone.configuration import Config from psyclone.core import AccessType @@ -114,3 +115,21 @@ def args(self) -> list[KernelArgument]: ''':returns: the list of arguments associated with this node. Override the base method and simply return our argument.''' return [self._scalar] + + def next_accesses(self) -> List[Node]: + ''' + next_accesses on base GlobalReduction class just uses the base + Statement implementation, returning an empty list. + + :returns: an empty list. + ''' + return super().next_accesses() + + def previous_accesses(self) -> List[Node]: + ''' + previous_accesses on base GlobalReduction class just uses the base + Statement implementation, returning an empty list. + + :returns: an empty list. + ''' + return super().previous_accesses() diff --git a/src/psyclone/psyGen.py b/src/psyclone/psyGen.py index 0da689f2a6..c92bd938bb 100644 --- a/src/psyclone/psyGen.py +++ b/src/psyclone/psyGen.py @@ -771,6 +771,24 @@ def node_str(self, colour=True): f"type='{self._halo_type}', depth={self._halo_depth}, " f"check_dirty={self._check_dirty}]") + def next_accesses(self) -> list[Node]: + ''' + next_accesses on base HaloExchange class just uses the base Statement + implementation, returning an empty list. + + :returns: an empty list. + ''' + return super().next_accesses() + + def previous_accesses(self) -> list[Node]: + ''' + previous_accesses on base HaloExchange class just uses the base + Statement implementation, returning an empty list. + + :returns: an empty list. + ''' + return super().previous_accesses() + class Kern(Statement): '''Base class representing a call to a sub-program unit from within the @@ -1163,6 +1181,24 @@ def lower_to_language_level(self): return super().lower_to_language_level() + def next_accesses(self) -> list[Node]: + ''' + next_accesses on base Kern class just uses the base Statement + implementation, returning an empty list. + + :returns: an empty list. + ''' + return super().next_accesses() + + def previous_accesses(self) -> list[Node]: + ''' + previous_accesses on base Kern class just uses the base Statement + implementation, returning an empty list. + + :returns: an empty list. + ''' + return super().previous_accesses() + class CodedKern(Kern): ''' diff --git a/src/psyclone/psyir/nodes/call.py b/src/psyclone/psyir/nodes/call.py index 23f2013f36..c2727b9842 100644 --- a/src/psyclone/psyir/nodes/call.py +++ b/src/psyclone/psyir/nodes/call.py @@ -40,7 +40,7 @@ from __future__ import annotations from collections.abc import Iterable -from typing import List, Tuple, Union, Optional +from typing import Tuple, Union, Optional from psyclone.configuration import Config from psyclone.core import AccessType, VariablesAccessMap @@ -49,6 +49,7 @@ from psyclone.psyir.nodes.container import Container from psyclone.psyir.nodes.statement import Statement from psyclone.psyir.nodes.datanode import DataNode +from psyclone.psyir.nodes.node import Node from psyclone.psyir.nodes.reference import Reference from psyclone.psyir.nodes.routine import Routine from psyclone.psyir.symbols import ( @@ -531,7 +532,7 @@ def copy(self): return new_copy - def get_callees(self) -> List[Routine]: + def get_callees(self) -> list[Routine]: ''' Searches for the implementation(s) of all potential target routines for this Call. It does *not* attempt to resolve static polymorphism @@ -787,7 +788,7 @@ def type_symbols_match(type1: Union[DataTypeSymbol, DataType], f"'{routine_arg_str}' ({dummy_type})" ) - def get_argument_map(self, routine: Routine) -> List[int]: + def get_argument_map(self, routine: Routine) -> list[int]: '''Return a list of indices mapping from each argument of this call to the corresponding entry in the argument list of the supplied routine. @@ -803,7 +804,7 @@ def get_argument_map(self, routine: Routine) -> List[int]: ''' # Create a copy of the list of actual arguments to the routine. # Once an argument has been successfully matched, set it to 'None' - routine_argument_list: List[DataSymbol] = ( + routine_argument_list: list[DataSymbol] = ( routine.symbol_table.argument_list[:] ) @@ -888,7 +889,7 @@ def get_argument_map(self, routine: Routine) -> List[int]: def get_callee( self, use_first_callee_and_no_arg_check: bool = False - ) -> Tuple[Routine, List[int]]: + ) -> Tuple[Routine, list[int]]: ''' Searches for the implementation(s) of the target routine for this Call including argument checks. @@ -940,3 +941,23 @@ def get_callee( f"No matching routine found for '{call_str}':" "\n" + error_msg ) + + def next_accesses(self) -> list[Node]: + ''' + Abstract method for finding the next_accesses of a statement. + Subclasses should override this according to their own structure to + return future accesses to any References contained in the statement. + + :returns: an empty list. + ''' + # FIXME Implement + return [] + + def previous_accesses(self) -> list[Node]: + ''' + Abstract method for finding the previous_accesses of a statement. + Subclasses should override this according to their own structure to + return previous accesses to any References contained in the statement. + ''' + # FIXME Implement + return [] diff --git a/src/psyclone/psyir/nodes/codeblock.py b/src/psyclone/psyir/nodes/codeblock.py index 5474b4d008..950997b5b6 100644 --- a/src/psyclone/psyir/nodes/codeblock.py +++ b/src/psyclone/psyir/nodes/codeblock.py @@ -258,6 +258,26 @@ def get_fortran_lines(self) -> list[str]: return [] raise NotImplementedError("Use appropriate CodeBlock subclass") + def next_accesses(self) -> list[Node]: + ''' + Abstract method for finding the next_accesses of a statement. + Subclasses should override this according to their own structure to + return future accesses to any References contained in the statement. + + :returns: an empty list. + ''' + # FIXME Implement + return [] + + def previous_accesses(self) -> list[Node]: + ''' + Abstract method for finding the previous_accesses of a statement. + Subclasses should override this according to their own structure to + return previous accesses to any References contained in the statement. + ''' + # FIXME Implement + return [] + class Fparser2CodeBlock(CodeBlock): ''' The fparser2 implementation of CodeBlock. ''' diff --git a/src/psyclone/psyir/nodes/directive.py b/src/psyclone/psyir/nodes/directive.py index d1368eed55..0d393080ff 100644 --- a/src/psyclone/psyir/nodes/directive.py +++ b/src/psyclone/psyir/nodes/directive.py @@ -50,6 +50,7 @@ from psyclone.psyir.nodes.array_of_structures_reference import ( ArrayOfStructuresReference) from psyclone.psyir.nodes.clause import Clause +from psyclone.psyir.nodes.node import Node from psyclone.psyir.nodes.reference import Reference from psyclone.psyir.nodes.schedule import Schedule from psyclone.psyir.nodes.statement import Statement @@ -180,6 +181,26 @@ def create_data_movement_deep_copy_refs(self): *base_args, members) return read_only, write_only, readwrites + def next_accesses(self) -> list[Node]: + ''' + Abstract method for finding the next_accesses of a statement. + Subclasses should override this according to their own structure to + return future accesses to any References contained in the statement. + + :returns: an empty list. + ''' + # FIXME Implement + return [] + + def previous_accesses(self) -> list[Node]: + ''' + Abstract method for finding the previous_accesses of a statement. + Subclasses should override this according to their own structure to + return previous accesses to any References contained in the statement. + ''' + # FIXME Implement + return [] + class RegionDirective(Directive): ''' diff --git a/src/psyclone/psyir/nodes/if_block.py b/src/psyclone/psyir/nodes/if_block.py index 2bc158b514..bb4bef37be 100644 --- a/src/psyclone/psyir/nodes/if_block.py +++ b/src/psyclone/psyir/nodes/if_block.py @@ -41,6 +41,7 @@ from psyclone.core import VariablesAccessMap from psyclone.errors import InternalError, GenerationError from psyclone.psyir.nodes.datanode import DataNode +from psyclone.psyir.nodes.node import Node from psyclone.psyir.nodes.schedule import Schedule from psyclone.psyir.nodes.statement import Statement @@ -195,3 +196,23 @@ def reference_accesses(self) -> VariablesAccessMap: if self.else_body: var_accesses.update(self.else_body.reference_accesses()) return var_accesses + + def next_accesses(self) -> list[Node]: + ''' + Abstract method for finding the next_accesses of a statement. + Subclasses should override this according to their own structure to + return future accesses to any References contained in the statement. + + :returns: an empty list. + ''' + # FIXME Implement + return [] + + def previous_accesses(self) -> list[Node]: + ''' + Abstract method for finding the previous_accesses of a statement. + Subclasses should override this according to their own structure to + return previous accesses to any References contained in the statement. + ''' + # FIXME Implement + return [] diff --git a/src/psyclone/psyir/nodes/loop.py b/src/psyclone/psyir/nodes/loop.py index 0ec4eb1f4d..1980c5e3aa 100644 --- a/src/psyclone/psyir/nodes/loop.py +++ b/src/psyclone/psyir/nodes/loop.py @@ -43,6 +43,7 @@ from psyclone.core import VariablesAccessMap from psyclone.psyir.nodes.datanode import DataNode +from psyclone.psyir.nodes.node import Node from psyclone.psyir.nodes.statement import Statement from psyclone.psyir.nodes.routine import Routine from psyclone.psyir.nodes.reference import Reference @@ -545,3 +546,23 @@ def enters_scope(self, scope, visited_nodes=None) -> bool: ''' # pylint: disable=unused-argument return False + + def next_accesses(self) -> list[Node]: + ''' + Abstract method for finding the next_accesses of a statement. + Subclasses should override this according to their own structure to + return future accesses to any References contained in the statement. + + :returns: an empty list. + ''' + # FIXME Implement + return [] + + def previous_accesses(self) -> list[Node]: + ''' + Abstract method for finding the previous_accesses of a statement. + Subclasses should override this according to their own structure to + return previous accesses to any References contained in the statement. + ''' + # FIXME Implement + return [] diff --git a/src/psyclone/psyir/nodes/psy_data_node.py b/src/psyclone/psyir/nodes/psy_data_node.py index 872b64986b..e3df5da451 100644 --- a/src/psyclone/psyir/nodes/psy_data_node.py +++ b/src/psyclone/psyir/nodes/psy_data_node.py @@ -732,6 +732,26 @@ def gen_type_bound_call(typename, methodname, argument_list=None, self.detach() return self.parent + def next_accesses(self) -> list[Node]: + ''' + Abstract method for finding the next_accesses of a statement. + Subclasses should override this according to their own structure to + return future accesses to any References contained in the statement. + + :returns: an empty list. + ''' + # FIXME Implement + return [] + + def previous_accesses(self) -> list[Node]: + ''' + Abstract method for finding the previous_accesses of a statement. + Subclasses should override this according to their own structure to + return previous accesses to any References contained in the statement. + ''' + # FIXME Implement + return [] + # For AutoAPI documentation generation __all__ = ['PSyDataNode'] diff --git a/src/psyclone/psyir/nodes/return_stmt.py b/src/psyclone/psyir/nodes/return_stmt.py index 5698d6fc26..68d828182f 100644 --- a/src/psyclone/psyir/nodes/return_stmt.py +++ b/src/psyclone/psyir/nodes/return_stmt.py @@ -38,6 +38,7 @@ ''' This module contains the Return node implementation.''' +from psyclone.psyir.nodes.node import Node from psyclone.psyir.nodes.statement import Statement @@ -51,3 +52,23 @@ class Return(Statement): _children_valid_format = "" _text_name = "Return" _colour = "yellow" + + def next_accesses(self) -> list[Node]: + ''' + Abstract method for finding the next_accesses of a statement. + Subclasses should override this according to their own structure to + return future accesses to any References contained in the statement. + + :returns: an empty list. + ''' + # FIXME Implement + return [] + + def previous_accesses(self) -> list[Node]: + ''' + Abstract method for finding the previous_accesses of a statement. + Subclasses should override this according to their own structure to + return previous accesses to any References contained in the statement. + ''' + # FIXME Implement + return [] diff --git a/src/psyclone/psyir/nodes/routine.py b/src/psyclone/psyir/nodes/routine.py index a5f44c8b58..7bcbbbaab1 100644 --- a/src/psyclone/psyir/nodes/routine.py +++ b/src/psyclone/psyir/nodes/routine.py @@ -567,6 +567,26 @@ def replace_with(self, node, keep_name_in_context=True): "replaced.") super().replace_with(node, keep_name_in_context=keep_name_in_context) + def next_accesses(self) -> list[Node]: + ''' + Abstract method for finding the next_accesses of a statement. + Subclasses should override this according to their own structure to + return future accesses to any References contained in the statement. + + :returns: an empty list. + ''' + # FIXME Implement + return [] + + def previous_accesses(self) -> list[Node]: + ''' + Abstract method for finding the previous_accesses of a statement. + Subclasses should override this according to their own structure to + return previous accesses to any References contained in the statement. + ''' + # FIXME Implement + return [] + # For automatic documentation generation __all__ = ["Routine"] diff --git a/src/psyclone/psyir/nodes/statement.py b/src/psyclone/psyir/nodes/statement.py index 05953eb240..79b27bbe60 100644 --- a/src/psyclone/psyir/nodes/statement.py +++ b/src/psyclone/psyir/nodes/statement.py @@ -36,15 +36,37 @@ ''' This module contains the Statement abstract node implementation.''' +import abc + from psyclone.psyir.nodes.node import Node from psyclone.psyir.commentable_mixin import CommentableMixin -class Statement(Node, CommentableMixin): +class Statement(Node, CommentableMixin, metaclass=abc.ABCMeta): ''' Abstract node representing a general PSyIR Statement. ''' + @abc.abstractmethod + def next_accesses(self) -> list[Node]: + ''' + Abstract method for finding the next_accesses of a statement. + Subclasses should override this according to their own structure to + return future accesses to any References contained in the statement. + + :returns: an empty list. + ''' + return [] + + @abc.abstractmethod + def previous_accesses(self) -> list[Node]: + ''' + Abstract method for finding the previous_accesses of a statement. + Subclasses should override this according to their own structure to + return previous accesses to any References contained in the statement. + ''' + return [] + # For automatic API documentation generation __all__ = ["Statement"] diff --git a/src/psyclone/psyir/nodes/while_loop.py b/src/psyclone/psyir/nodes/while_loop.py index f6e4d75553..3a1d60db62 100644 --- a/src/psyclone/psyir/nodes/while_loop.py +++ b/src/psyclone/psyir/nodes/while_loop.py @@ -39,6 +39,7 @@ from psyclone.core import VariablesAccessMap from psyclone.errors import InternalError, GenerationError from psyclone.psyir.nodes.datanode import DataNode +from psyclone.psyir.nodes.node import Node from psyclone.psyir.nodes.schedule import Schedule from psyclone.psyir.nodes.statement import Statement @@ -154,3 +155,23 @@ def reference_accesses(self) -> VariablesAccessMap: var_accesses = self.condition.reference_accesses() var_accesses.update(self.loop_body.reference_accesses()) return var_accesses + + def next_accesses(self) -> list[Node]: + ''' + Abstract method for finding the next_accesses of a statement. + Subclasses should override this according to their own structure to + return future accesses to any References contained in the statement. + + :returns: an empty list. + ''' + # FIXME Implement + return [] + + def previous_accesses(self) -> list[Node]: + ''' + Abstract method for finding the previous_accesses of a statement. + Subclasses should override this according to their own structure to + return previous accesses to any References contained in the statement. + ''' + # FIXME Implement + return [] diff --git a/src/psyclone/tests/psyir/backend/debug_writer_test.py b/src/psyclone/tests/psyir/backend/debug_writer_test.py index aca9bde203..5ba6521c84 100644 --- a/src/psyclone/tests/psyir/backend/debug_writer_test.py +++ b/src/psyclone/tests/psyir/backend/debug_writer_test.py @@ -85,6 +85,12 @@ def lower_to_language_level(self): def validate_global_constraints(self): raise NotImplementedError("This should not be called") + def next_accesses(self): + '''Needed on all Statement subclasses.''' + + def previous_accesses(self): + '''Needed on all Statement subclasses.''' + class MyDSLDataNode(DataNode): ''' A dummy DSL DataNode that doesn't like being lowered ''' def lower_to_language_level(self): diff --git a/src/psyclone/tests/psyir/backend/visitor_test.py b/src/psyclone/tests/psyir/backend/visitor_test.py index 9e109ca4aa..506cf77458 100644 --- a/src/psyclone/tests/psyir/backend/visitor_test.py +++ b/src/psyclone/tests/psyir/backend/visitor_test.py @@ -214,6 +214,12 @@ def lower_to_language_level(self): self.replace_with(new_node) return new_node + def next_accesses(self): + '''Needed on all Statement subclasses.''' + + def previous_accesses(self): + '''Needed on all Statement subclasses.''' + class MyVisitor(PSyIRVisitor): ''' Simple Visitor for Schedules and Return statements ''' diff --git a/src/psyclone/tests/psyir/nodes/node_test.py b/src/psyclone/tests/psyir/nodes/node_test.py index 73de0fc13b..7dc5a3423b 100644 --- a/src/psyclone/tests/psyir/nodes/node_test.py +++ b/src/psyclone/tests/psyir/nodes/node_test.py @@ -51,14 +51,19 @@ from psyclone.parse.algorithm import parse from psyclone.psyGen import PSyFactory, Kern from psyclone.psyir.backend.debug_writer import DebugWriter -from psyclone.psyir.nodes import Schedule, Reference, Container, Routine, \ - Assignment, Return, Loop, Literal, Statement, node, KernelSchedule, \ +from psyclone.psyir.nodes import ( + Schedule, Reference, Container, Routine, node, + Assignment, Return, Loop, Literal, KernelSchedule, Statement, BinaryOperation, ArrayReference, Call, Range +) from psyclone.psyir.nodes.node import ChildrenList, Node -from psyclone.psyir.symbols import DataSymbol, SymbolError, \ - ScalarType, SymbolTable, ArrayType, RoutineSymbol, NoType +from psyclone.psyir.symbols import ( + DataSymbol, SymbolError, ScalarType, SymbolTable, ArrayType, + RoutineSymbol, NoType +) from psyclone.tests.utilities import get_invoke from psyclone.psyir.nodes.node import colored +from psyclone.tests.test_files.dummy_statement import DummyStatement BASE_PATH = os.path.join(os.path.dirname(os.path.dirname(os.path.dirname( os.path.abspath(__file__)))), "test_files", "lfric") @@ -334,7 +339,7 @@ def test_compute_cached_abs_positions_error(): just happens with inconsistent parent-child connections). ''' parent = Schedule() - node1 = Statement() + node1 = DummyStatement() # Manually connect the _parent attribute which won't make a consistent # two-way relationship node1._parent = parent @@ -397,7 +402,7 @@ def test_node_abs_position_error(): happens with inconsistent parent-child connections). ''' parent = Schedule() - node1 = Statement() + node1 = DummyStatement() # Manually connect the _parent attribute which won't make a consistent # two-way relationship node1._parent = parent @@ -1153,8 +1158,8 @@ def test_children_setter(): assert isinstance(testnode.children, ChildrenList) # When is set up with a list, this becomes a ChildrenList - statement1 = Statement() - statement2 = Statement() + statement1 = DummyStatement() + statement2 = DummyStatement() testnode.children = [statement1, statement2] assert isinstance(testnode.children, ChildrenList) assert statement1.parent is testnode @@ -1176,8 +1181,8 @@ def test_children_setter(): def test_children_clear(): '''Test that the clear() method works correctly for a ChildrenList.''' testnode = Schedule() - stmt1 = Statement() - stmt2 = Statement() + stmt1 = DummyStatement() + stmt2 = DummyStatement() testnode.addchild(stmt1) testnode.addchild(stmt2) assert len(testnode.children) == 2 @@ -1273,11 +1278,11 @@ def test_lower_to_language_level(monkeypatch): # Monkeypatch the lower_to_language_level to just mark a flag def visited(self): self._visited_flag = True - monkeypatch.setattr(Statement, "lower_to_language_level", visited) + monkeypatch.setattr(DummyStatement, "lower_to_language_level", visited) testnode = Schedule() - node1 = Statement() - node2 = Statement() + node1 = DummyStatement() + node2 = DummyStatement() testnode.children = [node1, node2] # Execute method @@ -1296,9 +1301,9 @@ def test_replace_with(): '''Check that the replace_with method behaves as expected.''' parent_node = Schedule() - node1 = Statement() - node2 = Statement() - node3 = Statement() + node1 = DummyStatement() + node2 = DummyStatement() + node3 = DummyStatement() parent_node.children = [node1, node2, node3] new_node = Assignment() @@ -1374,8 +1379,8 @@ def test_replace_with_error2(): ''' parent = Schedule() - node1 = Statement() - node2 = Statement() + node1 = DummyStatement() + node2 = DummyStatement() with pytest.raises(TypeError) as info: node1.replace_with("hello") @@ -1418,9 +1423,9 @@ def test_pop_all_children(): # Create a PSyIR tree parent = Schedule() - node1 = Statement() + node1 = DummyStatement() parent.addchild(node1) - node2 = Statement() + node2 = DummyStatement() parent.addchild(node2) # Execute pop_all_children method @@ -1471,19 +1476,19 @@ def test_parent_references_coherency(): parent = Schedule() # Children addition methods - node1 = Statement() + node1 = DummyStatement() parent.addchild(node1) assert node1.parent is parent - node2 = Statement() + node2 = DummyStatement() parent.children.append(node2) assert node2.parent is parent - node3 = Statement() + node3 = DummyStatement() parent.children.extend([node3]) assert node3.parent is parent - node4 = Statement() + node4 = DummyStatement() parent.children.insert(0, node4) assert node4.parent is parent @@ -1518,7 +1523,7 @@ def test_node_constructor_with_parent(): wrong_parent = Schedule() # By default no parent reference is given - node = Statement() + node = DummyStatement() assert node.parent is None assert node.has_constructor_parent is False @@ -1630,8 +1635,8 @@ def test_equality(): parent1._symbol_table = symboltable parent2 = Schedule() parent2._symbol_table = symboltable - zero = Statement() - one = Statement() + zero = DummyStatement() + one = DummyStatement() assert parent1 != zero assert parent1 == parent2 @@ -1644,7 +1649,7 @@ def test_equality(): assert parent1 == parent2 # Add a second child to parent1 - two = Statement() + two = DummyStatement() parent1.addchild(two) assert parent1 != parent2 diff --git a/src/psyclone/tests/psyir/nodes/psy_data_node_test.py b/src/psyclone/tests/psyir/nodes/psy_data_node_test.py index f0d6581366..c00f693b84 100644 --- a/src/psyclone/tests/psyir/nodes/psy_data_node_test.py +++ b/src/psyclone/tests/psyir/nodes/psy_data_node_test.py @@ -45,13 +45,13 @@ from psyclone.psyir.nodes import ( CodeBlock, PSyDataNode, Schedule, Return, Routine) from psyclone.parse import ModuleManager -from psyclone.psyir.nodes.statement import Statement from psyclone.psyir.transformations import PSyDataTrans, TransformationError from psyclone.psyir.symbols import ( ContainerSymbol, ImportInterface, SymbolTable, DataTypeSymbol, UnresolvedType, DataSymbol, UnsupportedFortranType) from psyclone.psyGen import Kern from psyclone.tests.utilities import get_base_path, get_invoke +from psyclone.tests.test_files.dummy_statement import DummyStatement # ----------------------------------------------------------------------------- @@ -174,7 +174,7 @@ def test_psy_data_node_tree_correct(): # 3. No parent, but children: # =========================== - children = [Statement(), Statement()] + children = [DummyStatement(), DummyStatement()] psy_node = PSyDataNode.create(children, SymbolTable()) # The children must be connected to the schedule, which is @@ -193,10 +193,10 @@ def test_psy_data_node_tree_correct(): # ======================= parent = Schedule() # The children must be added to the parent before creating the ExtractNode - parent.addchild(Statement()) - parent.addchild(Statement()) + parent.addchild(DummyStatement()) + parent.addchild(DummyStatement()) # Add another child that must stay with the parent node - third_child = Statement() + third_child = DummyStatement() parent.addchild(third_child) assert parent.children[2] is third_child # Only move the first two children, leave the third where it is diff --git a/src/psyclone/tests/psyir/nodes/schedule_test.py b/src/psyclone/tests/psyir/nodes/schedule_test.py index 4ab008da79..9ca8a12630 100644 --- a/src/psyclone/tests/psyir/nodes/schedule_test.py +++ b/src/psyclone/tests/psyir/nodes/schedule_test.py @@ -40,12 +40,13 @@ import os import pytest -from psyclone.psyir.nodes import Schedule, Assignment, Range, Statement +from psyclone.psyir.nodes import Schedule, Assignment, Range from psyclone.psyir.nodes.node import colored from psyclone.psyir.symbols import SymbolTable from psyclone.psyGen import PSyFactory from psyclone.parse.algorithm import parse from psyclone.errors import GenerationError +from psyclone.tests.test_files.dummy_statement import DummyStatement BASE_PATH = os.path.join(os.path.dirname(os.path.dirname(os.path.dirname( @@ -66,7 +67,8 @@ def test_sched_init(): # A custom symbol table and parent and children nodes can be given as # arguments of Schedule. symtab = SymbolTable() - sched2 = Schedule(parent=sched, children=[Statement(), Statement()], + sched2 = Schedule(parent=sched, children=[DummyStatement(), + DummyStatement()], symbol_table=symtab) assert isinstance(sched2, Schedule) assert sched2.parent is sched diff --git a/src/psyclone/tests/psyir/transformations/inline_trans_test.py b/src/psyclone/tests/psyir/transformations/inline_trans_test.py index 6f9366357e..c16b802c80 100644 --- a/src/psyclone/tests/psyir/transformations/inline_trans_test.py +++ b/src/psyclone/tests/psyir/transformations/inline_trans_test.py @@ -46,11 +46,12 @@ from psyclone.psyir.backend.fortran import FortranWriter from psyclone.psyir.nodes import ( Assignment, Call, IntrinsicCall, Loop, Node, Reference, - Routine, Statement) + Routine) from psyclone.psyir.symbols import ( AutomaticInterface, DataSymbol, ImportInterface, UnresolvedType) from psyclone.psyir.transformations import ( InlineTrans, TransformationError) +from psyclone.tests.test_files.dummy_statement import DummyStatement from psyclone.tests.utilities import Compile, get_invoke MY_TYPE = (" integer, parameter :: ngrids = 10\n" @@ -2691,10 +2692,10 @@ def test_validate_automatic_array_sized_by_arg(fortran_reader, monkeypatch): inline_trans.validate(call) # Break Reference.previous_accesses() to exercise the InternalError. monkeypatch.setattr(call.arguments[1], "previous_accesses", - lambda: [Statement()]) + lambda: [DummyStatement()]) with pytest.raises(InternalError) as err: inline_trans.validate(call) - assert ("Unexpected node type (Statement) returned from Reference." + assert ("Unexpected node type (DummyStatement) returned from Reference." "previous_accesses()" in str(err.value)) diff --git a/src/psyclone/tests/psyir/transformations/transformations_test.py b/src/psyclone/tests/psyir/transformations/transformations_test.py index edc31fcfae..16dfdf6301 100644 --- a/src/psyclone/tests/psyir/transformations/transformations_test.py +++ b/src/psyclone/tests/psyir/transformations/transformations_test.py @@ -46,7 +46,7 @@ import pytest from fparser.common.readfortran import FortranStringReader from psyclone.psyir.nodes import ( - CodeBlock, Literal, Loop, Node, Reference, Schedule, Statement, + CodeBlock, Literal, Loop, Node, Reference, Schedule, ACCLoopDirective, OMPMasterDirective, Fparser2CodeBlock, OMPDoDirective, OMPLoopDirective, Routine) from psyclone.psyir.symbols import ( @@ -60,6 +60,7 @@ OMPSingleTrans, OMPMasterTrans) from psyclone.parse.algorithm import parse from psyclone.psyGen import PSyFactory +from psyclone.tests.test_files.dummy_statement import DummyStatement GOCEAN_BASE_PATH = os.path.join(os.path.dirname(os.path.abspath(__file__)), os.pardir, os.pardir, "test_files", @@ -95,7 +96,7 @@ def test_accloop(): assert trans.name == "ACCLoopTrans" assert str(trans) == "Adds an 'OpenACC loop' directive to a loop" - cnode = Statement() + cnode = DummyStatement() tdir = trans._directive([cnode]) assert isinstance(tdir, ACCLoopDirective) @@ -265,7 +266,7 @@ class DummyVAM: # pylint: disable=too-few-public-methods all_signatures = [DummySig()] def __getitem__(self, _): - return [DummyAccess(Statement())] + return [DummyAccess(DummyStatement())] monkeypatch.setattr(routine, "reference_accesses", lambda: DummyVAM()) with pytest.raises(TransformationError) as err: @@ -868,7 +869,7 @@ def test_profile_trans_invalid_name(value): # We need to have a schedule as parent, otherwise the node # (with no parent) will not be allowed. sched = Schedule() - node = Statement(parent=sched) + node = DummyStatement(parent=sched) sched.addchild(node) with pytest.raises(TransformationError) as excinfo: profile_trans.apply(node, options={"region_name": value}) diff --git a/src/psyclone/tests/test_files/dummy_statement.py b/src/psyclone/tests/test_files/dummy_statement.py new file mode 100644 index 0000000000..0636741005 --- /dev/null +++ b/src/psyclone/tests/test_files/dummy_statement.py @@ -0,0 +1,51 @@ +# ----------------------------------------------------------------------------- +# BSD 3-Clause License +# +# Copyright (c) 2017-2026, Science and Technology Facilities Council. +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: +# +# * Redistributions of source code must retain the above copyright notice, this +# list of conditions and the following disclaimer. +# +# * Redistributions in binary form must reproduce the above copyright notice, +# this list of conditions and the following disclaimer in the documentation +# and/or other materials provided with the distribution. +# +# * Neither the name of the copyright holder nor the names of its +# contributors may be used to endorse or promote products derived from +# this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS +# FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE +# COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, +# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, +# BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN +# ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +# POSSIBILITY OF SUCH DAMAGE. +# ----------------------------------------------------------------------------- +# Author A. B. G. Chalk, STFC Daresbury Lab + +'''This module contains a DummyStatement class used for tests where Statement +nodes are initialised.''' + +from psyclone.psyir.nodes import Statement + + +class DummyStatement(Statement): + + def next_accesses(self) -> None: + '''Empty implementation of the required next_accesses + abstractmethod.''' + + def previous_accesses(self) -> None: + '''Empty implementation of the required previous_accesses + abstractmethod. + ''' From 68c87c7bed372cebd179842ff14a0c93595f6780 Mon Sep 17 00:00:00 2001 From: LonelyCat124 <3043914+LonelyCat124@users.noreply.github.com.> Date: Mon, 17 Aug 2026 15:53:58 +0100 Subject: [PATCH 02/11] Implements next and previous accesses for Statement subclasses --- src/psyclone/psyir/nodes/call.py | 38 ++++++++++++++++------- src/psyclone/psyir/nodes/codeblock.py | 34 +++++++++++++------- src/psyclone/psyir/nodes/directive.py | 12 +++---- src/psyclone/psyir/nodes/if_block.py | 38 ++++++++++++++++------- src/psyclone/psyir/nodes/loop.py | 27 +++++++++------- src/psyclone/psyir/nodes/psy_data_node.py | 13 ++------ src/psyclone/psyir/nodes/return_stmt.py | 15 +++------ src/psyclone/psyir/nodes/routine.py | 14 +++------ src/psyclone/psyir/nodes/statement.py | 26 ++++++++++++++++ src/psyclone/psyir/nodes/while_loop.py | 23 ++++++++------ 10 files changed, 144 insertions(+), 96 deletions(-) diff --git a/src/psyclone/psyir/nodes/call.py b/src/psyclone/psyir/nodes/call.py index 221b0775c0..7b81d60d91 100644 --- a/src/psyclone/psyir/nodes/call.py +++ b/src/psyclone/psyir/nodes/call.py @@ -915,20 +915,34 @@ def get_callee( def next_accesses(self) -> list[Node]: ''' - Abstract method for finding the next_accesses of a statement. - Subclasses should override this according to their own structure to - return future accesses to any References contained in the statement. - - :returns: an empty list. + :returns: the next_accesses of children of this Call. ''' - # FIXME Implement - return [] + # Avoid circular import + # pylint: disable=import-outside-toplevel + from psyclone.psyir.tools import DefinitionUseChain + next_accesses = [] + # Find all the children references + refs = [ref for ref in self.walk(Reference) if not + isinstance(ref.symbol, RoutineSymbol)] + chain = DefinitionUseChain(refs) + access_dict = chain.find_forward_accesses() + for accesses in access_dict: + self._merge_accesses(next_accesses, accesses) + return next_accesses def previous_accesses(self) -> list[Node]: ''' - Abstract method for finding the previous_accesses of a statement. - Subclasses should override this according to their own structure to - return previous accesses to any References contained in the statement. + :returns: the previous_accesses of children of this Call. ''' - # FIXME Implement - return [] + # Avoid circular import + # pylint: disable=import-outside-toplevel + from psyclone.psyir.tools import DefinitionUseChain + prev_accesses = [] + # Find all the children references + refs = [ref for ref in self.walk(Reference) if not + isinstance(ref.symbol, RoutineSymbol)] + chain = DefinitionUseChain(refs) + access_dict = chain.find_backward_accesses() + for accesses in access_dict: + self._merge_accesses(prev_accesses, accesses) + return prev_accesses diff --git a/src/psyclone/psyir/nodes/codeblock.py b/src/psyclone/psyir/nodes/codeblock.py index 1a07834ade..dfcbb96caa 100644 --- a/src/psyclone/psyir/nodes/codeblock.py +++ b/src/psyclone/psyir/nodes/codeblock.py @@ -229,23 +229,33 @@ def get_fortran_lines(self) -> list[str]: def next_accesses(self) -> list[Node]: ''' - Abstract method for finding the next_accesses of a statement. - Subclasses should override this according to their own structure to - return future accesses to any References contained in the statement. - - :returns: an empty list. + :returns: the next_accesses for the child References of this + CodeBlock. ''' - # FIXME Implement - return [] + # Avoid circular import + # pylint: disable=import-outside-toplevel + from psyclone.psyir.tools import DefinitionUseChain + next_accesses = [] + chain = DefinitionUseChain(self.children) + accesses = chain.find_forward_accesses() + for access in accesses: + self._merge_accesses(next_accesses, accesses) + return next_accesses def previous_accesses(self) -> list[Node]: ''' - Abstract method for finding the previous_accesses of a statement. - Subclasses should override this according to their own structure to - return previous accesses to any References contained in the statement. + :returns: the previous_accesses for the child References of this + CodeBlock. ''' - # FIXME Implement - return [] + # Avoid circular import + # pylint: disable=import-outside-toplevel + from psyclone.psyir.tools import DefinitionUseChain + prev_accesses = [] + chain = DefinitionUseChain(self.children) + accesses = chain.find_backward_accesses() + for access in accesses: + self._merge_accesses(prev_accesses, accesses) + return prev_accesses class Fparser2CodeBlock(CodeBlock): diff --git a/src/psyclone/psyir/nodes/directive.py b/src/psyclone/psyir/nodes/directive.py index ea4ddd3666..bae5593f90 100644 --- a/src/psyclone/psyir/nodes/directive.py +++ b/src/psyclone/psyir/nodes/directive.py @@ -150,22 +150,18 @@ def create_data_movement_deep_copy_refs(self): def next_accesses(self) -> list[Node]: ''' - Abstract method for finding the next_accesses of a statement. - Subclasses should override this according to their own structure to - return future accesses to any References contained in the statement. + Directive classes don't have next_accesses to compute. :returns: an empty list. ''' - # FIXME Implement return [] def previous_accesses(self) -> list[Node]: ''' - Abstract method for finding the previous_accesses of a statement. - Subclasses should override this according to their own structure to - return previous accesses to any References contained in the statement. + Directive classes don't have previous_accesses to compute. + + :returns: an empty list. ''' - # FIXME Implement return [] diff --git a/src/psyclone/psyir/nodes/if_block.py b/src/psyclone/psyir/nodes/if_block.py index b96be57518..3feae11a23 100644 --- a/src/psyclone/psyir/nodes/if_block.py +++ b/src/psyclone/psyir/nodes/if_block.py @@ -166,22 +166,36 @@ def reference_accesses(self) -> VariablesAccessMap: var_accesses.update(self.else_body.reference_accesses()) return var_accesses + def next_accesses(self) -> list[Node]: ''' - Abstract method for finding the next_accesses of a statement. - Subclasses should override this according to their own structure to - return future accesses to any References contained in the statement. - - :returns: an empty list. + :returns: the combined next_accesses for the children of this IfBlock. ''' - # FIXME Implement - return [] + next_accesses = [] + new_accesses = self.condition.next_accesses() + self._merge_accesses(next_accesses, new_accesses) + for child in self.if_body: + self._merge_accesses(next_accesses, child.next_accesses()) + if self.else_body: + for child in self.else_body: + self._merge_accesses(next_accesses, child.next_accesses()) + + # FIXME Should we sort the output in some way? + return next_accesses def previous_accesses(self) -> list[Node]: ''' - Abstract method for finding the previous_accesses of a statement. - Subclasses should override this according to their own structure to - return previous accesses to any References contained in the statement. + :returns: the combined previous_accesses for the children of this + IfBlock. ''' - # FIXME Implement - return [] + prev_accesses = [] + new_accesses = self.condition.previous_accesses() + self._merge_accesses(prev_accesses, new_accesses) + for child in self.if_body: + self._merge_accesses(prev_accesses, child.previous_accesses()) + if self.else_body: + for child in self.else_body: + self._merge_accesses(prev_accesses, child.previous_accesses()) + + # FIXME Should we sort the output in some way? + return previous_accesses diff --git a/src/psyclone/psyir/nodes/loop.py b/src/psyclone/psyir/nodes/loop.py index b6de55c1e5..0c115f2452 100644 --- a/src/psyclone/psyir/nodes/loop.py +++ b/src/psyclone/psyir/nodes/loop.py @@ -517,20 +517,23 @@ def enters_scope(self, scope, visited_nodes=None) -> bool: def next_accesses(self) -> list[Node]: ''' - Abstract method for finding the next_accesses of a statement. - Subclasses should override this according to their own structure to - return future accesses to any References contained in the statement. - - :returns: an empty list. + :returns: the combined next_accesses for the children of this Loop. ''' - # FIXME Implement - return [] + next_accesses = [] + var_acceses = self.variable_reference.next_accesses() + self._merge_accesses(next_accesses, var_accesses) + for child in self.loop_body: + self._merge_accesses(next_accesses, child.next_accesses()) + return next_accesses def previous_accesses(self) -> list[Node]: ''' - Abstract method for finding the previous_accesses of a statement. - Subclasses should override this according to their own structure to - return previous accesses to any References contained in the statement. + :returns: the combined previous_accesses for the children of this + Loop. ''' - # FIXME Implement - return [] + prev_accesses = [] + var_acceses = self.variable_reference.previous_accesses() + self._merge_accesses(prev_accesses, var_accesses) + for child in self.loop_body: + self._merge_accesses(prev_accesses, child.previous_accesses()) + return prev_accesses diff --git a/src/psyclone/psyir/nodes/psy_data_node.py b/src/psyclone/psyir/nodes/psy_data_node.py index 96a62c618e..33482919b7 100644 --- a/src/psyclone/psyir/nodes/psy_data_node.py +++ b/src/psyclone/psyir/nodes/psy_data_node.py @@ -704,22 +704,15 @@ def gen_type_bound_call(typename, methodname, argument_list=None, def next_accesses(self) -> list[Node]: ''' - Abstract method for finding the next_accesses of a statement. - Subclasses should override this according to their own structure to - return future accesses to any References contained in the statement. - - :returns: an empty list. + :returns: an empty list as next_accesses isn't needed for PSyDataNode. ''' - # FIXME Implement return [] def previous_accesses(self) -> list[Node]: ''' - Abstract method for finding the previous_accesses of a statement. - Subclasses should override this according to their own structure to - return previous accesses to any References contained in the statement. + :returns: an empty list as previous_accesses isn't needed for + PSyDataNode. ''' - # FIXME Implement return [] diff --git a/src/psyclone/psyir/nodes/return_stmt.py b/src/psyclone/psyir/nodes/return_stmt.py index 79f8bae06e..a9db95e3ea 100644 --- a/src/psyclone/psyir/nodes/return_stmt.py +++ b/src/psyclone/psyir/nodes/return_stmt.py @@ -24,20 +24,13 @@ class Return(Statement): def next_accesses(self) -> list[Node]: ''' - Abstract method for finding the next_accesses of a statement. - Subclasses should override this according to their own structure to - return future accesses to any References contained in the statement. - - :returns: an empty list. + :returns: an empty list as there are no next_accesses after a Return. ''' - # FIXME Implement return [] def previous_accesses(self) -> list[Node]: ''' - Abstract method for finding the previous_accesses of a statement. - Subclasses should override this according to their own structure to - return previous accesses to any References contained in the statement. + :returns: the previous_accesses of the return statement's child. ''' - # FIXME Implement - return [] + return self.children[0].previous_accesses() + diff --git a/src/psyclone/psyir/nodes/routine.py b/src/psyclone/psyir/nodes/routine.py index 4706290acf..23e6c91dc3 100644 --- a/src/psyclone/psyir/nodes/routine.py +++ b/src/psyclone/psyir/nodes/routine.py @@ -569,22 +569,16 @@ def replace_with(self, node, keep_name_in_context=True): def next_accesses(self) -> list[Node]: ''' - Abstract method for finding the next_accesses of a statement. - Subclasses should override this according to their own structure to - return future accesses to any References contained in the statement. - - :returns: an empty list. + :returns: an empty list as PSyclone doesn't know about dependencies + that go outside of Routine scope. ''' - # FIXME Implement return [] def previous_accesses(self) -> list[Node]: ''' - Abstract method for finding the previous_accesses of a statement. - Subclasses should override this according to their own structure to - return previous accesses to any References contained in the statement. + :returns: an empty list as PSyclone doesn't know about dependencies + that go outside of Routine scope. ''' - # FIXME Implement return [] diff --git a/src/psyclone/psyir/nodes/statement.py b/src/psyclone/psyir/nodes/statement.py index dd49609638..d456e28755 100644 --- a/src/psyclone/psyir/nodes/statement.py +++ b/src/psyclone/psyir/nodes/statement.py @@ -35,9 +35,35 @@ def previous_accesses(self) -> list[Node]: Abstract method for finding the previous_accesses of a statement. Subclasses should override this according to their own structure to return previous accesses to any References contained in the statement. + + :returns: an empty list. ''' return [] + def _merge_accesses( + self, current_accesses: list[Node], new_accesses: list[Node] + ) -> None: + ''' + Helper function to merge access lists together for Statement + subclass next/previous_accesses functions. + Take all the accesses from new_accesses and adds them to the + current_accesses list if they're not already present and are + not contained in this nodes subtree. + + :param current_accesses: The list of currently computed dependent + accesses for this node. + :param new_accesses: The list of accesses to merge into + current_accesses. + ''' + for access in new_accesses: + in_subtree = access.is_descendant_of(self) + # If the access is not in the subtree of this node, and + # is not already present in the current_accesses array + # then add it to the array + if not in_subtree and all( + [acc is not access for acc in current_accesses]): + current_accesses.append(access) + # For automatic API documentation generation __all__ = ["Statement"] diff --git a/src/psyclone/psyir/nodes/while_loop.py b/src/psyclone/psyir/nodes/while_loop.py index da90f612b9..23e419433d 100644 --- a/src/psyclone/psyir/nodes/while_loop.py +++ b/src/psyclone/psyir/nodes/while_loop.py @@ -129,14 +129,15 @@ def reference_accesses(self) -> VariablesAccessMap: def next_accesses(self) -> list[Node]: ''' - Abstract method for finding the next_accesses of a statement. - Subclasses should override this according to their own structure to - return future accesses to any References contained in the statement. - - :returns: an empty list. + :returns: the combined next_accesses for the children of this + WhileLoop ''' - # FIXME Implement - return [] + next_accesses = [] + var_acceses = self.condition.next_accesses() + self._merge_accesses(next_accesses, var_accesses) + for child in self.loop_body: + self._merge_accesses(next_accesses, child.next_accesses()) + return next_accesses def previous_accesses(self) -> list[Node]: ''' @@ -144,5 +145,9 @@ def previous_accesses(self) -> list[Node]: Subclasses should override this according to their own structure to return previous accesses to any References contained in the statement. ''' - # FIXME Implement - return [] + prev_accesses = [] + var_acceses = self.condition.previous_accesses() + self._merge_accesses(prev_accesses, var_accesses) + for child in self.loop_body: + self._merge_accesses(prev_accesses, child.previous_accesses()) + return prev_accesses From dbf7ec2549849413b724b23106f332ffc244bf5e Mon Sep 17 00:00:00 2001 From: LonelyCat124 <3043914+LonelyCat124@users.noreply.github.com.> Date: Mon, 17 Aug 2026 15:59:59 +0100 Subject: [PATCH 03/11] linting --- src/psyclone/psyir/nodes/if_block.py | 3 +-- src/psyclone/psyir/nodes/loop.py | 4 ++-- src/psyclone/psyir/nodes/return_stmt.py | 1 - src/psyclone/psyir/nodes/while_loop.py | 4 ++-- 4 files changed, 5 insertions(+), 7 deletions(-) diff --git a/src/psyclone/psyir/nodes/if_block.py b/src/psyclone/psyir/nodes/if_block.py index 3feae11a23..0012e35197 100644 --- a/src/psyclone/psyir/nodes/if_block.py +++ b/src/psyclone/psyir/nodes/if_block.py @@ -166,7 +166,6 @@ def reference_accesses(self) -> VariablesAccessMap: var_accesses.update(self.else_body.reference_accesses()) return var_accesses - def next_accesses(self) -> list[Node]: ''' :returns: the combined next_accesses for the children of this IfBlock. @@ -198,4 +197,4 @@ def previous_accesses(self) -> list[Node]: self._merge_accesses(prev_accesses, child.previous_accesses()) # FIXME Should we sort the output in some way? - return previous_accesses + return prev_accesses diff --git a/src/psyclone/psyir/nodes/loop.py b/src/psyclone/psyir/nodes/loop.py index 0c115f2452..79df4a0e66 100644 --- a/src/psyclone/psyir/nodes/loop.py +++ b/src/psyclone/psyir/nodes/loop.py @@ -520,7 +520,7 @@ def next_accesses(self) -> list[Node]: :returns: the combined next_accesses for the children of this Loop. ''' next_accesses = [] - var_acceses = self.variable_reference.next_accesses() + var_accesses = self.variable_reference.next_accesses() self._merge_accesses(next_accesses, var_accesses) for child in self.loop_body: self._merge_accesses(next_accesses, child.next_accesses()) @@ -532,7 +532,7 @@ def previous_accesses(self) -> list[Node]: Loop. ''' prev_accesses = [] - var_acceses = self.variable_reference.previous_accesses() + var_accesses = self.variable_reference.previous_accesses() self._merge_accesses(prev_accesses, var_accesses) for child in self.loop_body: self._merge_accesses(prev_accesses, child.previous_accesses()) diff --git a/src/psyclone/psyir/nodes/return_stmt.py b/src/psyclone/psyir/nodes/return_stmt.py index a9db95e3ea..c85ef0ba04 100644 --- a/src/psyclone/psyir/nodes/return_stmt.py +++ b/src/psyclone/psyir/nodes/return_stmt.py @@ -33,4 +33,3 @@ def previous_accesses(self) -> list[Node]: :returns: the previous_accesses of the return statement's child. ''' return self.children[0].previous_accesses() - diff --git a/src/psyclone/psyir/nodes/while_loop.py b/src/psyclone/psyir/nodes/while_loop.py index 23e419433d..9922c4579f 100644 --- a/src/psyclone/psyir/nodes/while_loop.py +++ b/src/psyclone/psyir/nodes/while_loop.py @@ -133,7 +133,7 @@ def next_accesses(self) -> list[Node]: WhileLoop ''' next_accesses = [] - var_acceses = self.condition.next_accesses() + var_accesses = self.condition.next_accesses() self._merge_accesses(next_accesses, var_accesses) for child in self.loop_body: self._merge_accesses(next_accesses, child.next_accesses()) @@ -146,7 +146,7 @@ def previous_accesses(self) -> list[Node]: return previous accesses to any References contained in the statement. ''' prev_accesses = [] - var_acceses = self.condition.previous_accesses() + var_accesses = self.condition.previous_accesses() self._merge_accesses(prev_accesses, var_accesses) for child in self.loop_body: self._merge_accesses(prev_accesses, child.previous_accesses()) From 2dc8ddce41df536235967123201a21692831443c Mon Sep 17 00:00:00 2001 From: LonelyCat124 <3043914+LonelyCat124@users.noreply.github.com.> Date: Mon, 17 Aug 2026 16:03:12 +0100 Subject: [PATCH 04/11] Fixed linting from master merge --- src/psyclone/tests/psyir/transformations/inline_trans_test.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/psyclone/tests/psyir/transformations/inline_trans_test.py b/src/psyclone/tests/psyir/transformations/inline_trans_test.py index e94063f0ea..fe1738be4b 100644 --- a/src/psyclone/tests/psyir/transformations/inline_trans_test.py +++ b/src/psyclone/tests/psyir/transformations/inline_trans_test.py @@ -17,7 +17,7 @@ from psyclone.psyir.backend.fortran import FortranWriter from psyclone.psyir.nodes import ( Assignment, Call, IntrinsicCall, Loop, Node, Reference, - Routine, Statement, Literal) + Routine, Literal) from psyclone.psyir.symbols import ( AutomaticInterface, DataSymbol, ImportInterface, UnresolvedType, ScalarType) From 9610ac8a7d5e14f09fb010bb47d144651307ea35 Mon Sep 17 00:00:00 2001 From: LonelyCat124 <3043914+LonelyCat124@users.noreply.github.com.> Date: Wed, 19 Aug 2026 15:46:16 +0100 Subject: [PATCH 05/11] Fix condition for if_block --- src/psyclone/psyir/nodes/if_block.py | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/src/psyclone/psyir/nodes/if_block.py b/src/psyclone/psyir/nodes/if_block.py index 0012e35197..4185d7341e 100644 --- a/src/psyclone/psyir/nodes/if_block.py +++ b/src/psyclone/psyir/nodes/if_block.py @@ -11,6 +11,7 @@ from psyclone.errors import InternalError, GenerationError from psyclone.psyir.nodes.datanode import DataNode from psyclone.psyir.nodes.node import Node +from psyclone.psyir.nodes.reference import Reference from psyclone.psyir.nodes.schedule import Schedule from psyclone.psyir.nodes.statement import Statement @@ -171,8 +172,10 @@ def next_accesses(self) -> list[Node]: :returns: the combined next_accesses for the children of this IfBlock. ''' next_accesses = [] - new_accesses = self.condition.next_accesses() - self._merge_accesses(next_accesses, new_accesses) + # Find all the next_accesses for the References in the condition. + for ref in self.condition.walk(Reference): + new_accesses = ref.next_accesses() + self._merge_accesses(next_accesses, new_accesses) for child in self.if_body: self._merge_accesses(next_accesses, child.next_accesses()) if self.else_body: @@ -188,8 +191,10 @@ def previous_accesses(self) -> list[Node]: IfBlock. ''' prev_accesses = [] - new_accesses = self.condition.previous_accesses() - self._merge_accesses(prev_accesses, new_accesses) + # Find all the next_accesses for the References in the condition. + for ref in self.condition.walk(Reference): + new_accesses = ref.previous_accesses() + self._merge_accesses(prev_accesses, new_accesses) for child in self.if_body: self._merge_accesses(prev_accesses, child.previous_accesses()) if self.else_body: From 974b8cd60792b6fea883d09e6f6b35c15615250b Mon Sep 17 00:00:00 2001 From: LonelyCat124 <3043914+LonelyCat124@users.noreply.github.com.> Date: Wed, 19 Aug 2026 15:46:31 +0100 Subject: [PATCH 06/11] Changes towards initial if_block tests --- .../tests/psyir/nodes/if_block_test.py | 31 +++++++++++++++++-- 1 file changed, 29 insertions(+), 2 deletions(-) diff --git a/src/psyclone/tests/psyir/nodes/if_block_test.py b/src/psyclone/tests/psyir/nodes/if_block_test.py index d4affa9135..29b46a9a3d 100644 --- a/src/psyclone/tests/psyir/nodes/if_block_test.py +++ b/src/psyclone/tests/psyir/nodes/if_block_test.py @@ -8,8 +8,9 @@ ''' Performs py.test tests on the IfBlock PSyIR node. ''' import pytest -from psyclone.psyir.nodes import IfBlock, Literal, Reference, Schedule, \ - Return, Assignment +from psyclone.psyir.nodes import ( + IfBlock, Literal, Reference, Schedule, Return, Assignment +) from psyclone.psyir.symbols import DataSymbol, ScalarType from psyclone.errors import InternalError, GenerationError from psyclone.psyir.backend.fortran import FortranWriter @@ -256,3 +257,29 @@ def test_ifblock_children_validation(): ifblock.addchild(else_body) assert ("Item 'Schedule' can't be child 3 of 'If'. The valid format is: " "'DataNode, Schedule [, Schedule]'." in str(excinfo.value)) + + +def test_ifblock_next_accesses_condition(fortran_reader): + '''Test the next_accesses finds the next_accesses for references + in the ifblock's condition correctly.''' + + code = """subroutine test + integer :: i, j, k, l + + if(i > 3) then + j = 1 + else + k = 1 + end if + l = i + i = 1 + end subroutine test""" + + psyir = fortran_reader.psyir_from_source(code) + ifblock = psyir.walk(IfBlock)[0] + accesses = ifblock.next_accesses() + # The next_accesses are the two accesses to i after the ifblock. + assert len(accesses) == 2 + assigns = psyir.walk(Assignment) + assert accesses[0] is assignment[2].rhs + assert accesses[1] is assignment[3].lhs From 3a2d2eb39ae0394a81dae5be17b4482c984d3008 Mon Sep 17 00:00:00 2001 From: LonelyCat124 <3043914+LonelyCat124@users.noreply.github.com.> Date: Fri, 21 Aug 2026 10:43:31 +0100 Subject: [PATCH 07/11] Some coverage --- .../tests/psyir/nodes/assignment_test.py | 34 ++-- src/psyclone/tests/psyir/nodes/call_test.py | 40 +++- .../tests/psyir/nodes/codeblock_test.py | 45 ++++- .../tests/psyir/nodes/if_block_test.py | 116 ++++++++++- src/psyclone/tests/psyir/nodes/loop_test.py | 191 ++++++++++++++++++ .../tests/psyir/nodes/while_loop_test.py | 84 +++++++- 6 files changed, 482 insertions(+), 28 deletions(-) diff --git a/src/psyclone/tests/psyir/nodes/assignment_test.py b/src/psyclone/tests/psyir/nodes/assignment_test.py index cfe2a13641..31431e076c 100644 --- a/src/psyclone/tests/psyir/nodes/assignment_test.py +++ b/src/psyclone/tests/psyir/nodes/assignment_test.py @@ -370,10 +370,9 @@ def test_next_accesses(fortran_reader): reaches = assigns[0].next_accesses() sig = assigns[0].lhs.get_signature_and_indices()[0] - assert len(reaches) == 1 - assert len(reaches[sig]) == 2 - assert reaches[sig][0] is assigns[1].rhs.children[0] - assert reaches[sig][1] is assigns[1].lhs + assert len(reaches) == 2 + assert reaches[0] is assigns[1].rhs.children[0] + assert reaches[1] is assigns[1].lhs # Next test multiple References to different symbols in the # Assignment @@ -390,12 +389,10 @@ def test_next_accesses(fortran_reader): reaches = assigns[0].next_accesses() a_sig = assigns[0].lhs.get_signature_and_indices()[0] b_sig = assigns[0].rhs.get_signature_and_indices()[0] - assert len(reaches) == 2 - assert len(reaches[a_sig]) == 2 - assert reaches[a_sig][0] is assigns[1].rhs.children[0] - assert reaches[a_sig][1] is assigns[1].lhs - assert len(reaches[b_sig]) == 1 - assert reaches[b_sig][0] is assigns[2].lhs + assert len(reaches) == 3 + assert reaches[0] is assigns[1].rhs.children[0] + assert reaches[1] is assigns[1].lhs + assert reaches[2] is assigns[2].lhs # Test References inside an inquiry function are ignored psyir = fortran_reader.psyir_from_source( @@ -411,9 +408,7 @@ def test_next_accesses(fortran_reader): reaches = assigns[0].next_accesses() a_sig = assigns[0].lhs.get_signature_and_indices()[0] b_sig = assigns[0].rhs.children[0].get_signature_and_indices()[0] - assert len(reaches) == 2 - assert len(reaches[a_sig]) == 0 - assert len(reaches[b_sig]) == 0 + assert len(reaches) == 0 def test_previous_accesses(fortran_reader): @@ -432,8 +427,7 @@ def test_previous_accesses(fortran_reader): reaches = assigns[1].previous_accesses() sig = assigns[0].lhs.get_signature_and_indices()[0] assert len(reaches) == 1 - assert len(reaches[sig]) == 1 - assert reaches[sig][0] is assigns[0].lhs + assert reaches[0] is assigns[0].lhs # Next test multiple References to different symbols in the # Assignment @@ -451,10 +445,8 @@ def test_previous_accesses(fortran_reader): a_sig = assigns[2].lhs.get_signature_and_indices()[0] b_sig = assigns[2].rhs.get_signature_and_indices()[0] assert len(reaches) == 2 - assert len(reaches[a_sig]) == 1 - assert reaches[a_sig][0] is assigns[1].lhs - assert len(reaches[b_sig]) == 1 - assert reaches[b_sig][0] is assigns[0].lhs + assert reaches[0] is assigns[1].lhs + assert reaches[1] is assigns[0].lhs # Test References inside an inquiry function are ignored psyir = fortran_reader.psyir_from_source( @@ -470,6 +462,4 @@ def test_previous_accesses(fortran_reader): reaches = assigns[1].previous_accesses() a_sig = assigns[1].lhs.get_signature_and_indices()[0] b_sig = assigns[1].rhs.children[0].get_signature_and_indices()[0] - assert len(reaches) == 2 - assert len(reaches[a_sig]) == 0 - assert len(reaches[b_sig]) == 0 + assert len(reaches) == 0 diff --git a/src/psyclone/tests/psyir/nodes/call_test.py b/src/psyclone/tests/psyir/nodes/call_test.py index 13c7ae813d..fd2f5d2246 100644 --- a/src/psyclone/tests/psyir/nodes/call_test.py +++ b/src/psyclone/tests/psyir/nodes/call_test.py @@ -13,7 +13,7 @@ from psyclone.core import Signature from psyclone.errors import GenerationError from psyclone.psyir.nodes import ( - ArrayReference, BinaryOperation, Call, Literal, + ArrayReference, Assignment, BinaryOperation, Call, Literal, Node, Reference, Routine, Schedule, CallMatchingArgumentsNotFound) from psyclone.psyir.nodes.node import colored from psyclone.psyir.symbols import ( @@ -2051,3 +2051,41 @@ def test_call_datatype(fortran_reader): # TODO #1799: Improve datatype inference, the following one # has a definition that says is an ArrayType assert isinstance(calls[3].datatype, UnresolvedType) + + +def test_call_next_accesses(fortran_reader): + ''' Test the next_accesses method of Call gives the correct results.''' + psyir = fortran_reader.psyir_from_source(""" + subroutine test + use some_mod + integer :: i, j, k + call my_fun(i) + j = i + i = k + end subroutine test""") + call = psyir.walk(Call)[0] + access = call.next_accesses() + assigns = psyir.walk(Assignment) + # The result is the 2 following accesses to i. + assert len(access) == 2 + assert access[0] is assigns[0].rhs + assert access[1] is assigns[1].lhs + + +def test_call_previous_accesses(fortran_reader): + ''' Test the previous_accesses method of Call gives the correct results.''' + psyir = fortran_reader.psyir_from_source(""" + subroutine test + use some_mod + integer :: i, j, k + i = k + j = i + call my_fun(i) + end subroutine test""") + call = psyir.walk(Call)[0] + access = call.previous_accesses() + assigns = psyir.walk(Assignment) + # The result is the 2 following accesses to i. + assert len(access) == 2 + assert access[0] is assigns[1].rhs + assert access[1] is assigns[0].lhs diff --git a/src/psyclone/tests/psyir/nodes/codeblock_test.py b/src/psyclone/tests/psyir/nodes/codeblock_test.py index 689d373070..7083a3ffa8 100644 --- a/src/psyclone/tests/psyir/nodes/codeblock_test.py +++ b/src/psyclone/tests/psyir/nodes/codeblock_test.py @@ -12,7 +12,7 @@ from fparser.common.readfortran import FortranStringReader from psyclone.configuration import Config from psyclone.psyir.frontend.fortran import FortranReader -from psyclone.psyir.nodes import Reference, Schedule +from psyclone.psyir.nodes import Assignment, Reference, Schedule from psyclone.psyir.frontend.fparser2 import Fparser2Reader from psyclone.psyir.frontend.fortran_treesitter_reader import \ FortranTreeSitterReader @@ -346,3 +346,46 @@ def test_codeblock_has_potential_control_flow_jump(fortran_reader): assert codeblocks[2].has_potential_control_flow_jump() # labelled statement assert codeblocks[3].has_potential_control_flow_jump() + + +def test_codeblock_next_accesses(fortran_reader): + """Test that the next_accesses method works correctly for CodeBlocks.""" + code = """subroutine test() + integer :: i, j, k + print *, i + k = 0 ! Extra assignment to separate the CodeBlocks. + print *, j, i + j = 3 + i = 1 + end subroutine test""" + psyir = fortran_reader.psyir_from_source(code) + codeblocks = psyir.walk(CodeBlock) + assigns = psyir.walk(Assignment) + accesses = codeblocks[0].next_accesses() + assert len(accesses) == 1 + assert accesses[0] is codeblocks[1].children[1] + accesses = codeblocks[1].next_accesses() + assert len(accesses) == 2 + assert accesses[0] is assigns[1].lhs + assert accesses[1] is assigns[2].lhs + + +def test_codeblock_previous_accesses(fortran_reader): + """Test that the previous_accesses method works correctly for CodeBlocks.""" + code = """subroutine test() + integer :: i, j, k + i = 1 + j = 3 + print *, j, i + k = 0 ! Extra assignment to separate the CodeBlocks. + print *, i + end subroutine test""" + psyir = fortran_reader.psyir_from_source(code) + codeblocks = psyir.walk(CodeBlock) + assigns = psyir.walk(Assignment) + accesses = codeblocks[1].previous_accesses() + assert len(accesses) == 1 + assert accesses[0] is codeblocks[0].children[1] + accesses = codeblocks[0].previous_accesses() + assert accesses[0] is assigns[1].lhs + assert accesses[1] is assigns[0].lhs diff --git a/src/psyclone/tests/psyir/nodes/if_block_test.py b/src/psyclone/tests/psyir/nodes/if_block_test.py index 29b46a9a3d..319ed1dcd5 100644 --- a/src/psyclone/tests/psyir/nodes/if_block_test.py +++ b/src/psyclone/tests/psyir/nodes/if_block_test.py @@ -281,5 +281,117 @@ def test_ifblock_next_accesses_condition(fortran_reader): # The next_accesses are the two accesses to i after the ifblock. assert len(accesses) == 2 assigns = psyir.walk(Assignment) - assert accesses[0] is assignment[2].rhs - assert accesses[1] is assignment[3].lhs + assert accesses[0] is assigns[2].rhs + assert accesses[1] is assigns[3].lhs + + # Check that accesses within the ifblock aren't found. + code = """subroutine test + integer :: i, j, k + if (i > 3) then + i = 2 + else + i = 4 + end if + k = 1 + end subroutine test""" + + psyir = fortran_reader.psyir_from_source(code) + ifblock = psyir.walk(IfBlock)[0] + accesses = ifblock.next_accesses() + assert len(accesses) == 0 + + +def test_ifblock_next_accesses_bodies(fortran_reader): + '''Test the next_accesses method finds the next_accesses for + references in the ifblock's bodies.''' + code = """subroutine test + integer :: i, j, k, l + if(i > 3) then + j = 1 + else + k = 1 + end if + l = j + l = l + k + j = 3 + end subroutine test""" + psyir = fortran_reader.psyir_from_source(code) + ifblock = psyir.walk(IfBlock)[0] + accesses = ifblock.next_accesses() + assigns = psyir.walk(Assignment) + assert len(accesses) == 3 + # First access after the ifblock to j. + assert accesses[0] is assigns[2].rhs + # First access after the ifblock to k. + assert accesses[2] is assigns[3].rhs.children[1] + # Write access after the ifblock to j. + assert accesses[1] is assigns[4].lhs + + +def test_ifblock_previous_accesses_condition(fortran_reader): + '''Test the previous_accesses finds the previous_accesses for references + in the ifblock's condition correctly.''' + + code = """subroutine test + integer :: i, j, k, l + + i = 1 + l = i + if(i > 3) then + j = 1 + else + k = 1 + end if + end subroutine test""" + + psyir = fortran_reader.psyir_from_source(code) + ifblock = psyir.walk(IfBlock)[0] + accesses = ifblock.previous_accesses() + # The previous_accesses are the two accesses to i after the ifblock. + assert len(accesses) == 2 + assigns = psyir.walk(Assignment) + assert accesses[0] is assigns[1].rhs + assert accesses[1] is assigns[0].lhs + + # Check that accesses within the ifblock aren't found. + code = """subroutine test + integer :: i, j, k + k = 1 + if (i > 3) then + i = 2 + else + i = 4 + end if + end subroutine test""" + + psyir = fortran_reader.psyir_from_source(code) + ifblock = psyir.walk(IfBlock)[0] + accesses = ifblock.previous_accesses() + assert len(accesses) == 0 + + +def test_ifblock_previous_accesses_bodies(fortran_reader): + '''Test the previous_accesses method finds the previous_accesses for + references in the ifblock's bodies.''' + code = """subroutine test + integer :: i, j, k, l + j = 3 + l = l + k + l = j + if(i > 3) then + j = 1 + else + k = 1 + end if + end subroutine test""" + psyir = fortran_reader.psyir_from_source(code) + ifblock = psyir.walk(IfBlock)[0] + accesses = ifblock.previous_accesses() + assigns = psyir.walk(Assignment) + assert len(accesses) == 3 + # First access after the ifblock to j. + assert accesses[0] is assigns[2].rhs + # First access after the ifblock to k. + assert accesses[2] is assigns[1].rhs.children[1] + # Write access after the ifblock to j. + assert accesses[1] is assigns[0].lhs diff --git a/src/psyclone/tests/psyir/nodes/loop_test.py b/src/psyclone/tests/psyir/nodes/loop_test.py index 84e5645f4e..d41e1b8821 100644 --- a/src/psyclone/tests/psyir/nodes/loop_test.py +++ b/src/psyclone/tests/psyir/nodes/loop_test.py @@ -551,3 +551,194 @@ def test_loops_enters_scope(): # Always returns false, regardless of the scope, as the loop variable # by definition gets the first value assigned here from the loop bounds. assert not loop.enters_scope(None) + + +def test_loop_variable_next_accesses(fortran_reader): + """Test that the next_accesses function works correctly for the loop + variable.""" + code = """subroutine test + integer :: i, j, k + do i = 1, 100 + j = 2 + end do + i = k + end subroutine test""" + psyir = fortran_reader.psyir_from_source(code) + loop = psyir.walk(Loop)[0] + assigns = psyir.walk(Assignment) + accesses = loop.next_accesses() + assert len(accesses) == 1 + assert accesses[0] is assigns[1].lhs + + +def test_loop_variable_previous_accesses(fortran_reader): + """Test that the previous_accesses function works correctly for the loop + variable.""" + code = """subroutine test + integer :: i, j, k + i = k + do i = 1, 100 + j = 2 + end do + end subroutine test""" + psyir = fortran_reader.psyir_from_source(code) + loop = psyir.walk(Loop)[0] + assigns = psyir.walk(Assignment) + accesses = loop.previous_accesses() + assert len(accesses) == 0 + pytest.xfail(reason="#3486 Definition Use Chains don't yet account" + "for loop variables.") + # Correct implementation should result in: + # assert len(accesses) == 1 + # assert accesses[0] is assigns[0].lhs + + +def test_loop_start_value_next_accesses(fortran_reader): + """Test that the next_accesses function works correctly for the start + condition.""" + code = """subroutine test + integer :: i, j, k + do i = j, 100 + k = 1 + end do + j = 2 + end subroutine test""" + psyir = fortran_reader.psyir_from_source(code) + loop = psyir.walk(Loop)[0] + assigns = psyir.walk(Assignment) + accesses = loop.next_accesses() + assert len(accesses) == 1 + assert accesses[0] is assigns[1].lhs + + +def test_loop_start_value_previous_accesses(fortran_reader): + """Test that the previous_accesses function works correctly for the start + condition.""" + code = """subroutine test + integer :: i, j, k + j = 2 + do i = j, 100 + k = 1 + end do + end subroutine test""" + psyir = fortran_reader.psyir_from_source(code) + loop = psyir.walk(Loop)[0] + assigns = psyir.walk(Assignment) + accesses = loop.previous_accesses() + assert len(accesses) == 1 + assert accesses[0] is assigns[0].lhs + + +def test_loop_stop_value_next_accesses(fortran_reader): + """Test that the next_accesses function works correctly for the stop + condition.""" + code = """subroutine test + integer :: i, j, k + do i = 1, j + k = 1 + end do + j = 2 + end subroutine test""" + psyir = fortran_reader.psyir_from_source(code) + loop = psyir.walk(Loop)[0] + assigns = psyir.walk(Assignment) + accesses = loop.next_accesses() + assert len(accesses) == 1 + assert accesses[0] is assigns[1].lhs + + +def test_loop_stop_value_previous_accesses(fortran_reader): + """Test that the previous_accesses function works correctly for the stop + condition.""" + code = """subroutine test + integer :: i, j, k + j = 2 + do i = 1, j + k = 1 + end do + end subroutine test""" + psyir = fortran_reader.psyir_from_source(code) + loop = psyir.walk(Loop)[0] + assigns = psyir.walk(Assignment) + accesses = loop.previous_accesses() + assert len(accesses) == 1 + assert accesses[0] is assigns[0].lhs + +def test_loop_step_value_next_accesses(fortran_reader): + """Test that the next_accesses function works correctly for the step + condition.""" + code = """subroutine test + integer :: i, j, k + do i = 1, 100, j + k = 1 + end do + j = 2 + end subroutine test""" + psyir = fortran_reader.psyir_from_source(code) + loop = psyir.walk(Loop)[0] + assigns = psyir.walk(Assignment) + accesses = loop.next_accesses() + assert len(accesses) == 1 + assert accesses[0] is assigns[1].lhs + + +def test_loop_step_value_previous_accesses(fortran_reader): + """Test that the previous_accesses function works correctly for the step + condition.""" + code = """subroutine test + integer :: i, j, k + j = 2 + do i = 1, 100, j + k = 1 + end do + end subroutine test""" + psyir = fortran_reader.psyir_from_source(code) + loop = psyir.walk(Loop)[0] + assigns = psyir.walk(Assignment) + accesses = loop.previous_accesses() + assert len(accesses) == 1 + assert accesses[0] is assigns[0].lhs + + +def test_loop_body_next_accesses(fortran_reader): + """Test that the next_accesses function works correctly for the + loop_body.""" + code = """subroutine test + integer :: i, j, k + do i = 1, 100 + j = 4 * i + k = 3 + i + end do + j = j * 2 + k = k + 3 + end subroutine test""" + psyir = fortran_reader.psyir_from_source(code) + loop = psyir.walk(Loop)[0] + assigns = psyir.walk(Assignment) + accesses = loop.next_accesses() + assert len(accesses) == 4 + assert accesses[0] is assigns[2].rhs.children[0] + assert accesses[1] is assigns[2].lhs + assert accesses[2] is assigns[3].rhs.children[0] + assert accesses[3] is assigns[3].lhs + + +def test_loop_body_previous_accesses(fortran_reader): + """Test that the previous_accesses function works correctly for the + loop_body.""" + code = """subroutine test + integer :: i, j, k + k = k + 3 + j = j * 2 + do i = 1, 100 + j = 4 * i + k = 3 + i + end do + end subroutine test""" + psyir = fortran_reader.psyir_from_source(code) + loop = psyir.walk(Loop)[0] + assigns = psyir.walk(Assignment) + accesses = loop.previous_accesses() + assert len(accesses) == 2 + assert accesses[0] is assigns[1].lhs + assert accesses[1] is assigns[0].lhs diff --git a/src/psyclone/tests/psyir/nodes/while_loop_test.py b/src/psyclone/tests/psyir/nodes/while_loop_test.py index 61d85c4151..5e972b8c1e 100644 --- a/src/psyclone/tests/psyir/nodes/while_loop_test.py +++ b/src/psyclone/tests/psyir/nodes/while_loop_test.py @@ -10,8 +10,8 @@ import pytest from psyclone.errors import InternalError, GenerationError from psyclone.psyir.backend.fortran import FortranWriter -from psyclone.psyir.nodes import Assignment, BinaryOperation, Literal, \ - Reference, Return, Schedule, WhileLoop +from psyclone.psyir.nodes import (Assignment, BinaryOperation, Literal, + Reference, Return, Schedule, WhileLoop) from psyclone.psyir.nodes.node import colored from psyclone.psyir.symbols import DataSymbol, ScalarType from psyclone.tests.utilities import check_links @@ -139,3 +139,83 @@ def test_whileloop_can_be_printed(): assert "WhileLoop[]\n" in str(loop) assert "condition1" in str(loop) # Test condition is printed assert "Return[]" in str(loop) # Test loop body is printed + + +def test_whileloop_condition_next_accesses(fortran_reader): + '''Test that the next_accesses method works correctly on References + in the WhileLoop condition.''' + code = """subroutine test + integer :: i, j, k + + do while(i < 2) + j = 2 + end do + i = k + 1 + end subroutine test""" + psyir = fortran_reader.psyir_from_source(code) + + loop = psyir.walk(WhileLoop)[0] + assigns = psyir.walk(Assignment) + accesses = loop.next_accesses() + assert len(accesses) == 1 + assert accesses[0] is assigns[1].lhs + + +def test_whileloop_condition_previous_accesses(fortran_reader): + '''Test that the previous_accesses method works correctly on References + in the WhileLoop condition.''' + code = """subroutine test + integer :: i, j, k + + i = k + 1 + do while(i < 2) + j = 2 + end do + end subroutine test""" + psyir = fortran_reader.psyir_from_source(code) + + loop = psyir.walk(WhileLoop)[0] + assigns = psyir.walk(Assignment) + accesses = loop.previous_accesses() + assert len(accesses) == 1 + assert accesses[0] is assigns[0].lhs + + +def test_whileloop_body_next_accesses(fortran_reader): + '''Test that the next_accesses method works correctly on the + WhileLoop body.''' + code = """subroutine test + integer :: i, j, k + + do while(i < 2) + j = 2 + end do + j = k + 1 + end subroutine test""" + psyir = fortran_reader.psyir_from_source(code) + + loop = psyir.walk(WhileLoop)[0] + assigns = psyir.walk(Assignment) + accesses = loop.next_accesses() + assert len(accesses) == 1 + assert accesses[0] is assigns[1].lhs + + +def test_whileloop_body_previous_accesses(fortran_reader): + '''Test that the previous_accesses method works correctly on the + WhileLoop body.''' + code = """subroutine test + integer :: i, j, k + + j = k + 1 + do while(i < 2) + j = 2 + end do + end subroutine test""" + psyir = fortran_reader.psyir_from_source(code) + + loop = psyir.walk(WhileLoop)[0] + assigns = psyir.walk(Assignment) + accesses = loop.previous_accesses() + assert len(accesses) == 1 + assert accesses[0] is assigns[0].lhs From 0b02f50cb7f726de523a1489b7f8434072fdfe3e Mon Sep 17 00:00:00 2001 From: LonelyCat124 <3043914+LonelyCat124@users.noreply.github.com.> Date: Fri, 21 Aug 2026 10:51:33 +0100 Subject: [PATCH 08/11] linting --- src/psyclone/psyir/nodes/assignment.py | 24 ++++++++++++++----- src/psyclone/psyir/nodes/call.py | 8 +++---- src/psyclone/psyir/nodes/codeblock.py | 4 ++-- src/psyclone/psyir/nodes/loop.py | 14 +++++++---- src/psyclone/psyir/nodes/while_loop.py | 11 +++++---- .../tests/psyir/nodes/assignment_test.py | 10 -------- .../tests/psyir/nodes/codeblock_test.py | 3 ++- src/psyclone/tests/psyir/nodes/loop_test.py | 5 ++-- .../tests/psyir/nodes/while_loop_test.py | 8 +++---- 9 files changed, 50 insertions(+), 37 deletions(-) diff --git a/src/psyclone/psyir/nodes/assignment.py b/src/psyclone/psyir/nodes/assignment.py index 4a64a23aaf..dad0028ea6 100644 --- a/src/psyclone/psyir/nodes/assignment.py +++ b/src/psyclone/psyir/nodes/assignment.py @@ -7,7 +7,7 @@ ''' This module contains the Assignment node implementation.''' -from psyclone.core import VariablesAccessMap, AccessType, Signature +from psyclone.core import VariablesAccessMap, AccessType from psyclone.errors import InternalError from psyclone.psyir.nodes.literal import Literal from psyclone.psyir.nodes.array_reference import ArrayReference @@ -213,7 +213,7 @@ def is_literal_assignment(self): ''' return isinstance(self.rhs, Literal) - def previous_accesses(self) -> dict[Signature, list[Node]]: + def previous_accesses(self) -> list[Node]: ''' :returns: the nodes containing the previous accesses of the symbols accessed within this node. It can be multiple nodes for @@ -229,9 +229,15 @@ def previous_accesses(self) -> dict[Signature, list[Node]]: # pylint: disable=import-outside-toplevel from psyclone.psyir.tools import DefinitionUseChain chain = DefinitionUseChain(refs) - return chain.find_backward_accesses() - - def next_accesses(self) -> dict[Signature, list[Node]]: + accesses = chain.find_backward_accesses() + results = [] + for sig in accesses: + for access in accesses[sig]: + if all(x is not access for x in results): + results.append(access) + return results + + def next_accesses(self) -> list[Node]: ''' :returns: the nodes containing the next accesses of the symbols accessed within this node. It can be multiple nodes for @@ -247,4 +253,10 @@ def next_accesses(self) -> dict[Signature, list[Node]]: # pylint: disable=import-outside-toplevel from psyclone.psyir.tools import DefinitionUseChain chain = DefinitionUseChain(refs) - return chain.find_forward_accesses() + accesses = chain.find_forward_accesses() + results = [] + for sig in accesses: + for access in accesses[sig]: + if all(x is not access for x in results): + results.append(access) + return results diff --git a/src/psyclone/psyir/nodes/call.py b/src/psyclone/psyir/nodes/call.py index 7b81d60d91..d67b7a3a74 100644 --- a/src/psyclone/psyir/nodes/call.py +++ b/src/psyclone/psyir/nodes/call.py @@ -926,8 +926,8 @@ def next_accesses(self) -> list[Node]: isinstance(ref.symbol, RoutineSymbol)] chain = DefinitionUseChain(refs) access_dict = chain.find_forward_accesses() - for accesses in access_dict: - self._merge_accesses(next_accesses, accesses) + for access in access_dict: + self._merge_accesses(next_accesses, access_dict[access]) return next_accesses def previous_accesses(self) -> list[Node]: @@ -943,6 +943,6 @@ def previous_accesses(self) -> list[Node]: isinstance(ref.symbol, RoutineSymbol)] chain = DefinitionUseChain(refs) access_dict = chain.find_backward_accesses() - for accesses in access_dict: - self._merge_accesses(prev_accesses, accesses) + for access in access_dict: + self._merge_accesses(prev_accesses, access_dict[access]) return prev_accesses diff --git a/src/psyclone/psyir/nodes/codeblock.py b/src/psyclone/psyir/nodes/codeblock.py index dfcbb96caa..3fba4d3166 100644 --- a/src/psyclone/psyir/nodes/codeblock.py +++ b/src/psyclone/psyir/nodes/codeblock.py @@ -239,7 +239,7 @@ def next_accesses(self) -> list[Node]: chain = DefinitionUseChain(self.children) accesses = chain.find_forward_accesses() for access in accesses: - self._merge_accesses(next_accesses, accesses) + self._merge_accesses(next_accesses, accesses[access]) return next_accesses def previous_accesses(self) -> list[Node]: @@ -254,7 +254,7 @@ def previous_accesses(self) -> list[Node]: chain = DefinitionUseChain(self.children) accesses = chain.find_backward_accesses() for access in accesses: - self._merge_accesses(prev_accesses, accesses) + self._merge_accesses(prev_accesses, accesses[access]) return prev_accesses diff --git a/src/psyclone/psyir/nodes/loop.py b/src/psyclone/psyir/nodes/loop.py index 79df4a0e66..f519c8d792 100644 --- a/src/psyclone/psyir/nodes/loop.py +++ b/src/psyclone/psyir/nodes/loop.py @@ -520,8 +520,11 @@ def next_accesses(self) -> list[Node]: :returns: the combined next_accesses for the children of this Loop. ''' next_accesses = [] - var_accesses = self.variable_reference.next_accesses() - self._merge_accesses(next_accesses, var_accesses) + # Loop through the non loop_body children and compute accesses. + for child in self.children[0:4]: + for ref in child.walk(Reference): + var_accesses = ref.next_accesses() + self._merge_accesses(next_accesses, var_accesses) for child in self.loop_body: self._merge_accesses(next_accesses, child.next_accesses()) return next_accesses @@ -532,8 +535,11 @@ def previous_accesses(self) -> list[Node]: Loop. ''' prev_accesses = [] - var_accesses = self.variable_reference.previous_accesses() - self._merge_accesses(prev_accesses, var_accesses) + # Loop through the non loop_body children and compute accesses. + for child in self.children[0:4]: + for ref in child.walk(Reference): + var_accesses = ref.previous_accesses() + self._merge_accesses(prev_accesses, var_accesses) for child in self.loop_body: self._merge_accesses(prev_accesses, child.previous_accesses()) return prev_accesses diff --git a/src/psyclone/psyir/nodes/while_loop.py b/src/psyclone/psyir/nodes/while_loop.py index 9922c4579f..3240bdf636 100644 --- a/src/psyclone/psyir/nodes/while_loop.py +++ b/src/psyclone/psyir/nodes/while_loop.py @@ -11,6 +11,7 @@ from psyclone.errors import InternalError, GenerationError from psyclone.psyir.nodes.datanode import DataNode from psyclone.psyir.nodes.node import Node +from psyclone.psyir.nodes.reference import Reference from psyclone.psyir.nodes.schedule import Schedule from psyclone.psyir.nodes.statement import Statement @@ -133,8 +134,9 @@ def next_accesses(self) -> list[Node]: WhileLoop ''' next_accesses = [] - var_accesses = self.condition.next_accesses() - self._merge_accesses(next_accesses, var_accesses) + for ref in self.condition.walk(Reference): + var_accesses = ref.next_accesses() + self._merge_accesses(next_accesses, var_accesses) for child in self.loop_body: self._merge_accesses(next_accesses, child.next_accesses()) return next_accesses @@ -146,8 +148,9 @@ def previous_accesses(self) -> list[Node]: return previous accesses to any References contained in the statement. ''' prev_accesses = [] - var_accesses = self.condition.previous_accesses() - self._merge_accesses(prev_accesses, var_accesses) + for ref in self.condition.walk(Reference): + var_accesses = ref.previous_accesses() + self._merge_accesses(prev_accesses, var_accesses) for child in self.loop_body: self._merge_accesses(prev_accesses, child.previous_accesses()) return prev_accesses diff --git a/src/psyclone/tests/psyir/nodes/assignment_test.py b/src/psyclone/tests/psyir/nodes/assignment_test.py index 31431e076c..1d8709a00c 100644 --- a/src/psyclone/tests/psyir/nodes/assignment_test.py +++ b/src/psyclone/tests/psyir/nodes/assignment_test.py @@ -369,7 +369,6 @@ def test_next_accesses(fortran_reader): assigns = psyir.walk(Assignment) reaches = assigns[0].next_accesses() - sig = assigns[0].lhs.get_signature_and_indices()[0] assert len(reaches) == 2 assert reaches[0] is assigns[1].rhs.children[0] assert reaches[1] is assigns[1].lhs @@ -387,8 +386,6 @@ def test_next_accesses(fortran_reader): ) assigns = psyir.walk(Assignment) reaches = assigns[0].next_accesses() - a_sig = assigns[0].lhs.get_signature_and_indices()[0] - b_sig = assigns[0].rhs.get_signature_and_indices()[0] assert len(reaches) == 3 assert reaches[0] is assigns[1].rhs.children[0] assert reaches[1] is assigns[1].lhs @@ -406,8 +403,6 @@ def test_next_accesses(fortran_reader): ) assigns = psyir.walk(Assignment) reaches = assigns[0].next_accesses() - a_sig = assigns[0].lhs.get_signature_and_indices()[0] - b_sig = assigns[0].rhs.children[0].get_signature_and_indices()[0] assert len(reaches) == 0 @@ -425,7 +420,6 @@ def test_previous_accesses(fortran_reader): assigns = psyir.walk(Assignment) reaches = assigns[1].previous_accesses() - sig = assigns[0].lhs.get_signature_and_indices()[0] assert len(reaches) == 1 assert reaches[0] is assigns[0].lhs @@ -442,8 +436,6 @@ def test_previous_accesses(fortran_reader): ) assigns = psyir.walk(Assignment) reaches = assigns[2].previous_accesses() - a_sig = assigns[2].lhs.get_signature_and_indices()[0] - b_sig = assigns[2].rhs.get_signature_and_indices()[0] assert len(reaches) == 2 assert reaches[0] is assigns[1].lhs assert reaches[1] is assigns[0].lhs @@ -460,6 +452,4 @@ def test_previous_accesses(fortran_reader): ) assigns = psyir.walk(Assignment) reaches = assigns[1].previous_accesses() - a_sig = assigns[1].lhs.get_signature_and_indices()[0] - b_sig = assigns[1].rhs.children[0].get_signature_and_indices()[0] assert len(reaches) == 0 diff --git a/src/psyclone/tests/psyir/nodes/codeblock_test.py b/src/psyclone/tests/psyir/nodes/codeblock_test.py index 7083a3ffa8..08158fde24 100644 --- a/src/psyclone/tests/psyir/nodes/codeblock_test.py +++ b/src/psyclone/tests/psyir/nodes/codeblock_test.py @@ -371,7 +371,8 @@ def test_codeblock_next_accesses(fortran_reader): def test_codeblock_previous_accesses(fortran_reader): - """Test that the previous_accesses method works correctly for CodeBlocks.""" + """Test that the previous_accesses method works correctly for + CodeBlocks.""" code = """subroutine test() integer :: i, j, k i = 1 diff --git a/src/psyclone/tests/psyir/nodes/loop_test.py b/src/psyclone/tests/psyir/nodes/loop_test.py index d41e1b8821..49936c09df 100644 --- a/src/psyclone/tests/psyir/nodes/loop_test.py +++ b/src/psyclone/tests/psyir/nodes/loop_test.py @@ -583,12 +583,12 @@ def test_loop_variable_previous_accesses(fortran_reader): end subroutine test""" psyir = fortran_reader.psyir_from_source(code) loop = psyir.walk(Loop)[0] - assigns = psyir.walk(Assignment) accesses = loop.previous_accesses() assert len(accesses) == 0 pytest.xfail(reason="#3486 Definition Use Chains don't yet account" - "for loop variables.") + "for loop variables.") # Correct implementation should result in: + # assigns = psyir.walk(Assignment) # assert len(accesses) == 1 # assert accesses[0] is assigns[0].lhs @@ -664,6 +664,7 @@ def test_loop_stop_value_previous_accesses(fortran_reader): assert len(accesses) == 1 assert accesses[0] is assigns[0].lhs + def test_loop_step_value_next_accesses(fortran_reader): """Test that the next_accesses function works correctly for the step condition.""" diff --git a/src/psyclone/tests/psyir/nodes/while_loop_test.py b/src/psyclone/tests/psyir/nodes/while_loop_test.py index 5e972b8c1e..eb7cbf7d10 100644 --- a/src/psyclone/tests/psyir/nodes/while_loop_test.py +++ b/src/psyclone/tests/psyir/nodes/while_loop_test.py @@ -146,7 +146,7 @@ def test_whileloop_condition_next_accesses(fortran_reader): in the WhileLoop condition.''' code = """subroutine test integer :: i, j, k - + do while(i < 2) j = 2 end do @@ -166,7 +166,7 @@ def test_whileloop_condition_previous_accesses(fortran_reader): in the WhileLoop condition.''' code = """subroutine test integer :: i, j, k - + i = k + 1 do while(i < 2) j = 2 @@ -186,7 +186,7 @@ def test_whileloop_body_next_accesses(fortran_reader): WhileLoop body.''' code = """subroutine test integer :: i, j, k - + do while(i < 2) j = 2 end do @@ -206,7 +206,7 @@ def test_whileloop_body_previous_accesses(fortran_reader): WhileLoop body.''' code = """subroutine test integer :: i, j, k - + j = k + 1 do while(i < 2) j = 2 From cc4e97567cee052a24968ac57d513e7d7a682300 Mon Sep 17 00:00:00 2001 From: LonelyCat124 <3043914+LonelyCat124@users.noreply.github.com.> Date: Fri, 21 Aug 2026 13:24:34 +0100 Subject: [PATCH 09/11] Fixes and coverage improvements --- src/psyclone/psyir/nodes/return_stmt.py | 2 +- src/psyclone/tests/dependency_test.py | 8 ++++++++ src/psyclone/tests/psyGen_test.py | 8 ++++++++ src/psyclone/tests/psyir/nodes/directive_test.py | 8 ++++++++ src/psyclone/tests/psyir/nodes/node_test.py | 7 +++++++ .../tests/psyir/nodes/psy_data_node_test.py | 8 ++++++++ .../tests/psyir/nodes/return_stmt_test.py | 16 ++++++++++++++++ src/psyclone/tests/psyir/nodes/routine_test.py | 14 ++++++++++++++ src/psyclone/tests/test_files/dummy_statement.py | 6 ++++-- 9 files changed, 74 insertions(+), 3 deletions(-) diff --git a/src/psyclone/psyir/nodes/return_stmt.py b/src/psyclone/psyir/nodes/return_stmt.py index c85ef0ba04..f5ab075a88 100644 --- a/src/psyclone/psyir/nodes/return_stmt.py +++ b/src/psyclone/psyir/nodes/return_stmt.py @@ -32,4 +32,4 @@ def previous_accesses(self) -> list[Node]: ''' :returns: the previous_accesses of the return statement's child. ''' - return self.children[0].previous_accesses() + return [] diff --git a/src/psyclone/tests/dependency_test.py b/src/psyclone/tests/dependency_test.py index e8a34b99de..828e9eff86 100644 --- a/src/psyclone/tests/dependency_test.py +++ b/src/psyclone/tests/dependency_test.py @@ -588,3 +588,11 @@ def test_lfric_stub_boundary_dofmap(): create_arg_list = KernStubArgList(kernel) create_arg_list.generate(var_accesses=var_accesses) assert "boundary_dofs_op_1: READ" in str(var_accesses) + + +def test_lfrickern_accesses(): + '''Check that next/previous_accesses methods on Kern classes + return an empty list.''' + kernel = LFRicKern() + assert kernel.next_accesses() == [] + assert kernel.previous_accesses() == [] diff --git a/src/psyclone/tests/psyGen_test.py b/src/psyclone/tests/psyGen_test.py index 1b93ce34f2..b9633961ea 100644 --- a/src/psyclone/tests/psyGen_test.py +++ b/src/psyclone/tests/psyGen_test.py @@ -975,6 +975,14 @@ def test_haloexchange_unknown_halo_depth(): assert halo_exchange._halo_depth is None +def test_haloexchange_accesses(): + '''Test that the next/previous_accesses on HaloExchange class returns + an empty list.''' + halo_exchange = HaloExchange(None) + assert halo_exchange.next_accesses() == [] + assert halo_exchange.previous_acccesses() == [] + + def test_args_filter(): '''the args_filter() method is in both Loop() and Arguments() classes with the former method calling the latter. This example tests the diff --git a/src/psyclone/tests/psyir/nodes/directive_test.py b/src/psyclone/tests/psyir/nodes/directive_test.py index 2f6fc275e8..7a3cd00e7a 100644 --- a/src/psyclone/tests/psyir/nodes/directive_test.py +++ b/src/psyclone/tests/psyir/nodes/directive_test.py @@ -170,3 +170,11 @@ def test_standalonedirective_children_validation(): cdir.addchild(schedule) assert ("Item 'Schedule' can't be child 0 of 'StandaloneDirective'. The " "valid format is: 'Clause*'." in str(excinfo.value)) + + +def test_directive_accesses(): + '''Test that the next_accesses and previous_accesses methods + of Directive return an empty list.''' + x = nodes.StandaloneDirective() + assert x.next_accesses() == [] + assert x.previous_accesses() == [] diff --git a/src/psyclone/tests/psyir/nodes/node_test.py b/src/psyclone/tests/psyir/nodes/node_test.py index adedf9f68d..aafe1619e2 100644 --- a/src/psyclone/tests/psyir/nodes/node_test.py +++ b/src/psyclone/tests/psyir/nodes/node_test.py @@ -2104,3 +2104,10 @@ def test_get_last_descendant_node(fortran_reader): ifblock = psyir.children[0].children[0] assigns = psyir.walk(Assignment) assert ifblock.get_last_descendant_node() is assigns[2].rhs.children[1] + + +def test_statement_accesses(): + '''Test the Statement class accesses methods.''' + node = DummyStatement() + assert node.next_accesses() == [] + assert node.previous_accesses() == [] diff --git a/src/psyclone/tests/psyir/nodes/psy_data_node_test.py b/src/psyclone/tests/psyir/nodes/psy_data_node_test.py index 74c188a602..3d5b21d10b 100644 --- a/src/psyclone/tests/psyir/nodes/psy_data_node_test.py +++ b/src/psyclone/tests/psyir/nodes/psy_data_node_test.py @@ -559,3 +559,11 @@ def test_psy_data_node_gocean_inside_of_loop(fortran_writer): r"call psy_data_1 % PostEnd.*") assert re.search(correct_re, code, re.I) is not None + + +def test_psy_data_node_accesses(): + '''Test that the next/previous_accesses methods of PSyDataNode + return empty lists.''' + psy_node = PSyDataNode() + assert psy_node.next_accesses() == [] + assert psy_node.previous_accesses() == [] diff --git a/src/psyclone/tests/psyir/nodes/return_stmt_test.py b/src/psyclone/tests/psyir/nodes/return_stmt_test.py index ec72176c9c..45e3d02eda 100644 --- a/src/psyclone/tests/psyir/nodes/return_stmt_test.py +++ b/src/psyclone/tests/psyir/nodes/return_stmt_test.py @@ -38,3 +38,19 @@ def test_return_children_validation(): return_stmt.addchild(return_stmt1) assert ("Item 'Return' can't be child 0 of 'Return'. Return is a" " LeafNode and doesn't accept children.") in str(excinfo.value) + + +def test_return_stmt_accesses(fortran_reader): + '''Test that the return statement next/previous_accesses return an empty + list.''' + code = """subroutine test + integer :: i + i = 1 + return + i = 2 + end subroutine test""" + psyir = fortran_reader.psyir_from_source(code) + print(psyir.view()) + stmt = psyir.walk(Return)[0] + assert stmt.next_accesses() == [] + assert stmt.previous_accesses() == [] diff --git a/src/psyclone/tests/psyir/nodes/routine_test.py b/src/psyclone/tests/psyir/nodes/routine_test.py index 986a7e3d1b..a8510ac9ce 100644 --- a/src/psyclone/tests/psyir/nodes/routine_test.py +++ b/src/psyclone/tests/psyir/nodes/routine_test.py @@ -691,3 +691,17 @@ def test_outer_scope_accesses_module_data(fortran_reader): rt1.check_outer_scope_accesses(call, "call") assert ("'second' contains accesses to 'vaar' which is declared in the " "callee module scope" in str(err.value)) + + +def test_routine_accesses(fortran_reader): + ''' Test that the next/previous_accesses methods on Routine return + an empty list.''' + code = """subroutine test + integer :: i + i = 3 + i = 4 + end subroutine test""" + psyir = fortran_reader.psyir_from_source(code) + routine = psyir.children[0] + assert routine.next_accesses() == [] + assert routine.previous_accesses() == [] diff --git a/src/psyclone/tests/test_files/dummy_statement.py b/src/psyclone/tests/test_files/dummy_statement.py index 0636741005..c5008b3f40 100644 --- a/src/psyclone/tests/test_files/dummy_statement.py +++ b/src/psyclone/tests/test_files/dummy_statement.py @@ -41,11 +41,13 @@ class DummyStatement(Statement): - def next_accesses(self) -> None: + def next_accesses(self) -> list: '''Empty implementation of the required next_accesses abstractmethod.''' + return super().next_accesses() - def previous_accesses(self) -> None: + def previous_accesses(self) -> list: '''Empty implementation of the required previous_accesses abstractmethod. ''' + return super().previous_accesses() From af187d6cab4a7013e1ea9bdb5a42528b07760d0e Mon Sep 17 00:00:00 2001 From: LonelyCat124 <3043914+LonelyCat124@users.noreply.github.com.> Date: Fri, 21 Aug 2026 14:35:41 +0100 Subject: [PATCH 10/11] typo --- src/psyclone/tests/psyGen_test.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/psyclone/tests/psyGen_test.py b/src/psyclone/tests/psyGen_test.py index b9633961ea..b244dc1dbc 100644 --- a/src/psyclone/tests/psyGen_test.py +++ b/src/psyclone/tests/psyGen_test.py @@ -980,7 +980,7 @@ def test_haloexchange_accesses(): an empty list.''' halo_exchange = HaloExchange(None) assert halo_exchange.next_accesses() == [] - assert halo_exchange.previous_acccesses() == [] + assert halo_exchange.previous_accesses() == [] def test_args_filter(): From bf51be667f97d4065ca46bf62544c9cd900cd054 Mon Sep 17 00:00:00 2001 From: LonelyCat124 <3043914+LonelyCat124@users.noreply.github.com.> Date: Fri, 21 Aug 2026 15:00:19 +0100 Subject: [PATCH 11/11] Final coverage --- .../domain/common/psylayer/global_reduction_test.py | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/src/psyclone/tests/domain/common/psylayer/global_reduction_test.py b/src/psyclone/tests/domain/common/psylayer/global_reduction_test.py index 8a85e669eb..d3e6225ee6 100644 --- a/src/psyclone/tests/domain/common/psylayer/global_reduction_test.py +++ b/src/psyclone/tests/domain/common/psylayer/global_reduction_test.py @@ -129,3 +129,14 @@ def test_globalreduction_reference_accesses(): "GlobalReduction is held in a bespoke '_scalar' " "property.") assert list(vam) == ["FIXME"] + + +def test_globalreduction_accesses(): + '''Test the next/previous_accesses methods return an empty list + for a GlobalReduction.''' + _, invoke = get_invoke("15.14.3_sum_setval_field_builtin.f90", + api="lfric", dist_mem=True, idx=0) + schedule = invoke.schedule + global_sum = schedule.children[2] + assert global_sum.next_accesses() == [] + assert global_sum.previous_accesses() == []