Repository: stfc/PSyclone (python)

Issue:
Add `get_siblings` function/method
It'd be useful to have a way to get all 'siblings' of a node, i.e., all of its parents' children.

Pull request description:
Account for `siblings` special case
Closes #2369.

@sergisiso pointed out a special case I missed in #2378 - the case of a root node. Recommended fix applied.

Patch:
diff --git a/changelog b/changelog
index b9f2042bb..7fce7ad67 100644
--- a/changelog
+++ b/changelog
@@ -45,8 +45,7 @@
 	16) PR #2357 for #2353. Adds the ACC-loops NEMO transformation script
 	and includes its use in the integration testing.
 
-	17 PR #2378 for #2369. Adds sibling property to node to help tree
-	traversal.
+	17) PR #2380 and #2378 for #2369. Add PSyIR node.sibilings property.
 
 release 2.4.0 29th of September 2023
 
diff --git a/src/psyclone/psyir/nodes/node.py b/src/psyclone/psyir/nodes/node.py
index e13bc0924..4610c9764 100644
--- a/src/psyclone/psyir/nodes/node.py
+++ b/src/psyclone/psyir/nodes/node.py
@@ -963,7 +963,8 @@ class Node():
         :returns: list of sibling nodes, including self.
         :rtype: List[:py:class:`psyclone.psyir.nodes.Node`]
         '''
-        return self.parent.children
+        parent = self.parent
+        return [self] if parent is None else parent.children
 
     @property
     def has_constructor_parent(self):


Test patch:
diff --git a/src/psyclone/tests/psyGen_test.py b/src/psyclone/tests/psyGen_test.py
index 9fd9a89d0..958375658 100644
--- a/src/psyclone/tests/psyGen_test.py
+++ b/src/psyclone/tests/psyGen_test.py
@@ -2226,24 +2226,3 @@ def test_walk():
 
     binary_op_list = invoke.schedule.walk(BinaryOperation, Kern)
     assert not binary_op_list
-
-
-def test_siblings():
-    '''Tests the siblings functionality.'''
-
-    # This function contains an integer assignment followed by two loops
-    _, invoke = get_invoke("explicit_do_two_loops.f90", "nemo", 0)
-
-    # The initial integer assignment has two other siblings, whereas the
-    # assignments at the deepest levels of the loops have no other siblings
-    for assign in invoke.schedule.walk(Assignment):
-        siblings = assign.siblings
-        assert assign in siblings
-        assert len(siblings) == (1 if assign.ancestor(Loop) else 3)
-
-    # The two outer-most loops have each other as siblings, plus the initial
-    # integer assignment, whereas the inner loops have no other siblings
-    for loop in invoke.schedule.walk(Loop):
-        siblings = loop.siblings
-        assert loop in siblings
-        assert len(siblings) == (1 if loop.ancestor(Loop) else 3)
diff --git a/src/psyclone/tests/psyir/nodes/node_test.py b/src/psyclone/tests/psyir/nodes/node_test.py
index 68de62b38..0a7e8a2e3 100644
--- a/src/psyclone/tests/psyir/nodes/node_test.py
+++ b/src/psyclone/tests/psyir/nodes/node_test.py
@@ -32,7 +32,7 @@
 # POSSIBILITY OF SUCH DAMAGE.
 # -----------------------------------------------------------------------------
 # Authors R. W. Ford, A. R. Porter and S. Siso, STFC Daresbury Lab
-#         I. Kavcic, Met Office
+#         I. Kavcic and J. G. Wallwork, Met Office
 #         J. Henrichs, Bureau of Meteorology
 # -----------------------------------------------------------------------------
 
@@ -1580,3 +1580,49 @@ def test_path_from(fortran_reader):
         assigns[0].path_from(loops[0])
     assert ("Attempted to find path_from a non-ancestor 'Loop' "
             "node." in str(excinfo.value))
+
+
+def test_siblings(fortran_reader):
+    '''Tests the siblings method of the Node class.'''
+
+    code = '''subroutine test_siblings()
+    integer :: i, j, k
+    integer, dimension(2,2,2) :: arr
+
+    arr(1,1,1) = 0
+    do k = 1, 2
+       do j = 1, 2
+          do i = 1, 2
+             arr(i,j,k) = i*j*k
+          end do
+       end do
+    end do
+    do k = 1, 2
+       do j = 1, 2
+          do i = 1, 2
+             arr(i,j,k) = i*j*k
+          end do
+       end do
+    end do
+    end subroutine'''
+
+    psyir = fortran_reader.psyir_from_source(code)
+
+    # The initial assignment has two other siblings, whereas the assignments at
+    # the deepest levels of the loops have no other siblings
+    for assign in psyir.walk(Assignment):
+        siblings = assign.siblings
+        assert assign in siblings
+        assert len(siblings) == (1 if assign.ancestor(Loop) else 3)
+
+    # The two outer-most loops have each other as siblings, plus the initial
+    # integer assignment, whereas the inner loops have no other siblings
+    for loop in psyir.walk(Loop):
+        siblings = loop.siblings
+        assert loop in siblings
+        assert len(siblings) == (1 if loop.ancestor(Loop) else 3)
+
+    # Special case of a root node
+    root_siblings = psyir.siblings
+    assert len(root_siblings) == 1
+    assert root_siblings[0] is psyir