Repository: stfc/PSyclone
Pull request #2753 (2024-10-23 15:13:29)

Add `is_character` method for `DataNode`
Partially addresses #2717.

This PR merges one of two functions from the `types` module of [PSyACC](https://github.com/MetOffice/psyacc) into PSyclone.


Patch:
diff --git a/changelog b/changelog
index 21666b17d..083a31950 100644
--- a/changelog
+++ b/changelog
@@ -252,6 +252,8 @@
 
 	87) PR #2752 towards #2717. Adds Assignment.is_literal_assignment property.
 
+	88) PR #2753 towards #2717. Adds DataNode.is_character method.
+
 release 2.5.0 14th of February 2024
 
 	1) PR #2199 for #2189. Fix bugs with missing maps in enter data
diff --git a/src/psyclone/psyir/nodes/assignment.py b/src/psyclone/psyir/nodes/assignment.py
index 22c843b9f..8095204e0 100644
--- a/src/psyclone/psyir/nodes/assignment.py
+++ b/src/psyclone/psyir/nodes/assignment.py
@@ -213,9 +213,9 @@ class Assignment(Statement):
     @property
     def is_array_assignment(self):
         '''
-        returns: True if the lhs of the assignment is an array access with at \
+        :returns: True if the lhs of the assignment is an array access with at
             least one of its dimensions being a range and False otherwise.
-        rtype: bool
+        :rtype: bool
 
         '''
         # It's not sufficient simply to check for a Range node as that may be
@@ -249,9 +249,9 @@ class Assignment(Statement):
     @property
     def is_literal_assignment(self):
         '''
-        returns: True if the rhs of the assignment is a literal value and False
-            otherwise.
-        rtype: bool
+        :returns: True if the rhs of the assignment is a literal value and
+            False otherwise.
+        :rtype: bool
 
         '''
         return isinstance(self.rhs, Literal)
diff --git a/src/psyclone/psyir/nodes/datanode.py b/src/psyclone/psyir/nodes/datanode.py
index 153baade7..083a8e3c7 100644
--- a/src/psyclone/psyir/nodes/datanode.py
+++ b/src/psyclone/psyir/nodes/datanode.py
@@ -32,12 +32,13 @@
 # POSSIBILITY OF SUCH DAMAGE.
 # -----------------------------------------------------------------------------
 # Authors: A. R. Porter and S. Siso, STFC Daresbury Lab
+# Modified J. G. Wallwork, University of Cambridge
 # -----------------------------------------------------------------------------
 
 ''' This module contains the DataNode abstract node implementation.'''
 
 from psyclone.psyir.nodes.node import Node
-from psyclone.psyir.symbols.datatypes import UnresolvedType
+from psyclone.psyir.symbols.datatypes import ScalarType, UnresolvedType
 
 
 class DataNode(Node):
@@ -55,3 +56,27 @@ class DataNode(Node):
         :rtype: :py:class:`psyclone.psyir.symbols.UnresolvedType`
         '''
         return UnresolvedType()
+
+    def is_character(self, unknown_as=None):
+        '''
+        :param unknown_as: Determines behaviour in the case where it cannot be
+            determined whether the DataNode is a character. Defaults to None,
+            in which case an exception is raised.
+        :type unknown_as: Optional[bool]
+
+        :returns: True if this DataNode is a character, otherwise False.
+        :rtype: bool
+
+        :raises ValueError: if the intrinsic type cannot be determined.
+
+        '''
+        if not hasattr(self.datatype, "intrinsic"):
+            if unknown_as is None:
+                raise ValueError(
+                    "is_character could not resolve whether the expression"
+                    f" '{self.debug_string()}' operates on characters."
+                )
+            return unknown_as
+        return (
+            self.datatype.intrinsic == ScalarType.Intrinsic.CHARACTER
+        )


Test patch:
diff --git a/src/psyclone/tests/psyir/nodes/datanode_test.py b/src/psyclone/tests/psyir/nodes/datanode_test.py
index 421cac772..cde45d53e 100644
--- a/src/psyclone/tests/psyir/nodes/datanode_test.py
+++ b/src/psyclone/tests/psyir/nodes/datanode_test.py
@@ -32,13 +32,17 @@
 # POSSIBILITY OF SUCH DAMAGE.
 # -----------------------------------------------------------------------------
 # Author: A. R. Porter, STFC Daresbury Lab
+# Modified J. G. Wallwork, University of Cambridge
 # -----------------------------------------------------------------------------
 
 '''Performs pytest tests on the PSyIR DataNode.
 
 '''
-from psyclone.psyir.nodes import DataNode
-from psyclone.psyir.symbols import UnresolvedType
+import pytest
+
+from psyclone.psyir.nodes import DataNode, Reference, BinaryOperation
+from psyclone.psyir.symbols import (CHARACTER_TYPE, DataSymbol, UnresolvedType,
+                                    INTEGER_SINGLE_TYPE, REAL_TYPE)
 
 
 def test_datanode_datatype():
@@ -48,3 +52,30 @@ def test_datanode_datatype():
     '''
     dnode = DataNode()
     assert isinstance(dnode.datatype, UnresolvedType)
+
+
+def test_datanode_is_character():
+    '''Test that character expressions are marked correctly.
+    '''
+    reference = Reference(DataSymbol("char", CHARACTER_TYPE))
+    assert reference.is_character()
+
+    reference = Reference(DataSymbol("int", INTEGER_SINGLE_TYPE))
+    reference2 = Reference(DataSymbol("int2", INTEGER_SINGLE_TYPE))
+    bop = BinaryOperation.create(BinaryOperation.Operator.MUL,
+                                 reference, reference2)
+    assert not reference.is_character()
+    assert not reference2.is_character()
+    assert not bop.is_character()
+
+    reference = Reference(DataSymbol("real", REAL_TYPE))
+    assert not reference.is_character()
+
+    reference = Reference(DataSymbol("unknown", UnresolvedType()))
+    with pytest.raises(ValueError) as excinfo:
+        _ = reference.is_character()
+    assert ("is_character could not resolve whether the expression 'unknown'"
+            " operates on characters." in str(excinfo.value))
+    reference = Reference(DataSymbol("unknown", UnresolvedType()))
+    assert not reference.is_character(unknown_as=False)
+    assert reference.is_character(unknown_as=True)