Repository: stfc/PSyclone
Pull request #2103 (2023-04-11 20:03:20)

Support interfaces whose symbols are already declared (towards #1618)
The Met Office have algorithm code that looks like the following:

```
module test_mod
  type, public :: test_type
  end type
  interface test_type
     module procedure constructor
  end interface
contains
  function constructor()
  end function
end module
```

This causes problems for the PSyIR as the PSyIR assumes that an interface symbol has not been declared. This PR provides a fix to this problem. This fix is not a long term solution and should be replaced when interfaces are better supported in the PSyIR.



Patch:
diff --git a/changelog b/changelog
index 8f8269ddb..8f205ca5f 100644
--- a/changelog
+++ b/changelog
@@ -374,6 +374,9 @@
 	125) PR #2098 towards #2091. Split off and rename DynKernCallFactory
 	to LFRicKernCallFactory.
 
+	126) PR # 2103 towards #1618. Support Fortran interfaces whose symbols
+	are already declared.
+
 release 2.3.1 17th of June 2022
 
 	1) PR #1747 for #1720. Adds support for If blocks to PSyAD.
diff --git a/doc/developer_guide/psyir_symbols.rst b/doc/developer_guide/psyir_symbols.rst
index 2e4a01eae..9b4d5b8cc 100644
--- a/doc/developer_guide/psyir_symbols.rst
+++ b/doc/developer_guide/psyir_symbols.rst
@@ -249,3 +249,32 @@ specialisations are possible:
     >>> # sym2.specialise(DataSymbol, datatype=INTEGER_TYPE, constant_value=3.14)
     >>> # The following statement is valid and constant_value is set to 3
     >>> sym2.specialise(DataSymbol, datatype=INTEGER_TYPE, constant_value=3)
+
+
+Routine Interfaces
+==================
+
+Fortran supports generic interfaces. The Fortran standard rule `R1203`
+says that: `interface-stmt = INTERFACE [ generic-spec ]` where
+`generic-spec` is either (`R1207`) a `generic-name` or one of
+`OPERATOR`, `ASSIGNMENT` or `dtio-spec` (see
+``https://wg5-fortran.org/N1601-N1650/N1601.pdf``).
+
+At the moment the PSyIR only captures such interfaces if they have a
+`generic-name`. Interfaces which do not have a `generic-name` cause
+the routine containing the interface to be put in a `CodeBlock` (see
+issue #1971 for one case).
+
+If the interface does have a generic name and `generic-name` is not
+already declared as a PSyIR symbol then the interface is captured as a
+`RoutineSymbol` named as `generic-name` with an
+`UnknownFortranType`. The `generic-name` may already be declared as a
+PSyIR symbol if it references a type declaration. In this case the
+interface is still captured as a `RoutineSymbol` with an
+`UnknownFortranType`, but the name of the `RoutineSymbol` is
+`_psyclone_internal_` followed by the `generic-name`, i.e. it is given
+an internal name. This should not clash with any other symbol names as
+names should not start with `_`. In both cases the content of the
+interface is captured as text in the `UnknownFortranType` so the
+`RoutineSymbol` name is not used in the Fortran backend.
+
diff --git a/src/psyclone/psyir/frontend/fparser2.py b/src/psyclone/psyir/frontend/fparser2.py
index 00e53dbf1..249691a3b 100644
--- a/src/psyclone/psyir/frontend/fparser2.py
+++ b/src/psyclone/psyir/frontend/fparser2.py
@@ -2088,9 +2088,21 @@ class Fparser2Reader():
                 # corresponding implementation with that name.)
                 # We store its definition using an UnknownFortranType so that
                 # we can recreate it in the Fortran backend.
-                parent.symbol_table.add(
-                    RoutineSymbol(name, UnknownFortranType(str(node).lower()),
-                                  visibility=vis))
+                try:
+                    parent.symbol_table.add(
+                        RoutineSymbol(
+                            name, UnknownFortranType(str(node).lower()),
+                            visibility=vis))
+                except KeyError:
+                    # This symbol has already been declared. However
+                    # we still want to output the interface so we
+                    # store it in the PSyIR as an UnkownFortranType
+                    # with an internal name.
+                    parent.symbol_table.new_symbol(
+                        root_name=f"_psyclone_internal_{name}",
+                        symbol_type=RoutineSymbol,
+                        datatype=UnknownFortranType(str(node).lower()),
+                        visibility=vis)
 
             elif isinstance(node, Fortran2003.Type_Declaration_Stmt):
                 try:
@@ -2467,7 +2479,7 @@ class Fparser2Reader():
         # An INCLUDE can appear anywhere so we have to allow for the case
         # where we have no enclosing Routine.
         unit = parent.ancestor((Routine, Container), include_self=True)
-        # pylint: disable="unidiomatic-typecheck"
+        # pylint: disable=unidiomatic-typecheck
         if isinstance(unit, Routine):
             if unit.is_program:
                 out_txt = f"program '{unit.name}'. "
@@ -2477,6 +2489,7 @@ class Fparser2Reader():
             out_txt = f"module '{unit.name}'. "
         else:
             out_txt = f"code:\n{str(node.get_root())}\n"
+        # pylint: enable=unidiomatic-typecheck
         filename = node.children[0].string
         if isinstance(node, Fortran2003.Include_Stmt):
             err_msg = (


Test patch:
diff --git a/src/psyclone/tests/psyir/frontend/fparser2_interface_block_test.py b/src/psyclone/tests/psyir/frontend/fparser2_interface_block_test.py
index 4c35436e7..f5a396947 100644
--- a/src/psyclone/tests/psyir/frontend/fparser2_interface_block_test.py
+++ b/src/psyclone/tests/psyir/frontend/fparser2_interface_block_test.py
@@ -1,7 +1,7 @@
 # -----------------------------------------------------------------------------
 # BSD 3-Clause License
 #
-# Copyright (c) 2021, Science and Technology Facilities Council.
+# Copyright (c) 2021-2023, Science and Technology Facilities Council.
 # All rights reserved.
 #
 # Redistribution and use in source and binary forms, with or without
@@ -32,16 +32,17 @@
 # POSSIBILITY OF SUCH DAMAGE.
 # -----------------------------------------------------------------------------
 # Author: A. R. Porter, STFC Daresbury Lab
+# Modified: R. W. Ford, STFC Daresbury Lab
 # -----------------------------------------------------------------------------
 
 ''' Performs py.test tests on the handling of interface blocks in the fparser2
     PSyIR front-end. '''
 
-from __future__ import absolute_import
-
 from fparser.two import Fortran2003
+
 from psyclone.psyir.nodes import Container, CodeBlock, FileContainer
-from psyclone.psyir.symbols import RoutineSymbol, Symbol, UnknownFortranType
+from psyclone.psyir.symbols import (
+    RoutineSymbol, Symbol, UnknownFortranType, DataTypeSymbol)
 
 
 def test_named_interface(fortran_reader):
@@ -80,6 +81,40 @@ def test_named_interface(fortran_reader):
     assert eos.visibility == Symbol.Visibility.PUBLIC
 
 
+def test_named_interface_declared(fortran_reader):
+    ''' Test that the frontend creates a RoutineSymbol of
+    UnknownFortranType for a named interface block when the symbol
+    name has already been declared. '''
+    test_module = '''
+    module test_mod
+      type test
+      end type test
+      interface test
+        module procedure test_code
+      end interface test
+    contains
+      function test_code()
+      end function test_code
+    end module test_mod
+    '''
+    file_container = fortran_reader.psyir_from_source(test_module)
+    container = file_container.children[0]
+    assert isinstance(container, Container)
+    # type symbol
+    assert container.symbol_table.lookup("test")
+    test_symbol = container.symbol_table.lookup("test")
+    assert isinstance(test_symbol, DataTypeSymbol)
+    # interface symbol
+    assert container.symbol_table.lookup("_psyclone_internal_test")
+    test_symbol = container.symbol_table.lookup("_psyclone_internal_test")
+    assert isinstance(test_symbol, RoutineSymbol)
+    assert isinstance(test_symbol.datatype, UnknownFortranType)
+    assert (test_symbol.datatype.declaration == "interface test\n"
+            "  module procedure test_code\n"
+            "end interface test")
+    assert test_symbol.visibility == Symbol.Visibility.PUBLIC
+
+
 def test_generic_interface(fortran_reader):
     ''' Test that we create a CodeBlock if a module contains an interface block
         that defines interfaces to external routines. '''