Repository: pyccel/pyccel (python)

Issue:
Fortran translation does not allocate for list comprehension
### Describe the bug
The translated Fortran code does not allocate memory before attempting to populate the vector, leading to a segmentation fault when the program tries to access an unallocated memory region.

### To Reproduce
```python
if __name__ == "__main__":
    a = [i for i in range(10)]
    print(a[0])
```

### Error details

```fortran
program prog_prog_boo

  use boo
  use Vector_integer8_mod

  use, intrinsic :: ISO_C_Binding, only : i64 => C_INT64_T
  use, intrinsic :: ISO_FORTRAN_ENV, only : stdout => output_unit

  implicit none

  integer(i64) :: i
  type(Vector_integer8) :: a
  integer(i64) :: Dummy_0000

  Dummy_0000 = 0_i64
  do i = 0_i64, 9_i64
    a%of(Dummy_0000 + 1_i64) = i
    Dummy_0000 = Dummy_0000 + 1_i64
  end do
  write(stdout, '(I0)', advance="yes") a%of(1_i64)

end program prog_prog_boo
```

### Expected behavior
The vector should be allocated before it is populated.

### Language
Fortran

Pull request:
Fix missing allocations for lists in Fortran
Use the `Allocate.alloc_type` property to add missing allocations for lists in Fortran. Fixes #2036.

**Commit Summary**
- Activate leak checks for containers for Fortran
- Apply unravelling algorithms to lists
- Remove unnecessary `_print` call in `FCodePrinter._print_CodeBlock`
- Handle `resize` and `reserve` cases for list allocations in Fortran
- Simplify printing of list indexing
- Reactivate list comprehension tests

Patch:
diff --git a/.github/actions/valgrind_run/action.yml b/.github/actions/valgrind_run/action.yml
index 7c8a0aca..855b92a8 100644
--- a/.github/actions/valgrind_run/action.yml
+++ b/.github/actions/valgrind_run/action.yml
@@ -3,16 +3,19 @@ name: 'Pyccel valgrind script'
 runs:
   using: "composite"
   steps:
-    - name: Test with valgrind for memory leaks
+    - name: Test with valgrind for memory leaks in ndarrays
       run: |
         pyccel --language=c --flags="-g -O0" leaks_check.py
         valgrind --leak-check=full --error-exitcode=1 ./leaks_check
       shell: bash
       working-directory: ./tests/ndarrays
     
-    - name: Test with valgrind for memory leaks in stc_containers
+    - name: Test with valgrind for memory leaks in built-in containers
       run: |
         pyccel --language=c --flags="-g -O0" leaks_check.py
         valgrind --leak-check=full --error-exitcode=1 ./leaks_check
+        pyccel-clean -p
+        pyccel --language=fortran --flags="-g -O0" leaks_check.py
+        valgrind --leak-check=full --error-exitcode=1 ./leaks_check
       shell: bash
       working-directory: ./tests/stc_containers
diff --git a/pyccel/ast/utilities.py b/pyccel/ast/utilities.py
index d982ca6b..f2a7ad6b 100644
--- a/pyccel/ast/utilities.py
+++ b/pyccel/ast/utilities.py
@@ -20,17 +20,18 @@ from .builtins      import (builtin_functions_dict,
                             PythonRange, PythonList, PythonTuple, PythonSet)
 from .cmathext      import cmath_mod
 from .datatypes     import HomogeneousTupleType, InhomogeneousTupleType, PythonNativeInt
+from .datatypes     import StringType
 from .internals     import PyccelFunction, Slice
 from .itertoolsext  import itertools_mod
 from .literals      import LiteralInteger, LiteralEllipsis, Nil
 from .mathext       import math_mod
-from .sysext        import sys_mod
-
 from .numpyext      import (NumpyEmpty, NumpyArray, numpy_mod,
                             NumpyTranspose, NumpyLinspace)
+from .numpytypes    import NumpyNDArrayType
 from .operators     import PyccelAdd, PyccelMul, PyccelIs, PyccelArithmeticOperator
 from .operators     import PyccelUnarySub
 from .scipyext      import scipy_mod
+from .sysext        import sys_mod
 from .typingext     import typing_mod
 from .variable      import Variable, IndexedElement
 
@@ -188,27 +189,33 @@ def split_positional_keyword_arguments(*args):
 #==============================================================================
 def compatible_operation(*args, language_has_vectors = True):
     """
-    Indicates whether an operation requires an index to be
-    correctly understood
+    Indicate whether an operation only uses compatible arguments.
+
+    Indicate whether an operation requires an index to be
+    correctly interpreted in the target language or if the arguments
+    are already compatible.
 
     Parameters
-    ==========
-    args      : list of TypedAstNode
-                The operator arguments
+    ----------
+    *args : list of TypedAstNode
+        The operator arguments.
     language_has_vectors : bool
-                Indicates if the language has support for vector
-                operations of the same shape
-    Results
-    =======
-    compatible : bool
-                 A boolean indicating if the operation is compatible
+        Indicates if the language has support for vector
+        operations of the same shape.
+
+    Returns
+    -------
+    bool
+        A boolean indicating if the operation is compatible.
     """
-    if language_has_vectors:
+    if language_has_vectors and any(isinstance(a.class_type, NumpyNDArrayType) for a in args):
         # If the shapes don't match then an index must be required
         shapes = [a.shape[::-1] if a.order == 'F' else a.shape for a in args if a.rank != 0]
         shapes = set(tuple(d if d == LiteralInteger(1) else -1 for d in s) for s in shapes)
         order  = set(a.order for a in args if a.order is not None)
         return len(shapes) <= 1 and len(order) <= 1
+    elif any(isinstance(a.class_type, StringType) for a in args):
+        return True
     else:
         return all(a.rank == 0 for a in args)
 
diff --git a/pyccel/codegen/printing/fcode.py b/pyccel/codegen/printing/fcode.py
index 24491487..012d7bde 100644
--- a/pyccel/codegen/printing/fcode.py
+++ b/pyccel/codegen/printing/fcode.py
@@ -2007,7 +2007,7 @@ class FCodePrinter(CodePrinter):
                 body_stmts.append(self._additional_code)
                 self._additional_code = ''
             body_stmts.append(line)
-        return ''.join(self._print(b) for b in body_stmts)
+        return ''.join(body_stmts)
 
     # TODO the ifs as they are are, is not optimal => use elif
     def _print_SymbolicAssign(self, expr):
@@ -2157,6 +2157,19 @@ class FCodePrinter(CodePrinter):
 
             return code
 
+        elif isinstance(class_type, HomogeneousListType):
+            if expr.alloc_type == 'resize':
+                var_code = self._print(expr.variable)
+                container_type = expr.variable.class_type
+                container = self._print(container_type)
+                size_code = self._print(expr.shape[0])
+                return f'{var_code} = {container}({size_code})\n'
+            elif expr.alloc_type == 'reserve':
+                var_code = self._print(expr.variable)
+                size_code = self._print(expr.shape[0])
+                return '{var_code} % reserve({size_code})\n'
+            else:
+                return ''
         elif isinstance(class_type, (HomogeneousContainerType, DictType)):
             return ''
 
@@ -3341,11 +3354,11 @@ class FCodePrinter(CodePrinter):
                             PyccelAdd(_shape, ind, simplify = True), ind)
 
         if isinstance(base.class_type, HomogeneousListType):
-            if any(isinstance(i, Slice) for i in inds):
-                raise NotImplementedError("Slice indexing not implemented for lists")
-            inds = [PyccelAdd(i, LiteralInteger(1), simplify=True) for i in inds]
-            inds_code = ", ".join(self._print(i) for i in inds)
-            return f"{base_code}%of({inds_code})"
+            assert len(inds) == 1
+            ind = inds[0]
+            assert not isinstance(ind, Slice)
+            ind_code = self._print(PyccelAdd(inds[0], LiteralInteger(1), simplify=True))
+            return f"{base_code}%of({ind_code})"
         elif isinstance(base.class_type, (NumpyNDArrayType, HomogeneousTupleType)):
             inds_code = ", ".join(self._print(i) for i in inds)
             return f"{base_code}({inds_code})"


Test patch:
diff --git a/tests/codegen/fcode/test_fcode_codegen.py b/tests/codegen/fcode/test_fcode_codegen.py
index 71acd9b0..a609013c 100644
--- a/tests/codegen/fcode/test_fcode_codegen.py
+++ b/tests/codegen/fcode/test_fcode_codegen.py
@@ -15,7 +15,7 @@ base_dir = os.path.dirname(os.path.realpath(__file__))
 path_dir = os.path.join(base_dir, 'scripts')
 
 files = sorted(os.listdir(path_dir))
-failing_files = {'ListComprehension.py': 'List comprehension not yet implemented with gFTL',
+failing_files = {'ListComprehension.py': 'Lists of tuples not yet implemented with gFTL',
                  'lists.py': 'List slicing not yet implemented with gFTL',
                  'Functional_Stmts.py': 'List slicing not yet implemented with gFTL',
                  'complex_numbers.py': 'List slicing not yet implemented with gFTL',
diff --git a/tests/epyccel/test_functionals.py b/tests/epyccel/test_functionals.py
index 07fe43e4..1c69ee47 100644
--- a/tests/epyccel/test_functionals.py
+++ b/tests/epyccel/test_functionals.py
@@ -7,20 +7,6 @@ import pytest
 from pyccel import epyccel
 from modules import functionals
 
-@pytest.fixture( params=[
-        pytest.param("fortran", marks = [
-            pytest.mark.skip(reason="Fortran does not support list comprehensions. See #1948 (now applicable to Fortran)"),
-            pytest.mark.fortran]),
-        pytest.param("c", marks = [
-            pytest.mark.skip(reason="C does not support list comprehensions. See #1948"),
-            pytest.mark.c]),
-        pytest.param("python", marks = pytest.mark.python)
-    ],
-    scope = "module"
-)
-def language(request):
-    return request.param
-
 def compare_epyccel(f, language, *args):
     f2 = epyccel(f, language=language)
     out1 = f(*args)
@@ -62,11 +48,23 @@ def test_functional_for_2d_dependant_range(language):
     compare_epyccel(functionals.functional_for_2d_dependant_range_2, language)
     compare_epyccel(functionals.functional_for_2d_dependant_range_3, language)
 
+@pytest.mark.parametrize( 'language', (
+        pytest.param("fortran", marks = [
+            pytest.mark.skip(reason="lists of tuples are not yes supported"),
+            pytest.mark.fortran]
+        ),
+        pytest.param("c", marks = [
+            pytest.mark.skip(reason="lists of tuples are not yes supported"),
+            pytest.mark.c]
+        ),
+        pytest.param("python", marks = pytest.mark.python)
+    )
+)
 def test_functional_for_2d_array_range(language):
     idx = randint(28)
     compare_epyccel(functionals.functional_for_2d_array_range, language,idx)
 
-def test_functional_for_2d_array_range_const(language):
+def test_functional_for_2d_range_const(language):
     compare_epyccel(functionals.functional_for_2d_range_const, language)
 
 def test_functional_for_3d_range(language):
diff --git a/tests/epyccel/test_loops.py b/tests/epyccel/test_loops.py
index 293cd0de..3b24578f 100644
--- a/tests/epyccel/test_loops.py
+++ b/tests/epyccel/test_loops.py
@@ -77,18 +77,6 @@ def test_double_loop_on_2d_array_F(language):
     f2( y )
     assert np.array_equal( x, y )
 
-@pytest.mark.parametrize( 'language', (
-        pytest.param("fortran", marks = [
-            pytest.mark.skip(reason="list comprehension not supported yet, related issue #1948"),
-            pytest.mark.fortran]
-        ),
-        pytest.param("c", marks = [
-            pytest.mark.skip(reason="C does not support list indexing yet, related issue #1948"),
-            pytest.mark.c]
-        ),
-        pytest.param("python", marks = pytest.mark.python)
-    )
-)
 def test_product_loop_on_2d_array_C(language):
 
     f1 = loops.product_loop_on_2d_array_C
@@ -101,18 +89,6 @@ def test_product_loop_on_2d_array_C(language):
     f2( y )
     assert np.array_equal( x, y )
 
-@pytest.mark.parametrize( 'language', (
-        pytest.param("fortran", marks = [
-            pytest.mark.skip(reason="list comprehension not supported yet, related issue #1948"),
-            pytest.mark.fortran]
-        ),
-        pytest.param("c", marks = [
-            pytest.mark.skip(reason="C does not support list indexing yet, related issue #1948"),
-            pytest.mark.c]
-        ),
-        pytest.param("python", marks = pytest.mark.python)
-    )
-)
 def test_product_loop_on_2d_array_F(language):
 
     f1 = loops.product_loop_on_2d_array_F
@@ -125,18 +101,6 @@ def test_product_loop_on_2d_array_F(language):
     f2( y )
     assert np.array_equal( x, y )
 
-@pytest.mark.parametrize( 'language', (
-        pytest.param("fortran", marks = [
-            pytest.mark.skip(reason="list comprehension not supported yet, related issue #1948"),
-            pytest.mark.fortran]
-        ),
-        pytest.param("c", marks = [
-            pytest.mark.skip(reason="C does not support list indexing yet, related issue #1948"),
-            pytest.mark.c]
-        ),
-        pytest.param("python", marks = pytest.mark.python)
-    )
-)
 def test_product_loop(language):
 
     f1 = loops.product_loop
@@ -150,10 +114,7 @@ def test_product_loop(language):
     assert np.array_equal( x, y )
 
 @pytest.mark.parametrize( 'language', (
-        pytest.param("fortran", marks = [
-            pytest.mark.skip(reason="list comprehension not supported yet, related issue #1948"),
-            pytest.mark.fortran]
-        ),
+        pytest.param("fortran", marks = pytest.mark.fortran),
         pytest.param("c", marks = [
             pytest.mark.xfail(reason="Function in function not implemented in C"),
             pytest.mark.c]
@@ -189,18 +150,6 @@ def test_enumerate_on_1d_array_with_start(language):
     assert np.array_equal( f1(z, 5), f2(z, 5) )
     assert np.array_equal( f1(z,-2), f2(z,-2) )
 
-@pytest.mark.parametrize( 'language', (
-        pytest.param("fortran", marks = [
-            pytest.mark.skip(reason="list comprehension not supported yet, related issue #1948"),
-            pytest.mark.fortran]
-        ),
-        pytest.param("c", marks = [
-            pytest.mark.skip(reason="C does not support list indexing yet, related issue #1948"),
-            pytest.mark.c]
-        ),
-        pytest.param("python", marks = pytest.mark.python)
-    )
-)
 def test_zip_prod(language):
 
     f1 = loops.zip_prod