Repository: stfc/PSyclone (python)

Issue:
`Reference2ArrayRangeTrans` introduces erroneous colon notation in `ALLOCATED` intrinsic
Consider the following example.

```fortran
module my_module
  implicit none
  public

  contains
  subroutine my_subroutine()
    integer :: my_dim
    integer, allocatable, dimension(:,:) :: my_array

    my_dim = 10

    if (.NOT.ALLOCATED(my_array)) then
      ALLOCATE(my_array(1:my_dim,1:my_dim))
    end if

  end subroutine my_subroutine

end module my_module
```

If I apply the transformation script

```python
from psyclone.psyir import nodes
from psyclone.psyir.transformations import Reference2ArrayRangeTrans
from psyclone.psyir import symbols
from psyclone.transformations import TransformationError


def trans(psy):
    for invoke in psy.invokes.invoke_list:
        for reference in invoke.schedule.walk(nodes.Reference, stop_type=nodes.Reference):
            if isinstance(reference.symbol, symbols.DataSymbol):
                try:
                    Reference2ArrayRangeTrans().apply(reference)
                except TransformationError:
                    pass
    return psy
```

then I get the output

```fortran
module my_module
  implicit none
  public

  contains
  subroutine my_subroutine()
    integer :: my_dim
    integer, allocatable, dimension(:,:) :: my_array

    my_dim = 10
    if (.NOT.ALLOCATED(my_array(:,:))) then
      ALLOCATE(my_array(1:my_dim,1:my_dim))
    end if

  end subroutine my_subroutine

end module my_module
```

The compiler I'm using (NVHPC) doesn't like the colon notation being used inside the `ALLOCATED` intrinsic call.

Pull request description:
(Closes #2351) Fix ref2arrayrange allocate bug

Patch:
diff --git a/changelog b/changelog
index 2e0f2e543..6dd37eba1 100644
--- a/changelog
+++ b/changelog
@@ -37,6 +37,8 @@
 	13) PR #2361 for #2360. Fix LFRic Loop.independent_iterations with domain
 	information and allow sequential directives on loops with dependencies.
 
+	14) PR #2365 for #2351. Fix ref2arrayrange allocated bug.
+
 release 2.4.0 29th of September 2023
 
 	1) PR #1758 for #1741. Splits the PSyData read functionality into a
diff --git a/src/psyclone/psyir/nodes/intrinsic_call.py b/src/psyclone/psyir/nodes/intrinsic_call.py
index 3a2c369c1..4a6df9b49 100644
--- a/src/psyclone/psyir/nodes/intrinsic_call.py
+++ b/src/psyclone/psyir/nodes/intrinsic_call.py
@@ -929,7 +929,7 @@ class IntrinsicCall(Call):
             for child in self._children:
                 child.reference_accesses(var_accesses)
 
-    # TODO #2102: Maybe the two properties below can be removed if intrinsic
+    # TODO #2102: Maybe the three properties below can be removed if intrinsic
     # is a symbol, as they would act as the super() implementation.
     @property
     def is_elemental(self):
@@ -952,6 +952,16 @@ class IntrinsicCall(Call):
         '''
         return self.intrinsic.is_pure
 
+    @property
+    def is_inquiry(self):
+        '''
+        :returns: whether the routine being called is a query function (i.e.
+            returns information about its argument rather than accessing any
+            data referenced by the argument). If this information is not known
+            then it returns None.
+        :rtype: NoneType | bool
+        '''
+        return self.intrinsic.is_inquiry
 
 # TODO #658 this can be removed once we have support for determining the
 # type of a PSyIR expression.
diff --git a/src/psyclone/psyir/transformations/reference2arrayrange_trans.py b/src/psyclone/psyir/transformations/reference2arrayrange_trans.py
index e6599f0ca..372ee4686 100644
--- a/src/psyclone/psyir/transformations/reference2arrayrange_trans.py
+++ b/src/psyclone/psyir/transformations/reference2arrayrange_trans.py
@@ -133,11 +133,11 @@ class Reference2ArrayRangeTrans(Transformation):
         :param options: a dict with options for transformations.
         :type options: Optional[Dict[str, Any]]
 
-        :raises TransformationError: if the node is not a Reference \
-            node or the Reference node not does not reference an array \
+        :raises TransformationError: if the node is not a Reference
+            node or the Reference node not does not reference an array
             symbol.
-        :raises TransformationError: if the Reference node is \
-            within an LBOUND, UBOUND, SIZE or DEALLOCATE intrinsic.
+        :raises TransformationError: if the Reference node is within an
+            inquiry or DEALLOCATE intrinsic.
 
         '''
         # TODO issue #1858. Add support for structures containing arrays.
@@ -150,19 +150,16 @@ class Reference2ArrayRangeTrans(Transformation):
             raise TransformationError(
                 f"The supplied node should be a Reference to a symbol "
                 f"that is an array, but '{node.symbol.name}' is not.")
-        if (isinstance(node.parent, IntrinsicCall) and
-                node.parent.intrinsic in [
-                    IntrinsicCall.Intrinsic.LBOUND,
-                    IntrinsicCall.Intrinsic.UBOUND,
-                    IntrinsicCall.Intrinsic.SIZE]):
+        if isinstance(node.parent, IntrinsicCall) and node.parent.is_inquiry:
             raise TransformationError(
-                "References to arrays within LBOUND, UBOUND or SIZE "
-                "intrinsics should not be transformed.")
+                f"References to arrays passed as arguments to intrinsic "
+                f"enquiry routine '{node.parent.routine.name}' should not be "
+                f"transformed.")
         if (isinstance(node.parent, IntrinsicCall) and
                 node.parent.routine.name in ["DEALLOCATE"]):
             raise TransformationError(LazyString(
-                lambda: f"References to arrays within "
-                f"{node.parent.routine.name} intrinsics should not be "
+                lambda: f"References to arrays passed to "
+                f"'{node.parent.routine.name}' intrinsics should not be "
                 f"transformed, but found:\n {node.parent.debug_string()}"))
 
     def apply(self, node, options=None):


Test patch:
diff --git a/src/psyclone/tests/psyir/nodes/intrinsic_call_test.py b/src/psyclone/tests/psyir/nodes/intrinsic_call_test.py
index bc1d0a44e..28a6184f5 100644
--- a/src/psyclone/tests/psyir/nodes/intrinsic_call_test.py
+++ b/src/psyclone/tests/psyir/nodes/intrinsic_call_test.py
@@ -46,12 +46,19 @@ import pytest
 
 from psyclone.core import VariablesAccessInfo
 from psyclone.psyir.nodes import (
-    ArrayReference, Literal, IntrinsicCall, Reference, Schedule, Assignment)
+    ArrayReference, Literal, Reference, Schedule, Assignment)
+from psyclone.psyir.nodes.intrinsic_call import IntrinsicCall, IAttr
 from psyclone.psyir.symbols import (
     ArrayType, DataSymbol, INTEGER_TYPE, IntrinsicSymbol, REAL_TYPE,
     BOOLEAN_TYPE, CHARACTER_TYPE)
 
 
+def test_intrinsic_enum():
+    '''Basic test for the IntrinsicCall.Intrinsic enum.'''
+    assert isinstance(IntrinsicCall.Intrinsic.MINVAL, IAttr)
+    assert hash(IntrinsicCall.Intrinsic.MINVAL) == hash("MINVAL")
+
+
 def test_intrinsiccall_constructor():
     '''Tests that the class' constructor and its parent are called
     correctly.
@@ -99,6 +106,14 @@ def test_intrinsiccall_is_pure():
     assert intrinsic.is_pure is False
 
 
+def test_intrinsiccall_is_inquiry():
+    '''Test that the is_inquiry() method works as expected.'''
+    intrinsic = IntrinsicCall(IntrinsicCall.Intrinsic.SUM)
+    assert intrinsic.is_inquiry is False
+    intrinsic = IntrinsicCall(IntrinsicCall.Intrinsic.ALLOCATED)
+    assert intrinsic.is_inquiry is True
+
+
 @pytest.mark.parametrize("intrinsic, result", [
                 (IntrinsicCall.Intrinsic.ABS, True),
                 (IntrinsicCall.Intrinsic.MIN, True),
diff --git a/src/psyclone/tests/psyir/transformations/reference2arrayrange_trans_test.py b/src/psyclone/tests/psyir/transformations/reference2arrayrange_trans_test.py
index 155224122..19de804ef 100644
--- a/src/psyclone/tests/psyir/transformations/reference2arrayrange_trans_test.py
+++ b/src/psyclone/tests/psyir/transformations/reference2arrayrange_trans_test.py
@@ -32,7 +32,7 @@
 # POSSIBILITY OF SUCH DAMAGE.
 # -----------------------------------------------------------------------------
 # Author: R. W. Ford, STFC Daresbury Lab
-# Modified: S. Siso, STFC Daresbury Lab
+# Modified: A. R. Porter and S. Siso, STFC Daresbury Lab
 
 '''Module containing tests for the Reference2ArrayRangeLoopTrans
 transformation.'''
@@ -40,10 +40,10 @@ transformation.'''
 import pytest
 
 from psyclone.psyGen import Transformation
-from psyclone.psyir.nodes import Reference, Literal, IntrinsicCall
+from psyclone.psyir.nodes import IfBlock, IntrinsicCall, Literal, Reference
 from psyclone.psyir.symbols import DataSymbol, REAL_TYPE
-from psyclone.psyir.transformations import Reference2ArrayRangeTrans, \
-    TransformationError
+from psyclone.psyir.transformations import (Reference2ArrayRangeTrans,
+                                            TransformationError)
 
 CODE = (
     "program test\n"
@@ -245,18 +245,22 @@ def test_validate_range(fortran_reader):
 
 def test_validate_query(fortran_reader):
     '''Test that the validate method raises an exception if the Reference
-    is within one of the LBOUND, UBOUND or SIZE query functions.
+    is within one of the ALLOCATED, LBOUND, UBOUND or SIZE query functions.
 
     '''
     code = (
         "program test\n"
         "  real :: a(10),b(10)\n"
         "  integer :: i,c\n"
+        "  real, dimension(:), allocatable :: igor\n"
         "  do i = lbound(a,1), ubound(a,1)\n"
         "     a(i) = 0.0\n"
         "  end do\n"
         "  b(:) = 0.0\n"
         "  c = size(b,1)\n"
+        "  if(allocated(igor))then\n"
+        "     igor = 4.0\n"
+        "  end if\n"
         "end program test\n")
     psyir = fortran_reader.psyir_from_source(code)
     trans = Reference2ArrayRangeTrans()
@@ -264,12 +268,13 @@ def test_validate_query(fortran_reader):
     # Check the references to 'a' in lbound and ubound do not get modified
     loop = psyir.children[0].children[0]
     locations = [loop.start_expr, loop.stop_expr]
-    for location in locations:
+    for text, location in zip(["LBOUND", "UBOUND"], locations):
         for reference in location.walk(Reference):
             with pytest.raises(TransformationError) as info:
                 trans.validate(reference)
-            assert ("References to arrays within LBOUND, UBOUND or SIZE "
-                    "intrinsics should not be transformed." in str(info.value))
+            assert (f"References to arrays passed as arguments to intrinsic "
+                    f"enquiry routine '{text}' should not be transformed."
+                    in str(info.value))
 
     # Check the references to 'b' in the hidden lbound and ubound
     # intrinsics within 'b(:)' do not get modified.
@@ -280,16 +285,24 @@ def test_validate_query(fortran_reader):
         if type(reference) is Reference:
             with pytest.raises(TransformationError) as info:
                 trans.validate(reference)
-            assert ("References to arrays within LBOUND, UBOUND or SIZE "
-                    "intrinsics should not be transformed." in str(info.value))
+            assert ("References to arrays passed as arguments to intrinsic "
+                    "enquiry routine '" in str(info.value))
 
     # Check the reference to 'b' in the size intrinsics does not get modified
     assignment = psyir.children[0].children[2]
     reference = assignment.children[1].children[0]
     with pytest.raises(TransformationError) as info:
         trans.validate(reference)
-    assert ("References to arrays within LBOUND, UBOUND or SIZE "
-            "intrinsics should not be transformed." in str(info.value))
+    assert ("References to arrays passed as arguments to intrinsic enquiry "
+            "routine 'SIZE' should not be transformed." in str(info.value))
+
+    ifblock = psyir.walk(IfBlock)[0]
+    allocd = ifblock.condition
+    assert isinstance(allocd, IntrinsicCall)
+    with pytest.raises(TransformationError) as info:
+        trans.validate(allocd.children[0])
+    assert ("References to arrays passed as arguments to intrinsic enquiry "
+            "routine 'ALLOCATED' should not be transformed" in str(info.value))
 
 
 def test_validate_structure(fortran_reader):
@@ -328,8 +341,8 @@ def test_validate_deallocate(fortran_reader):
     trans = Reference2ArrayRangeTrans()
     with pytest.raises(TransformationError) as info:
         trans.validate(reference)
-    assert ("References to arrays within DEALLOCATE intrinsics should not be "
-            "transformed, but found:\n DEALLOCATE(a)" in str(info.value))
+    assert ("References to arrays passed to 'DEALLOCATE' intrinsics should not"
+            " be transformed, but found:\n DEALLOCATE(a)" in str(info.value))
 
 
 def test_apply_validate():