Repository: stfc/PSyclone

Issue:
psyir support for arrays declared with indexed bounds
We need to add support for arrays declared like a(0:n-1). At the moment they are stored as unknown fortran type, however this caused psyad to fail in poly2d_w3_reconstruction_kernel_mod.F90

Discussion:

Patch:
diff --git a/changelog b/changelog
index b89c572f5..fd629466c 100644
--- a/changelog
+++ b/changelog
@@ -408,6 +408,9 @@
 
 	137) PR #967 towards #2132. Add GH workflows for self-hosted runner.
 
+	138) PR #2084 for #1801. Improve handling of assume-shape arrays with
+	specified bounds.
+
 release 2.3.1 17th of June 2022
 
 	1) PR #1747 for #1720. Adds support for If blocks to PSyAD.
diff --git a/src/psyclone/psyir/backend/fortran.py b/src/psyclone/psyir/backend/fortran.py
index d872c025e..ade86b2ab 100644
--- a/src/psyclone/psyir/backend/fortran.py
+++ b/src/psyclone/psyir/backend/fortran.py
@@ -460,7 +460,12 @@ class FortranWriter(LanguageWriter):
                 # Lower and upper bounds of an array declaration specified
                 # by literal constant, symbol reference, or computed dimension
                 lower_expression = self._visit(index.lower)
-                upper_expression = self._visit(index.upper)
+                if isinstance(index.upper, ArrayType.Extent):
+                    # We have an assumed-shape array (R514) where only the
+                    # lower bound is specified.
+                    upper_expression = ""
+                else:
+                    upper_expression = self._visit(index.upper)
                 if lower_expression == "1":
                     # Lower bound of 1 is the default in Fortran
                     dims.append(upper_expression)
diff --git a/src/psyclone/psyir/frontend/fparser2.py b/src/psyclone/psyir/frontend/fparser2.py
index 249691a3b..eaf4afa91 100644
--- a/src/psyclone/psyir/frontend/fparser2.py
+++ b/src/psyclone/psyir/frontend/fparser2.py
@@ -1289,6 +1289,9 @@ class Fparser2Reader():
 
             :raises NotImplementedError: if an unsupported form of array \
                                          bound is found.
+            :raises GenerationError: invalid Fortran declaration of an \
+                upper bound without an associated lower bound.
+
             '''
             if isinstance(bound_expr, Fortran2003.Int_Literal_Constant):
                 return Literal(bound_expr.items[0], INTEGER_TYPE)
@@ -1339,7 +1342,28 @@ class Fparser2Reader():
                                      Fortran2003.Assumed_Size_Spec)):
 
             if isinstance(dim, Fortran2003.Assumed_Shape_Spec):
-                shape.append(None)
+                # Assumed_Shape_Spec has two children holding the lower and
+                # upper bounds. It is valid Fortran (R514) to specify only the
+                # lower bound:
+                # ":" -> Assumed_Shape_Spec(None, None)
+                # "4:" -> Assumed_Shape_Spec(Int_Literal_Constant('4', None),
+                #                            None)
+                lower = (_process_bound(dim.children[0]) if dim.children[0]
+                         else None)
+                if dim.children[1]:
+                    upper = _process_bound(dim.children[1])
+                else:
+                    upper = ArrayType.Extent.ATTRIBUTE if lower else None
+
+                if upper and not lower:
+                    raise GenerationError(
+                        f"Found an assumed-shape array declaration with only "
+                        f"an upper bound ({dimensions}). This is not valid "
+                        f"Fortran.")
+                if upper:
+                    shape.append((lower, upper))
+                else:
+                    shape.append(None)
 
             elif isinstance(dim, Fortran2003.Explicit_Shape_Spec):
                 try:
diff --git a/src/psyclone/psyir/symbols/datatypes.py b/src/psyclone/psyir/symbols/datatypes.py
index 34189e7bf..fd37e8106 100644
--- a/src/psyclone/psyir/symbols/datatypes.py
+++ b/src/psyclone/psyir/symbols/datatypes.py
@@ -1,7 +1,7 @@
 # -----------------------------------------------------------------------------
 # BSD 3-Clause License
 #
-# Copyright (c) 2019-2022, Science and Technology Facilities Council.
+# Copyright (c) 2019-2023, Science and Technology Facilities Council.
 # All rights reserved.
 #
 # Redistribution and use in source and binary forms, with or without
@@ -497,17 +497,23 @@ class ArrayType(DataType):
         # pylint: disable=import-outside-toplevel
         from psyclone.psyir.nodes import DataNode, Reference
 
-        def _validate_data_node(dim_node):
+        def _validate_data_node(dim_node, is_lower_bound=False):
             '''
             Checks that the supplied DataNode is valid as part of an
             array-shape specification.
 
-            :param dim_node: the DataNode to check.
-            :type dim_node: :py:class:`psyclone.psyir.nodes.DataNode`
+            :param dim_node: the node to check for validity.
+            :type dim_node: int | :py:class:`psyclone.psyir.nodes.DataNode`
+            :param bool is_lower_bound: whether the supplied node represents \
+                                        the lower bound of an array shape.
 
             :raises TypeError: if the DataNode is not valid in this context.
 
             '''
+            if isinstance(dim_node, int):
+                # An integer value is always valid.
+                return
+
             # When issue #1799 is addressed then check that the
             # datatype returned is an int (or is unknown). For the
             # moment, just check that if the DataNode is a
@@ -532,35 +538,40 @@ class ArrayType(DataType):
                 # TODO #1089 - add check that any References are not to a
                 # local datasymbol that is not constant (as this would have
                 # no value).
+                return
+
+            if isinstance(dim_node, ArrayType.Extent):
+                if is_lower_bound:
+                    raise TypeError(
+                        "If present, the lower bound in an ArrayType 'shape' "
+                        "must represent a value but found ArrayType.Extent")
+                return
+
+            if isinstance(dim_node, DataNode):
+                return
+
+            raise TypeError(
+                f"DataSymbol shape-list elements can only be 'int', "
+                f"ArrayType.Extent, 'DataNode' or a 2-tuple thereof but found "
+                f"'{type(dim_node).__name__}'.")
 
+        # ---------------------------------------------------------------------
         if not isinstance(extents, list):
             raise TypeError(
                 f"ArrayType 'shape' must be of type list but found "
                 f"'{type(extents).__name__}'.")
 
         for dimension in extents:
-            if isinstance(dimension, DataNode):
-                _validate_data_node(dimension)
-            elif isinstance(dimension, tuple):
+            if isinstance(dimension, tuple):
                 if len(dimension) != 2:
                     raise TypeError(
                         f"A DataSymbol shape-list element specifying lower "
                         f"and upper bounds must be a 2-tuple but "
                         f"'{dimension}' has {len(dimension)} entries.")
-                for dim in dimension:
-                    if isinstance(dim, DataNode):
-                        _validate_data_node(dim)
-                    elif not isinstance(dim, int):
-                        raise TypeError(
-                            f"A DataSymbol shape-list element specifying lower"
-                            f" and upper bounds must be a 2-tuple containing "
-                            f"either int or DataNode entries but '{dimension}'"
-                            f" contains '{type(dim).__name__}'")
-            elif not isinstance(dimension, (self.Extent, int)):
-                raise TypeError(
-                    f"DataSymbol shape list elements can only be 'int', "
-                    f"ArrayType.Extent, 'DataNode' or tuple but found "
-                    f"'{type(dimension).__name__}'.")
+                _validate_data_node(dimension[0], is_lower_bound=True)
+                _validate_data_node(dimension[1])
+            else:
+                _validate_data_node(dimension)
 
     def __str__(self):
         '''


Test patch:
diff --git a/src/psyclone/tests/psyir/backend/fortran_test.py b/src/psyclone/tests/psyir/backend/fortran_test.py
index 41c9e89de..09c91ba60 100644
--- a/src/psyclone/tests/psyir/backend/fortran_test.py
+++ b/src/psyclone/tests/psyir/backend/fortran_test.py
@@ -651,6 +651,15 @@ def test_fw_gen_vardecl(fortran_writer):
     result = fortran_writer.gen_vardecl(symbol)
     assert result == "integer, dimension(2,2,:) :: dummy2\n"
 
+    # Assumed-size array with specified lower bound
+    array_type = ArrayType(INTEGER_TYPE,
+                           [2, 2, (-1, ArrayType.Extent.ATTRIBUTE)])
+    symbol = DataSymbol("dummy3", array_type,
+                        interface=ArgumentInterface(
+                            ArgumentInterface.Access.UNKNOWN))
+    result = fortran_writer.gen_vardecl(symbol)
+    assert result == "integer, dimension(2,2,-1:) :: dummy3\n"
+
     # Allocatable array
     array_type = ArrayType(REAL_TYPE, [ArrayType.Extent.DEFERRED,
                                        ArrayType.Extent.DEFERRED])
diff --git a/src/psyclone/tests/psyir/frontend/fparser2_test.py b/src/psyclone/tests/psyir/frontend/fparser2_test.py
index e9db704c6..ce79dfcd4 100644
--- a/src/psyclone/tests/psyir/frontend/fparser2_test.py
+++ b/src/psyclone/tests/psyir/frontend/fparser2_test.py
@@ -43,9 +43,10 @@ import fparser
 from fparser.common.readfortran import FortranStringReader
 from fparser.common.sourceinfo import FortranFormat
 from fparser.two import Fortran2003
-from fparser.two.Fortran2003 import Assignment_Stmt, Dimension_Attr_Spec, \
-    Execution_Part, Name, Return_Stmt, Specification_Part, \
-    Stmt_Function_Stmt, Subroutine_Subprogram, Type_Declaration_Stmt
+from fparser.two.Fortran2003 import (
+    Assignment_Stmt, Dimension_Attr_Spec, Execution_Part, Name, Return_Stmt,
+    Specification_Part, Stmt_Function_Stmt, Subroutine_Subprogram,
+    Type_Declaration_Stmt)
 from fparser.two.utils import walk
 
 from psyclone.errors import InternalError, GenerationError
@@ -1195,6 +1196,35 @@ def test_process_array_declarations():
     assert reference.symbol is ddim
     assert isinstance(reference.symbol.datatype, DeferredType)
 
+    # Extent given by range
+    reader = FortranStringReader("integer :: var(2:4)")
+    fparser2spec = Fortran2003.Specification_Part(reader).content[0]
+    processor.process_declarations(fake_parent, [fparser2spec], [])
+    symbol = fake_parent.symbol_table.lookup("var")
+    assert len(symbol.shape) == 1
+    assert symbol.datatype.shape[0].lower.value == "2"
+    assert symbol.datatype.shape[0].upper.value == "4"
+
+    reader = FortranStringReader("integer :: var1(2:ddim)")
+    fparser2spec = Fortran2003.Specification_Part(reader).content[0]
+    processor.process_declarations(fake_parent, [fparser2spec], [])
+    symbol = fake_parent.symbol_table.lookup("var1")
+    assert len(symbol.shape) == 1
+    assert isinstance(symbol.datatype.shape[0], ArrayType.ArrayBounds)
+    assert symbol.datatype.shape[0].lower.value == "2"
+    assert symbol.datatype.shape[0].upper.symbol is ddim
+
+    reader = FortranStringReader("integer :: var2(4:)")
+    fparser2spec = Fortran2003.Specification_Part(reader).content[0]
+    processor.process_declarations(fake_parent, [fparser2spec], [])
+    symbol = fake_parent.symbol_table.lookup("var2")
+    assert len(symbol.shape) == 1
+    # Shape should be an ArrayBounds with known lower bound and ATTRIBUTE
+    # upper.
+    assert isinstance(symbol.datatype.shape[0], ArrayType.ArrayBounds)
+    assert symbol.datatype.shape[0].lower.value == "4"
+    assert symbol.datatype.shape[0].upper == ArrayType.Extent.ATTRIBUTE
+
 
 @pytest.mark.usefixtures("f2008_parser")
 def test_process_not_supported_declarations():
@@ -1251,6 +1281,19 @@ def test_process_not_supported_declarations():
     l11sym = fake_parent.symbol_table.lookup("l11")
     assert isinstance(l11sym.datatype, UnknownFortranType)
 
+    # Assumed-size array with specified upper bound. fparser2 does spot that
+    # this is invalid so we have to break the parse tree it produces for
+    # a valid case.
+    reader = FortranStringReader("integer :: l12(:)")
+    fparser2spec = Fortran2003.Specification_Part(reader).content[0]
+    # Break the parse tree
+    aspec = walk(fparser2spec, Fortran2003.Assumed_Shape_Spec)[0]
+    aspec.items = (None, Fortran2003.Int_Literal_Constant('4', None))
+    with pytest.raises(GenerationError) as err:
+        processor.process_declarations(fake_parent, [fparser2spec], [])
+    assert ("assumed-shape array declaration with only an upper bound (: 4). "
+            "This is not valid Fortran" in str(err.value))
+
 
 def test_process_save_attribute_declarations(parser):
     ''' Test that the SAVE attribute in a declaration is supported when
diff --git a/src/psyclone/tests/psyir/symbols/datatype_test.py b/src/psyclone/tests/psyir/symbols/datatype_test.py
index 655330790..e07c3d287 100644
--- a/src/psyclone/tests/psyir/symbols/datatype_test.py
+++ b/src/psyclone/tests/psyir/symbols/datatype_test.py
@@ -1,7 +1,7 @@
 # -----------------------------------------------------------------------------
 # BSD 3-Clause License
 #
-# Copyright (c) 2020-2022, Science and Technology Facilities Council.
+# Copyright (c) 2020-2023, Science and Technology Facilities Council.
 # All rights reserved.
 #
 # Redistribution and use in source and binary forms, with or without
@@ -42,9 +42,10 @@ import pytest
 from psyclone.errors import InternalError
 from psyclone.psyir.nodes import Literal, BinaryOperation, Reference, \
     Container, KernelSchedule
-from psyclone.psyir.symbols import DataType, DeferredType, ScalarType, \
-    ArrayType, UnknownFortranType, DataSymbol, StructureType, NoType, \
-    INTEGER_TYPE, REAL_TYPE, Symbol, DataTypeSymbol, SymbolTable
+from psyclone.psyir.symbols import (
+    ArrayType, DataType, DeferredType, ScalarType, UnknownType,
+    UnknownFortranType, DataSymbol, StructureType, NoType,
+    INTEGER_TYPE, REAL_TYPE, Symbol, DataTypeSymbol, SymbolTable)
 
 
 # Abstract DataType class
@@ -262,6 +263,7 @@ def test_scalartype_immutable():
 
 
 # ArrayType class
+
 def test_arraytype():
     '''Test that the ArrayType class __init__ works as expected. Test the
     different dimension datatypes that are supported.'''
@@ -348,6 +350,16 @@ def test_arraytype_datatypesymbol():
     assert atype.precision is None
 
 
+def test_arraytype_unknowntype():
+    '''Test that we can construct an ArrayType when the type of the elements
+    is an UnknownType.'''
+    utype = UnknownFortranType("integer, pointer :: var")
+    atype = ArrayType(utype, [8])
+    assert isinstance(atype, ArrayType)
+    assert atype.datatype is utype
+    assert atype.precision is None
+
+
 def test_arraytype_invalid_shape():
     '''Test that the ArrayType class raises an exception when the shape
     argument is the wrong type.
@@ -385,9 +397,9 @@ def test_arraytype_invalid_shape_dimension_2():
     scalar_type = ScalarType(ScalarType.Intrinsic.REAL, 4)
     with pytest.raises(TypeError) as excinfo:
         _ = ArrayType(scalar_type, [None])
-    assert ("DataSymbol shape list elements can only be 'int', "
-            "ArrayType.Extent, 'DataNode' or tuple but found 'NoneType'."
-            in str(excinfo.value))
+    assert ("DataSymbol shape-list elements can only be 'int', "
+            "ArrayType.Extent, 'DataNode' or a 2-tuple thereof but found "
+            "'NoneType'." in str(excinfo.value))
 
 
 @pytest.mark.xfail(reason="issue #1089. Support for this check needs to be"
@@ -414,7 +426,8 @@ def test_arraytype_invalid_shape_dimension_3():
 def test_arraytype_invalid_shape_bounds():
     ''' Check that the ArrayType class raises the expected exception when
     one of the dimensions of the shape list is a tuple that does not contain
-    either an int or a DataNode or is not a scalar.'''
+    either an int or a DataNode or is not a scalar. Also test when an invalid
+    lower bound is specified.'''
     scalar_type = ScalarType(ScalarType.Intrinsic.REAL, 4)
     with pytest.raises(TypeError) as excinfo:
         _ = ArrayType(scalar_type, [(1, 4, 1)])
@@ -423,19 +436,23 @@ def test_arraytype_invalid_shape_bounds():
             str(excinfo.value))
     with pytest.raises(TypeError) as excinfo:
         _ = ArrayType(scalar_type, [(1, None)])
-    assert ("A DataSymbol shape-list element specifying lower and upper bounds"
-            " must be a 2-tuple containing either int or DataNode entries but "
-            "'(1, None)' contains 'NoneType'" in str(excinfo.value))
+    assert ("DataSymbol shape-list elements can only be 'int', ArrayType."
+            "Extent, 'DataNode' or a 2-tuple thereof but found 'NoneType'." in
+            str(excinfo.value))
     with pytest.raises(TypeError) as excinfo:
         _ = ArrayType(scalar_type, [(None, 1)])
-    assert ("A DataSymbol shape-list element specifying lower and upper bounds"
-            " must be a 2-tuple containing either int or DataNode entries but "
-            "'(None, 1)' contains 'NoneType'" in str(excinfo.value))
+    assert ("DataSymbol shape-list elements can only be 'int', ArrayType."
+            "Extent, 'DataNode' or a 2-tuple thereof but found 'NoneType'" in
+            str(excinfo.value))
     with pytest.raises(TypeError) as excinfo:
         _ = ArrayType(scalar_type, [10, (None, 1)])
-    assert ("A DataSymbol shape-list element specifying lower and upper bounds"
-            " must be a 2-tuple containing either int or DataNode entries but "
-            "'(None, 1)' contains 'NoneType'" in str(excinfo.value))
+    assert ("DataSymbol shape-list elements can only be 'int', ArrayType."
+            "Extent, 'DataNode' or a 2-tuple thereof but found 'NoneType'" in
+            str(excinfo.value))
+    with pytest.raises(TypeError) as err:
+        _ = ArrayType(scalar_type, [(ArrayType.Extent.ATTRIBUTE, 15)])
+    assert ("If present, the lower bound in an ArrayType 'shape' must "
+            "represent a value but found ArrayType.Extent" in str(err.value))
     scalar_type = ScalarType(ScalarType.Intrinsic.REAL, 4)
     symbol = DataSymbol("fred", scalar_type, constant_value=3.0)
     with pytest.raises(TypeError) as excinfo:
@@ -532,6 +549,23 @@ def test_arraytype_eq():
     assert data_type1 != ArrayType(iscalar_type, [10, 10])
 
 
+# UnknownType tests
+
+def test_unknown_type_declaration_setter():
+    '''Test the declaration setter/getter in UnknownType. We have to subclass
+    UnknownType as it is virtual.'''
+
+    class HardType(UnknownType):
+        '''Concrete sub-class of UnknownType for testing.'''
+        def __str__(self):
+            return "HardType"
+
+    htype = HardType("real var2;")
+    assert htype.declaration == "real var2;"
+    htype.declaration = "real var;"
+    assert htype.declaration == "real var;"
+
+
 # UnknownFortranType tests
 
 def test_unknown_fortran_type():
diff --git a/src/psyclone/tests/psyir/transformations/arrayrange2loop_trans_test.py b/src/psyclone/tests/psyir/transformations/arrayrange2loop_trans_test.py
index 8bd6fcd3c..8fa81b70f 100644
--- a/src/psyclone/tests/psyir/transformations/arrayrange2loop_trans_test.py
+++ b/src/psyclone/tests/psyir/transformations/arrayrange2loop_trans_test.py
@@ -36,7 +36,6 @@
 '''Module containing tests for the ArrayRange2LoopTrans
 transformation.'''
 
-from __future__ import absolute_import
 
 import pytest
 
@@ -112,12 +111,10 @@ def create_array_x(symbol_table):
     elements in the array dimension using a range node. In Fortran
     array notation this is "x(:)".
 
-    :param symbol_table: the symbol table to which we add the array \
-        symbol.
+    :param symbol_table: the symbol table to which we add the array symbol.
     :type symbol_table: :py:class:`psyclone.psyir.symbol.SymbolTable`
 
-    :returns: an array reference that accesses all elements of the \
-        array "x".
+    :returns: an array reference that accesses all elements of the array "x".
     :rtype: :py:class:`psyclone.psyir.nodes.ArrayReference`
 
     '''
@@ -127,8 +124,8 @@ def create_array_x(symbol_table):
 
 
 def create_array_y(symbol_table):
-    '''Utility routine that creates and returns an array reference to a
-    two-dimensional array "y". The array reference accesses all
+    '''Utility routine that creates and returns an array reference to an
+    assumed-size, two-dimensional array "y". The array reference accesses all
     elements in the 2nd dimension of the array using a range node and
     the n'th element of the 1st dimension. In Fortran array notation
     this is "y(n,:)".
@@ -143,7 +140,10 @@ def create_array_y(symbol_table):
     :rtype: :py:class:`psyclone.psyir.nodes.ArrayReference`
 
     '''
-    array_symbol = DataSymbol("y", ArrayType(REAL_TYPE, [10, 10]))
+    array_symbol = DataSymbol("y",
+                              ArrayType(REAL_TYPE,
+                                        [(10, ArrayType.Extent.ATTRIBUTE),
+                                         10]))
     symbol_table.add(array_symbol)
     return ArrayReference.create(array_symbol,
                                  [Reference(symbol_table.lookup("n")),
diff --git a/src/psyclone/tests/psyir/transformations/inline_trans_test.py b/src/psyclone/tests/psyir/transformations/inline_trans_test.py
index a481b0d44..a0a8e404f 100644
--- a/src/psyclone/tests/psyir/transformations/inline_trans_test.py
+++ b/src/psyclone/tests/psyir/transformations/inline_trans_test.py
@@ -565,18 +565,24 @@ def test_apply_struct_local_limits_routine(fortran_reader, fortran_writer,
         f"{MY_TYPE}"
         f"contains\n"
         f"  subroutine run_it()\n"
+        f"    real zarg(13)\n"
+        f"    real ardvarkarg(4:10)\n"
         f"    type(my_type) :: var_list(10)\n"
         f"    type(my_type), dimension(2:8) :: varat2\n"
-        f"    call sub3(var_list(:), 5, 6)\n"
-        f"    call sub3(varat2(:), 5, 6)\n"
-        f"    call sub3(varat2(3:7), 4, 5)\n"
+        f"    call sub3(var_list(:), 5, 6, zarg)\n"
+        f"    call sub3(varat2(:), 5, 6, ardvarkarg)\n"
+        f"    call sub3(varat2(3:7), 4, 5, zarg(2:))\n"
         f"  end subroutine run_it\n"
-        f"  subroutine sub3(y, start, stop)\n"
+        f"  subroutine sub3(y, start, stop, z)\n"
         f"    type(my_type), dimension(4:6) :: y\n"
+        # TODO #2125 - if 'start' is used for the lower bound instead of a
+        # literal then the inlined code is incorrect.
+        f"    real, dimension(3:) :: z\n"
         f"    integer :: start, stop\n"
         f"    y(:)%data(2) = 2.0\n"
         f"    y(4:5)%local%nx = 4\n"
         f"    y(start:stop+1)%local%nx = -3\n"
+        f"    z(start+1) = 8.0\n"
         f"  end subroutine sub3\n"
         f"end module test_mod\n")
     psyir = fortran_reader.psyir_from_source(code)
@@ -588,12 +594,16 @@ def test_apply_struct_local_limits_routine(fortran_reader, fortran_writer,
     # explicit bounds so these have to be taken into account.
     assert "var_list(4 - 4 + 1:6 - 4 + 1)%data(2) = 2.0" in output
     assert "var_list(4 - 4 + 1:5 - 4 + 1)%local%nx = 4" in output
+    # Element 3 in routine corresponds to element 1 in caller
+    assert "zarg(5 + 1 - 3 + 1) = 8.0" in output
     # Element 4 in routine corresponds to element 1 in caller
     assert "var_list(5 - 4 + 1:6 + 1 - 4 + 1)%local%nx = -3" in output
     # Custom limits  in declarations for both formal and actual.
     # Element 4 in routine corresponds to element 2 in caller.
     assert "varat2(4 - 4 + 2:6 - 4 + 2)%data(2) = 2.0\n" in output
     assert "varat2(4 - 4 + 2:5 - 4 + 2)%local%nx = 4\n" in output
+    # Element 3 in routine corresponds to element 4 in caller.
+    assert "ardvarkarg(5 + 1 - 3 + 4) = 8.0" in output
     # A local access of '1' corresponds to the start of the array which is
     # index '2' at the call site.
     assert "varat2(5 - 4 + 2:6 + 1 - 4 + 2)%local%nx = -3\n" in output