Repository: stfc/PSyclone
Pull request #2366 (2023-10-16 15:51:33)

(Towards #342) tighten-up ACCRoutineTrans.validate() and test


Patch:
diff --git a/changelog b/changelog
index 2ee32a3fd..c4919006c 100644
--- a/changelog
+++ b/changelog
@@ -57,6 +57,9 @@
 	21) PR #2362 for #2243 and #2312. Add support for Fortran save statements.
 
 	22) PR #2379 for #2370. Adds a Node.get_sibling_lists() method.
+	
+	23) PR #2366 towards #342. Improve symbols and tighten-up ACCRoutineTrans
+	validation.
 
 release 2.4.0 29th of September 2023
 
diff --git a/examples/gocean/eg2/acc_transform.py b/examples/gocean/eg2/acc_transform.py
index 3ceffd6ad..ce8c5a7e9 100644
--- a/examples/gocean/eg2/acc_transform.py
+++ b/examples/gocean/eg2/acc_transform.py
@@ -1,7 +1,7 @@
 # -----------------------------------------------------------------------------
 # BSD 3-Clause License
 #
-# Copyright (c) 2018-2022, Science and Technology Facilities Council.
+# Copyright (c) 2018-2023, Science and Technology Facilities Council.
 # All rights reserved.
 #
 # Redistribution and use in source and binary forms, with or without
@@ -38,9 +38,8 @@
 function via the -s option. Transforms all kernels in the invoke
 to have them compiled for an OpenACC accelerator. '''
 
-from __future__ import print_function
-from psyclone.transformations import ACCParallelTrans, \
-    ACCEnterDataTrans, ACCLoopTrans, ACCRoutineTrans
+from psyclone.transformations import (
+    ACCParallelTrans, ACCEnterDataTrans, ACCLoopTrans, ACCRoutineTrans)
 from psyclone.psyir.nodes import Loop
 
 
diff --git a/examples/gocean/eg4/acc_transform.py b/examples/gocean/eg4/acc_transform.py
index 2a0583834..e1586a298 100644
--- a/examples/gocean/eg4/acc_transform.py
+++ b/examples/gocean/eg4/acc_transform.py
@@ -1,7 +1,7 @@
 # -----------------------------------------------------------------------------
 # BSD 3-Clause License
 #
-# Copyright (c) 2018-2022, Science and Technology Facilities Council.
+# Copyright (c) 2018-2023, Science and Technology Facilities Council.
 # All rights reserved.
 #
 # Redistribution and use in source and binary forms, with or without
@@ -39,9 +39,9 @@ function via the -s option. Transforms all kernels in the invoke
 to have them compiled for an OpenACC accelerator. '''
 
 from psyclone.domain.common.transformations import KernelModuleInlineTrans
-from psyclone.transformations import ACCParallelTrans, \
-    ACCEnterDataTrans, ACCLoopTrans, ACCRoutineTrans, \
-    KernelImportsToArguments
+from psyclone.transformations import (
+    ACCParallelTrans, ACCEnterDataTrans, ACCLoopTrans, ACCRoutineTrans,
+    KernelImportsToArguments)
 from psyclone.psyir.nodes import Loop
 
 
diff --git a/examples/nemo/scripts/acc_kernels_trans.py b/examples/nemo/scripts/acc_kernels_trans.py
index fee23e91f..9e0bcf1e0 100755
--- a/examples/nemo/scripts/acc_kernels_trans.py
+++ b/examples/nemo/scripts/acc_kernels_trans.py
@@ -121,10 +121,12 @@ class ExcludeSettings():
     '''
     Class to hold settings on what to exclude from OpenACC KERNELS regions.
 
-    :param Optional[dict] settings: map of settings to override.
+    :param Optional[dict[str, bool]] settings: map of settings to override.
 
     '''
-    def __init__(self, settings={}):
+    def __init__(self, settings=None):
+        if settings is None:
+            settings = {}
         # Whether we exclude IFs where the logical expression is not a
         # comparison operation.
         self.ifs_scalars = settings.get("ifs_scalars", False)
@@ -413,7 +415,9 @@ def trans(psy):
             if not invoke.schedule.walk(Loop):
                 calls = invoke.schedule.walk(Call)
                 if all(call.is_available_on_device() for call in calls):
-                    ACC_ROUTINE_TRANS.apply(sched)
+                    # SIGN_ARRAY_1D has a CodeBlock because of a WHERE without
+                    # array notation. (TODO #717)
+                    ACC_ROUTINE_TRANS.apply(sched, options={"force": True})
                     continue
 
         # Attempt to add OpenACC directives unless we are ignoring this routine
diff --git a/examples/nemo/scripts/acc_loops_trans.py b/examples/nemo/scripts/acc_loops_trans.py
index 48b0ea68e..5a686f9d2 100755
--- a/examples/nemo/scripts/acc_loops_trans.py
+++ b/examples/nemo/scripts/acc_loops_trans.py
@@ -114,7 +114,10 @@ def trans(psy):
             if not invoke.schedule.walk(Loop):
                 calls = invoke.schedule.walk(Call)
                 if all(call.is_available_on_device() for call in calls):
-                    ACCRoutineTrans().apply(invoke.schedule)
+                    # SIGN_ARRAY_1D has a CodeBlock because of a WHERE without
+                    # array notation. (TODO #717)
+                    ACCRoutineTrans().apply(invoke.schedule,
+                                            options={"force": True})
                     continue
 
         insert_explicit_loop_parallelism(
diff --git a/src/psyclone/domain/lfric/arg_ordering.py b/src/psyclone/domain/lfric/arg_ordering.py
index 019e3262a..28b1ec89a 100644
--- a/src/psyclone/domain/lfric/arg_ordering.py
+++ b/src/psyclone/domain/lfric/arg_ordering.py
@@ -82,6 +82,9 @@ class ArgOrdering:
         # as this creates a double reference (with
         # self._kernel.ancestor(InvokeSchedule)._symbol_table) to that table
         # and might go stale e.g. if the tree is copied.
+        # In fact, using the same symbol table as the Invoke is a bit odd as
+        # we are describing kernel *arguments* here so they will have a
+        # different interface to those in the Schedule of the invoke.
         if invoke_sched:
             self._symtab = invoke_sched.symbol_table
         else:
diff --git a/src/psyclone/domain/lfric/kernel_interface.py b/src/psyclone/domain/lfric/kernel_interface.py
index 1da4755c0..19e1e5c6c 100644
--- a/src/psyclone/domain/lfric/kernel_interface.py
+++ b/src/psyclone/domain/lfric/kernel_interface.py
@@ -42,11 +42,12 @@ kernel based on the kernel metadata.
 '''
 from psyclone.core import AccessType
 from psyclone.domain.lfric import ArgOrdering, LFRicConstants
+from psyclone.domain.lfric.lfric_symbol_table import LFRicSymbolTable
 from psyclone.domain.lfric.lfric_types import LFRicTypes
 from psyclone.errors import InternalError
 from psyclone.psyir.frontend.fparser2 import INTENT_MAPPING
 from psyclone.psyir.nodes import Reference
-from psyclone.psyir.symbols import SymbolTable, ArgumentInterface
+from psyclone.psyir.symbols import ArgumentInterface
 
 
 # pylint: disable=too-many-public-methods, no-member
@@ -101,8 +102,15 @@ class KernelInterface(ArgOrdering):
 
     def __init__(self, kern):
         super().__init__(kern)
-        self._symbol_table = SymbolTable()
-        self._arglist = []
+        # We need a brand new symbol table, not a reference to the one for
+        # the Schedule containing this kernel call (which is what the
+        # ArgOrdering constructor defaults to). This is so that we can
+        # specify the correct interface for those symbols passed
+        # as arguments.
+        # TODO #1934 - we should not keep a reference to a SymbolTable here.
+        # We should just be using the one associated with the kernel routine
+        # but currently a Kern is not a Call to a Routine.
+        self._symtab = LFRicSymbolTable()
 
     def generate(self, var_accesses=None):
         '''Call the generate base class then add the argument list as it can't
@@ -119,7 +127,7 @@ class KernelInterface(ArgOrdering):
         # the end after incrementally adding symbols to the _args
         # list, as it is not possible to incrementally add symbols to
         # the symbol table argument list.
-        self._symbol_table.specify_argument_list(self._arglist)
+        self._symtab.specify_argument_list(self._arglist)
         # While data dependence analysis does not use the symbol
         # table, see #845, we have to provide variable information
         # separately. This is done by using the base class append()
@@ -132,7 +140,7 @@ class KernelInterface(ArgOrdering):
                    ArgumentInterface.Access.READWRITE: AccessType.READWRITE,
                    ArgumentInterface.Access.WRITE: AccessType.WRITE}
         len_arglist = len(self._arglist)
-        for symbol in self._symbol_table.symbols:
+        for symbol in self._symtab.symbols:
             self.append(symbol.name, var_accesses,
                         mode=mapping[symbol.interface.access])
         self._arglist = self._arglist[:len_arglist]
@@ -147,7 +155,7 @@ class KernelInterface(ArgOrdering):
             py:class:`psyclone.core.VariablesAccessInfo`
 
         '''
-        symbol = self._symbol_table.find_or_create_tag(
+        symbol = self._symtab.find_or_create_tag(
             "cell", symbol_type=LFRicTypes("CellPositionDataSymbol"),
             interface=self._read_access)
         self._arglist.append(symbol)
@@ -162,7 +170,7 @@ class KernelInterface(ArgOrdering):
             py:class:`psyclone.core.VariablesAccessInfo`
 
         '''
-        symbol = self._symbol_table.find_or_create_tag(
+        symbol = self._symtab.find_or_create_tag(
             "nlayers", symbol_type=LFRicTypes("MeshHeightDataSymbol"),
             interface=self._read_access)
         self._arglist.append(symbol)
@@ -225,7 +233,7 @@ class KernelInterface(ArgOrdering):
 
         '''
         fs_name = argvect.function_space.orig_name
-        undf_symbol = self._symbol_table.find_or_create_tag(
+        undf_symbol = self._symtab.find_or_create_tag(
             f"undf_{fs_name}", fs=fs_name,
             symbol_type=LFRicTypes("NumberOfUniqueDofsDataSymbol"),
             interface=self._read_access)
@@ -240,7 +248,7 @@ class KernelInterface(ArgOrdering):
             raise NotImplementedError(message) from info
         for idx in range(argvect.vector_size):
             tag = f"{argvect.name}_v{idx}"
-            field_data_symbol = self._symbol_table.find_or_create_tag(
+            field_data_symbol = self._symtab.find_or_create_tag(
                 tag, symbol_type=field_class, dims=[Reference(undf_symbol)],
                 fs=fs_name, interface=interface)
 
@@ -267,7 +275,7 @@ class KernelInterface(ArgOrdering):
         '''
         fs_name = arg.function_space.orig_name
 
-        undf_symbol = self._symbol_table.find_or_create_tag(
+        undf_symbol = self._symtab.find_or_create_tag(
             f"undf_{fs_name}",
             symbol_type=LFRicTypes("NumberOfUniqueDofsDataSymbol"),
             fs=fs_name, interface=self._read_access)
@@ -279,7 +287,7 @@ class KernelInterface(ArgOrdering):
             message = (f"kernel interface does not support a field of type "
                        f"'{arg.intrinsic_type}'.")
             raise NotImplementedError(message) from info
-        field_data_symbol = self._symbol_table.find_or_create_tag(
+        field_data_symbol = self._symtab.find_or_create_tag(
             arg.name, interface=ArgumentInterface(INTENT_MAPPING[arg.intent]),
             symbol_type=field_class, dims=[Reference(undf_symbol)], fs=fs_name)
 
@@ -343,33 +351,35 @@ class KernelInterface(ArgOrdering):
 
         :param arg: the operator to add.
         :type arg: :py:class:`psyclone.dynamo0p3.DynKernelArgument`
-        :param var_accesses: an unused optional argument that stores \
+        :param var_accesses: an unused optional argument that stores
             information about variable accesses.
-        :type var_accesses: :\
-            py:class:`psyclone.core.VariablesAccessInfo`
+        :type var_accesses: :py:class:`psyclone.core.VariablesAccessInfo`
 
-        :raises NotImplementedError: if the datatype of the field is \
+        :raises NotImplementedError: if the datatype of the field is
             not supported.
 
         '''
         fs_from_name = arg.function_space_from.orig_name
 
-        ndf_symbol_from = self._symbol_table.find_or_create_tag(
+        ndf_symbol_from = self._symtab.find_or_create_tag(
             f"ndf_{fs_from_name}", fs=fs_from_name,
             symbol_type=LFRicTypes("NumberOfDofsDataSymbol"),
             interface=self._read_access)
         fs_to_name = arg.function_space_to.orig_name
-        ndf_symbol_to = self._symbol_table.find_or_create_tag(
+        ndf_symbol_to = self._symtab.find_or_create_tag(
             f"ndf_{fs_to_name}", fs=fs_to_name,
             symbol_type=LFRicTypes("NumberOfDofsDataSymbol"),
             interface=self._read_access)
 
-        ncells = LFRicTypes("NumberOfCellsDataSymbol")(
-            "ncell_3d", interface=self._read_access)
-        self._symbol_table.add(ncells)
+        # We may already have a symbol for this argument as we add it for each
+        # operator (TODO #2074).
+        ncells = self._symtab.find_or_create(
+            "ncell_3d",
+            symbol_type=LFRicTypes("NumberOfCellsDataSymbol"),
+            interface=self._read_access)
         self._arglist.append(ncells)
 
-        op_arg_symbol = self._symbol_table.find_or_create_tag(
+        op_arg_symbol = self._symtab.find_or_create_tag(
             arg.name, symbol_type=LFRicTypes("OperatorDataSymbol"),
             dims=[Reference(ndf_symbol_from), Reference(ndf_symbol_to),
                   Reference(ncells)],
@@ -415,7 +425,7 @@ class KernelInterface(ArgOrdering):
             "real": LFRicTypes("LFRicRealScalarDataSymbol"),
             "logical": LFRicTypes("LFRicLogicalScalarDataSymbol")}
         try:
-            symbol = self._symbol_table.find_or_create_tag(
+            symbol = self._symtab.find_or_create_tag(
                 scalar_arg.name,
                 symbol_type=mapping[scalar_arg.intrinsic_type],
                 interface=ArgumentInterface(INTENT_MAPPING[scalar_arg.intent]))
@@ -443,7 +453,7 @@ class KernelInterface(ArgOrdering):
 
         '''
         fs_name = function_space.orig_name
-        ndf_symbol = self._symbol_table.find_or_create_tag(
+        ndf_symbol = self._symtab.find_or_create_tag(
             f"ndf_{fs_name}", fs=fs_name,
             symbol_type=LFRicTypes("NumberOfDofsDataSymbol"),
             interface=self._read_access)
@@ -482,19 +492,19 @@ class KernelInterface(ArgOrdering):
 
         '''
         fs_name = function_space.orig_name
-        undf_symbol = self._symbol_table.find_or_create_tag(
+        undf_symbol = self._symtab.find_or_create_tag(
             f"undf_{fs_name}", fs=fs_name,
             symbol_type=LFRicTypes("NumberOfUniqueDofsDataSymbol"),
             interface=self._read_access)
         self._arglist.append(undf_symbol)
 
         fs_name = function_space.orig_name
-        ndf_symbol = self._symbol_table.find_or_create_tag(
+        ndf_symbol = self._symtab.find_or_create_tag(
             f"ndf_{fs_name}", fs=fs_name,
             symbol_type=LFRicTypes("NumberOfDofsDataSymbol"),
             interface=self._read_access)
 
-        dofmap_symbol = self._symbol_table.find_or_create_tag(
+        dofmap_symbol = self._symtab.find_or_create_tag(
             f"dofmap_{fs_name}", fs=fs_name,
             symbol_type=LFRicTypes("DofMapDataSymbol"),
             dims=[Reference(ndf_symbol)], interface=self._read_access)
@@ -647,49 +657,49 @@ class KernelInterface(ArgOrdering):
         # The kernel captures all the required quadrature shapes
         for shape in self._kern.qr_rules:
             if shape == "gh_quadrature_xyoz":
-                nqp_xy = self._symbol_table.find_or_create_tag(
+                nqp_xy = self._symtab.find_or_create_tag(
                     "nqp_xy",
                     symbol_type=LFRicTypes("NumberOfQrPointsInXyDataSymbol"),
                     interface=self._read_access)
-                nqp_z = self._symbol_table.find_or_create_tag(
+                nqp_z = self._symtab.find_or_create_tag(
                     "nqp_z",
                     symbol_type=LFRicTypes("NumberOfQrPointsInZDataSymbol"),
                     interface=self._read_access)
-                weights_xy = self._symbol_table.find_or_create_tag(
+                weights_xy = self._symtab.find_or_create_tag(
                     "weights_xy",
                     symbol_type=LFRicTypes("QrWeightsInXyDataSymbol"),
                     dims=[Reference(nqp_xy)], interface=self._read_access)
-                weights_z = self._symbol_table.find_or_create_tag(
+                weights_z = self._symtab.find_or_create_tag(
                     "weights_z",
                     symbol_type=LFRicTypes("QrWeightsInZDataSymbol"),
                     dims=[Reference(nqp_z)], interface=self._read_access)
                 self._arglist.extend([nqp_xy, nqp_z, weights_xy, weights_z])
             elif shape == "gh_quadrature_face":
-                nfaces = self._symbol_table.find_or_create_tag(
+                nfaces = self._symtab.find_or_create_tag(
                     "nfaces",
                     symbol_type=LFRicTypes("NumberOfFacesDataSymbol"),
                     interface=self._read_access)
-                nqp = self._symbol_table.find_or_create_tag(
+                nqp = self._symtab.find_or_create_tag(
                     "nqp_faces",
                     symbol_type=LFRicTypes(
                         "NumberOfQrPointsInFacesDataSymbol"),
                     interface=self._read_access)
-                weights = self._symbol_table.find_or_create_tag(
+                weights = self._symtab.find_or_create_tag(
                     "weights_faces",
                     symbol_type=LFRicTypes("QrWeightsInFacesDataSymbol"),
                     dims=[Reference(nqp)], interface=self._read_access)
                 self._arglist.extend([nfaces, nqp, weights])
             elif shape == "gh_quadrature_edge":
-                nedges = self._symbol_table.find_or_create_tag(
+                nedges = self._symtab.find_or_create_tag(
                     "nedges",
                     symbol_type=LFRicTypes("NumberOfEdgesDataSymbol"),
                     interface=self._read_access)
-                nqp = self._symbol_table.find_or_create_tag(
+                nqp = self._symtab.find_or_create_tag(
                     "nqp_edges",
                     symbol_type=LFRicTypes(
                         "NumberOfQrPointsInEdgesDataSymbol"),
                     interface=self._read_access)
-                weights = self._symbol_table.find_or_create_tag(
+                weights = self._symtab.find_or_create_tag(
                     "weights_edges",
                     symbol_type=LFRicTypes("QrWeightsInEdgesDataSymbol"),
                     dims=[Reference(nqp)], interface=self._read_access)
@@ -736,7 +746,7 @@ class KernelInterface(ArgOrdering):
         const = LFRicConstants()
         for shape in self._kern.eval_shapes:
             fs_name = function_space.orig_name
-            ndf_symbol = self._symbol_table.find_or_create_tag(
+            ndf_symbol = self._symtab.find_or_create_tag(
                 f"ndf_{fs_name}",
                 symbol_type=LFRicTypes("NumberOfDofsDataSymbol"),
                 fs=fs_name, interface=self._read_access)
@@ -746,11 +756,11 @@ class KernelInterface(ArgOrdering):
             quad_name = shape.split("_")[-1]
             basis_tag = basis_name_func(qr_var="qr_"+quad_name)
             if shape == "gh_quadrature_xyoz":
-                nqp_xy = self._symbol_table.find_or_create_tag(
+                nqp_xy = self._symtab.find_or_create_tag(
                     "nqp_xy",
                     symbol_type=LFRicTypes("NumberOfQrPointsInXyDataSymbol"),
                     interface=self._read_access)
-                nqp_z = self._symbol_table.find_or_create_tag(
+                nqp_z = self._symtab.find_or_create_tag(
                     "nqp_z",
                     symbol_type=LFRicTypes("NumberOfQrPointsInZDataSymbol"),
                     interface=self._read_access)
@@ -761,11 +771,11 @@ class KernelInterface(ArgOrdering):
                                 Reference(nqp_z)],
                     fs_name, interface=self._read_access)
             elif shape == "gh_quadrature_face":
-                nfaces = self._symbol_table.find_or_create_tag(
+                nfaces = self._symtab.find_or_create_tag(
                     "nfaces",
                     symbol_type=LFRicTypes("NumberOfFacesDataSymbol"),
                     interface=self._read_access)
-                nqp = self._symbol_table.find_or_create_tag(
+                nqp = self._symtab.find_or_create_tag(
                     "nqp_faces",
                     symbol_type=LFRicTypes(
                         "NumberOfQrPointsInFacesDataSymbol"),
@@ -777,11 +787,11 @@ class KernelInterface(ArgOrdering):
                                 Reference(nfaces)],
                     fs_name, interface=self._read_access)
             elif shape == "gh_quadrature_edge":
-                nedges = self._symbol_table.find_or_create_tag(
+                nedges = self._symtab.find_or_create_tag(
                     "nedges",
                     symbol_type=LFRicTypes("NumberOfEdgesDataSymbol"),
                     interface=self._read_access)
-                nqp = self._symbol_table.find_or_create_tag(
+                nqp = self._symtab.find_or_create_tag(
                     "nqp_edges",
                     symbol_type=LFRicTypes(
                         "NumberOfQrPointsInEdgesDataSymbol"),
@@ -805,5 +815,5 @@ class KernelInterface(ArgOrdering):
                 raise InternalError(
                     f"Unrecognised quadrature or evaluator shape '{shape}'. "
                     f"Expected one of: {const.VALID_EVALUATOR_SHAPES}.")
-            self._symbol_table.add(arg)
+            self._symtab.add(arg)
             self._arglist.append(arg)
diff --git a/src/psyclone/dynamo0p3.py b/src/psyclone/dynamo0p3.py
index b2f7fa9e0..f314d4308 100644
--- a/src/psyclone/dynamo0p3.py
+++ b/src/psyclone/dynamo0p3.py
@@ -3218,8 +3218,8 @@ class DynCellIterators(LFRicCollection):
     def __init__(self, kern_or_invoke):
         super().__init__(kern_or_invoke)
 
-        self._nlayers_name = self._symbol_table.\
-            find_or_create_integer_symbol("nlayers", tag="nlayers").name
+        self._nlayers_name = self._symbol_table.find_or_create_tag(
+            "nlayers", symbol_type=LFRicTypes("MeshHeightDataSymbol")).name
 
         # Store a reference to the first field/operator object that
         # we can use to look-up nlayers in the PSy layer.
diff --git a/src/psyclone/psyad/tl2ad.py b/src/psyclone/psyad/tl2ad.py
index 5e433bf66..b95b58cac 100644
--- a/src/psyclone/psyad/tl2ad.py
+++ b/src/psyclone/psyad/tl2ad.py
@@ -263,7 +263,7 @@ def _add_precision_symbol(symbol, table):
     :type table: :py:class:`psyclone.psyir.symbols.SymbolTable`
 
     :raises TypeError: if the supplied symbol is not of the correct type.
-    :raises NotImplementedError: if the supplied symbol is not local or \
+    :raises NotImplementedError: if the supplied symbol is not local or
                                  explicitly imported.
 
     '''
@@ -292,7 +292,9 @@ def _add_precision_symbol(symbol, table):
         kind_symbol = symbol.copy()
         kind_symbol.interface = ImportInterface(kind_contr_sym)
         table.add(kind_symbol)
-    elif symbol.is_automatic or symbol.is_modulevar or symbol.is_constant:
+    elif not (symbol.is_unresolved or symbol.is_argument):
+        # The symbol is declared somewhere within a parent scope and is not an
+        # argument.
         table.add(symbol.copy())
     else:
         raise NotImplementedError(
diff --git a/src/psyclone/psyir/frontend/fparser2.py b/src/psyclone/psyir/frontend/fparser2.py
index 2294581c6..bf1e2411e 100644
--- a/src/psyclone/psyir/frontend/fparser2.py
+++ b/src/psyclone/psyir/frontend/fparser2.py
@@ -670,7 +670,7 @@ def _kind_find_or_create(name, symbol_table):
     If it is a DataSymbol then it must have a datatype of
     'integer', 'deferred' or 'unknown'. If it is deferred then the fact
     that we now know that this Symbol represents a KIND parameter means we
-    can change the datatype to be 'integer'.
+    can change the datatype to be 'integer' and mark it as constant.
 
     If the existing symbol is a generic Symbol then it is replaced with
     a new DataSymbol of type 'integer'.
@@ -696,17 +696,9 @@ def _kind_find_or_create(name, symbol_table):
         # pylint: disable=unidiomatic-typecheck
         if type(kind_symbol) is Symbol:
             # There is an existing entry but it's only a generic Symbol
-            # so we need to replace it with a DataSymbol of integer type.
-            # Since the lookup() above looks through *all* ancestor symbol
-            # tables, we have to find precisely which table the existing
-            # Symbol is in.
-            table = kind_symbol.find_symbol_table(symbol_table.node)
-            new_symbol = DataSymbol(lower_name,
-                                    default_integer_type(),
-                                    visibility=kind_symbol.visibility,
-                                    interface=kind_symbol.interface)
-            table.swap(kind_symbol, new_symbol)
-            kind_symbol = new_symbol
+            # so we need to specialise it to a DataSymbol of integer type.
+            kind_symbol.specialise(DataSymbol, datatype=default_integer_type(),
+                                   is_constant=True)
         elif isinstance(kind_symbol, DataSymbol):
 
             if not (isinstance(kind_symbol.datatype,
@@ -723,6 +715,7 @@ def _kind_find_or_create(name, symbol_table):
             # so set it to the default.
             if isinstance(kind_symbol.datatype, DeferredType):
                 kind_symbol.datatype = default_integer_type()
+                kind_symbol.is_constant = True
         else:
             raise TypeError(
                 f"A symbol representing a kind parameter must be an instance "
@@ -736,7 +729,8 @@ def _kind_find_or_create(name, symbol_table):
             symbol_table.node, lower_name,
             symbol_type=DataSymbol,
             datatype=default_integer_type(),
-            visibility=symbol_table.default_visibility)
+            visibility=symbol_table.default_visibility,
+            is_constant=True)
     return kind_symbol
 
 
diff --git a/src/psyclone/psyir/symbols/datasymbol.py b/src/psyclone/psyir/symbols/datasymbol.py
index e8a9c7d1e..1ffe9552f 100644
--- a/src/psyclone/psyir/symbols/datasymbol.py
+++ b/src/psyclone/psyir/symbols/datasymbol.py
@@ -112,19 +112,22 @@ class DataSymbol(TypedSymbol):
 
         if "initial_value" in kwargs:
             new_initial_value = kwargs.pop("initial_value")
-        elif not hasattr(self, '_initial_value'):
+        if not hasattr(self, '_initial_value'):
             # Initialise this attribute if we reach this point and this object
-            # doesn't already have it.
+            # doesn't already have it (which may happen if this symbol has been
+            # specialised from a Symbol).
             self._initial_value = None
 
         if "is_constant" in kwargs:
             new_is_constant_value = kwargs.pop("is_constant")
-        elif not hasattr(self, '_is_constant'):
+        if not hasattr(self, '_is_constant'):
             # At least initialise it if we reach this point and it doesn't
-            # exist
+            # exist (which may happen if this symbol has been specialised from
+            # a Symbol).
             self._is_constant = False
 
         # Record whether an explicit value has been supplied for 'interface'
+        # (before it is consumed by the super method).
         interface_supplied = "interface" in kwargs
 
         super()._process_arguments(**kwargs)
@@ -134,26 +137,16 @@ class DataSymbol(TypedSymbol):
         if new_initial_value is not None:
             self.initial_value = new_initial_value
 
-        # Now that we know whether or not we have an intial_value, we can
-        # call the is_constant setter.
+        # Now that we know whether or not we have an intial_value and an
+        # interface, we can call the is_constant setter.
         if new_is_constant_value is not None:
             self.is_constant = new_is_constant_value
 
-        # A run-time constant must have a StaticInterface or an
-        # ImportInterface. If the user did not supply an explicit interface
-        # then default to StaticInterface. If they did supply
-        # one then we check it is valid.
-        if self.is_constant:
-            if interface_supplied:
-                if not (self.is_static or self.is_import):
-                    raise ValueError(
-                        f"A DataSymbol representing a constant must have "
-                        f"either a StaticInterface or an ImportInterface but "
-                        f"'{self.name}' has interface '{self.interface}'.")
-            else:
-                # No explicit interface was supplied and this Symbol represents
-                # a runtime constant so change its interface to be static.
-                self.interface = StaticInterface()
+        if self.is_constant and not interface_supplied and self.is_automatic:
+            # No explicit interface was supplied and this Symbol represents
+            # a runtime constant so change its interface to be static if it
+            # would otherwise default to Automatic.
+            self.interface = StaticInterface()
 
     @property
     def is_constant(self):
@@ -171,13 +164,20 @@ class DataSymbol(TypedSymbol):
             constant.
 
         :raises ValueError: if `value` is True but this symbol does not have an
-            initial value set and does not have an ImportInterface.
+            initial value or an import or unresolved interface.
 
         '''
-        if value and not self.is_import and self.initial_value is None:
-            raise ValueError(
-                f"DataSymbol '{self.name}' does not have an initial value set "
-                f"and is not imported and therefore cannot be a constant.")
+        if (value and not (self.is_import or self.is_unresolved) and
+                self.initial_value is None):
+            # A Symbol of UnknownType could have initialisation within its
+            # original declaration.
+            # pylint: disable=import-outside-toplevel
+            from psyclone.psyir.symbols.datatypes import UnknownType
+            if not isinstance(self.datatype, UnknownType):
+                raise ValueError(
+                    f"DataSymbol '{self.name}' cannot be a constant because it"
+                    f" does not have an initial value or an import or "
+                    f"unresolved interface.")
         self._is_constant = value
 
     @property
diff --git a/src/psyclone/psyir/symbols/symbol.py b/src/psyclone/psyir/symbols/symbol.py
index f3465035b..a0b80be4b 100644
--- a/src/psyclone/psyir/symbols/symbol.py
+++ b/src/psyclone/psyir/symbols/symbol.py
@@ -41,10 +41,10 @@
 
 from enum import Enum
 from psyclone.errors import PSycloneError, InternalError
-from psyclone.psyir.symbols.interfaces import AutomaticInterface, \
-    SymbolInterface, ArgumentInterface, UnresolvedInterface, ImportInterface, \
-    UnknownInterface, CommonBlockInterface, DefaultModuleInterface, \
-    StaticInterface
+from psyclone.psyir.symbols.interfaces import (
+    AutomaticInterface, SymbolInterface, ArgumentInterface,
+    UnresolvedInterface, ImportInterface, UnknownInterface,
+    CommonBlockInterface, DefaultModuleInterface, StaticInterface)
 
 
 class SymbolError(PSycloneError):
@@ -228,28 +228,27 @@ class Symbol():
 
     def resolve_deferred(self):
         '''
-        Search for the Container in which this Symbol is defined and
-        create and return a symbol of the correct class and type. If the
-        class and type of the looked-up symbol are the same as this one,
-        some specialisations of this method update the differing
-        properties in place rather than create a new symbol.
-        If this symbol does not have an ImportInterface then there is no
-        lookup needed and we just return this symbol.
+        Update the properties of this Symbol by using the definition imported
+        from the external Container. If this symbol does not have an
+        ImportInterface then there is no lookup needed and we just return this
+        symbol.
 
-        :returns: a symbol object with the class and type determined by \
+        :returns: a symbol object with the class and type determined by
                   examining the Container from which it is imported.
         :rtype: subclass of :py:class:`psyclone.psyir.symbols.Symbol`
 
         '''
         if self.is_import:
             extern_symbol = self.get_external_symbol()
-            # Create a new symbol object of the same class as the one
-            # we've just looked up but with the interface and visibility
-            # of the current symbol.
-            new_sym = extern_symbol.copy()
-            new_sym.interface = self.interface
-            new_sym.visibility = self.visibility
-            return new_sym
+            init_value = None
+            if extern_symbol.initial_value:
+                init_value = extern_symbol.initial_value.copy()
+            # Specialise the existing Symbol in-place so that all References
+            # to it remain valid.
+            self.specialise(type(extern_symbol),
+                            datatype=extern_symbol.datatype,
+                            is_constant=extern_symbol.is_constant,
+                            initial_value=init_value)
         return self
 
     @property
diff --git a/src/psyclone/psyir/symbols/symbol_table.py b/src/psyclone/psyir/symbols/symbol_table.py
index b7461d8fb..bf1039241 100644
--- a/src/psyclone/psyir/symbols/symbol_table.py
+++ b/src/psyclone/psyir/symbols/symbol_table.py
@@ -958,7 +958,7 @@ class SymbolTable():
         :type new_symbol: :py:class:`psyclone.psyir.symbols.Symbol`
 
         :raises TypeError: if either old/new_symbol are not Symbols.
-        :raises SymbolError: if `old_symbol` and `new_symbol` don't have \
+        :raises SymbolError: if `old_symbol` and `new_symbol` don't have
                              the same name (after normalising).
         '''
         if not isinstance(old_symbol, Symbol):
diff --git a/src/psyclone/transformations.py b/src/psyclone/transformations.py
index 46ea76c8d..46b2da594 100644
--- a/src/psyclone/transformations.py
+++ b/src/psyclone/transformations.py
@@ -55,21 +55,23 @@ from psyclone.dynamo0p3 import LFRicHaloExchangeEnd, LFRicHaloExchangeStart, \
 from psyclone.errors import InternalError
 from psyclone.gocean1p0 import GOInvokeSchedule
 from psyclone.nemo import NemoInvokeSchedule
-from psyclone.psyGen import Transformation, CodedKern, Kern, InvokeSchedule, \
-    BuiltIn
-from psyclone.psyir.nodes import ACCDataDirective, ACCDirective, \
-    ACCEnterDataDirective, ACCKernelsDirective, ACCLoopDirective, \
-    ACCParallelDirective, ACCRoutineDirective, Assignment, CodeBlock, \
-    Directive, Loop, Node, OMPDeclareTargetDirective, \
-    OMPDirective, OMPMasterDirective, \
-    OMPParallelDirective, OMPParallelDoDirective, OMPSerialDirective, \
-    OMPSingleDirective, OMPTaskloopDirective, PSyDataNode, Reference, \
-    Return, Routine, Schedule
+from psyclone.psyGen import (Transformation, CodedKern, Kern, InvokeSchedule,
+                             BuiltIn)
+from psyclone.psyir.nodes import (
+    ACCDataDirective, ACCDirective,
+    ACCEnterDataDirective, ACCKernelsDirective, ACCLoopDirective,
+    ACCParallelDirective, ACCRoutineDirective, Assignment, Call, CodeBlock,
+    Directive, IntrinsicCall, Loop, Node, OMPDeclareTargetDirective,
+    OMPDirective, OMPMasterDirective,
+    OMPParallelDirective, OMPParallelDoDirective, OMPSerialDirective,
+    OMPSingleDirective, OMPTaskloopDirective, PSyDataNode, Reference,
+    Return, Routine, Schedule)
 from psyclone.psyir.nodes.array_mixin import ArrayMixin
 from psyclone.psyir.nodes.structure_member import StructureMember
 from psyclone.psyir.nodes.structure_reference import StructureReference
-from psyclone.psyir.symbols import ArgumentInterface, DataSymbol, \
-    DeferredType, INTEGER_TYPE, ScalarType, Symbol, SymbolError
+from psyclone.psyir.symbols import (
+    ArgumentInterface, DataSymbol, DeferredType, INTEGER_TYPE, ScalarType,
+    Symbol, SymbolError)
 from psyclone.psyir.transformations.loop_trans import LoopTrans
 from psyclone.psyir.transformations.omp_loop_trans import OMPLoopTrans
 from psyclone.psyir.transformations.parallel_loop_trans import \
@@ -2116,11 +2118,9 @@ class Dynamo0p3KernelConstTrans(Transformation):
             # Create a new symbol with a known constant value then swap
             # it with the argument. The argument then becomes xxx_dummy
             # and is unused within the kernel body.
-            # TODO: Temporarily use unsafe name change until the name
-            # space manager is introduced into the SymbolTable (Issue
-            # #321).
             orig_name = symbol.name
-            local_symbol = DataSymbol(orig_name+"_dummy", INTEGER_TYPE,
+            new_name = symbol_table.next_available_name(f"{orig_name}_dummy")
+            local_symbol = DataSymbol(new_name, INTEGER_TYPE,
                                       is_constant=True, initial_value=value)
             symbol_table.add(local_symbol)
             symbol_table.swap_symbol_properties(symbol, local_symbol)
@@ -2426,6 +2426,7 @@ class ACCRoutineTrans(Transformation):
     >>> kern = schedule.children[0].children[0].children[0]
     >>> # Transform the kernel
     >>> rtrans.apply(kern)
+
     '''
     @property
     def name(self):
@@ -2471,33 +2472,37 @@ class ACCRoutineTrans(Transformation):
         Perform checks that the supplied kernel or routine can be transformed.
 
         :param node: the kernel which is the target of the transformation.
-        :type node: :py:class:`psyclone.psyGen.Kern` or \
+        :type node: :py:class:`psyclone.psyGen.Kern` |
                     :py:class:`psyclone.psyir.nodes.Routine`
         :param options: a dictionary with options for transformations.
         :type options: Optional[Dict[str, Any]]
 
         :raises TransformationError: if the node is not a kernel or a routine.
         :raises TransformationError: if the target is a built-in kernel.
-        :raises TransformationError: if it is a kernel but without an \
+        :raises TransformationError: if it is a kernel but without an
                                      associated PSyIR.
-        :raises TransformationError: if any of the symbols in the kernel are \
+        :raises TransformationError: if any of the symbols in the kernel are
                                      accessed via a module use statement.
+        :raises TransformationError: if the kernel contains any calls to other
+                                     routines.
         '''
         super().validate(node, options)
 
+        force = options.get("force", False) if options else False
+
         if not isinstance(node, Kern) and not isinstance(node, Routine):
             raise TransformationError(
                 f"The ACCRoutineTrans must be applied to a sub-class of "
                 f"Kern or Routine but got '{type(node).__name__}'.")
 
         # If it is a kernel call it must have an accessible implementation
-        if isinstance(node, Kern):
-            if isinstance(node, BuiltIn):
-                raise TransformationError(
-                    f"Applying ACCRoutineTrans to a built-in kernel is not yet"
-                    f" supported and kernel '{node.name}' is of type "
-                    f"'{type(node).__name__}'")
+        if isinstance(node, BuiltIn):
+            raise TransformationError(
+                f"Applying ACCRoutineTrans to a built-in kernel is not yet"
+                f" supported and kernel '{node.name}' is of type "
+                f"'{type(node).__name__}'")
 
+        if isinstance(node, Kern):
             # Get the PSyIR routine from the associated kernel. If there is an
             # exception (this could mean that there is no associated tree
             # or that the frontend failed to convert it into PSyIR) reraise it
@@ -2508,20 +2513,74 @@ class ACCRoutineTrans(Transformation):
                 raise TransformationError(
                     f"Failed to create PSyIR for kernel '{node.name}'. "
                     f"Cannot transform such a kernel.") from error
-
-            # Check that the kernel does not access any data or routines via a
-            # module 'use' statement
-            imported_variables = kernel_schedule.symbol_table.imported_symbols
-            if imported_variables:
+            k_or_r = "Kernel"
+        else:
+            # Supplied node is a PSyIR Routine which *is* a Schedule.
+            kernel_schedule = node
+            k_or_r = "routine"
+
+        # Check that the routine does not access any data that is imported via
+        # a 'use' statement.
+        # TODO #2271 - this implementation will not catch symbols from literal
+        # precisions or intialisation expressions.
+        refs = kernel_schedule.walk(Reference)
+        for ref in refs:
+            if ref.symbol.is_import:
+                # resolve_deferred does nothing if the Symbol type is known.
+                try:
+                    ref.symbol.resolve_deferred()
+                except SymbolError:
+                    # TODO #11 - log that we failed to resolve this Symbol.
+                    pass
+                if (isinstance(ref.symbol, DataSymbol) and
+                        ref.symbol.is_constant):
+                    # An import of a compile-time constant is fine.
+                    continue
+                raise TransformationError(
+                    f"{k_or_r} '{node.name}' accesses the symbol "
+                    f"'{ref.symbol}' which is imported. If this symbol "
+                    f"represents data then it must first be converted to a "
+                    f"{k_or_r} argument using the KernelImportsToArguments "
+                    f"transformation.")
+
+        # We forbid CodeBlocks because we can't be certain that what they
+        # contain can be executed on a GPU. However, we do permit the user
+        # to override this check.
+        cblocks = kernel_schedule.walk(CodeBlock)
+        if not force:
+            if cblocks:
+                cblock_txt = ("\n  " + "\n  ".join(str(node) for node in
+                                                   cblocks[0].get_ast_nodes)
+                              + "\n")
+                option_txt = "options={'force': True}"
+                raise TransformationError(
+                    f"Cannot safely add 'ACC routine' to {k_or_r} "
+                    f"'{node.name}' because its PSyIR contains one or more "
+                    f"CodeBlocks:{cblock_txt}You may use '{option_txt}' to "
+                    f"override this check.")
+        else:
+            # Check any accesses within CodeBlocks.
+            # TODO #2271 - this will be handled as part of the checking to be
+            # implemented using the dependence analysis.
+            for cblock in cblocks:
+                names = cblock.get_symbol_names()
+                for name in names:
+                    sym = kernel_schedule.symbol_table.lookup(name)
+                    if sym.is_import:
+                        raise TransformationError(
+                            f"{k_or_r} '{node.name}' accesses the symbol "
+                            f"'{sym.name}' within a CodeBlock and this symbol "
+                            f"is imported. 'ACC routine' cannot be added to "
+                            f"such a {k_or_r}.")
+
+        calls = kernel_schedule.walk(Call)
+        for call in calls:
+            if not isinstance(call, IntrinsicCall):
+                call_str = call.debug_string().rstrip("\n")
                 raise TransformationError(
-                    f"The Symbol Table for kernel '{node.name}' contains the "
-                    f"following symbol(s) with imported interface: "
-                    f"{[sym.name for sym in imported_variables]}. If these "
-                    f"symbols represent data then they must first be converted"
-                    f" to kernel arguments using the KernelImportsToArguments "
-                    f"transformation. If the symbols represent external "
-                    f"routines then PSyclone cannot currently transform this "
-                    f"kernel for execution on an OpenACC device (issue #342).")
+                    f"{k_or_r} '{node.name}' calls another routine "
+                    f"'{call_str}' and therefore cannot have "
+                    f"'ACC routine' added to it (TODO #342).")
 
 
 class ACCKernelsTrans(RegionTrans):


Test patch:
diff --git a/src/psyclone/tests/domain/gocean/transformations/gocean1p0_transformations_test.py b/src/psyclone/tests/domain/gocean/transformations/gocean1p0_transformations_test.py
index a4cecac6e..6039c78ca 100644
--- a/src/psyclone/tests/domain/gocean/transformations/gocean1p0_transformations_test.py
+++ b/src/psyclone/tests/domain/gocean/transformations/gocean1p0_transformations_test.py
@@ -1,7 +1,7 @@
 # -----------------------------------------------------------------------------
 # BSD 3-Clause License
 #
-# Copyright (c) 2017-2022, Science and Technology Facilities Council.
+# Copyright (c) 2017-2023, Science and Technology Facilities Council.
 # All rights reserved.
 #
 # Redistribution and use in source and binary forms, with or without
@@ -47,7 +47,7 @@ from psyclone.domain.gocean.transformations import GOceanLoopFuseTrans
 from psyclone.errors import GenerationError
 from psyclone.gocean1p0 import GOKern
 from psyclone.psyGen import Kern
-from psyclone.psyir.nodes import Loop, Routine, ACCEnterDataDirective
+from psyclone.psyir.nodes import Loop, Routine
 from psyclone.psyir.transformations import LoopFuseTrans, LoopTrans, \
     TransformationError
 from psyclone.transformations import ACCKernelsTrans, ACCRoutineTrans, \
@@ -1484,8 +1484,9 @@ def test_accroutinetrans_module_use():
     rtrans = ACCRoutineTrans()
     with pytest.raises(TransformationError) as err:
         rtrans.apply(kernels[0])
-    assert ("imported interface: ['rdt']. If these symbols represent data then"
-            " they must first" in str(err.value))
+    assert ("accesses the symbol 'rdt: Symbol<Import(container='model_mod')>' "
+            "which is imported. If this symbol "
+            "represents data then it must first" in str(err.value))
 
 
 def test_accroutinetrans_with_kern(fortran_writer, monkeypatch):
diff --git a/src/psyclone/tests/domain/lfric/kernel_interface_test.py b/src/psyclone/tests/domain/lfric/kernel_interface_test.py
index 4a802c21d..465a973b9 100644
--- a/src/psyclone/tests/domain/lfric/kernel_interface_test.py
+++ b/src/psyclone/tests/domain/lfric/kernel_interface_test.py
@@ -71,7 +71,7 @@ def test_init():
     assert isinstance(kernel_interface._read_access, ArgumentInterface)
     assert (kernel_interface._read_access.access ==
             ArgumentInterface.Access.READ)
-    assert isinstance(kernel_interface._symbol_table, SymbolTable)
+    assert isinstance(kernel_interface._symtab, SymbolTable)
     assert kernel_interface._arglist == []
 
 
@@ -95,18 +95,18 @@ def test_generate(var_accesses):
     kernel_interface = KernelInterface(kernel0)
     kernel_interface.generate(var_accesses=var_accesses)
     # Check symbols
-    nlayers_symbol = kernel_interface._symbol_table.lookup("nlayers")
+    nlayers_symbol = kernel_interface._symtab.lookup("nlayers")
     assert isinstance(nlayers_symbol, LFRicTypes("MeshHeightDataSymbol"))
-    undf_w0_symbol = kernel_interface._symbol_table.lookup("undf_w0")
+    undf_w0_symbol = kernel_interface._symtab.lookup("undf_w0")
     assert isinstance(undf_w0_symbol,
                       LFRicTypes("NumberOfUniqueDofsDataSymbol"))
-    f1_field_symbol = kernel_interface._symbol_table.lookup("f1")
+    f1_field_symbol = kernel_interface._symtab.lookup("f1")
     assert isinstance(f1_field_symbol, LFRicTypes("RealFieldDataSymbol"))
-    f2_field_symbol = kernel_interface._symbol_table.lookup("f2")
+    f2_field_symbol = kernel_interface._symtab.lookup("f2")
     assert isinstance(f2_field_symbol, LFRicTypes("RealFieldDataSymbol"))
-    ndf_w0_symbol = kernel_interface._symbol_table.lookup("ndf_w0")
+    ndf_w0_symbol = kernel_interface._symtab.lookup("ndf_w0")
     assert isinstance(ndf_w0_symbol, LFRicTypes("NumberOfDofsDataSymbol"))
-    dofmap_w0_symbol = kernel_interface._symbol_table.lookup("dofmap_w0")
+    dofmap_w0_symbol = kernel_interface._symtab.lookup("dofmap_w0")
     assert isinstance(dofmap_w0_symbol, LFRicTypes("DofMapDataSymbol"))
     # Check function spaces
     assert undf_w0_symbol.fs == "w0"
@@ -125,7 +125,7 @@ def test_generate(var_accesses):
     assert isinstance(dofmap_w0_symbol.shape[0].upper, Reference)
     assert dofmap_w0_symbol.shape[0].upper.symbol is ndf_w0_symbol
     # Check argument list
-    arg_list = kernel_interface._symbol_table.argument_datasymbols
+    arg_list = kernel_interface._symtab.argument_datasymbols
     assert len(arg_list) == 6
     assert arg_list[0] is nlayers_symbol
     assert arg_list[1] is undf_w0_symbol
@@ -158,7 +158,7 @@ def test_cell_position():
     '''
     kernel_interface = KernelInterface(None)
     kernel_interface.cell_position()
-    symbol = kernel_interface._symbol_table.lookup("cell")
+    symbol = kernel_interface._symtab.lookup("cell")
     assert isinstance(symbol, LFRicTypes("CellPositionDataSymbol"))
     assert isinstance(symbol.interface, ArgumentInterface)
     assert (symbol.interface.access ==
@@ -173,7 +173,7 @@ def test_mesh_height():
     '''
     kernel_interface = KernelInterface(None)
     kernel_interface.mesh_height()
-    symbol = kernel_interface._symbol_table.lookup("nlayers")
+    symbol = kernel_interface._symtab.lookup("nlayers")
     assert isinstance(symbol, LFRicTypes("MeshHeightDataSymbol"))
     assert isinstance(symbol.interface, ArgumentInterface)
     assert (symbol.interface.access ==
@@ -231,7 +231,7 @@ def test_field_vector(monkeypatch):
 
     # undf symbol declared
     undf_tag = f"undf_{vector_arg.function_space.orig_name}"
-    undf_symbol = kernel_interface._symbol_table.lookup(undf_tag)
+    undf_symbol = kernel_interface._symtab.lookup(undf_tag)
     assert isinstance(undf_symbol, LFRicTypes("NumberOfUniqueDofsDataSymbol"))
     assert isinstance(undf_symbol.interface, ArgumentInterface)
     assert (undf_symbol.interface.access ==
@@ -241,7 +241,7 @@ def test_field_vector(monkeypatch):
     # space specified and dimensioned correctly
     for idx in range(vector_arg.vector_size):
         tag = f"{vector_arg.name}_v{idx}"
-        symbol = kernel_interface._symbol_table.lookup(tag)
+        symbol = kernel_interface._symtab.lookup(tag)
         assert isinstance(symbol,
                           LFRicTypes("RealVectorFieldDataSymbol"))
         assert isinstance(symbol.interface, ArgumentInterface)
@@ -280,7 +280,7 @@ def test_field(monkeypatch):
 
     # undf symbol declared
     undf_tag = f"undf_{field_arg.function_space.orig_name}"
-    undf_symbol = kernel_interface._symbol_table.lookup(undf_tag)
+    undf_symbol = kernel_interface._symtab.lookup(undf_tag)
     assert isinstance(undf_symbol, LFRicTypes("NumberOfUniqueDofsDataSymbol"))
     assert isinstance(undf_symbol.interface, ArgumentInterface)
     assert (undf_symbol.interface.access ==
@@ -289,7 +289,7 @@ def test_field(monkeypatch):
     # field declared, added to argument list, correct function
     # space specified and dimensioned correctly
     tag = field_arg.name
-    symbol = kernel_interface._symbol_table.lookup(tag)
+    symbol = kernel_interface._symtab.lookup(tag)
     assert isinstance(symbol, LFRicTypes("RealFieldDataSymbol"))
     assert isinstance(symbol.interface, ArgumentInterface)
     assert (symbol.interface.access ==
@@ -360,7 +360,7 @@ def test_operator():
     # fs_from symbol declared
     fs_from_name = operator_arg.function_space_from.orig_name
     fs_from_tag = f"ndf_{fs_from_name}"
-    fs_from_symbol = kernel_interface._symbol_table.lookup(fs_from_tag)
+    fs_from_symbol = kernel_interface._symtab.lookup(fs_from_tag)
     assert isinstance(fs_from_symbol, LFRicTypes("NumberOfDofsDataSymbol"))
     assert fs_from_symbol.fs == fs_from_name
     assert isinstance(fs_from_symbol.interface, ArgumentInterface)
@@ -370,7 +370,7 @@ def test_operator():
     # fs_to symbol declared
     fs_to_name = operator_arg.function_space_from.orig_name
     fs_to_tag = f"ndf_{fs_to_name}"
-    fs_to_symbol = kernel_interface._symbol_table.lookup(fs_to_tag)
+    fs_to_symbol = kernel_interface._symtab.lookup(fs_to_tag)
     assert isinstance(fs_to_symbol, LFRicTypes("NumberOfDofsDataSymbol"))
     assert fs_to_symbol.fs == fs_to_name
     assert isinstance(fs_to_symbol.interface, ArgumentInterface)
@@ -378,7 +378,7 @@ def test_operator():
             kernel_interface._read_access.access)
 
     # ncells symbol declared
-    ncells_symbol = kernel_interface._symbol_table.lookup("ncell_3d")
+    ncells_symbol = kernel_interface._symtab.lookup("ncell_3d")
     assert isinstance(ncells_symbol, LFRicTypes("NumberOfCellsDataSymbol"))
     assert isinstance(ncells_symbol.interface, ArgumentInterface)
     assert (ncells_symbol.interface.access ==
@@ -387,7 +387,7 @@ def test_operator():
     # operator declared, added to argument list, correct function
     # spaces specified and dimensioned correctly
     tag = operator_arg.name
-    symbol = kernel_interface._symbol_table.lookup(tag)
+    symbol = kernel_interface._symtab.lookup(tag)
     assert isinstance(symbol, LFRicTypes("OperatorDataSymbol"))
     assert isinstance(symbol.interface, ArgumentInterface)
     assert (symbol.interface.access ==
@@ -432,7 +432,7 @@ def test_scalar(monkeypatch):
     scalar_arg = kernel.args[1]
     kernel_interface = KernelInterface(None)
     kernel_interface.scalar(scalar_arg)
-    symbol = kernel_interface._symbol_table.lookup(scalar_arg.name)
+    symbol = kernel_interface._symtab.lookup(scalar_arg.name)
     assert isinstance(symbol, LFRicTypes("LFRicIntegerScalarDataSymbol"))
     assert isinstance(symbol.interface, ArgumentInterface)
     assert (symbol.interface.access ==
@@ -455,7 +455,7 @@ def test_fs_common():
     function_space = FunctionSpace("w3", None)
     kernel_interface.fs_common(function_space)
     fs_name = function_space.orig_name
-    symbol = kernel_interface._symbol_table.lookup(f"ndf_{fs_name}")
+    symbol = kernel_interface._symtab.lookup(f"ndf_{fs_name}")
     assert isinstance(symbol, LFRicTypes("NumberOfDofsDataSymbol"))
     assert isinstance(symbol.interface, ArgumentInterface)
     assert (symbol.interface.access ==
@@ -484,7 +484,7 @@ def test_fs_compulsory_field():
     fs_name = function_space.orig_name
 
     # undf declared and added to argument list
-    symbol = kernel_interface._symbol_table.lookup(f"undf_{fs_name}")
+    symbol = kernel_interface._symtab.lookup(f"undf_{fs_name}")
     assert isinstance(symbol, LFRicTypes("NumberOfUniqueDofsDataSymbol"))
     assert isinstance(symbol.interface, ArgumentInterface)
     assert (symbol.interface.access ==
@@ -492,7 +492,7 @@ def test_fs_compulsory_field():
     assert kernel_interface._arglist[-2] is symbol
 
     # ndf declared
-    ndf_symbol = kernel_interface._symbol_table.lookup(f"ndf_{fs_name}")
+    ndf_symbol = kernel_interface._symtab.lookup(f"ndf_{fs_name}")
     assert isinstance(ndf_symbol, LFRicTypes("NumberOfDofsDataSymbol"))
     assert isinstance(ndf_symbol.interface, ArgumentInterface)
     assert (ndf_symbol.interface.access ==
@@ -501,7 +501,7 @@ def test_fs_compulsory_field():
     # dofmap declared, added to argument list, correct function
     # space specified and dimensioned correctly
     tag = f"dofmap_{fs_name}"
-    symbol = kernel_interface._symbol_table.lookup(tag)
+    symbol = kernel_interface._symtab.lookup(tag)
     assert isinstance(symbol, LFRicTypes("DofMapDataSymbol"))
     assert isinstance(symbol.interface, ArgumentInterface)
     assert symbol.interface.access == kernel_interface._read_access.access
@@ -555,27 +555,27 @@ def test_basis_xyoz():
     kernel_interface.basis(w1_fs)
 
     # ndf declared
-    ndf_symbol = kernel_interface._symbol_table.lookup(f"ndf_{fs_name}")
+    ndf_symbol = kernel_interface._symtab.lookup(f"ndf_{fs_name}")
     assert isinstance(ndf_symbol, LFRicTypes("NumberOfDofsDataSymbol"))
     assert isinstance(ndf_symbol.interface, ArgumentInterface)
     assert (ndf_symbol.interface.access ==
             kernel_interface._read_access.access)
     # nqp_xy declared
-    nqph_symbol = kernel_interface._symbol_table.lookup("nqp_xy")
+    nqph_symbol = kernel_interface._symtab.lookup("nqp_xy")
     assert isinstance(nqph_symbol,
                       LFRicTypes("NumberOfQrPointsInXyDataSymbol"))
     assert isinstance(nqph_symbol.interface, ArgumentInterface)
     assert (nqph_symbol.interface.access ==
             kernel_interface._read_access.access)
     # nqp_z declared
-    nqpv_symbol = kernel_interface._symbol_table.lookup("nqp_z")
+    nqpv_symbol = kernel_interface._symtab.lookup("nqp_z")
     assert isinstance(nqpv_symbol,
                       LFRicTypes("NumberOfQrPointsInZDataSymbol"))
     assert isinstance(nqpv_symbol.interface, ArgumentInterface)
     assert (nqpv_symbol.interface.access ==
             kernel_interface._read_access.access)
     # basis declared and added to argument list
-    basis_symbol = kernel_interface._symbol_table.lookup("basis_w1_qr_xyoz")
+    basis_symbol = kernel_interface._symtab.lookup("basis_w1_qr_xyoz")
     assert isinstance(basis_symbol,
                       LFRicTypes("BasisFunctionQrXyozDataSymbol"))
     assert isinstance(basis_symbol.interface, ArgumentInterface)
@@ -616,26 +616,26 @@ def test_basis_face():
     kernel_interface.basis(w1_fs)
 
     # ndf declared
-    ndf_symbol = kernel_interface._symbol_table.lookup(f"ndf_{fs_name}")
+    ndf_symbol = kernel_interface._symtab.lookup(f"ndf_{fs_name}")
     assert isinstance(ndf_symbol, LFRicTypes("NumberOfDofsDataSymbol"))
     assert isinstance(ndf_symbol.interface, ArgumentInterface)
     assert (ndf_symbol.interface.access ==
             kernel_interface._read_access.access)
     # nfaces declared
-    nfaces_symbol = kernel_interface._symbol_table.lookup("nfaces")
+    nfaces_symbol = kernel_interface._symtab.lookup("nfaces")
     assert isinstance(nfaces_symbol, LFRicTypes("NumberOfFacesDataSymbol"))
     assert isinstance(nfaces_symbol.interface, ArgumentInterface)
     assert (nfaces_symbol.interface.access ==
             kernel_interface._read_access.access)
     # nqp declared
-    nqp_symbol = kernel_interface._symbol_table.lookup("nqp_faces")
+    nqp_symbol = kernel_interface._symtab.lookup("nqp_faces")
     assert isinstance(
         nqp_symbol, LFRicTypes("NumberOfQrPointsInFacesDataSymbol"))
     assert isinstance(nqp_symbol.interface, ArgumentInterface)
     assert (nqp_symbol.interface.access ==
             kernel_interface._read_access.access)
     # basis declared and added to argument list
-    basis_symbol = kernel_interface._symbol_table.lookup("basis_w1_qr_face")
+    basis_symbol = kernel_interface._symtab.lookup("basis_w1_qr_face")
     assert isinstance(basis_symbol,
                       LFRicTypes("BasisFunctionQrFaceDataSymbol"))
     assert isinstance(basis_symbol.interface, ArgumentInterface)
@@ -675,26 +675,26 @@ def test_basis_edge():
     kernel_interface.basis(w1_fs)
 
     # ndf declared
-    ndf_symbol = kernel_interface._symbol_table.lookup(f"ndf_{fs_name}")
+    ndf_symbol = kernel_interface._symtab.lookup(f"ndf_{fs_name}")
     assert isinstance(ndf_symbol, LFRicTypes("NumberOfDofsDataSymbol"))
     assert isinstance(ndf_symbol.interface, ArgumentInterface)
     assert (ndf_symbol.interface.access ==
             kernel_interface._read_access.access)
     # nedges declared
-    nedges_symbol = kernel_interface._symbol_table.lookup("nedges")
+    nedges_symbol = kernel_interface._symtab.lookup("nedges")
     assert isinstance(nedges_symbol, LFRicTypes("NumberOfEdgesDataSymbol"))
     assert isinstance(nedges_symbol.interface, ArgumentInterface)
     assert (nedges_symbol.interface.access ==
             kernel_interface._read_access.access)
     # nqp declared
-    nqp_symbol = kernel_interface._symbol_table.lookup("nqp_edges")
+    nqp_symbol = kernel_interface._symtab.lookup("nqp_edges")
     assert isinstance(
         nqp_symbol, LFRicTypes("NumberOfQrPointsInEdgesDataSymbol"))
     assert isinstance(nqp_symbol.interface, ArgumentInterface)
     assert (nqp_symbol.interface.access ==
             kernel_interface._read_access.access)
     # basis declared and added to argument list
-    basis_symbol = kernel_interface._symbol_table.lookup("basis_w1_qr_edge")
+    basis_symbol = kernel_interface._symtab.lookup("basis_w1_qr_edge")
     assert isinstance(basis_symbol,
                       LFRicTypes("BasisFunctionQrEdgeDataSymbol"))
     assert isinstance(basis_symbol.interface, ArgumentInterface)
@@ -736,27 +736,27 @@ def test_diff_basis():
     kernel_interface.diff_basis(w2_fs)
 
     # ndf declared
-    ndf_symbol = kernel_interface._symbol_table.lookup(f"ndf_{fs_name}")
+    ndf_symbol = kernel_interface._symtab.lookup(f"ndf_{fs_name}")
     assert isinstance(ndf_symbol, LFRicTypes("NumberOfDofsDataSymbol"))
     assert isinstance(ndf_symbol.interface, ArgumentInterface)
     assert (ndf_symbol.interface.access ==
             kernel_interface._read_access.access)
     # nqp_xy declared
-    nqph_symbol = kernel_interface._symbol_table.lookup("nqp_xy")
+    nqph_symbol = kernel_interface._symtab.lookup("nqp_xy")
     assert isinstance(nqph_symbol,
                       LFRicTypes("NumberOfQrPointsInXyDataSymbol"))
     assert isinstance(nqph_symbol.interface, ArgumentInterface)
     assert (nqph_symbol.interface.access ==
             kernel_interface._read_access.access)
     # nqp_z declared
-    nqpv_symbol = kernel_interface._symbol_table.lookup("nqp_z")
+    nqpv_symbol = kernel_interface._symtab.lookup("nqp_z")
     assert isinstance(
         nqpv_symbol, LFRicTypes("NumberOfQrPointsInZDataSymbol"))
     assert isinstance(nqpv_symbol.interface, ArgumentInterface)
     assert (nqpv_symbol.interface.access ==
             kernel_interface._read_access.access)
     # diff basis declared and added to argument list
-    diff_basis_symbol = kernel_interface._symbol_table.lookup(
+    diff_basis_symbol = kernel_interface._symtab.lookup(
         "diff_basis_w2_qr_xyoz")
     assert isinstance(
         diff_basis_symbol, LFRicTypes("DiffBasisFunctionQrXyozDataSymbol"))
@@ -805,7 +805,7 @@ def test_ref_element_properties():
     '''
     kernel_interface = KernelInterface(None)
     kernel_interface.ref_element_properties()
-    assert len(kernel_interface._symbol_table.symbols) == 0
+    assert len(kernel_interface._symtab.symbols) == 0
     assert len(kernel_interface._arglist) == 0
 
 
@@ -819,7 +819,7 @@ def test_mesh_properties():
     '''
     kernel_interface = KernelInterface(None)
     kernel_interface.mesh_properties()
-    assert len(kernel_interface._symbol_table.symbols) == 0
+    assert len(kernel_interface._symtab.symbols) == 0
     assert len(kernel_interface._arglist) == 0
 
 
@@ -839,7 +839,7 @@ def test_quad_rule_xyoz():
     kernel_interface.quad_rule()
 
     # nqp_xy declared and added to argument list
-    nqph_symbol = kernel_interface._symbol_table.lookup("nqp_xy")
+    nqph_symbol = kernel_interface._symtab.lookup("nqp_xy")
     assert isinstance(
         nqph_symbol, LFRicTypes("NumberOfQrPointsInXyDataSymbol"))
     assert isinstance(nqph_symbol.interface, ArgumentInterface)
@@ -847,7 +847,7 @@ def test_quad_rule_xyoz():
             kernel_interface._read_access.access)
     assert kernel_interface._arglist[-4] is nqph_symbol
     # nqp_z declared and added to argument list
-    nqpv_symbol = kernel_interface._symbol_table.lookup("nqp_z")
+    nqpv_symbol = kernel_interface._symtab.lookup("nqp_z")
     assert isinstance(
         nqpv_symbol, LFRicTypes("NumberOfQrPointsInZDataSymbol"))
     assert isinstance(nqpv_symbol.interface, ArgumentInterface)
@@ -855,7 +855,7 @@ def test_quad_rule_xyoz():
             kernel_interface._read_access.access)
     assert kernel_interface._arglist[-3] is nqpv_symbol
     # weights_xy declared and added to argument list
-    weightsh_symbol = kernel_interface._symbol_table.lookup("weights_xy")
+    weightsh_symbol = kernel_interface._symtab.lookup("weights_xy")
     assert isinstance(
         weightsh_symbol, LFRicTypes("QrWeightsInXyDataSymbol"))
     assert isinstance(weightsh_symbol.interface, ArgumentInterface)
@@ -866,7 +866,7 @@ def test_quad_rule_xyoz():
     assert isinstance(weightsh_symbol.shape[0].upper, Reference)
     assert weightsh_symbol.shape[0].upper.symbol is nqph_symbol
     # weights_z declared and added to argument list
-    weightsz_symbol = kernel_interface._symbol_table.lookup("weights_z")
+    weightsz_symbol = kernel_interface._symtab.lookup("weights_z")
     assert isinstance(
         weightsz_symbol, LFRicTypes("QrWeightsInZDataSymbol"))
     assert isinstance(weightsz_symbol.interface, ArgumentInterface)
@@ -894,14 +894,14 @@ def test_quad_rule_face():
     kernel_interface.quad_rule()
 
     # nfaces declared and added to argument list
-    nfaces_symbol = kernel_interface._symbol_table.lookup("nfaces")
+    nfaces_symbol = kernel_interface._symtab.lookup("nfaces")
     assert isinstance(nfaces_symbol, LFRicTypes("NumberOfFacesDataSymbol"))
     assert isinstance(nfaces_symbol.interface, ArgumentInterface)
     assert (nfaces_symbol.interface.access ==
             kernel_interface._read_access.access)
     assert kernel_interface._arglist[-3] is nfaces_symbol
     # nqp declared and added to argument list
-    nqp_symbol = kernel_interface._symbol_table.lookup("nqp_faces")
+    nqp_symbol = kernel_interface._symtab.lookup("nqp_faces")
     assert isinstance(
         nqp_symbol, LFRicTypes("NumberOfQrPointsInFacesDataSymbol"))
     assert isinstance(nqp_symbol.interface, ArgumentInterface)
@@ -909,7 +909,7 @@ def test_quad_rule_face():
             kernel_interface._read_access.access)
     assert kernel_interface._arglist[-2] is nqp_symbol
     # weights declared and added to argument list
-    weights_symbol = kernel_interface._symbol_table.lookup("weights_faces")
+    weights_symbol = kernel_interface._symtab.lookup("weights_faces")
     assert isinstance(weights_symbol,
                       LFRicTypes("QrWeightsInFacesDataSymbol"))
     assert isinstance(weights_symbol.interface, ArgumentInterface)
@@ -937,14 +937,14 @@ def test_quad_rule_edge():
     kernel_interface.quad_rule()
 
     # nedges declared and added to argument list
-    nedges_symbol = kernel_interface._symbol_table.lookup("nedges")
+    nedges_symbol = kernel_interface._symtab.lookup("nedges")
     assert isinstance(nedges_symbol, LFRicTypes("NumberOfEdgesDataSymbol"))
     assert isinstance(nedges_symbol.interface, ArgumentInterface)
     assert (nedges_symbol.interface.access ==
             kernel_interface._read_access.access)
     assert kernel_interface._arglist[-3] is nedges_symbol
     # nqp declared and added to argument list
-    nqp_symbol = kernel_interface._symbol_table.lookup("nqp_edges")
+    nqp_symbol = kernel_interface._symtab.lookup("nqp_edges")
     assert isinstance(
         nqp_symbol, LFRicTypes("NumberOfQrPointsInEdgesDataSymbol"))
     assert isinstance(nqp_symbol.interface, ArgumentInterface)
@@ -952,7 +952,7 @@ def test_quad_rule_edge():
             kernel_interface._read_access.access)
     assert kernel_interface._arglist[-2] is nqp_symbol
     # weights declared and added to argument list
-    weights_symbol = kernel_interface._symbol_table.lookup("weights_edges")
+    weights_symbol = kernel_interface._symtab.lookup("weights_edges")
     assert isinstance(weights_symbol,
                       LFRicTypes("QrWeightsInEdgesDataSymbol"))
     assert isinstance(weights_symbol.interface, ArgumentInterface)
diff --git a/src/psyclone/tests/psyir/frontend/fparser2_intrinsic_handler_test.py b/src/psyclone/tests/psyir/frontend/fparser2_intrinsic_handler_test.py
index afaefe3f8..01f2a3f43 100644
--- a/src/psyclone/tests/psyir/frontend/fparser2_intrinsic_handler_test.py
+++ b/src/psyclone/tests/psyir/frontend/fparser2_intrinsic_handler_test.py
@@ -50,7 +50,7 @@ from psyclone.errors import InternalError
 from psyclone.psyir.frontend.fparser2 import (
     Fparser2Reader, _get_arg_names, _canonicalise_minmaxsum)
 from psyclone.psyir.nodes import (
-    Schedule, Assignment, Reference, IntrinsicCall, CodeBlock)
+    Schedule, Assignment, Reference, IntrinsicCall, Literal, CodeBlock)
 from psyclone.psyir.symbols import (
     REAL_TYPE, DataSymbol, UnknownFortranType, INTEGER_TYPE, SymbolTable,
     ArrayType, RoutineSymbol, AutomaticInterface)
@@ -378,7 +378,9 @@ def test_handling_nested_intrinsic():
     # Declare all the symbols needed by the test code.
     symtab = fake_parent.symbol_table
     symtab.add(DataSymbol("jk", INTEGER_TYPE))
-    symtab.add(DataSymbol("wp", INTEGER_TYPE))
+    symtab.add(DataSymbol("wp", INTEGER_TYPE,
+                          is_constant=True,
+                          initial_value=Literal("4", INTEGER_TYPE)))
     symtab.add(DataSymbol("rcpi", REAL_TYPE))
     symtab.add(DataSymbol("rLfus", REAL_TYPE))
     symtab.add(DataSymbol("ze_z", REAL_TYPE))
diff --git a/src/psyclone/tests/psyir/frontend/fparser2_kind_params_test.py b/src/psyclone/tests/psyir/frontend/fparser2_kind_params_test.py
index bec76c60c..49a297b44 100644
--- a/src/psyclone/tests/psyir/frontend/fparser2_kind_params_test.py
+++ b/src/psyclone/tests/psyir/frontend/fparser2_kind_params_test.py
@@ -48,7 +48,8 @@ from psyclone.psyir.frontend.fparser2 import (Fparser2Reader,
 from psyclone.psyir.nodes import KernelSchedule
 from psyclone.psyir.symbols import (DataSymbol, ScalarType, UnknownFortranType,
                                     RoutineSymbol, SymbolTable, Symbol,
-                                    DeferredType, ContainerSymbol)
+                                    DeferredType, ContainerSymbol,
+                                    UnresolvedInterface)
 
 
 def process_declarations(code):
@@ -161,7 +162,7 @@ def test_kind_param_deferredtype():
     ''' Check that the _kind_find_or_create() routine changes the type of
     a Symbol representing a kind parameter if it is of DeferredType. '''
     symbol_table = SymbolTable()
-    wp_sym = DataSymbol("wp", DeferredType())
+    wp_sym = DataSymbol("wp", DeferredType(), interface=UnresolvedInterface())
     symbol_table.add(wp_sym)
     kind_sym = _kind_find_or_create("wp", symbol_table)
     assert kind_sym is wp_sym
@@ -175,7 +176,7 @@ def test_wrong_type_kind_param():
     type.
 
     '''
-    fake_parent, _ = process_declarations("integer :: r_def\n"
+    fake_parent, _ = process_declarations("integer, parameter :: r_def=4\n"
                                           "real(kind=r_def) :: var2")
     r_def = fake_parent.symbol_table.lookup("r_def")
     # Monkeypatch this DataSymbol so that it appears to be a RoutineSymbol
diff --git a/src/psyclone/tests/psyir/frontend/fparser2_where_handler_test.py b/src/psyclone/tests/psyir/frontend/fparser2_where_handler_test.py
index 1952469ef..1b703c2c4 100644
--- a/src/psyclone/tests/psyir/frontend/fparser2_where_handler_test.py
+++ b/src/psyclone/tests/psyir/frontend/fparser2_where_handler_test.py
@@ -59,18 +59,23 @@ def process_where(code, fparser_cls, symbols=None):
     PSyIR and fparser2 parse trees.
 
     :param str code: Fortran code to process.
-    :param type fparser_cls: the fparser2 class to instantiate to \
+    :param type fparser_cls: the fparser2 class to instantiate to
                              represent the supplied Fortran.
-    :param symbols: list of symbol names that must be added to the symbol \
+    :param symbols: list of symbol names that must be added to the symbol
                     table before constructing the PSyIR.
-    :type symbols: list of str
+    :type symbols: List[str]
 
-    :returns: 2-tuple of a parent PSyIR Schedule and the created instance of \
+    :returns: 2-tuple of a parent PSyIR Schedule and the created instance of
               the requested fparser2 class.
-    :rtype: (:py:class:`psyclone.psyir.nodes.Schedule`, \
-             :py:class:`fparser.two.utils.Base`)
+    :rtype: Tuple[:py:class:`psyclone.psyir.nodes.Schedule`,
+                  :py:class:`fparser.two.utils.Base`]
     '''
     sched = Schedule()
+    # Always add the 'wp' kind parameter as this must have specific properties.
+    sched.symbol_table.new_symbol("wp", symbol_type=DataSymbol,
+                                  datatype=INTEGER_TYPE,
+                                  initial_value=Literal("8", INTEGER_TYPE),
+                                  is_constant=True)
     if symbols:
         for sym_name in symbols:
             sched.symbol_table.new_symbol(sym_name)
@@ -94,7 +99,7 @@ def test_where_broken_tree():
     fake_parent, fparser2spec = process_where(
         "WHERE (ptsu(:, :, :) /= 0._wp)\n"
         "  z1_st(:, :, :) = 1._wp / ptsu(:, :, :)\n"
-        "END WHERE\n", Fortran2003.Where_Construct, ["ptsu", "wp", "z1_st"])
+        "END WHERE\n", Fortran2003.Where_Construct, ["ptsu", "z1_st"])
     processor = Fparser2Reader()
     # Test with unexpected clause by adding an extra end-where statement
     assert isinstance(fparser2spec.content[-1], Fortran2003.End_Where_Stmt)
@@ -128,7 +133,7 @@ def test_elsewhere_broken_tree():
         "  z1_st(:, :, :) = 1._wp / ptsu(:, :, :)\n"
         "ELSE WHERE\n"
         "  z1_st(:, :, :) = 0._wp\n"
-        "END WHERE\n", Fortran2003.Where_Construct, ["ptsu", "wp", "z1_st"])
+        "END WHERE\n", Fortran2003.Where_Construct, ["ptsu", "z1_st"])
     processor = Fparser2Reader()
     # Insert an additional Elsewhere_Stmt
     assert isinstance(fparser2spec.content[-3], Fortran2003.Elsewhere_Stmt)
@@ -149,7 +154,7 @@ def test_missing_array_notation_expr(mask):
     fake_parent, _ = process_where(f"WHERE ({mask} /= 0._wp)\n"
                                    f"z1_st(:, :, :) = 1._wp / ptsu(:, :, :)\n"
                                    f"END WHERE\n", Fortran2003.Where_Construct,
-                                   ["ptsu", "wp", "z1_st"])
+                                   ["ptsu", "z1_st"])
     assert isinstance(fake_parent.children[0], CodeBlock)
 
 
@@ -161,7 +166,7 @@ def test_labelled_where():
     fake_parent, _ = process_where("100 WHERE (ptsu /= 0._wp)\n"
                                    "  z1_st(:, :, :) = 1._wp / ptsu(:, :, :)\n"
                                    "END WHERE\n", Fortran2003.Where_Construct,
-                                   ["ptsu", "wp", "z1_st"])
+                                   ["ptsu", "z1_st"])
     assert isinstance(fake_parent.children[0], CodeBlock)
 
 
@@ -174,7 +179,7 @@ def test_missing_array_notation_lhs():
     fake_parent, _ = process_where("WHERE (ptsu(:,:,:) /= 0._wp)\n"
                                    "  z1_st = 1._wp / ptsu(:, :, :)\n"
                                    "END WHERE\n", Fortran2003.Where_Construct,
-                                   ["ptsu", "wp", "z1_st"])
+                                   ["ptsu", "z1_st"])
     assert isinstance(fake_parent.children[0], CodeBlock)
 
 
@@ -186,7 +191,7 @@ def test_missing_array_notation_in_assign():
     fake_parent, _ = process_where("WHERE (ptsu(:,:,:) /= 0._wp)\n"
                                    "  z1_st = 1._wp\n"
                                    "END WHERE\n", Fortran2003.Where_Construct,
-                                   ["ptsu", "wp", "z1_st"])
+                                   ["ptsu", "z1_st"])
     assert isinstance(fake_parent.children[0], CodeBlock)
 
 
@@ -420,7 +425,7 @@ def test_elsewhere():
                                    "ELSEWHERE\n"
                                    "  z1_st(:, :, :) = 0._wp\n"
                                    "END WHERE\n", Fortran2003.Where_Construct,
-                                   ["ptsu", "wp", "z1_st"])
+                                   ["ptsu", "z1_st"])
     # This should become:
     #
     # if ptsu(ji,jj,jk) > 10._wp)then
diff --git a/src/psyclone/tests/psyir/symbols/datasymbol_test.py b/src/psyclone/tests/psyir/symbols/datasymbol_test.py
index 0b1c235c0..409a3d9b7 100644
--- a/src/psyclone/tests/psyir/symbols/datasymbol_test.py
+++ b/src/psyclone/tests/psyir/symbols/datasymbol_test.py
@@ -45,10 +45,10 @@ from fparser.two import Fortran2003
 
 from psyclone.psyir.symbols import (
     DataSymbol, ContainerSymbol, Symbol, DataTypeSymbol, AutomaticInterface,
-    ImportInterface, ArgumentInterface, UnresolvedInterface,
+    ImportInterface, ArgumentInterface, StaticInterface, UnresolvedInterface,
     ScalarType, ArrayType, REAL_SINGLE_TYPE, REAL_DOUBLE_TYPE, REAL4_TYPE,
     REAL8_TYPE, INTEGER_SINGLE_TYPE, INTEGER_DOUBLE_TYPE, INTEGER4_TYPE,
-    BOOLEAN_TYPE, CHARACTER_TYPE, DeferredType)
+    BOOLEAN_TYPE, CHARACTER_TYPE, DeferredType, UnknownFortranType)
 from psyclone.psyir.nodes import (Literal, Reference, BinaryOperation, Return,
                                   CodeBlock)
 
@@ -66,20 +66,22 @@ def test_datasymbol_initialisation():
     assert isinstance(DataSymbol('a', real_kind_type),
                       DataSymbol)
     assert isinstance(DataSymbol('a', INTEGER_SINGLE_TYPE), DataSymbol)
-    # Run-time constant.
+    # Run-time constant with no interface specified.
     sym = DataSymbol('a', REAL_DOUBLE_TYPE, is_constant=True,
                      initial_value=0.0)
     assert isinstance(sym, DataSymbol)
     assert sym.is_constant
-    # Defaults to StaticInterface for a run-time constant.
+    # Interface defaults to static for a constant with initial value.
     assert sym.is_static
-    # Run-time constant can only have StaticInterface or ImportInterface.
+
+    # Run-time constant can only have StaticInterface, UnresolvedInterface
+    # or ImportInterface.
     with pytest.raises(ValueError) as err:
         _ = DataSymbol('a', INTEGER_DOUBLE_TYPE, is_constant=True,
-                       initial_value=1, interface=AutomaticInterface())
-    assert ("A DataSymbol representing a constant must have either a "
-            "StaticInterface or an ImportInterface but 'a' has interface "
-            "'Automatic'" in str(err.value))
+                       interface=StaticInterface())
+    assert ("DataSymbol 'a' cannot be a constant because it does not have an "
+            "initial value or an import or unresolved interface."
+            in str(err.value))
 
     assert isinstance(DataSymbol('a', INTEGER4_TYPE),
                       DataSymbol)
@@ -164,8 +166,8 @@ def test_datasymbol_specialise_and_process_arguments():
     with pytest.raises(ValueError) as error:
         sym5.specialise(DataSymbol, datatype=INTEGER_SINGLE_TYPE,
                         is_constant=True)
-    assert ("DataSymbol 'symbol5' does not have an initial value set and is "
-            "not imported and therefore cannot be a constant."
+    assert ("DataSymbol 'symbol5' cannot be a constant because it does not "
+            "have an initial value or an import or unresolved interface."
             in str(error.value))
     # The absence of an initial value is permitted if the symbol has an
     # ImportInterface.
@@ -175,6 +177,22 @@ def test_datasymbol_specialise_and_process_arguments():
                     is_constant=True, interface=ImportInterface(csym))
     assert sym6.is_constant
     assert sym6.is_import
+    # It is also permitted if the symbol is of UnknownType.
+    sym7 = Symbol("symbol7")
+    sym7.specialise(DataSymbol,
+                    datatype=UnknownFortranType("integer :: symbol7 = 5"),
+                    is_constant=True)
+    assert sym7.is_constant
+    assert sym7.is_static
+    sym8 = Symbol("symbol8")
+    with pytest.raises(ValueError) as err:
+        sym8.specialise(
+            DataSymbol, datatype=INTEGER_SINGLE_TYPE, is_constant=True,
+            initial_value=Literal("1", INTEGER_SINGLE_TYPE),
+            interface=ArgumentInterface(ArgumentInterface.Access.READ))
+    assert ("Error setting initial value for symbol 'symbol8'. A DataSymbol "
+            "with an ArgumentInterface can not have an initial value."
+            in str(err.value))
 
 
 def test_datasymbol_can_be_printed():
@@ -302,8 +320,9 @@ def test_datasymbol_initial_value_setter_invalid():
     # is_constant specified but without an initial_value
     with pytest.raises(ValueError) as error:
         DataSymbol('a', BOOLEAN_TYPE, is_constant=True)
-    assert ("DataSymbol 'a' does not have an initial value set and is not "
-            "imported and therefore cannot be a constant" in str(error.value))
+    assert ("DataSymbol 'a' cannot be a constant because it does not have an "
+            "initial value or an import or unresolved interface."
+            in str(error.value))
 
 
 def test_datasymbol_is_constant():
@@ -323,8 +342,9 @@ def test_datasymbol_is_constant():
     sym.initial_value = None
     with pytest.raises(ValueError) as err:
         sym.is_constant = True
-    assert ("DataSymbol 'a' does not have an initial value set and is not "
-            "imported and therefore cannot be a constant." in str(err.value))
+    assert ("DataSymbol 'a' cannot be a constant because it does not have an "
+            "initial value or an import or unresolved interface."
+            in str(err.value))
 
 
 @pytest.mark.usefixtures("parser")
@@ -428,6 +448,10 @@ def test_datasymbol_copy():
             ScalarType.Precision.UNDEFINED)
     assert symbol.initial_value is None
 
+    new_new_symbol = new_symbol.copy()
+    assert new_symbol.initial_value == new_new_symbol.initial_value
+    assert new_symbol.initial_value is not new_new_symbol.initial_value
+
 
 def test_datasymbol_copy_properties():
     '''Test that the DataSymbol copy_properties method works as expected.'''
diff --git a/src/psyclone/tests/psyir/symbols/symbol_test.py b/src/psyclone/tests/psyir/symbols/symbol_test.py
index 2660e0b0f..1ac2d0aba 100644
--- a/src/psyclone/tests/psyir/symbols/symbol_test.py
+++ b/src/psyclone/tests/psyir/symbols/symbol_test.py
@@ -361,12 +361,29 @@ def test_symbol_resolve_deferred(monkeypatch):
     monkeypatch.setattr(bsym, "get_external_symbol",
                         lambda: DataSymbol("b", INTEGER_SINGLE_TYPE))
     new_sym = bsym.resolve_deferred()
-    # We should have a brand new symbol but with some of the properties
-    # of the original 'bsym' symbol.
-    assert new_sym is not bsym
+    # The symbol should be the same instance as before but with properties and
+    # type obtained from the other table.
+    assert new_sym is bsym
+    assert isinstance(new_sym, DataSymbol)
     assert new_sym.datatype == INTEGER_SINGLE_TYPE
     assert new_sym.visibility == Symbol.Visibility.PRIVATE
     assert new_sym.is_import
+    # Repeat but test when the imported Symbol is a parameter.
+    csym = Symbol("c", visibility=Symbol.Visibility.PRIVATE,
+                  interface=ImportInterface(other_container))
+    # Monkeypatch the get_external_symbol() method so that it just returns
+    # a new DataSymbol
+    monkeypatch.setattr(
+        csym, "get_external_symbol",
+        lambda: DataSymbol("c", INTEGER_SINGLE_TYPE,
+                           is_constant=True,
+                           initial_value=Literal("1", INTEGER_SINGLE_TYPE)))
+    new_sym = csym.resolve_deferred()
+    assert new_sym is csym
+    assert new_sym.datatype == INTEGER_SINGLE_TYPE
+    assert new_sym.is_import
+    assert new_sym.is_constant
+    assert new_sym.initial_value == Literal("1", INTEGER_SINGLE_TYPE)
 
 
 def test_symbol_array_handling():
diff --git a/src/psyclone/tests/psyir/transformations/kernel_transformation_test.py b/src/psyclone/tests/psyir/transformations/kernel_transformation_test.py
index dfcd8de7e..554098357 100644
--- a/src/psyclone/tests/psyir/transformations/kernel_transformation_test.py
+++ b/src/psyclone/tests/psyir/transformations/kernel_transformation_test.py
@@ -1,7 +1,7 @@
 # -----------------------------------------------------------------------------
 # BSD 3-Clause License
 #
-# Copyright (c) 2018-2022, Science and Technology Facilities Council.
+# Copyright (c) 2018-2023, Science and Technology Facilities Council.
 # All rights reserved.
 #
 # Redistribution and use in source and binary forms, with or without
@@ -48,9 +48,10 @@ from psyclone.domain.lfric.lfric_builtins import LFRicBuiltIn
 from psyclone.generator import GenerationError
 from psyclone.psyGen import Kern
 from psyclone.psyir.nodes import Routine, FileContainer
+from psyclone.psyir.symbols import DataSymbol, INTEGER_TYPE
 from psyclone.psyir.transformations import TransformationError
-from psyclone.transformations import ACCRoutineTrans, \
-    Dynamo0p3KernelConstTrans
+from psyclone.transformations import (ACCRoutineTrans,
+                                      Dynamo0p3KernelConstTrans)
 
 from psyclone.tests.gocean_build import GOceanBuild
 from psyclone.tests.lfric_build import LFRicBuild
@@ -59,7 +60,7 @@ from psyclone.tests.utilities import get_invoke
 
 def setup_module():
     '''
-    This setup routine ensures that and pre-exisiting Config object is
+    This setup routine ensures that any pre-exisiting Config object is
     wiped when this module is first entered and the teardown function below
     guarantees it for subsequent tests.  (Necessary when running tests in
     parallel.)
@@ -266,6 +267,129 @@ def test_new_same_kern_single(kernel_outputdir, monkeypatch):
     assert out_files == [new_kernels[1].module_name+".f90"]
 
 
+def test_accroutine_validate_wrong_node_type():
+    '''
+    Test that the validate() method of ACCRoutineTrans rejects a node of the
+    wrong type.
+
+    '''
+    rtrans = ACCRoutineTrans()
+    with pytest.raises(TransformationError) as err:
+        rtrans.apply(FileContainer("fred"))
+    assert ("The ACCRoutineTrans must be applied to a sub-class of Kern or "
+            "Routine but got 'FileContainer'" in str(err.value))
+
+
+def test_accroutine_validate_no_schedule(monkeypatch):
+    '''
+    Test that the validate() method of ACCRoutineTrans catches any errors
+    generated when attempting to get the PSyIR of a kernel.
+
+    '''
+    _, invoke = get_invoke("1_single_invoke.f90", api="dynamo0.3", idx=0)
+    sched = invoke.schedule
+    kernels = sched.walk(Kern)
+    kern = kernels[0]
+    # We monkeypatch the 'get_kernel_schedule' method of DynKern so that it
+    # just raises an exception.
+
+    def broken(_1_):
+        raise GenerationError("this is just a test")
+    monkeypatch.setattr(kern, "get_kernel_schedule", broken)
+
+    rtrans = ACCRoutineTrans()
+    with pytest.raises(TransformationError) as err:
+        rtrans.validate(kern)
+    assert ("Failed to create PSyIR for kernel 'testkern_code'. Cannot "
+            "transform such a kernel." in str(err.value))
+
+
+def test_accroutinetrans_validate_no_import(fortran_reader):
+    '''
+    Test the validate() method of ACCRoutineTrans rejects a kernel that
+    accesses imported data unless that data is known to be a compile-
+    time constant.
+
+    '''
+    code = '''\
+module my_mod
+  use other_mod, only: some_data
+contains
+  subroutine my_sub(arg)
+    integer :: arg
+    arg = arg + some_data
+  end subroutine my_sub
+end module my_mod'''
+    psyir = fortran_reader.psyir_from_source(code)
+    routine = psyir.walk(Routine)[0]
+    rtrans = ACCRoutineTrans()
+    with pytest.raises(TransformationError) as err:
+        rtrans.validate(routine)
+    assert ("Transformation Error: routine 'my_sub' accesses the symbol "
+            "'some_data: Symbol<Import(container='other_mod')>' which is "
+            "imported. If this symbol represents data "
+            "then it must first be converted to a routine argument using the "
+            "KernelImportsToArguments transformation." in str(err.value))
+    # Specialise the imported symbol and make it constant.
+    sym = psyir.children[0].symbol_table.lookup("some_data")
+    sym.specialise(DataSymbol, datatype=INTEGER_TYPE, is_constant=True)
+    # Validation should now pass.
+    rtrans.validate(routine)
+
+
+def test_accroutinetrans_validate_no_cblock(fortran_reader):
+    '''
+    Test the validate() method of ACCRoutineTrans rejects a kernel that
+    contains a CodeBlock.
+
+    '''
+    code = '''\
+module my_mod
+  use other_mod, only: some_data
+contains
+  subroutine my_sub(arg)
+    integer :: arg
+    write(*,*) arg + some_data
+  end subroutine my_sub
+end module my_mod'''
+    psyir = fortran_reader.psyir_from_source(code)
+    routine = psyir.walk(Routine)[0]
+    rtrans = ACCRoutineTrans()
+    with pytest.raises(TransformationError) as err:
+        rtrans.validate(routine)
+    assert ("Transformation Error: Cannot safely add 'ACC routine' to routine "
+            "'my_sub' because its PSyIR contains one or more CodeBlocks:\n"
+            "  WRITE(*, *)" in str(err.value))
+    assert ("You may use 'options={'force': True}' to override this check."
+            in str(err.value))
+    # Using 'force' will force the variable accesses to be checked.
+    with pytest.raises(TransformationError) as err:
+        rtrans.validate(routine, options={'force': True})
+    assert ("Transformation Error: routine 'my_sub' accesses the symbol "
+            "'some_data' within a CodeBlock and this symbol is imported. "
+            "'ACC routine' cannot be added to such a routine."
+            in str(err.value))
+
+
+def test_accroutinetrans_validate_no_call():
+    '''
+    Test the validate() method of ACCRoutineTrans rejects a kernel that calls
+    another routine.
+
+    '''
+    psy, invoke = get_invoke("1.15_invoke_kern_with_call.f90", api="dynamo0.3",
+                             idx=0)
+    sched = invoke.schedule
+    kernel = sched.coded_kernels()[0]
+    rtrans = ACCRoutineTrans()
+    with pytest.raises(TransformationError) as err:
+        rtrans.validate(kernel)
+    assert ("Kernel 'testkern_with_call_code' calls another routine "
+            "'call xyz2llr(coord(1), coord(2), coord(3), lon, lat, radius)' "
+            "and therefore cannot have 'ACC routine' added to it"
+            in str(err.value))
+
+
 def test_1kern_trans(kernel_outputdir):
     ''' Check that we generate the correct code when an invoke contains
     the same kernel more than once but only one of them is transformed. '''
diff --git a/src/psyclone/tests/test_files/dynamo0p3/1.15_invoke_kern_with_call.f90 b/src/psyclone/tests/test_files/dynamo0p3/1.15_invoke_kern_with_call.f90
new file mode 100644
index 000000000..c2f55886c
--- /dev/null
+++ b/src/psyclone/tests/test_files/dynamo0p3/1.15_invoke_kern_with_call.f90
@@ -0,0 +1,56 @@
+! -----------------------------------------------------------------------------
+! BSD 3-Clause License
+!
+! Copyright (c) 2023, 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. R. Porter, STFC Daresbury Lab
+
+program single_invoke
+
+  ! Description: single kernel specified in an invoke call where the kernel
+  ! itself contains a call.
+  use constants_mod, only: r_def
+  use field_mod,     only: field_type
+  use testkern_with_call_mod,  only: testkern_with_call_type
+
+  implicit none
+
+  type(field_type) :: geopotential
+  type(field_type), dimension(3) :: chi
+  real(r_def)      :: gravity = 9.8
+  real(r_def)      :: scaled_radius = 6.4E6
+
+  call invoke( testkern_with_call_type( geopotential,  &
+                                        chi,           &
+                                        gravity,       &
+                                        scaled_radius ) )
+
+end program single_invoke
diff --git a/src/psyclone/tests/test_files/dynamo0p3/testkern_with_call_mod.F90 b/src/psyclone/tests/test_files/dynamo0p3/testkern_with_call_mod.F90
new file mode 100644
index 000000000..6c2e98c1e
--- /dev/null
+++ b/src/psyclone/tests/test_files/dynamo0p3/testkern_with_call_mod.F90
@@ -0,0 +1,117 @@
+! -----------------------------------------------------------------------------
+! BSD 3-Clause License
+!
+! Copyright (c) 2023, 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. R. Porter, STFC Daresbury Lab
+
+! Example of a Kernel containing a call to another routine.
+module testkern_with_call_mod
+
+  use argument_mod
+  use fs_continuity_mod
+  use kernel_mod
+  use constants_mod
+
+  implicit none
+
+  type, extends(kernel_type) :: testkern_with_call_type
+  private
+  type(arg_type) :: meta_args(4) = (/                         &
+       arg_type(gh_field, gh_real, gh_write, w3),             &
+       arg_type(gh_field * 3, gh_real, gh_read, any_space_9), &
+       arg_type(gh_scalar, gh_real, gh_read),                 &
+       arg_type(gh_scalar, gh_real, gh_read)/)
+  TYPE(func_type) :: meta_funcs(1) = (/func_type(any_space_9, gh_basis)/)
+  INTEGER :: operates_on = cell_column
+  INTEGER :: gh_shape = gh_evaluator
+   contains
+     procedure, nopass :: code => testkern_with_call_code
+  end type testkern_with_call_type
+
+contains
+
+  subroutine testkern_with_call_code(nlayers, phi, chi_1, chi_2, chi_3, &
+       gravity, planet_radius, ndf_w3, undf_w3, map_w3, &
+       ndf_chi, undf_chi, map_chi, chi_basis)
+    integer(kind=i_def), intent(in) :: nlayers
+    integer(kind=i_def), intent(in) :: ndf_w3
+    integer(kind=i_def), intent(in) :: undf_w3
+    integer(kind=i_def), intent(in) :: ndf_chi
+    integer(kind=i_def), intent(in) :: undf_chi
+    integer(kind=i_def), dimension(ndf_w3), intent(in) :: map_w3
+    integer(kind=i_def), dimension(ndf_chi), intent(in) :: map_chi
+    real(kind=r_def), dimension(undf_w3), intent(inout) :: phi
+    real(kind=r_def), dimension(undf_chi), intent(in) :: chi_1
+    real(kind=r_def), dimension(undf_chi), intent(in) :: chi_2
+    real(kind=r_def), dimension(undf_chi), intent(in) :: chi_3
+    real(kind=r_def), dimension(1,ndf_chi,ndf_w3), intent(in) :: chi_basis
+    real(kind=r_def), intent(in) :: gravity
+    real(kind=r_def), intent(in) :: planet_radius
+    integer(kind=i_def) :: df
+    integer(kind=i_def) :: dfc
+    integer(kind=i_def) :: k
+    real(kind=r_def), dimension(3) :: coord
+    real(kind=r_def) :: lat
+    real(kind=r_def) :: lon
+    real(kind=r_def) :: radius
+    real(kind=r_def) :: shallow_switch
+    real(kind=r_def) :: height
+    real(kind=r_def), dimension(ndf_chi) :: chi_1_e
+    real(kind=r_def), dimension(ndf_chi) :: chi_2_e
+    real(kind=r_def), dimension(ndf_chi) :: chi_3_e
+    real(kind=r_def) :: phi_shallow
+    real(kind=r_def) :: phi_deep
+
+    do k = 0, nlayers-1
+       do dfc = 1, ndf_chi
+          chi_1_e(dfc) = chi_1( map_chi(dfc) + k)
+          chi_2_e(dfc) = chi_2( map_chi(dfc) + k)
+          chi_3_e(dfc) = chi_3( map_chi(dfc) + k)
+       end do
+
+       do df = 1, ndf_w3
+          coord(:) = 0.0_r_def
+          do dfc = 1, ndf_chi
+             coord(1) = coord(1) + chi_1_e(dfc)*chi_basis(1,dfc,df)
+             coord(2) = coord(2) + chi_2_e(dfc)*chi_basis(1,dfc,df)
+             coord(3) = coord(3) + chi_3_e(dfc)*chi_basis(1,dfc,df)
+          end do
+          call xyz2llr(coord(1), coord(2), coord(3), lon, lat, radius)
+          phi_shallow = gravity * radius
+          phi_deep = -gravity * planet_radius * &
+               (planet_radius / radius - 1.0_r_def)
+          phi(map_w3(df) + k) = shallow_switch * phi_shallow + &
+               (1.0_r_def - shallow_switch) * phi_deep
+       end do
+    end do
+
+  end subroutine testkern_with_call_code
+
+end module testkern_with_call_mod