Repository: stfc/PSyclone (python)

Issue:
[psyir] fparser2 frontend currently ignores implicit statements
To date we have always (implicitly) assumed that the Fortran we are dealing with is using `implicit none`. The fparser2 psyir frontend needs extending to check that this is the case and to abort if not.

Pull request description:
(Closes #1254) catch unsupported statements in implicit-part
This fixes a long-standing issue whereby we were silently ignoring things that could appear in the 'implicit-part' of a specification-part. We now catch things like `implicit real (a-h, o-z)` and the awfulness that is ENTRY statements.

Patch:
diff --git a/changelog b/changelog
index 6dd37eba1..326bc6b51 100644
--- a/changelog
+++ b/changelog
@@ -39,6 +39,9 @@
 
 	14) PR #2365 for #2351. Fix ref2arrayrange allocated bug.
 
+	15) PR #2363 for #1254. Fortran FORMAT, ENTRY and IMPLICIT statements
+	produce CodeBlocks instead of losing the statements.
+
 release 2.4.0 29th of September 2023
 
 	1) PR #1758 for #1741. Splits the PSyData read functionality into a
diff --git a/src/psyclone/psyir/frontend/fparser2.py b/src/psyclone/psyir/frontend/fparser2.py
index c71ba2d99..9f49fcfcc 100644
--- a/src/psyclone/psyir/frontend/fparser2.py
+++ b/src/psyclone/psyir/frontend/fparser2.py
@@ -2437,11 +2437,21 @@ class Fparser2Reader():
                 # These node types are handled separately
                 pass
             elif isinstance(node, Fortran2003.Implicit_Part):
+                # Anything other than a PARAMETER statement or an
+                # IMPLICIT NONE means we can't handle this code.
                 # Any PARAMETER statements are handled separately by the
                 # call to _process_parameter_stmts below.
-                # TODO #1254: We currently silently ignore the rest of
-                # the Implicit_Part statements
-                pass
+                # Any ENTRY statements are checked for in _subroutine_handler.
+                child_nodes = walk(node, Fortran2003.Format_Stmt)
+                if child_nodes:
+                    raise NotImplementedError(
+                        f"Error processing implicit-part: Format statements "
+                        f"are not supported but found '{child_nodes[0]}'")
+                child_nodes = walk(node, Fortran2003.Implicit_Stmt)
+                if any(imp.children != ('NONE',) for imp in child_nodes):
+                    raise NotImplementedError(
+                        f"Error processing implicit-part: implicit variable "
+                        f"declarations not supported but found '{node}'")
             else:
                 raise NotImplementedError(
                     f"Error processing declarations: fparser2 node of type "
@@ -4398,7 +4408,8 @@ class Fparser2Reader():
 
 
         :raises NotImplementedError: if the node contains a Contains clause.
-        :raises NotImplementedError: if an unsupported prefix is found or no \
+        :raises NotImplementedError: if the node contains an ENTRY statement.
+        :raises NotImplementedError: if an unsupported prefix is found or no
             explicit type information is available for a Function.
 
         '''
@@ -4412,6 +4423,12 @@ class Fparser2Reader():
             raise NotImplementedError("PSyclone doesn't yet support 'Contains'"
                                       " inside a Subroutine or Function")
 
+        entry_stmts = walk(node, Fortran2003.Entry_Stmt)
+        if entry_stmts:
+            raise NotImplementedError(
+                f"PSyclone does not support routines that contain one or more "
+                f"ENTRY statements but found '{entry_stmts[0]}'")
+
         name = node.children[0].children[1].string
         routine = Routine(name, parent=parent)
 


Test patch:
diff --git a/src/psyclone/tests/psyir/frontend/fparser2_container_test.py b/src/psyclone/tests/psyir/frontend/fparser2_container_test.py
index bae8116b5..6ba4c1757 100644
--- a/src/psyclone/tests/psyir/frontend/fparser2_container_test.py
+++ b/src/psyclone/tests/psyir/frontend/fparser2_container_test.py
@@ -38,7 +38,6 @@
 ''' Performs py.test tests on the container generation in the fparser2 PSyIR
     front-end. '''
 
-from __future__ import absolute_import
 import pytest
 
 from fparser.common.readfortran import FortranStringReader
@@ -46,7 +45,7 @@ from fparser.two import Fortran2003
 
 from psyclone.psyir.frontend.fparser2 import Fparser2Reader
 from psyclone.errors import InternalError, GenerationError
-from psyclone.psyir.nodes import Container, KernelSchedule
+from psyclone.psyir.nodes import Container, FileContainer, KernelSchedule
 from psyclone.psyir.symbols import Symbol, RoutineSymbol
 
 
@@ -57,6 +56,7 @@ def test_generate_container(parser):
     module dummy_mod
         use mod1
         use mod2, only: var1
+        implicit none
         real :: modvar1
     contains
         subroutine dummy_code(f1, f2, f3)
@@ -339,3 +339,42 @@ def test_broken_access_spec(parser):
     with pytest.raises(InternalError) as err:
         processor.process_declarations(fake_parent, fparser2spec.children, [])
     assert "Unexpected Access Spec attribute 'not-private'" in str(err.value)
+
+
+def test_unsupported_implicit_part(parser):
+    '''
+    Test that an unsupported implicit statment results in the expected error.
+    '''
+    fake_parent = FileContainer("dummy")
+    processor = Fparser2Reader()
+    code = '''\
+      module my_modulename
+        implicit real (a-h,o-z)
+        integer, private :: var3
+      end module my_modulename'''
+    reader = FortranStringReader(code)
+    fparser2spec = parser(reader).children[0].children[1]
+    with pytest.raises(NotImplementedError) as err:
+        processor.process_declarations(fake_parent, fparser2spec.children, [])
+    assert ("implicit variable declarations not supported but found "
+            "'IMPLICIT REAL(A - H, O - Z)'" in str(err.value))
+
+
+def test_unsupported_format_stmt(parser):
+    '''
+    Test that an unsupported format statement appearing in the implicit-part
+    of a specification-part in a module results in the expected error.
+    '''
+    fake_parent = FileContainer("dummy")
+    processor = Fparser2Reader()
+    code = '''\
+      module my_modulename
+5       FORMAT (1E12.4, I10)
+        integer, private :: var3
+      end module my_modulename'''
+    reader = FortranStringReader(code)
+    fparser2spec = parser(reader).children[0].children[1]
+    with pytest.raises(NotImplementedError) as err:
+        processor.process_declarations(fake_parent, fparser2spec.children, [])
+    assert ("Error processing implicit-part: Format statements are not "
+            "supported but found 'FORMAT(1E12.4, I10)'" in str(err.value))
diff --git a/src/psyclone/tests/psyir/frontend/fparser2_subroutine_handler_test.py b/src/psyclone/tests/psyir/frontend/fparser2_subroutine_handler_test.py
index e73d2e578..081d68c40 100644
--- a/src/psyclone/tests/psyir/frontend/fparser2_subroutine_handler_test.py
+++ b/src/psyclone/tests/psyir/frontend/fparser2_subroutine_handler_test.py
@@ -362,6 +362,7 @@ def test_supported_prefix(fortran_reader, fn_prefix, routine_type):
         f"module a\n"
         f"contains\n"
         f"  {fn_prefix} {routine_type} my_func()\n"
+        f"    implicit none\n"
         f"    real :: my_func\n"
         f"    my_func = 1.0\n"
         f"  end {routine_type} my_func\n"
@@ -404,6 +405,7 @@ def test_unsupported_char_len_function(fortran_reader):
     code = ("module a\n"
             "contains\n"
             "  character(len=2) function my_func()\n"
+            "    implicit none\n"
             "    my_func = 'aa'\n"
             "  end function my_func\n"
             "end module\n")
@@ -486,3 +488,49 @@ def test_unsupported_contains_function(fortran_reader):
     cblock = psyir.children[0]
     assert isinstance(cblock, CodeBlock)
     assert "SUBROUTINE" in str(cblock.get_ast_nodes[0])
+
+
+def test_implicit_declns(fortran_reader):
+    '''Test that we catch an implicit statement inside either a function
+    or a subroutine.
+    '''
+    code = '''\
+    function a(b, c, d)
+      implicit  REAL(wp) (A-H,O-Z)
+      a = c + d
+    end function'''
+    psyir = fortran_reader.psyir_from_source(code)
+    cblock = psyir.children[0]
+    assert isinstance(cblock, CodeBlock)
+    code = '''\
+    subroutine my_sub(b, c, d)
+      implicit  REAL(wp) (A-H,O-Z)
+      a = c + d
+    end subroutine'''
+    psyir = fortran_reader.psyir_from_source(code)
+    cblock = psyir.children[0]
+    assert isinstance(cblock, CodeBlock)
+
+
+def test_entry_stmt(parser):
+    '''
+    Check that the expected error is raised if we encounter an ENTRY statement.
+    '''
+    code = '''\
+    subroutine sub(b, c, d)
+      real :: a, b, c, d
+      a = c + d
+      return
+    entry a_no_really(b, c, d)
+      a = c * d
+      return
+    end subroutine sub'''
+    fake_parent = FileContainer("dummy")
+    processor = Fparser2Reader()
+    reader = FortranStringReader(code)
+    fparser2spec = parser(reader)
+    with pytest.raises(NotImplementedError) as err:
+        processor._subroutine_handler(fparser2spec.children[0], fake_parent)
+    assert ("PSyclone does not support routines that contain one or more ENTRY"
+            " statements but found 'ENTRY a_no_really(b, c, d)'"
+            in str(err.value))