Repository: stfc/PSyclone (python)

Issue:
InlineTrans should ignore formal arguments when checking for symbol clashes
 As reported by Hugo, the following code:

    def test_apply_argument_clash(fortran_reader, fortran_writer):
        '''
        Check that the dummy arguments to the inlined routine are not included
        when checking for clashes (since they will be replaced by the formal
        arguments to the call).
        '''

        code_clash = """
      subroutine run_it()
        integer :: i
        integer :: Istr
        real :: a(10)

        do i=Istr,10
          a(i) = 1.0
        end do

        call sub(Istr)
      end subroutine run_it

      subroutine sub(Istr)
        integer :: Istr
        real :: x
        x = 2.0*x
        call sub_sub(Istr)
      end subroutine sub

      subroutine sub_sub(Istr)
        integer :: i
        integer :: Istr
        real :: b(10)

        do i=Istr,10
          b(i) = 1.0
        end do
      end subroutine sub_sub"""

        psyir = fortran_reader.psyir_from_source(code_clash)
        call = psyir.walk(Call)[1]
        inline_trans = InlineTrans()
        inline_trans.apply(call)

will fail because of the check on whether the symbol table of the routine `sub_sub` can be merged with the table of the routine `sub`. This is because of `istr` but actually, we should be ignoring that because, as a formal argument to the routine being inlined, it will be replaced anyway. Hugo has kindly provided a patch for this which I'll use.

Pull request:
(Closes #2507) extend InlineTrans to ignore clash between formal arguments

Patch:
diff --git a/changelog b/changelog
index 5112b5491..8d9a9478e 100644
--- a/changelog
+++ b/changelog
@@ -11,6 +11,9 @@
 
 	5) PR #2506. Update test and integration test dependencies.
 
+	6) PR #2508 for #2507. Add a symbols_to_ignore argument to some
+	symbol_table operations and use it to fix inline transformation issues.
+
 release 2.5.0 14th of February 2024
 
 	1) PR #2199 for #2189. Fix bugs with missing maps in enter data
diff --git a/src/psyclone/psyir/backend/fortran.py b/src/psyclone/psyir/backend/fortran.py
index 630870d64..43888bb5b 100644
--- a/src/psyclone/psyir/backend/fortran.py
+++ b/src/psyclone/psyir/backend/fortran.py
@@ -1183,7 +1183,15 @@ class FortranWriter(LanguageWriter):
             whole_routine_scope = type(node.symbol_table)()
 
             for schedule in node.walk(Schedule):
-                whole_routine_scope.merge(schedule.symbol_table)
+                sched_table = schedule.symbol_table
+                # We can't declare a routine inside itself so make sure we
+                # skip any RoutineSymbol representing this routine.
+                try:
+                    rsym = sched_table.lookup_with_tag("own_routine_symbol")
+                    skip = [rsym] if isinstance(rsym, RoutineSymbol) else []
+                except KeyError:
+                    skip = []
+                whole_routine_scope.merge(sched_table, skip)
 
             # Replace the symbol table
             node.symbol_table.detach()
diff --git a/src/psyclone/psyir/symbols/symbol_table.py b/src/psyclone/psyir/symbols/symbol_table.py
index 2a7f00bec..cb7f3fd6e 100644
--- a/src/psyclone/psyir/symbols/symbol_table.py
+++ b/src/psyclone/psyir/symbols/symbol_table.py
@@ -42,6 +42,7 @@
 # pylint: disable=too-many-lines
 
 from collections import OrderedDict
+from collections.abc import Iterable
 import inspect
 import copy
 
@@ -561,21 +562,33 @@ class SymbolTable():
 
         self._symbols[key] = new_symbol
 
-    def check_for_clashes(self, other_table):
+    def check_for_clashes(self, other_table, symbols_to_skip=()):
         '''
         Checks the symbols in the supplied table against those in
         this table. If there is a name clash that cannot be resolved by
-        renaming then a SymbolError is raised.
+        renaming then a SymbolError is raised. Any symbols appearing
+        in `symbols_to_skip` are excluded from the checks.
 
         :param other_table: the table for which to check for clashes.
         :type other_table: :py:class:`psyclone.psyir.symbols.SymbolTable`
-
-        :raises SymbolError: if there would be an unresolvable name clash \
+        :param symbols_to_skip: an optional list of symbols to exclude from
+            the check.
+        :type symbols_to_skip: Iterable[
+            :py:class:`psyclone.psyir.symbols.Symbol`]
+
+        :raises TypeError: if symbols_to_skip is supplied but is not an
+            instance of Iterable.
+        :raises SymbolError: if there would be an unresolvable name clash
             when importing symbols from `other_table` into this table.
 
         '''
+        if not isinstance(symbols_to_skip, Iterable):
+            raise TypeError(
+                f"check_for_clashes: 'symbols_to_skip' must be an instance of "
+                f"Iterable but got '{type(symbols_to_skip).__name__}'")
+
         for other_sym in other_table.symbols:
-            if other_sym.name not in self:
+            if other_sym.name not in self or other_sym in symbols_to_skip:
                 continue
             # We have a name clash.
             this_sym = self.lookup(other_sym.name)
@@ -617,7 +630,7 @@ class SymbolTable():
         (This is a preliminary step to adding all symbols from other_table to
         this table.)
 
-        :param other_table: the symbol table from which to take container \
+        :param other_table: the symbol table from which to take container
                             symbols.
         :type other_table: :py:class:`psyclone.psyir.symbols.SymbolTable`
 
@@ -666,38 +679,25 @@ class SymbolTable():
                         self.lookup(csym.name),
                         orig_name=isym.interface.orig_name)
 
-    def _add_symbols_from_table(self, other_table, include_arguments=True):
+    def _add_symbols_from_table(self, other_table, symbols_to_skip=()):
         '''
         Takes symbols from the supplied symbol table and adds them to this
-        table. _add_container_symbols_from_table() must have been called
+        table (unless they appear in `symbols_to_skip`).
+        _add_container_symbols_from_table() must have been called
         before this method in order to handle any Container Symbols and update
         those Symbols imported from them.
 
         :param other_table: the symbol table from which to add symbols.
         :type other_table: :py:class:`psyclone.psyir.symbols.SymbolTable`
-        :param bool include_arguments: whether or not to include symbols that \
-                                       are routine arguments.
+        :param symbols_to_skip: an optional list of symbols to exclude from
+                                the merge.
+        :type symbols_to_skip: Iterable[
+            :py:class:`psyclone.psyir.symbols.Symbol`]
 
-        :raises InternalError: if an imported symbol is found that has not \
+        :raises InternalError: if an imported symbol is found that has not
             already been updated to refer to a Container in this table.
 
         '''
-        if include_arguments:
-            symbols_to_skip = []
-        else:
-            symbols_to_skip = other_table.argument_list[:]
-
-        try:
-            # In the case where the 'other_table' belongs to a routine,
-            # we don't want or need the symbol representing that routine.
-            rsym = other_table.lookup_with_tag("own_routine_symbol")
-            if isinstance(rsym, RoutineSymbol):
-                # We only want to skip RoutineSymbols, not DataSymbols (which
-                # we may have if we have a Fortran function).
-                symbols_to_skip.append(rsym)
-        except KeyError:
-            pass
-
         for old_sym in other_table.symbols:
 
             if old_sym in symbols_to_skip or isinstance(old_sym,
@@ -732,32 +732,35 @@ class SymbolTable():
                     other_table.rename_symbol(old_sym, new_name)
                     self.add(old_sym)
 
-    def merge(self, other_table, include_arguments=True):
+    def merge(self, other_table, symbols_to_skip=()):
         '''Merges all of the symbols found in `other_table` into this
         table. Symbol objects in *either* table may be renamed in the
         event of clashes.
 
-        If `other_table` belongs to a Routine and contains a symbol with the
-        same name as the Routine (i.e. a function in Fortran) then that symbol
-        is *not* added to this symbol table. Also, if `include_arguments` is
-        False then any Symbols representing formal routine arguments are
-        excluded.
+        Any Symbols appearing in `symbols_to_skip` are excluded.
 
         :param other_table: the symbol table from which to add symbols.
         :type other_table: :py:class:`psyclone.psyir.symbols.SymbolTable`
-        :param bool include_arguments: whether to include Symbols that \
-            represent routine arguments.
+        :param symbols_to_skip: an optional list of Symbols to exclude from
+                                the merge.
+        :type symbols_to_skip: Iterable[
+            :py:class:`psyclone.psyir.symbols.Symbol`]
 
         :raises TypeError: if `other_table` is not a SymbolTable.
+        :raises TypeError: if `symbols_to_skip` is not an Iterable.
         :raises SymbolError: if name clashes prevent the merge.
 
         '''
         if not isinstance(other_table, SymbolTable):
             raise TypeError(f"SymbolTable.merge() expects a SymbolTable "
                             f"instance but got '{type(other_table).__name__}'")
-
+        if not isinstance(symbols_to_skip, Iterable):
+            raise TypeError(
+                f"SymbolTable.merge() expects 'symbols_to_skip' to be an "
+                f"Iterable but got '{type(symbols_to_skip).__name__}'")
         try:
-            self.check_for_clashes(other_table)
+            self.check_for_clashes(other_table,
+                                   symbols_to_skip=symbols_to_skip)
         except SymbolError as err:
             raise SymbolError(
                 f"Cannot merge {other_table.view()} with {self.view()} due to "
@@ -767,8 +770,9 @@ class SymbolTable():
         self._add_container_symbols_from_table(other_table)
 
         # Copy each Symbol from the supplied table into this one, excluding
-        # ContainerSymbols and, optionally, those that represent formal args.
-        self._add_symbols_from_table(other_table, include_arguments)
+        # ContainerSymbols and any listed in `symbols_to_skip`.
+        self._add_symbols_from_table(other_table,
+                                     symbols_to_skip=symbols_to_skip)
 
     def swap_symbol_properties(self, symbol1, symbol2):
         '''Swaps the properties of symbol1 and symbol2 apart from the symbol
diff --git a/src/psyclone/psyir/transformations/inline_trans.py b/src/psyclone/psyir/transformations/inline_trans.py
index bf27c9b4d..255f9213e 100644
--- a/src/psyclone/psyir/transformations/inline_trans.py
+++ b/src/psyclone/psyir/transformations/inline_trans.py
@@ -46,7 +46,8 @@ from psyclone.psyir.nodes import (
 from psyclone.psyir.nodes.array_mixin import ArrayMixin
 from psyclone.psyir.symbols import (
     ArgumentInterface, ArrayType, DataSymbol, UnresolvedType, INTEGER_TYPE,
-    StaticInterface, Symbol, SymbolError, UnknownInterface, UnsupportedType)
+    RoutineSymbol, StaticInterface, Symbol, SymbolError, UnknownInterface,
+    UnsupportedType)
 from psyclone.psyir.transformations.reference2arrayrange_trans import (
     Reference2ArrayRangeTrans)
 from psyclone.psyir.transformations.transformation_error import (
@@ -159,7 +160,9 @@ class InlineTrans(Transformation):
 
         # Shallow copy the symbols from the routine into the table at the
         # call site.
-        table.merge(routine_table, include_arguments=False)
+        table.merge(routine_table,
+                    symbols_to_skip=self._symbols_to_skip(routine_table))
+
         # When constructing new references to replace references to formal
         # args, we need to know whether any of the actual arguments are array
         # accesses. If they use 'array notation' (i.e. represent a whole array)
@@ -213,7 +216,7 @@ class InlineTrans(Transformation):
                 idx += 1
                 parent.addchild(child, idx)
 
-        # If the scope we merged the inlined functions symbol table into
+        # If the scope we merged the inlined function's symbol table into
         # is not a Routine scope then we now merge that symbol table into
         # the ancestor Routine. This avoids issues like #2424 when
         # applying ParallelLoopTrans to loops containing inlined calls.
@@ -223,6 +226,38 @@ class InlineTrans(Transformation):
             scope.symbol_table.detach()
             replacement.attach(scope)
 
+    def _symbols_to_skip(self, table):
+        '''
+        Constructs a list of those Symbols in the table of the called routine
+        that must be excluded when merging that table with the one at the
+        call site.
+
+        These are:
+         - those Symbols representing routine arguments;
+         - any RoutineSymbol representing the called routine itself.
+
+        :param table: the symbol table of the routine to be inlined.
+        :type table: :py:class:`psyclone.psyir.symbols.SymbolTable`
+
+        :returns: those Symbols that must be skipped when merging the
+                  supplied table into the one at the call site.
+        :rtype: list[:py:class:`psyclone.psyir.symbols.Symbol`]
+
+        '''
+        # We need to exclude formal arguments and any RoutineSymbol
+        # representing the routine itself.
+        symbols_to_skip = table.argument_list[:]
+        try:
+            # We don't want or need the symbol representing that routine.
+            rsym = table.lookup_with_tag("own_routine_symbol")
+            if isinstance(rsym, RoutineSymbol):
+                # We only want to skip RoutineSymbols, not DataSymbols (which
+                # we may have if we have a Fortran function).
+                symbols_to_skip.append(rsym)
+        except KeyError:
+            pass
+        return symbols_to_skip
+
     def _replace_formal_arg(self, ref, call_node, formal_args):
         '''
         Recursively combines any References to formal arguments in the supplied
@@ -672,7 +707,9 @@ class InlineTrans(Transformation):
         # We can't handle a clash between (apparently) different symbols that
         # share a name but are imported from different containers.
         try:
-            table.check_for_clashes(routine_table)
+            table.check_for_clashes(
+                routine_table,
+                symbols_to_skip=self._symbols_to_skip(routine_table))
         except SymbolError as err:
             raise TransformationError(
                 f"One or more symbols from routine '{routine.name}' cannot be "


Test patch:
diff --git a/src/psyclone/tests/domain/lfric/lfric_dofmaps_test.py b/src/psyclone/tests/domain/lfric/lfric_dofmaps_test.py
index f9e2ff60a..d60ef3a31 100644
--- a/src/psyclone/tests/domain/lfric/lfric_dofmaps_test.py
+++ b/src/psyclone/tests/domain/lfric/lfric_dofmaps_test.py
@@ -52,7 +52,6 @@ from psyclone.psyGen import PSyFactory
 # Constants
 BASE_PATH = os.path.join(os.path.dirname(os.path.abspath(__file__)),
                          os.pardir, os.pardir, "test_files", "dynamo0p3")
-
 TEST_API = "dynamo0.3"
 
 
diff --git a/src/psyclone/tests/psyir/backend/fortran_routine_test.py b/src/psyclone/tests/psyir/backend/fortran_routine_test.py
index 9ccb0ab51..6f4df2e3a 100644
--- a/src/psyclone/tests/psyir/backend/fortran_routine_test.py
+++ b/src/psyclone/tests/psyir/backend/fortran_routine_test.py
@@ -71,7 +71,7 @@ def test_fw_routine(fortran_reader, fortran_writer, monkeypatch, tmpdir):
     # Generate Fortran from the PSyIR schedule
     result = fortran_writer(schedule)
 
-    assert (
+    expected = (
         "subroutine tmp(a, b, c)\n"
         "  use iso_c_binding, only : c_int\n"
         "  real, dimension(:), intent(out) :: a\n"
@@ -84,8 +84,17 @@ def test_fw_routine(fortran_reader, fortran_writer, monkeypatch, tmpdir):
         "    a = b / c\n"
         "  end if\n"
         "\n"
-        "end subroutine tmp\n") in result
+        "end subroutine tmp\n")
+    assert expected in result
     assert Compile(tmpdir).string_compiles(result)
+    # Repeat for the situation where the 'own_routine_symbol' is not present
+    # in the Routine's symbol table.
+    routine = psyir.walk(nodes.Routine)[0]
+    rsym = routine.symbol_table.lookup_with_tag("own_routine_symbol")
+    assert isinstance(rsym, symbols.RoutineSymbol)
+    routine.symbol_table.remove(rsym)
+    result = fortran_writer(schedule)
+    assert expected in result
 
     # Add distinctly named symbols in the routine sub-scopes
     sub_scopes = schedule.walk(nodes.Schedule)[1:]
@@ -213,31 +222,36 @@ def test_fw_routine_program(fortran_reader, fortran_writer, tmpdir):
     assert Compile(tmpdir).string_compiles(result)
 
 
-def test_fw_routine_function(fortran_reader, fortran_writer, tmpdir):
+@pytest.mark.parametrize("result_decl", ["real :: val",
+                                         "real, volatile :: val"])
+def test_fw_routine_function(fortran_reader, fortran_writer, tmpdir,
+                             result_decl):
     ''' Check that the FortranWriter outputs a function when a routine node
-    is found with return_symbol set.
+    is found with return_symbol set. We check for both supported and
+    unsupported (e.g. with the 'volatile' attribute) forms of declaration for
+    that symbol.
 
     '''
-    code = ("module test\n"
-            "implicit none\n"
-            "real :: a\n"
-            "contains\n"
-            "function tmp(b) result(val)\n"
-            "  real :: val\n"
-            "  real :: b\n"
-            "  val = a + b\n"
-            "end function tmp\n"
-            "end module test")
+    code = (f"module test\n"
+            f"implicit none\n"
+            f"real :: a\n"
+            f"contains\n"
+            f"function tmp(b) result(val)\n"
+            f"  {result_decl}\n"
+            f"  real :: b\n"
+            f"  val = a + b\n"
+            f"end function tmp\n"
+            f"end module test")
     container = fortran_reader.psyir_from_source(code)
     # Generate Fortran from PSyIR
     result = fortran_writer(container)
     assert (
-        "  contains\n"
-        "  function tmp(b) result(val)\n"
-        "    real :: b\n"
-        "    real :: val\n\n"
-        "    val = a + b\n\n"
-        "  end function tmp\n" in result)
+        f"  contains\n"
+        f"  function tmp(b) result(val)\n"
+        f"    real :: b\n"
+        f"    {result_decl}\n\n"
+        f"    val = a + b\n\n"
+        f"  end function tmp\n" in result.lower())
     assert Compile(tmpdir).string_compiles(result)
 
 
diff --git a/src/psyclone/tests/psyir/symbols/symbol_table_test.py b/src/psyclone/tests/psyir/symbols/symbol_table_test.py
index d42d27975..fce063a41 100644
--- a/src/psyclone/tests/psyir/symbols/symbol_table_test.py
+++ b/src/psyclone/tests/psyir/symbols/symbol_table_test.py
@@ -691,14 +691,26 @@ def test_check_for_clashes_cannot_rename():
     # Add a clash between two symbols where neither is a Container or has an
     # ImportInterface.
     del table1._symbols["prostetnic"]
-    table1.add(symbols.DataSymbol("jeltz", symbols.INTEGER_TYPE,
-                                  interface=symbols.ArgumentInterface()))
-    table2.add(symbols.DataSymbol("jeltz", symbols.INTEGER_TYPE,
-                                  interface=symbols.ArgumentInterface()))
+    jsym1 = symbols.DataSymbol("jeltz", symbols.INTEGER_TYPE,
+                               interface=symbols.ArgumentInterface())
+    table1.add(jsym1)
+    table1.specify_argument_list([jsym1])
+    jsym2 = symbols.DataSymbol("jeltz", symbols.INTEGER_TYPE,
+                               interface=symbols.ArgumentInterface())
+    table2.add(jsym2)
+    table2.specify_argument_list([jsym2])
     with pytest.raises(symbols.SymbolError) as err:
         table1.check_for_clashes(table2)
     assert ("Cannot rename symbol 'jeltz' because it is a routine argument "
             "and as such may be named in a Call." in str(err.value))
+    # This clash can be ignored by telling the checker to ignore any routine
+    # arguments.
+    table1.check_for_clashes(table2, symbols_to_skip=table2.argument_list[:])
+    # Ensure the symbols_to_skip argument is type-checked.
+    with pytest.raises(TypeError) as err:
+        table1.check_for_clashes(table2, symbols_to_skip=None)
+    assert ("symbols_to_skip' must be an instance of Iterable but got "
+            "'NoneType'" in str(err.value))
 
 
 def test_table_merge():
@@ -710,13 +722,19 @@ def test_table_merge():
         table1.merge("zaphod")
     assert ("merge() expects a SymbolTable instance but got 'str'" in
             str(err.value))
+    # 'symbols_to_skip' must be an Iterable (if provided).
+    with pytest.raises(TypeError) as err:
+        table1.merge(table2, symbols_to_skip=None)
+    assert ("merge() expects 'symbols_to_skip' to be an Iterable but got "
+            "'NoneType'" in str(err.value))
     # Can merge empty tables.
     table1.merge(table2)
     assert not table1._symbols
     # Simple merge.
     table2.add(symbols.DataSymbol("beeblebrox", symbols.INTEGER_TYPE))
-    # 'Own' routine symbol excluded.
-    table2.add(symbols.RoutineSymbol("dent"), tag="own_routine_symbol")
+    # A symbol we will exclude from the merge.
+    dent = symbols.RoutineSymbol("dent")
+    table2.add(dent, tag="own_routine_symbol")
     # Precision symbol should be included.
     wp_sym = symbols.DataSymbol("wp", symbols.INTEGER_TYPE, is_constant=True,
                                 initial_value=8)
@@ -724,7 +742,7 @@ def test_table_merge():
     table2.add(symbols.DataSymbol(
         "marvin",
         symbols.ScalarType(symbols.ScalarType.Intrinsic.REAL, wp_sym)))
-    table1.merge(table2)
+    table1.merge(table2, symbols_to_skip=[dent])
     assert table1.lookup("beeblebrox")
     assert "dent" not in table1
     assert "marvin" in table1
@@ -746,14 +764,6 @@ def test_table_merge():
     table3.specify_argument_list([arg_sym])
     table1.merge(table3)
     assert table1.lookup("trillian") is arg_sym
-    # Check that arguments are ignored if requested.
-    table4 = symbols.SymbolTable()
-    arg_sym2 = symbols.DataSymbol("arthur", symbols.INTEGER_TYPE,
-                                  interface=symbols.ArgumentInterface())
-    table4.add(arg_sym2)
-    table4.specify_argument_list([arg_sym2])
-    table1.merge(table4, include_arguments=False)
-    assert "arthur" not in table1
 
 
 def test_merge_container_syms():
diff --git a/src/psyclone/tests/psyir/transformations/inline_trans_test.py b/src/psyclone/tests/psyir/transformations/inline_trans_test.py
index 3e83cf113..f7ab845a2 100644
--- a/src/psyclone/tests/psyir/transformations/inline_trans_test.py
+++ b/src/psyclone/tests/psyir/transformations/inline_trans_test.py
@@ -44,7 +44,8 @@ from psyclone.configuration import Config
 from psyclone.errors import InternalError
 from psyclone.psyir.nodes import Call, IntrinsicCall, Reference, Routine, Loop
 from psyclone.psyir.symbols import (
-    DataSymbol, UnresolvedType, AutomaticInterface)
+    ArgumentInterface, AutomaticInterface, DataSymbol, INTEGER_TYPE,
+    RoutineSymbol, SymbolTable, UnresolvedType)
 from psyclone.psyir.transformations import (
     InlineTrans, TransformationError)
 from psyclone.tests.utilities import Compile
@@ -2272,6 +2273,38 @@ def test_find_routine_exception(fortran_reader, monkeypatch):
             in str(info.value))
 
 
+# _symbols_to_skip
+
+def test_symbols_to_skip():
+    '''Test that the _symbols_to_skip() utility method returns the expected
+    list of symbols to skip when merging the table of a called routine into
+    the table of the call site.
+
+    '''
+    inline_trans = InlineTrans()
+    table = SymbolTable()
+    # A locally-scoped symbol should not be skipped.
+    not_arg = DataSymbol("not_arg", INTEGER_TYPE)
+    table.add(not_arg)
+    skipped = inline_trans._symbols_to_skip(table)
+    assert not skipped
+    # Add an 'argument' Symbol. This should be skipped.
+    arg1 = DataSymbol(
+        "arg1", INTEGER_TYPE,
+        interface=ArgumentInterface(ArgumentInterface.Access.READ))
+    table.add(arg1)
+    table.specify_argument_list([arg1])
+    skipped = inline_trans._symbols_to_skip(table)
+    assert skipped == [arg1]
+    # Add a RoutineSymbol and tag it so that it appears to represents the
+    # routine associated with the table. This too should be skipped.
+    rsym = RoutineSymbol("me_myself")
+    table.add(rsym, tag="own_routine_symbol")
+    skipped = inline_trans._symbols_to_skip(table)
+    assert len(skipped) == 2
+    assert arg1 in skipped and rsym in skipped
+
+
 # _find_routine_in_container
 
 def test_find_routine_in_container_no_container(fortran_reader):
@@ -2438,3 +2471,47 @@ def test_apply_merges_symbol_table_with_routine(fortran_reader):
     inline_trans.apply(routine)
     # The i_1 symbol is the renamed i from the inlined call.
     assert psyir.walk(Routine)[0].symbol_table.get_symbols()['i_1'] is not None
+
+
+def test_apply_argument_clash(fortran_reader, fortran_writer, tmpdir):
+    '''
+    Check that the formal arguments to the inlined routine are not included
+    when checking for clashes (since they will be replaced by the actual
+    arguments to the call).
+    '''
+
+    code_clash = """
+  subroutine sub(Istr)
+    integer :: Istr
+    real :: x
+    x = 2.0*x
+    call sub_sub(Istr)
+  end subroutine sub
+
+  subroutine sub_sub(Istr)
+    integer :: i
+    integer :: Istr
+    real :: b(10)
+
+    b(Istr:10) = 1.0
+  end subroutine sub_sub"""
+
+    psyir = fortran_reader.psyir_from_source(code_clash)
+    call = psyir.walk(Call)[0]
+    inline_trans = InlineTrans()
+    inline_trans.apply(call)
+    expected = '''\
+subroutine sub(istr)
+  integer :: istr
+  real :: x
+  integer :: i
+  real, dimension(10) :: b
+
+  x = 2.0 * x
+  b(istr:) = 1.0
+
+end subroutine sub
+'''
+    output = fortran_writer(psyir)
+    assert expected in output
+    assert Compile(tmpdir).string_compiles(output)