Repository: pyccel/pyccel
Pull request #1977: Improve list comprehension
Author: login: jalalium
Labels: name: Ready_to_merge
description: Approved by senior developer. Ready for final approval and merge
name: urgent
description: Affects all PRs and should be merged as soon as possible
Created: 2024-09-02T10:29:38Z | Merged: 2025-03-17T18:03:40Z

This PR aims to fix issue #1297
This PR refactors the FunctionalFor support in Pyccel by replacing direct array indexing with the use of Python lists and the list_append function in C.
It also aims at supporting list comprehension with conditions.


Diff:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index b96921e846..92011145e4 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -133,6 +133,10 @@ All notable changes to this project will be documented in this file.
 -   #2229 : Fix annotation of variables that are returned in a function whose result type is annotated.
 -   #2238 : Fix incorrect memory handling for temporary variable.
 -   #2239 : Fix missing library directory for Python shared library.
+-   #1410 : Fix conditional statement not working inside of a list comprehension.
+-   #1297 : Fix iteration over an `enumerate`, `map` or `zip` in a list comprehension.
+-   #2098 : Fix using multiple list comprehensions.
+-   #1948 : Fix list comprehension does not work in C.
 -   #2245 : Fix internal error when an inhomogeneous tuple appears as an indexed element.
 
 ### Changed
diff --git a/pyccel/ast/datatypes.py b/pyccel/ast/datatypes.py
index b77f37ca75..c51b28cb7e 100644
--- a/pyccel/ast/datatypes.py
+++ b/pyccel/ast/datatypes.py
@@ -358,6 +358,39 @@ def __and__(self, other):
         else:
             return NotImplemented
 
+    @property
+    def min_value(self):
+        """
+        Get the minimum representable value for this datatype.
+
+        This property returns the smallest value that can be represented by
+        this numeric datatype. It is useful for validation, boundary checks,
+        and ensuring that generated code respects the limits of the type.
+
+        Returns
+        -------
+        int
+            The minimum value that can be represented.
+        """
+        return numpy.iinfo(int).min
+
+    @property
+    def max_value(self):
+        """
+        Get the maximum representable value for this datatype.
+
+        This property returns the smallest value that can be represented by
+        this numeric datatype. It is useful for validation, boundary checks,
+        and ensuring that generated code respects the limits of the type.
+
+        Returns
+        -------
+        int
+            The maximum value that can be represented.
+        """
+        return numpy.iinfo(int).max
+
+
 class PythonNativeFloat(PythonNativeNumericType):
     """
     Class representing Python's native floating point type.
@@ -378,6 +411,39 @@ def __add__(self, other):
         else:
             return NotImplemented
 
+    @property
+    def min_value(self):
+        """
+        Get the minimum representable value for this datatype.
+
+        This property returns the smallest value that can be represented by
+        this numeric datatype. It is useful for validation, boundary checks,
+        and ensuring that generated code respects the limits of the type.
+
+        Returns
+        -------
+        float
+            The minimum value that can be represented.
+        """
+        return numpy.finfo(float).min
+
+    @property
+    def max_value(self):
+        """
+        Get the maximum representable value for this datatype.
+
+        This property returns the smallest value that can be represented by
+        this numeric datatype. It is useful for validation, boundary checks,
+        and ensuring that generated code respects the limits of the type.
+
+        Returns
+        -------
+        float
+            The maximum value that can be represented.
+        """
+        return numpy.finfo(float).max
+
+
 class PythonNativeComplex(PythonNativeNumericType):
     """
     Class representing Python's native complex type.
diff --git a/pyccel/ast/functionalexpr.py b/pyccel/ast/functionalexpr.py
index 4b55e3d871..d208c145c9 100644
--- a/pyccel/ast/functionalexpr.py
+++ b/pyccel/ast/functionalexpr.py
@@ -56,9 +56,16 @@ class FunctionalFor(TypedAstNode):
     target_type : PyccelSymbol, optional
         The type of the result of the functional for. This is useful at
         the syntactic stage to pass along the final type of the lhs (list/set/array/etc).
+    operations : dict
+        A dictionary mapping each type of comprehension (e.g. list, array, etc.)
+        to the operation used for populating it.
+    conditions : list[If|None]
+        A list of filter conditions corresponding to each for-loop in the comprehension.
+        Each element of this list is either an `If` instance that describes the filtering
+        condition for that loop, or `None` if no condition is applied in that loop.
     """
-    __slots__ = ('_loops','_expr', '_lhs', '_indices','_index',
-            '_shape','_class_type', '_target_type')
+    __slots__ = ('_loops','_expr', '_lhs', '_indices','_index', '_operations',
+            '_shape','_class_type', '_target_type', '_conditions')
     _attribute_nodes = ('_loops','_expr', '_lhs', '_indices','_index')
 
     def __init__(
@@ -68,14 +75,20 @@ def __init__(
         lhs=None,
         indices=None,
         index=None,
-        target_type=None
+        *,
+        target_type=None,
+        operations=None,
+        conditions
         ):
+
         self._loops   = loops
         self._expr    = expr
         self._lhs     = lhs
         self._indices = indices
         self._index   = index
+        self._operations = operations
         self._target_type = target_type
+        self._conditions = conditions
         super().__init__()
 
         if pyccel_stage != 'syntactic':
@@ -102,6 +115,25 @@ def indices(self):
     def index(self):
         return self._index
 
+    @property
+    def operations(self):
+        """
+        A dictionary mapping each type of comprehension to the operation used for populating it.
+
+        For example, for list comprehensions we might use 
+        ``{'list': 'append'}``, and for NumPy arrays, (which require a fixed size
+        at compile time), we might use  ``{'numpy_array': [Assign_node]}``. 
+        This mapping allows the code generator to select the appropriate operation 
+        when building the final data structure from the comprehension.
+
+        Returns
+        -------
+        dict
+            A dictionary that maps comprehension types (e.g. 'list', 'numpy_array') to 
+            the corresponding operation (append call, assign, etc.).
+        """
+        return self._operations
+
     @property
     def target_type(self):
         """
@@ -112,6 +144,24 @@ def target_type(self):
         """
         return self._target_type
 
+    @property
+    def conditions(self):
+        """
+        A list of filter conditions for each loop in the comprehension.
+
+        If not `None`, each element of this list is either an `If` instance 
+        that describes the filtering condition for that loop, or `None` if 
+        no condition is applied. These conditions collectively determine
+        whether each item produced by the loops is included in the final result.
+
+        Returns
+        -------
+        list[If|None], or None
+            The list of filter conditions for each for-loop in 
+            the comprehension, or `None` if not specified.
+        """
+        return self._conditions
+
 #==============================================================================
 class GeneratorComprehension(FunctionalFor):
     """ Super class for all functions which reduce generator expressions to scalars
diff --git a/pyccel/ast/literals.py b/pyccel/ast/literals.py
index fcf292eb45..92638c09cb 100644
--- a/pyccel/ast/literals.py
+++ b/pyccel/ast/literals.py
@@ -447,7 +447,7 @@ def convert_to_literal(value, dtype = None):
         The Python value 'value' expressed as a literal
         with the specified dtype.
     """
-    from .operators import PyccelUnarySub # Imported here to avoid circular import
+    from .operators import PyccelUnarySub, PyccelMinus # Imported here to avoid circular import
 
     # Calculate the default datatype
     if dtype is None:
@@ -474,6 +474,8 @@ def convert_to_literal(value, dtype = None):
     if isinstance(primitive_type, PrimitiveIntegerType):
         if value >= 0:
             literal_val = LiteralInteger(value, dtype)
+        elif value == -9223372036854775808:
+            literal_val =  PyccelMinus(PyccelUnarySub(LiteralInteger(-value-1, dtype)), LiteralInteger(1))
         else:
             literal_val = PyccelUnarySub(LiteralInteger(-value, dtype))
     elif isinstance(primitive_type, PrimitiveFloatingPointType):
diff --git a/pyccel/ast/numpytypes.py b/pyccel/ast/numpytypes.py
index e4e7cf1882..39d8110323 100644
--- a/pyccel/ast/numpytypes.py
+++ b/pyccel/ast/numpytypes.py
@@ -176,6 +176,38 @@ class NumpyFloat64Type(NumpyNumericType):
     _primitive_type = PrimitiveFloatingPointType()
     _precision = 8
 
+    @property
+    def min_value(self):
+        """
+        Get the minimum representable value for this datatype.
+
+        This property returns the smallest value that can be represented by
+        this numeric datatype. It is useful for validation, boundary checks,
+        and ensuring that generated code respects the limits of the type.
+
+        Returns
+        -------
+        float64
+            The minimum value that can be represented.
+        """
+        return np.finfo(np.float64).min
+
+    @property
+    def max_value(self):
+        """
+        Get the maximum representable value for this datatype.
+
+        This property returns the smallest value that can be represented by
+        this numeric datatype. It is useful for validation, boundary checks,
+        and ensuring that generated code respects the limits of the type.
+
+        Returns
+        -------
+        float64
+            The maximum value that can be represented.
+        """
+        return np.finfo(np.float64).max
+
 
 class NumpyFloat128Type(NumpyNumericType):
     """
diff --git a/pyccel/ast/sympy_helper.py b/pyccel/ast/sympy_helper.py
index b249c9748d..b29136909a 100644
--- a/pyccel/ast/sympy_helper.py
+++ b/pyccel/ast/sympy_helper.py
@@ -13,7 +13,8 @@
 
 from pyccel.utilities.strings import create_incremented_string
 
-from .builtins  import PythonRange, PythonTuple
+from .builtins  import PythonRange, PythonTuple, PythonMin, PythonMax
+
 from .datatypes import PrimitiveIntegerType
 from .internals import PyccelArrayShapeElement
 from .literals  import LiteralInteger, LiteralFloat, LiteralComplex
@@ -106,6 +107,14 @@ def sympy_to_pyccel(expr, symbol_map):
         else:
             return MathCeil(arg)
 
+    elif isinstance(expr, sp.Min):
+        args = [sympy_to_pyccel(a, symbol_map) for a in expr.args]
+        return PythonMin(*args)
+
+    elif isinstance(expr, sp.Max):
+        args = [sympy_to_pyccel(a, symbol_map) for a in expr.args]
+        return PythonMax(*args)
+
     elif isinstance(expr, sp.Tuple):
         args = [sympy_to_pyccel(a, symbol_map) for a in expr]
         return PythonTuple(*args)
@@ -217,6 +226,14 @@ def pyccel_to_sympy(expr, symbol_map, used_names):
     elif isinstance(expr, MathCeil):
         return sp.ceiling(pyccel_to_sympy(expr.args[0], symbol_map, used_names))
 
+    elif isinstance(expr, PythonMin):
+        args = [pyccel_to_sympy(ee, symbol_map, used_names) for e in expr.args for ee in e]
+        return sp.Min(*args)
+
+    elif isinstance(expr, PythonMax):
+        args = [pyccel_to_sympy(ee, symbol_map, used_names) for e in expr.args for ee in e]
+        return sp.Max(*args)
+
     elif expr in symbol_map.values():
         return list(symbol_map.keys())[list(symbol_map.values()).index(expr)]
 
diff --git a/pyccel/codegen/printing/ccode.py b/pyccel/codegen/printing/ccode.py
index f78661e843..dbaaf6ca74 100644
--- a/pyccel/codegen/printing/ccode.py
+++ b/pyccel/codegen/printing/ccode.py
@@ -1876,7 +1876,10 @@ def _print_Allocate(self, expr):
             variable_address = self._print(ObjectAddress(expr.variable))
             container_type = self.get_c_type(expr.variable.class_type)
             if expr.alloc_type == 'reserve':
-                return free_code + f'{container_type}_reserve({variable_address}, {size});\n'
+                if expr.status != 'unallocated':
+                    return (f'{container_type}_clear({variable_address});\n'
+                            f'{container_type}_reserve({variable_address}, {size});\n')
+                return f'{container_type}_reserve({variable_address}, {size});\n'
             elif expr.alloc_type == 'resize':
                 return f'{container_type}_resize({variable_address}, {size}, {0});\n'
             return free_code
diff --git a/pyccel/codegen/printing/fcode.py b/pyccel/codegen/printing/fcode.py
index 00546c70e3..fb56d9faa6 100644
--- a/pyccel/codegen/printing/fcode.py
+++ b/pyccel/codegen/printing/fcode.py
@@ -2311,7 +2311,10 @@ def _print_Allocate(self, expr):
             elif expr.alloc_type == 'reserve':
                 var_code = self._print(expr.variable)
                 size_code = self._print(expr.shape[0])
-                return f'call {var_code} % reserve({size_code})\n'
+                if expr.status == 'unallocated':
+                    return f'call {var_code} % reserve({size_code})\n'
+                return (f'call {var_code} % clear()\n'
+                        f'call {var_code} % reserve({size_code})\n')
             else:
                 return ''
         elif isinstance(class_type, (HomogeneousContainerType, DictType, StringType)):
diff --git a/pyccel/codegen/printing/pycode.py b/pyccel/codegen/printing/pycode.py
index f548cd1a34..d66d1c574e 100644
--- a/pyccel/codegen/printing/pycode.py
+++ b/pyccel/codegen/printing/pycode.py
@@ -10,7 +10,7 @@
 
 from pyccel.ast.builtins   import PythonMin, PythonMax, PythonType, PythonBool, PythonInt, PythonFloat
 from pyccel.ast.builtins   import PythonComplex, DtypePrecisionToCastFunction, PythonTuple
-from pyccel.ast.core       import CodeBlock, Import, Assign, FunctionCall, For, AsName, FunctionAddress
+from pyccel.ast.core       import CodeBlock, Import, Assign, FunctionCall, For, AsName, FunctionAddress, If
 from pyccel.ast.core       import IfSection, FunctionDef, Module, PyccelFunctionDef
 from pyccel.ast.datatypes  import HomogeneousTupleType, HomogeneousListType, HomogeneousSetType
 from pyccel.ast.datatypes  import VoidType, DictType, InhomogeneousTupleType
@@ -22,6 +22,7 @@
 from pyccel.ast.variable   import DottedName, Variable, IndexedElement
 from pyccel.ast.utilities  import builtin_import_registry as pyccel_builtin_import_registry
 from pyccel.ast.utilities  import decorators_mod
+from pyccel.ast.builtin_methods.list_methods import ListAppend
 
 from pyccel.parser.semantic import magic_method_map
 
@@ -97,25 +98,30 @@ def _format_code(self, lines):
 
     def _find_functional_expr_and_iterables(self, expr):
         """
+        Extract the central expression and iterables from a FunctionalFor or GeneratorComprehension.
+
         Traverse through the loop representing a FunctionalFor or GeneratorComprehension
-        to extract the central expression and the different iterable objects
+        to extract the central expression and the different iterable objects.
 
         Parameters
         ----------
         expr : FunctionalFor
+               The loop or generator comprehension to be analyzed.
 
         Returns
         -------
         body      : TypedAstNode
-                    The expression inside the for loops
+                    The expression inside the for loops.
         iterables : list of Iterables
-                    The iterables over which the for loops iterate
+                    The iterables over which the for loops iterate.
         """
         dummy_var = expr.index
         iterables = []
-        body = expr.loops[1]
-        while not isinstance(body, Assign):
-            if isinstance(body, CodeBlock):
+        body = expr.loops[-1]
+        while not isinstance(body, (Assign, ListAppend)):
+            if isinstance(body, If):
+                body = body.blocks[0].body.body[0]
+            elif isinstance(body, CodeBlock):
                 body = list(body.body)
                 while isinstance(body[0], FunctionalFor):
                     func_for = body.pop(0)
@@ -660,15 +666,20 @@ def _print_For(self, expr):
     def _print_FunctionalFor(self, expr):
         body, iterators = self._find_functional_expr_and_iterables(expr)
         lhs = self._print(expr.lhs)
-        body = self._print(body.rhs)
-        for_loops = ' '.join(['for {} in {}'.format(self._print(idx), self._print(iters))
-                        for idx, iters in zip(expr.indices, iterators)])
+        condition = ''
+        if isinstance(body, Assign):
+            body = self._print(body.rhs)
+        else:
+            assert isinstance(body, ListAppend)
+            body = self._print(body.args[0])
 
-        name = self._aliases.get(type(expr),'array')
-        if name == 'array':
-            self.add_import(Import('numpy', [AsName(NumpyArray, 'array')]))
+        for_loops = ' '.join(f'for {self._print(idx)} in {self._print(iters)}{" if " + self._print(condition.blocks[0].condition) if condition else ""}'
+                             for idx, iters, condition in zip(expr.indices, iterators, expr.conditions))
 
-        return '{} = {}([{} {}])\n'.format(lhs, name, body, for_loops)
+        if isinstance(expr.class_type, NumpyNDArrayType):
+            array = self._get_numpy_name(NumpyArray)
+            return f'{lhs} = {array}([{body} {for_loops} {condition}])\n'
+        return f'{lhs} = [{body} {for_loops} {condition}]\n'
 
     def _print_GeneratorComprehension(self, expr):
         body, iterators = self._find_functional_expr_and_iterables(expr)
@@ -684,8 +695,8 @@ def _print_GeneratorComprehension(self, expr):
                     rhs = type(body.rhs)(*args)
 
         body = self._print(rhs)
-        for_loops = ' '.join(['for {} in {}'.format(self._print(idx), self._print(iters))
-                        for idx, iters in zip(expr.indices, iterators)])
+        for_loops = ' '.join(f'for {self._print(idx)} in {self._print(iters)}{" if " + self._print(condition.blocks[0].condition) if condition else ""}'
+                             for idx, iters, condition in zip(expr.indices, iterators, expr.conditions))
 
         if expr.get_user_nodes(FunctionalFor):
             return '{}({} {})'.format(expr.name, body, for_loops)
diff --git a/pyccel/parser/semantic.py b/pyccel/parser/semantic.py
index d331af0013..b85a5d20c7 100644
--- a/pyccel/parser/semantic.py
+++ b/pyccel/parser/semantic.py
@@ -1922,11 +1922,14 @@ def _assign_GeneratorComprehension(self, lhs_name, expr):
         index   = Variable(PythonNativeInt(),self.scope.get_new_name('to_delete'), is_temp=True)
         self.scope.insert_variable(index)
         new_expr = []
-        while isinstance(loop, For):
+        while isinstance(loop, (For, If)):
+
             nlevels+=1
             self._get_for_iterators(loop.iterable, loop.target, new_expr)
 
             loop_elem = loop.body.body[0]
+            if isinstance(loop_elem, If):
+                loop_elem = loop_elem.blocks[0].body.body[0]
 
             if isinstance(loop_elem, Assign):
                 # If the result contains a GeneratorComprehension, treat it and replace
@@ -1964,11 +1967,11 @@ def _assign_GeneratorComprehension(self, lhs_name, expr):
                 val = convert_to_literal(0, dtype)
             d_var = self._infer_type(PyccelAdd(result, val))
         elif isinstance(expr, FunctionalMin):
-            val = math_constants['inf']
             d_var = self._infer_type(result)
+            val = convert_to_literal(d_var['class_type'].max_value)
         elif isinstance(expr, FunctionalMax):
-            val = PyccelUnarySub(math_constants['inf'])
             d_var = self._infer_type(result)
+            val = convert_to_literal(d_var['class_type'].min_value)
 
         # Infer the final dtype of the expression
         class_type = d_var.pop('class_type')
@@ -2005,11 +2008,11 @@ def _assign_GeneratorComprehension(self, lhs_name, expr):
         indices = [self._visit(i) for i in expr.indices]
 
         if isinstance(expr, FunctionalSum):
-            expr_new = FunctionalSum(loops, lhs=lhs, indices = indices)
+            expr_new = FunctionalSum(loops, lhs=lhs, indices = indices, conditions=expr.conditions)
         elif isinstance(expr, FunctionalMin):
-            expr_new = FunctionalMin(loops, lhs=lhs, indices = indices)
+            expr_new = FunctionalMin(loops, lhs=lhs, indices = indices, conditions=expr.conditions)
         elif isinstance(expr, FunctionalMax):
-            expr_new = FunctionalMax(loops, lhs=lhs, indices = indices)
+            expr_new = FunctionalMax(loops, lhs=lhs, indices = indices, conditions=expr.conditions)
         expr_new.set_current_ast(expr.python_ast)
         return expr_new
 
@@ -3970,90 +3973,127 @@ def _visit_For(self, expr):
             for_expr.end_annotation = expr.end_annotation
         return for_expr
 
-
     def _visit_FunctionalFor(self, expr):
-        old_index   = expr.index
-        new_index   = self.scope.get_new_name()
-        # Identity comparison is required to only find duplicates of the index caused
-        # by the construction of FunctionalFor.
-        expr.substitute(old_index, new_index, is_equivalent = lambda x,y: x is y)
+        """
+        Visit and transform a FunctionalFor AST node into an equivalent code block.
 
-        target  = expr.expr
-        index   = new_index
-        indices = [self.scope.get_expected_name(i) for i in expr.indices]
-        dims    = []
-        body    = expr.loops[1]
+        This method processes a `FunctionalFor` expression and transforms the loop structure
+        into a corresponding code block.
 
-        idx_subs = {}
-        #scope = self.create_new_loop_scope()
+        Parameters
+        ----------
+        expr : pyccel.ast.functionalexpr.FunctionalFor
+            The FunctionalFor AST node.
 
-        # The symbols created to represent unknown valued objects are temporary
+        Returns
+        -------
+        pyccel.ast.basic.CodeBlock
+            A code block containing the equivalent loops and necessary variable allocations for the given `FunctionalFor` expression.
+        """
+        target  = expr.expr
+        indices = []
+        dims = []
+        idx_subs = {}
         tmp_used_names = self.scope.all_used_symbols.copy()
         i = 0
-        while isinstance(body, For):
+        loops = list(expr.loops)
+
+        # Inner function to handle PythonNativeInt variables
+        def handle_int_loop_variable(var_name, var_scope):
+            indices.append(var_name)
+            var = self._create_variable(var_name, PythonNativeInt(), None, {}, insertion_scope=var_scope)
+            return var
+
+        # Inner function to handle iterable variables
+        def handle_iterable_variable(var_name, element, var_scope):
+            indices.append(var_name)
+            dvar = self._infer_type(element)
+            class_type = dvar.pop('class_type')
+            if class_type.rank > 0:
+                class_type = class_type.switch_rank(class_type.rank - 1)
+                dvar['shape'] = dvar['shape'][1:]
+            if class_type.rank == 0:
+                dvar['shape'] = None
+                dvar['memory_handling'] = 'stack'
+            var = self._create_variable(var_name, class_type, None, dvar, insertion_scope=var_scope)
+            return var
+
+        for loop, condition in zip(loops, expr.conditions):
+            if condition:
+                loop.insert2body(condition)
+
+        while len(loops) > 1:
+            outer_loop = loops.pop()
+            inserted_into = loops[-1]
+            if inserted_into.body.body:
+                inserted_into.body.body[0].blocks[0].body.insert2body(outer_loop)
+            else:
+                inserted_into.insert2body(outer_loop)
+
+        body = loops[0]
+
+        while isinstance(body, (For, If)):
+
+            if isinstance(body, If):
+                body = None if not body.blocks[0].body.body else body.blocks[0].body.body[0]
+                continue
 
-            stop  = None
+            stop = None
             start = LiteralInteger(0)
-            step  = LiteralInteger(1)
-            var   = indices[i]
-            i += 1
-            a     = self._visit(body.iterable)
+            step = LiteralInteger(1)
+            variables = []
+            a = self._get_iterable(self._visit(body.iterable))
             if isinstance(a, PythonRange):
-                var   = self._create_variable(var, PythonNativeInt(), start, {})
-                dvar  = self._infer_type(var)
-                stop  = a.stop
+                var_name = self.scope.get_expected_name(expr.indices[i])
+                variables.append(handle_int_loop_variable(var_name, body.scope))
                 start = a.start
+                stop  = a.stop
                 step  = a.step
 
-            elif isinstance(a, (PythonZip, PythonEnumerate)):
-                dvar  = self._infer_type(a.element)
-                class_type = dvar.pop('class_type')
-                if class_type.rank > 0:
-                    class_type = class_type.switch_rank(class_type.rank-1)
-                    dvar['shape'] = (dvar['shape'])[1:]
-                if class_type.rank == 0:
-                    dvar['shape'] = None
-                    dvar['memory_handling'] = 'stack'
-                var  = Variable(class_type, var, **dvar)
+            elif isinstance(a, PythonEnumerate):
+                var_name1 = self.scope.get_expected_name(expr.indices[i][0])
+                var_name2 = self.scope.get_expected_name(expr.indices[i][1])
+                variables.append(handle_int_loop_variable(var_name1, body.scope))
+                variables.append(handle_iterable_variable(var_name2, a.element, body.scope))
                 stop = a.element.shape[0]
 
-            elif isinstance(a, Variable):
-                dvar  = self._infer_type(a)
-                class_type = dvar.pop('class_type')
-                if class_type.rank > 0:
-                    class_type = class_type.switch_rank(class_type.rank-1)
-                    dvar['shape'] = (dvar['shape'])[1:]
-                if class_type.rank == 0:
-                    dvar['shape'] = None
-                    dvar['memory_handling'] = 'stack'
-                var  = Variable(class_type, var, **dvar)
-                stop = a.shape[0]
+            elif isinstance(a, PythonZip):
+                for idx, arg in enumerate(a.args):
+                    var = self.scope.get_expected_name(expr.indices[i][idx])
+                    variables.append(handle_iterable_variable(var, arg, body.scope))
+                stop = a.get_range().stop
+
+            elif isinstance(a, VariableIterator):
+                var = self.scope.get_expected_name(expr.indices[i])
+                variables.append(handle_iterable_variable(var, a.variable, body.scope))
+                stop = a.variable.shape[0]
 
             else:
                 errors.report(PYCCEL_RESTRICTION_TODO,
                               bounding_box=(self.current_ast_node.lineno, self.current_ast_node.col_offset),
                               severity='fatal')
-            existing_var = self.scope.find(var.name, 'variables')
-            if existing_var:
-                if self._infer_type(existing_var) != dvar:
-                    errors.report(f"Variable {var} already exists with different type",
-                            symbol = expr, severity='error')
-            else:
-                self.scope.insert_variable(var)
-            step.invalidate_node()
+
+            for var in variables:
+                existing_var = self.scope.find(var.name, 'variables')
+                if var.name == expr.lhs:
+                    errors.report(f"Variable {var} has the same name as the left hand side",
+                            symbol = expr, severity='fatal')
+                if existing_var or var.name ==  expr.lhs:
+                    if self._infer_type(existing_var)['class_type'] != var.class_type:
+                        errors.report(f"Variable {var} already exists with different type",
+                                symbol = expr, severity='fatal')
+                else:
+                    self.scope.insert_variable(var)
+
             step  = pyccel_to_sympy(step , idx_subs, tmp_used_names)
-            start.invalidate_node()
             start = pyccel_to_sympy(start, idx_subs, tmp_used_names)
-            stop.invalidate_node()
             stop  = pyccel_to_sympy(stop , idx_subs, tmp_used_names)
             size = (stop - start) / step
             if (step != 1):
                 size = ceiling(size)
-
-            body = body.body.body[0]
+            body = None if not body.body.body else body.body.body[0]
             dims.append((size, step, start, stop))
-
-        # we now calculate the size of the array which will be allocated
+            i += 1
 
         for idx in indices:
             var = self.get_variable(idx)
@@ -4113,12 +4153,6 @@ def _visit_FunctionalFor(self, expr):
                           symbol=expr,
                           severity='fatal')
 
-        # TODO find a faster way to calculate dim
-        # when step>1 and not isinstance(dim, Sum)
-        # maybe use the c++ library of sympy
-
-        # we annotate the target to infere the type of the list created
-
         target = self._visit(target)
         d_var = self._infer_type(target)
 
@@ -4130,7 +4164,7 @@ def _visit_FunctionalFor(self, expr):
                           severity='fatal')
 
         d_var['memory_handling'] = 'heap'
-        target_type_name = expr.target_type
+        target_type_name = 'list' if not expr.target_type else expr.target_type
         if isinstance(target_type_name, DottedName):
             lhs = target_type_name.name[0] if len(target_type_name.name) == 2 \
                     else DottedName(*target_type_name.name[:-1])
@@ -4149,7 +4183,10 @@ def _visit_FunctionalFor(self, expr):
             errors.report("Unrecognised output type from functional for.\n"+PYCCEL_RESTRICTION_TODO,
                           symbol=expr,
                           severity='fatal')
-
+        if target_type_name != 'list' and any(cond is not None for cond in  expr.conditions):
+            errors.report("Cannot handle if statements in list comprehensions if lhs is a numpy array.\
+                          List length cannot be calculated.\n" + PYCCEL_RESTRICTION_TODO,
+                           symbol=expr, severity='error')
         try:
             class_type = type_container[conversion_func.cls_name](class_type)
         except TypeError:
@@ -4167,7 +4204,7 @@ def _visit_FunctionalFor(self, expr):
         # TODO [YG, 30.10.2020]:
         #  - Check if we should allow the possibility that is_stack_array=True
         # ...
-        lhs_symbol = expr.lhs.base
+        lhs_symbol = expr.lhs
         ne = []
         lhs = self._assign_lhs_variable(lhs_symbol, d_var, rhs=expr, new_expressions=ne)
         lhs_alloc = ne[0]
@@ -4176,22 +4213,59 @@ def _visit_FunctionalFor(self, expr):
             errors.report(LIST_OF_TUPLES, symbol=expr, severity='error')
 
         target.invalidate_node()
+        operations = []
+        assign = None
+        target_conversion_func = self._visit(target_type_name)
+        if (isinstance(target_conversion_func, PyccelFunctionDef)
+                and target_conversion_func.cls_name is NumpyArray):
+            old_index   = expr.index
+            new_index   = self.scope.get_new_name()
+            expr.substitute(old_index, new_index, is_equivalent = lambda x,y: x is y)
+            array_ops = expr.operations['numpy_array']
+            for operation in array_ops:
+                operation.substitute(old_index, new_index, is_equivalent = lambda x,y: x is y)
+            assign = array_ops[0]
+            assign = self._visit(array_ops[0])
+            operations.extend(array_ops[1:])
+            index = new_index
+            index = self._visit(index)
+        elif target_conversion_func == "'list'":
+            index = None
+            operations.extend(expr.operations['list'])
+        else:
+            errors.report("Unrecognised target for functional for.\n"+PYCCEL_RESTRICTION_TODO,
+                          symbol=expr,
+                          severity='fatal')
 
-        loops = [self._visit(i) for i in expr.loops]
-        index = self._visit(index)
+        if expr.loops[-1].body.body:
+            for operation in operations:
+                expr.loops[-1].body.body[0].blocks[0].body.insert2body(self._visit(operation))
+        else:
+            for operation in operations:
+                expr.loops[-1].insert2body(self._visit(operation))
+
+
+        loops = [self._visit(i) for i in loops]
+        if assign:
+            loops = [assign, *loops]
 
         l = loops[-1]
+        cnt = 0
         for idx in indices:
             assert isinstance(l, For)
-            # Sub in indices as defined here for coherent naming
             if idx.is_temp:
-                self.scope.remove_variable(l.target[0])
-                l.substitute(l.target[0], idx_subs[idx])
-            l = l.body.body[-1]
-
-        #self.exit_loop_scope()
+                self.scope.remove_variable(l.target[cnt])
+                l.substitute(l.target[cnt], idx_subs[idx])
+            cnt += 1
+            if cnt == len(l.target):
+                if l.body.body:
+                    if isinstance(l.body.body[0], If):
+                        l = l.body.body[0].blocks[0].body.body[0]
+                    else:
+                        l = l.body.body[0]
+                cnt = 0
 
-        return CodeBlock([lhs_alloc, FunctionalFor(loops, lhs=lhs, indices=indices, index=index)])
+        return CodeBlock([lhs_alloc, FunctionalFor(loops, lhs=lhs, index=index, indices=expr.indices, target_type=target_type_name, conditions=expr.conditions)])
 
     def _visit_GeneratorComprehension(self, expr):
         lhs = self.check_for_variable(expr.lhs)
diff --git a/pyccel/parser/syntactic.py b/pyccel/parser/syntactic.py
index 27f4228880..32f8dfff53 100644
--- a/pyccel/parser/syntactic.py
+++ b/pyccel/parser/syntactic.py
@@ -1183,26 +1183,68 @@ def _visit_For(self, stmt):
     def _visit_comprehension(self, stmt):
 
         scope = self.create_new_loop_scope()
+        condition = None
 
         self._in_lhs_assign = True
         iterator = self._visit(stmt.target)
         self._in_lhs_assign = False
         iterable = self._visit(stmt.iter)
 
-        self.exit_loop_scope()
-
         if stmt.ifs:
-            errors.report("Cannot handle if statements in list comprehensions. List length cannot be calculated.\n" + PYCCEL_RESTRICTION_TODO,
-                    symbol=stmt, severity='error')
+            cond = PyccelAnd(*[self._visit(if_cond)
+                                      for if_cond in stmt.ifs])
+            condition = If(IfSection(cond, CodeBlock([])))
 
-        expr = For(iterator, iterable, [], scope=scope)
+        self.exit_loop_scope()
+
+        expr = (For(iterator, iterable, [], scope=scope), condition)
         return expr
 
     def _visit_ListComp(self, stmt):
+        """
+        Converts a list comprehension statement into a `FunctionalFor` AST object.
+
+        This method translates the list comprehension into an equivalent `FunctionalFor`
+        
+        Parameters
+        ----------
+        stmt : ast.stmt
+            Object to visit of type X.
+
+        Returns
+        -------
+        pyccel.ast.functionalexpr.FunctionalFor
+            AST object which is the syntactic equivalent of the list comprehension.
+        """
+
+        def create_target_operations():
+            operations = {'list' : [], 'numpy_array' : []}
+            index = PyccelSymbol('_', is_temp=True)
+            args = [index]
+            target = IndexedElement(lhs, *args)
+            target = Assign(target, result)
+            assign1 = Assign(index, LiteralInteger(0))
+            assign1.set_current_ast(stmt)
+            operations['numpy_array'].append(assign1)
+            target.set_current_ast(stmt)
+            operations['numpy_array'].append(target)
+            assign2 = Assign(index, PyccelAdd(index, LiteralInteger(1)))
+            assign2.set_current_ast(stmt)
+            operations['numpy_array'].append(assign2)
+            operations['list'].append(DottedName(lhs, FunctionCall('append', [FunctionCallArgument(result)])))
+
+            return index, operations
+
 
         result = self._visit(stmt.elt)
+        output_type = None
+        comprehensions = list(self._visit(stmt.generators))
+        generators = [c[0] for c in comprehensions]
+        conditions = [c[1] for c in comprehensions]
 
-        generators = list(self._visit(stmt.generators))
+        parent = self._context[-2]
+        if isinstance(parent, ast.Call):
+            output_type = self._visit(parent.func)
 
         success = isinstance(self._context[-2],ast.Assign)
         if not success and len(self._context) > 2:
@@ -1222,40 +1264,25 @@ def _visit_ListComp(self, stmt):
             else:
                 raise NotImplementedError("A list comprehension cannot be unpacked")
 
-        parent = self._context[-2]
-        if isinstance(parent, ast.Call):
-            output_type = self._visit(parent.func)
-        else:
-            output_type = 'list'
-
-        index = PyccelSymbol('_', is_temp=True)
-
-        args = [index]
-        target = IndexedElement(lhs, *args)
-        target = Assign(target, result)
-        assign1 = Assign(index, LiteralInteger(0))
-        assign1.set_current_ast(stmt)
-        target.set_current_ast(stmt)
-        generators[-1].insert2body(target)
-        assign2 = Assign(index, PyccelAdd(index, LiteralInteger(1)))
-        assign2.set_current_ast(stmt)
-        generators[-1].insert2body(assign2)
-
-        indices = [generators[-1].target]
-        while len(generators) > 1:
-            F = generators.pop()
-            generators[-1].insert2body(F)
-            indices.append(generators[-1].target)
-        indices = indices[::-1]
+        indices = [generator.target for generator in generators]
+        index, operations = create_target_operations()
 
-        return FunctionalFor([assign1, generators[-1]],target.rhs, target.lhs,
-                             indices, index, target_type = output_type)
+        return FunctionalFor(generators, result, lhs,indices,index=index,
+                             conditions=conditions,
+                             target_type=output_type, operations=operations)
 
     def _visit_GeneratorExp(self, stmt):
+        conditions = []
+        generators = []
+        indices = []
+        condition = None
 
         result = self._visit(stmt.elt)
 
-        generators = self._visit(stmt.generators)
+        comprehensions = list(self._visit(stmt.generators))
+        generators = [c[0] for c in comprehensions]
+        conditions = [c[1] for c in comprehensions]
+
         parent = self._context[-2]
         if not isinstance(parent, ast.Call):
             raise NotImplementedError("GeneratorExp is not the argument of a function call")
@@ -1278,20 +1305,35 @@ def _visit_GeneratorExp(self, stmt):
             body = Assign(lhs, body)
 
         body.set_current_ast(parent)
-        indices = []
-        generators = list(generators)
-        while len(generators) > 0:
-            indices.append(generators[-1].target)
+
+        for loop, condition in zip(generators, conditions):
+            if condition:
+                loop.insert2body(condition)
+
+        if generators[-1].body.body: # this is an If node
+            if_node = generators[-1].body.body[0]
+            if_node.blocks[0].body.insert2body(body)
+        else:
             generators[-1].insert2body(body)
-            body = generators.pop()
+
+        while len(generators) > 1:
+            indices.append(generators[-1].target)
+            outer_loop = generators.pop()
+            inserted_into = generators[-1]
+            if inserted_into.body.body:
+                inserted_into.body.body[0].blocks[0].body.insert2body(outer_loop)
+            else:
+                inserted_into.insert2body(outer_loop)
+        indices.append(generators[-1].target)
 
         indices = indices[::-1]
+
         if name == 'sum':
-            expr = FunctionalSum(body, result, lhs, indices)
+            expr = FunctionalSum(generators[0], result, lhs, indices, conditions=conditions)
         elif name == 'min':
-            expr = FunctionalMin(body, result, lhs, indices)
+            expr = FunctionalMin(generators[0], result, lhs, indices, conditions=conditions)
         elif name == 'max':
-            expr = FunctionalMax(body, result, lhs, indices)
+            expr = FunctionalMax(generators[0], result, lhs, indices, conditions=conditions)
         else:
             expr = EmptyNode()
             errors.report(PYCCEL_RESTRICTION_TODO,
diff --git a/tests/epyccel/modules/functionals.py b/tests/epyccel/modules/functionals.py
index aa4244e51d..489963b505 100644
--- a/tests/epyccel/modules/functionals.py
+++ b/tests/epyccel/modules/functionals.py
@@ -60,3 +60,39 @@ def functional_for_3d_range():
 def unknown_length_functional(x : 'int[:]'):
     a = [i*3 for i in range(len(x)*2)]
     return len(a), a[0], a[-1]
+
+def functional_with_enumerate():
+    a = [x + 1 for x in range(10)]
+    b = [i * j for i,j in enumerate(a)]
+    return len(b), b[0], b[1], b[2]
+
+def functional_with_enumerate_with_start():
+    a = [x - 3  for x in range(10)]
+    b = [i * j for i,j in enumerate(a, start=5)]
+    return len(b), b[0], b[1], b[2]
+
+def functional_with_condition():
+    a = [x if x %2 == 0 else -x for x in range(30)]
+    return len(a), a[0], a[1], a[2], a[3]
+
+def functional_with_zip():
+    a = [x ** 2 for x in range(8)]
+    b = [0, 1, 2]
+    c = [k-y for k,y in zip(a,b)]
+    return len(c), c[0], c[1], c[2]
+
+def functional_with_multiple_zips():
+    a = [x ** 2 for x in range(8)]
+    b = [0, 1, 2]
+    c = [y + 1 for y in range(10)]
+    d = [i + j + k for i, j, k in zip(a, b, c)]
+    return len(d), d[0], d[1], d[2]
+
+def functional_filter_and_transform():
+    a = [x + 1 for x in range(10) if x % 2 == 0]
+    return len(a), a[0], a[1], a[2]
+
+
+def functional_with_multiple_conditions():
+    a = [x + 1 - y for x in range(10) if x % 2 == 0 for y in range(12) if y % 3 == 0]
+    return len(a), a[0], a[1], a[2]
diff --git a/tests/epyccel/test_arrays.py b/tests/epyccel/test_arrays.py
index 02bcbc6df3..7595d0ace5 100644
--- a/tests/epyccel/test_arrays.py
+++ b/tests/epyccel/test_arrays.py
@@ -4138,11 +4138,11 @@ def test_array_float_nested_C_array_initialization_3(language):
 #==============================================================================
 @pytest.mark.parametrize( 'language', (
         pytest.param("fortran", marks = [
-            pytest.mark.skip(reason="List comprehension not yet implemented"),
+            pytest.mark.xfail(reason="Lists of lists are not yet supported in Fortran, related issue #2210"),
             pytest.mark.fortran]
         ),
         pytest.param("c", marks = [
-            pytest.mark.xfail(reason="List indexing is not yet supported in C, related issue #1876"),
+            pytest.mark.xfail(reason="Lists of lists are not yet supported in C, related issue #2210"),
             pytest.mark.c]
         ),
         pytest.param("python", marks = pytest.mark.python)
diff --git a/tests/epyccel/test_epyccel_generators.py b/tests/epyccel/test_epyccel_generators.py
index c9a470bf42..622ba6f0ab 100644
--- a/tests/epyccel/test_epyccel_generators.py
+++ b/tests/epyccel/test_epyccel_generators.py
@@ -1,7 +1,7 @@
 # pylint: disable=missing-function-docstring, missing-module-docstring
 import pytest
 import numpy as np
-from numpy.random import randint, rand
+from numpy.random import randint
 
 from pyccel import epyccel
 
@@ -92,7 +92,6 @@ def f():
     f_epyc = epyccel(f, language = language)
 
     assert f() == f_epyc()
-
 @pytest.mark.parametrize( 'language', (
         pytest.param("fortran", marks = pytest.mark.fortran),
         pytest.param("c", marks = [
@@ -113,7 +112,7 @@ def f():
 def test_expression1(language):
     def f(b : 'float[:]'):
         n = b.shape[0]
-        return (2*sum(b[i] for i in range(n))**5+5)*min(j+1. for j in b)**4+9
+        return (2*sum(b[i] for i in range(n))**5+5)*min(j+1. for j in b)**4+9*max(j+1. for j in b)**4
 
     n = randint(1,10)
     x = np.array(randint(100, size=n), dtype=float)
@@ -199,6 +198,70 @@ def f(a0 : 'int[:]'):
 
     assert f(x) == f_epyc(x)
 
+def test_sum_with_condition(language):
+    def f():
+        v = sum(i for i in range(20) if i % 2 == 1)
+        return v
+
+    f_epyc = epyccel(f, language = language)
+    assert f() == f_epyc()
+
+def test_sum_with_multiple_conditions(language):
+    def f():
+        v = sum(i - j for i in range(20) if i % 2 == 1 for j in range(30) if j % 3 == 1)
+        return v
+
+    f_epyc = epyccel(f, language = language)
+    assert f() == f_epyc()
+
+def test_max_with_condition(language):
+    def f():
+        v = max(i for i in range(20) if i % 2 == 1)
+        return v
+
+    f_epyc = epyccel(f, language = language)
+    assert f() == f_epyc()
+
+def test_max_with_condition_float(language):
+    def f():
+        v = max(i/2 for i in range(20) if i % 2 == 1)
+        return v
+
+    f_epyc = epyccel(f, language = language)
+    assert f() == f_epyc()
+
+def test_max_with_multiple_conditions(language):
+    def f():
+        v = max(i - j for i in range(20) if i % 2 == 1 for j in range(30) if j % 3 == 1)
+        return v
+
+    f_epyc = epyccel(f, language = language)
+    assert f() == f_epyc()
+
+def test_min_with_condition(language):
+    def f():
+        v = min(i for i in range(20) if i % 2 == 1)
+        return v
+
+    f_epyc = epyccel(f, language = language)
+    assert f() == f_epyc()
+
+def test_min_with_condition_float(language):
+    def f():
+        v = min(i/2 for i in range(20) if i % 2 == 1)
+        return v
+
+    f_epyc = epyccel(f, language = language)
+    assert f() == f_epyc()
+
+def test_min_with_multiple_conditions(language):
+    def f():
+        v = min(i - j for i in range(20) if i % 2 == 1 for j in range(30) if j % 3 == 1)
+        return v
+
+    f_epyc = epyccel(f, language = language)
+    assert f() == f_epyc()
+
 def test_sum_with_two_variables(language):
     def f():
         x = sum(i-j for i in range(10) for j in range(7))
diff --git a/tests/epyccel/test_functionals.py b/tests/epyccel/test_functionals.py
index 3bf1bcb789..b699e30c50 100644
--- a/tests/epyccel/test_functionals.py
+++ b/tests/epyccel/test_functionals.py
@@ -61,6 +61,7 @@ def test_functional_for_2d_dependant_range(language):
         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)
@@ -74,3 +75,25 @@ def test_functional_for_3d_range(language):
 def test_unknown_length_functional(language):
     y = array(randint(100, size = 20), dtype = int)
     compare_epyccel(functionals.unknown_length_functional, language, y)
+
+def test_functional_with_enumerate(language):
+    compare_epyccel(functionals.functional_with_enumerate, language)
+
+def test_functional_with_enumerate_with_start(language):
+    compare_epyccel(functionals.functional_with_enumerate_with_start, language)
+
+def test_functional_with_condition(language):
+    compare_epyccel(functionals.functional_with_condition, language)
+
+def test_functional_with_zip(language):
+    compare_epyccel(functionals.functional_with_zip, language)
+
+def test_functional_with_multiple_zips(language):
+    compare_epyccel(functionals.functional_with_multiple_zips, language)
+
+def test_functional_filter_and_transform(language):
+    compare_epyccel(functionals.functional_with_condition, language)
+
+def test_functional_with_multiple_conditions(language):
+    compare_epyccel(functionals.functional_with_multiple_conditions, language)
+


Reviews:
user: login: pyccel-bot[bot]
body: There seems to be lines in this PR which aren't tested. Please take a look at my comments and add tests which cover the new code.

If this is modified code which cannot be easily tested in this PR please open an issue to request that this code be either removed or tested. Once you have done that please leave a message on the relevant conversation beginning with the line `/bot accept` and referencing the issue.

Similarly if the new code cannot be tested for some reason, please leave a comment beginning with the line `/bot accept` on the relevant conversation explaining why the code can't be tested.


state: CHANGES_REQUESTED
author_association: NONE
submitted_at: 2024-09-04T19:47:27Z
user: login: EmilyBourne
body: I'm a little early but I saw you were trying to mark as ready so I thought I'd review while I'm free
state: CHANGES_REQUESTED
author_association: MEMBER
submitted_at: 2024-09-04T19:58:13Z
user: login: jalalium
state: COMMENTED
author_association: MEMBER
submitted_at: 2024-09-04T20:08:02Z
user: login: jalalium
state: COMMENTED
author_association: MEMBER
submitted_at: 2024-09-04T20:09:57Z
user: login: jalalium
state: COMMENTED
author_association: MEMBER
submitted_at: 2024-09-04T20:11:26Z
user: login: jalalium
state: COMMENTED
author_association: MEMBER
submitted_at: 2024-09-04T20:12:04Z
user: login: EmilyBourne
state: COMMENTED
author_association: MEMBER
submitted_at: 2024-09-04T20:42:24Z
user: login: EmilyBourne
state: COMMENTED
author_association: MEMBER
submitted_at: 2024-09-04T20:46:28Z
user: login: jalalium
state: COMMENTED
author_association: MEMBER
submitted_at: 2024-09-04T21:14:59Z
user: login: EmilyBourne
state: COMMENTED
author_association: MEMBER
submitted_at: 2024-09-05T07:08:26Z
user: login: jalalium
state: COMMENTED
author_association: MEMBER
submitted_at: 2024-09-06T11:55:02Z
user: login: jalalium
state: COMMENTED
author_association: MEMBER
submitted_at: 2024-09-06T11:56:49Z
user: login: EmilyBourne
state: COMMENTED
author_association: MEMBER
submitted_at: 2024-09-06T20:49:20Z
user: login: EmilyBourne
state: COMMENTED
author_association: MEMBER
submitted_at: 2024-09-06T20:51:13Z
user: login: pyccel-bot[bot]
body: There seems to be lines in this PR which aren't tested. Please take a look at my comments and add tests which cover the new code.

If this is modified code which cannot be easily tested in this PR please open an issue to request that this code be either removed or tested. Once you have done that please leave a message on the relevant conversation beginning with the line `/bot accept` and referencing the issue.

Similarly if the new code cannot be tested for some reason, please leave a comment beginning with the line `/bot accept` on the relevant conversation explaining why the code can't be tested.


state: CHANGES_REQUESTED
author_association: NONE
submitted_at: 2024-12-04T15:14:09Z
user: login: jalalium
state: COMMENTED
author_association: MEMBER
submitted_at: 2024-12-04T15:29:32Z
user: login: jalalium
state: COMMENTED
author_association: MEMBER
submitted_at: 2024-12-04T15:29:37Z
user: login: pyccel-bot[bot]
state: COMMENTED
author_association: NONE
submitted_at: 2024-12-04T15:52:23Z
user: login: pyccel-bot[bot]
body: There seems to be lines in this PR which aren't tested. Please take a look at my comments and add tests which cover the new code.

If this is modified code which cannot be easily tested in this PR please open an issue to request that this code be either removed or tested. Once you have done that please leave a message on the relevant conversation beginning with the line `/bot accept` and referencing the issue.

Similarly if the new code cannot be tested for some reason, please leave a comment beginning with the line `/bot accept` on the relevant conversation explaining why the code can't be tested.


state: CHANGES_REQUESTED
author_association: NONE
submitted_at: 2024-12-04T15:52:24Z
user: login: pyccel-bot[bot]
state: COMMENTED
author_association: NONE
submitted_at: 2024-12-04T18:30:34Z
user: login: pyccel-bot[bot]
state: COMMENTED
author_association: NONE
submitted_at: 2024-12-04T18:30:35Z
user: login: pyccel-bot[bot]
body: Good job ! Your PR is using all the code it added/changed.

state: APPROVED
author_association: NONE
submitted_at: 2024-12-04T18:30:36Z
user: login: pyccel-bot[bot]
body: Good job ! Your PR is using all the code it added/changed.

state: APPROVED
author_association: NONE
submitted_at: 2024-12-04T19:26:40Z
user: login: EmilyBourne
state: COMMENTED
author_association: MEMBER
submitted_at: 2024-12-05T08:28:09Z
user: login: EmilyBourne
body: This looks like a good start, but I'm not sure about a few things. In particular, your use of the syntactic stage, and the systematic creation of temporary indices
state: CHANGES_REQUESTED
author_association: MEMBER
submitted_at: 2024-12-05T09:34:38Z
user: login: jalalium
state: COMMENTED
author_association: MEMBER
submitted_at: 2024-12-05T11:12:22Z
user: login: jalalium
state: COMMENTED
author_association: MEMBER
submitted_at: 2024-12-05T11:18:03Z
user: login: jalalium
state: COMMENTED
author_association: MEMBER
submitted_at: 2024-12-05T11:19:52Z
user: login: jalalium
state: COMMENTED
author_association: MEMBER
submitted_at: 2024-12-05T11:28:36Z
user: login: EmilyBourne
state: COMMENTED
author_association: MEMBER
submitted_at: 2024-12-05T12:52:13Z
user: login: EmilyBourne
state: COMMENTED
author_association: MEMBER
submitted_at: 2024-12-05T12:52:27Z
user: login: EmilyBourne
state: COMMENTED
author_association: MEMBER
submitted_at: 2024-12-05T12:55:33Z
user: login: pyccel-bot[bot]
body: There seems to be lines in this PR which aren't tested. Please take a look at my comments and add tests which cover the new code.

If this is modified code which cannot be easily tested in this PR please open an issue to request that this code be either removed or tested. Once you have done that please leave a message on the relevant conversation beginning with the line `/bot accept` and referencing the issue.

Similarly if the new code cannot be tested for some reason, please leave a comment beginning with the line `/bot accept` on the relevant conversation explaining why the code can't be tested.


state: CHANGES_REQUESTED
author_association: NONE
submitted_at: 2025-01-06T10:57:30Z
user: login: EmilyBourne
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-01-13T13:15:58Z
user: login: EmilyBourne
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-01-13T13:16:41Z
user: login: jalalium
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-01-14T14:58:19Z
user: login: pyccel-bot[bot]
body: There seems to be lines in this PR which aren't tested. Please take a look at my comments and add tests which cover the new code.

If this is modified code which cannot be easily tested in this PR please open an issue to request that this code be either removed or tested. Once you have done that please leave a message on the relevant conversation beginning with the line `/bot accept` and referencing the issue.

Similarly if the new code cannot be tested for some reason, please leave a comment beginning with the line `/bot accept` on the relevant conversation explaining why the code can't be tested.


state: CHANGES_REQUESTED
author_association: NONE
submitted_at: 2025-01-14T15:13:57Z
user: login: pyccel-bot[bot]
body: There seems to be lines in this PR which aren't tested. Please take a look at my comments and add tests which cover the new code.

If this is modified code which cannot be easily tested in this PR please open an issue to request that this code be either removed or tested. Once you have done that please leave a message on the relevant conversation beginning with the line `/bot accept` and referencing the issue.

Similarly if the new code cannot be tested for some reason, please leave a comment beginning with the line `/bot accept` on the relevant conversation explaining why the code can't be tested.


state: CHANGES_REQUESTED
author_association: NONE
submitted_at: 2025-01-14T15:24:19Z
user: login: jalalium
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-01-14T17:18:17Z
user: login: jalalium
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-01-14T17:18:43Z
user: login: jalalium
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-01-14T17:29:22Z
user: login: jalalium
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-01-14T17:37:10Z
user: login: pyccel-bot[bot]
state: COMMENTED
author_association: NONE
submitted_at: 2025-01-14T19:20:07Z
user: login: pyccel-bot[bot]
state: COMMENTED
author_association: NONE
submitted_at: 2025-01-14T19:20:08Z
user: login: pyccel-bot[bot]
body: There seems to be lines in this PR which aren't tested. Please take a look at my comments and add tests which cover the new code.

If this is modified code which cannot be easily tested in this PR please open an issue to request that this code be either removed or tested. Once you have done that please leave a message on the relevant conversation beginning with the line `/bot accept` and referencing the issue.

Similarly if the new code cannot be tested for some reason, please leave a comment beginning with the line `/bot accept` on the relevant conversation explaining why the code can't be tested.


state: CHANGES_REQUESTED
author_association: NONE
submitted_at: 2025-01-14T19:20:10Z
user: login: pyccel-bot[bot]
body: There seems to be lines in this PR which aren't tested. Please take a look at my comments and add tests which cover the new code.

If this is modified code which cannot be easily tested in this PR please open an issue to request that this code be either removed or tested. Once you have done that please leave a message on the relevant conversation beginning with the line `/bot accept` and referencing the issue.

Similarly if the new code cannot be tested for some reason, please leave a comment beginning with the line `/bot accept` on the relevant conversation explaining why the code can't be tested.


state: CHANGES_REQUESTED
author_association: NONE
submitted_at: 2025-01-14T20:27:50Z
user: login: EmilyBourne
body: Review done halfway. But there are still unanswered questions from my previous look through
state: CHANGES_REQUESTED
author_association: MEMBER
submitted_at: 2025-01-20T15:34:28Z
user: login: pyccel-bot[bot]
body: There seems to be lines in this PR which aren't tested. Please take a look at my comments and add tests which cover the new code.

If this is modified code which cannot be easily tested in this PR please open an issue to request that this code be either removed or tested. Once you have done that please leave a message on the relevant conversation beginning with the line `/bot accept` and referencing the issue.

Similarly if the new code cannot be tested for some reason, please leave a comment beginning with the line `/bot accept` on the relevant conversation explaining why the code can't be tested.


state: CHANGES_REQUESTED
author_association: NONE
submitted_at: 2025-03-06T13:42:17Z
user: login: EmilyBourne
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-13T15:34:47Z
user: login: EmilyBourne
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-13T15:35:49Z
user: login: EmilyBourne
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-13T15:35:53Z
user: login: EmilyBourne
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-13T15:36:02Z
user: login: pyccel-bot[bot]
body: Good job ! Your PR is using all the code it added/changed.

state: APPROVED
author_association: NONE
submitted_at: 2025-03-13T15:58:12Z
user: login: EmilyBourne
body: This looks much better than the last version but I still have several questions/comments.
state: CHANGES_REQUESTED
author_association: MEMBER
submitted_at: 2025-03-13T16:49:05Z
user: login: jalalium
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-16T13:46:32Z
user: login: jalalium
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-16T13:53:51Z
user: login: jalalium
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-16T13:56:15Z
user: login: jalalium
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-16T14:07:00Z
user: login: EmilyBourne
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-16T14:26:55Z
user: login: EmilyBourne
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-16T14:28:45Z
user: login: EmilyBourne
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-16T14:29:45Z
user: login: EmilyBourne
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-16T14:37:30Z
user: login: EmilyBourne
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-16T14:42:17Z
user: login: EmilyBourne
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-16T14:43:24Z
user: login: EmilyBourne
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-16T14:45:34Z
user: login: EmilyBourne
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-16T14:58:12Z
user: login: jalalium
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-16T15:27:23Z
user: login: jalalium
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-16T15:28:10Z
user: login: jalalium
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-16T15:31:02Z
user: login: jalalium
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-16T15:37:36Z
user: login: EmilyBourne
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-16T15:42:28Z
user: login: EmilyBourne
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-16T15:49:17Z
user: login: jalalium
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-16T17:01:15Z
user: login: jalalium
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-16T17:01:40Z
user: login: jalalium
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-16T17:01:59Z
user: login: jalalium
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-16T17:11:21Z
user: login: EmilyBourne
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-16T17:20:41Z
user: login: EmilyBourne
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-16T17:21:15Z
user: login: EmilyBourne
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-16T17:22:38Z
user: login: EmilyBourne
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-16T17:54:27Z
user: login: jalalium
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-17T06:00:58Z
user: login: jalalium
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-17T06:01:17Z
user: login: jalalium
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-17T06:01:28Z
user: login: jalalium
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-17T06:07:38Z
user: login: jalalium
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-17T06:08:17Z
user: login: jalalium
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-17T06:09:54Z
user: login: pyccel-bot[bot]
body: There seems to be lines in this PR which aren't tested. Please take a look at my comments and add tests which cover the new code.

If this is modified code which cannot be easily tested in this PR please open an issue to request that this code be either removed or tested. Once you have done that please leave a message on the relevant conversation beginning with the line `/bot accept` and referencing the issue.

Similarly if the new code cannot be tested for some reason, please leave a comment beginning with the line `/bot accept` on the relevant conversation explaining why the code can't be tested.


state: CHANGES_REQUESTED
author_association: NONE
submitted_at: 2025-03-17T06:33:11Z
user: login: pyccel-bot[bot]
state: COMMENTED
author_association: NONE
submitted_at: 2025-03-17T07:44:40Z
user: login: pyccel-bot[bot]
state: COMMENTED
author_association: NONE
submitted_at: 2025-03-17T07:44:41Z
user: login: pyccel-bot[bot]
body: There seems to be lines in this PR which aren't tested. Please take a look at my comments and add tests which cover the new code.

If this is modified code which cannot be easily tested in this PR please open an issue to request that this code be either removed or tested. Once you have done that please leave a message on the relevant conversation beginning with the line `/bot accept` and referencing the issue.

Similarly if the new code cannot be tested for some reason, please leave a comment beginning with the line `/bot accept` on the relevant conversation explaining why the code can't be tested.


state: CHANGES_REQUESTED
author_association: NONE
submitted_at: 2025-03-17T07:44:41Z
user: login: pyccel-bot[bot]
state: COMMENTED
author_association: NONE
submitted_at: 2025-03-17T07:44:42Z
user: login: pyccel-bot[bot]
state: COMMENTED
author_association: NONE
submitted_at: 2025-03-17T07:44:43Z
user: login: pyccel-bot[bot]
state: COMMENTED
author_association: NONE
submitted_at: 2025-03-17T08:19:27Z
user: login: pyccel-bot[bot]
state: COMMENTED
author_association: NONE
submitted_at: 2025-03-17T08:19:28Z
user: login: pyccel-bot[bot]
state: COMMENTED
author_association: NONE
submitted_at: 2025-03-17T08:19:29Z
user: login: pyccel-bot[bot]
state: COMMENTED
author_association: NONE
submitted_at: 2025-03-17T08:19:30Z
user: login: pyccel-bot[bot]
state: COMMENTED
author_association: NONE
submitted_at: 2025-03-17T08:19:31Z
user: login: pyccel-bot[bot]
state: COMMENTED
author_association: NONE
submitted_at: 2025-03-17T08:19:32Z
user: login: pyccel-bot[bot]
state: COMMENTED
author_association: NONE
submitted_at: 2025-03-17T08:19:33Z
user: login: pyccel-bot[bot]
state: COMMENTED
author_association: NONE
submitted_at: 2025-03-17T08:19:33Z
user: login: pyccel-bot[bot]
state: COMMENTED
author_association: NONE
submitted_at: 2025-03-17T08:19:34Z
user: login: pyccel-bot[bot]
state: COMMENTED
author_association: NONE
submitted_at: 2025-03-17T08:19:35Z
user: login: pyccel-bot[bot]
body: Good job ! Your PR is using all the code it added/changed.

state: APPROVED
author_association: NONE
submitted_at: 2025-03-17T08:19:36Z
user: login: EmilyBourne
state: CHANGES_REQUESTED
author_association: MEMBER
submitted_at: 2025-03-17T08:26:25Z
user: login: EmilyBourne
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-17T08:31:18Z
user: login: jalalium
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-17T08:35:54Z
user: login: jalalium
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-17T08:40:12Z
user: login: jalalium
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-17T08:46:33Z
user: login: EmilyBourne
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-17T08:49:14Z
user: login: jalalium
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-17T08:50:27Z
user: login: EmilyBourne
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-17T08:52:01Z
user: login: EmilyBourne
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-17T08:55:10Z
user: login: jalalium
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-17T09:08:43Z
user: login: jalalium
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-17T09:21:50Z
user: login: jalalium
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-17T09:26:04Z
user: login: jalalium
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-17T09:26:18Z
user: login: jalalium
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-17T09:28:35Z
user: login: EmilyBourne
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-17T09:49:16Z
user: login: jalalium
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-17T10:18:27Z
user: login: EmilyBourne
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-17T10:23:33Z
user: login: pyccel-bot[bot]
body: Good job ! Your PR is using all the code it added/changed.

state: APPROVED
author_association: NONE
submitted_at: 2025-03-17T10:50:36Z
user: login: pyccel-bot[bot]
body: Good job ! Your PR is using all the code it added/changed.

state: APPROVED
author_association: NONE
submitted_at: 2025-03-17T11:17:24Z
user: login: EmilyBourne
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-17T11:49:13Z
user: login: EmilyBourne
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-17T11:54:28Z
user: login: EmilyBourne
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-17T11:58:46Z
user: login: EmilyBourne
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-17T12:00:21Z
user: login: EmilyBourne
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-17T12:05:27Z
user: login: jalalium
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-17T13:49:07Z
user: login: jalalium
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-17T13:49:14Z
user: login: jalalium
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-17T13:49:20Z
user: login: jalalium
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-17T13:49:34Z
user: login: EmilyBourne
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-17T14:48:16Z
user: login: EmilyBourne
body: Congratulations on this enormous super import PR. I am not entirely convinced by the implementation of `min_value`/`max_value` but I am happy to leave this for a later PR to make this functionality available for tomorrow
state: APPROVED
author_association: MEMBER
submitted_at: 2025-03-17T16:04:00Z
user: login: pyccel-bot[bot]
body: Good job ! Your PR is using all the code it added/changed.

state: APPROVED
author_association: NONE
submitted_at: 2025-03-17T17:56:29Z

Review comments:
diff_hunk: @@ -61,6 +61,7 @@ All notable changes to this project will be documented in this file.
 -   #1892 : Fix implementation of list function when an iterable is passed as parameter.
 -   #1924 : Fix internal error arising in Duplicate or list comprehensions.
 -   #1970 : Fix missing `TypeError` for wrong type passed as optional argument.
+-   #1297 : Fix iteration over an enum, map or zip in a functional for
path: CHANGELOG.md
user: login: EmilyBourne
body: ```suggestion
-   #1297 : Fix iteration over an enum, map or zip in a list comprehension.
```
Functional for is Pyccel terminology 
created_at: 2024-09-04T19:37:11Z
author_association: MEMBER
diff_hunk: @@ -1156,7 +1156,7 @@ def __init__(self, *args):
             if lengths:
                 self._length = min(lengths)
             else:
-                self._length = self.args[0].shape[0]
+                self._length = PythonMin([a.shape[0] for a in self.args])
path: pyccel/ast/builtins.py
user: login: EmilyBourne
body: Does `PythonMin` have a `simplify` argument? It would be nice to get a literal if all the shapes are known
created_at: 2024-09-04T19:38:28Z
author_association: MEMBER
diff_hunk: @@ -1387,6 +1387,9 @@ def _print_Declare(self, expr):
             assert init == ''
             preface = ''
             init    = ' = {.shape = NULL}'
+        elif declaration_type.startswith('vec'):
path: pyccel/codegen/printing/ccode.py
user: login: EmilyBourne
body: Safer to check the type exactly

```suggestion
        elif isinstance(var.class_type, HomogeneousListType):
```

created_at: 2024-09-04T19:40:19Z
author_association: MEMBER
diff_hunk: @@ -1649,7 +1652,13 @@ def _print_Allocate(self, expr):
         free_code = ''
         variable = expr.variable
         if isinstance(variable.class_type, (HomogeneousListType, HomogeneousSetType, DictType)):
-            return ''
+            if len(variable.alloc_shape) > 0 and isinstance(variable.class_type, HomogeneousListType):
path: pyccel/codegen/printing/ccode.py
user: login: EmilyBourne
body: Is list the only type with a reserve function? I wouldn't be surprised if one was also available for set and dict
created_at: 2024-09-04T19:41:14Z
author_association: MEMBER
diff_hunk: @@ -1649,7 +1652,13 @@ def _print_Allocate(self, expr):
         free_code = ''
         variable = expr.variable
         if isinstance(variable.class_type, (HomogeneousListType, HomogeneousSetType, DictType)):
-            return ''
+            if len(variable.alloc_shape) > 0 and isinstance(variable.class_type, HomogeneousListType):
+                size = self._print(variable.alloc_shape[0])
+                variable_address = self._print(ObjectAddress(expr.variable))
+                container_type = self.get_c_type(expr.variable.class_type)
+                return f'{container_type}_reserve({variable_address}, {size});\n'
+            else:
+                return ''
         if variable.rank > 0:
             #free the array if its already allocated and checking if its not null if the status is unknown
path: pyccel/codegen/printing/ccode.py
user: login: EmilyBourne
body: Should there be a call to clear for lists if the status is unknown?
created_at: 2024-09-04T19:42:35Z
author_association: MEMBER
diff_hunk: @@ -2353,6 +2362,13 @@ def _print_PythonImag(self, expr):
     def _print_PythonConjugate(self, expr):
         return 'conj({})'.format(self._print(expr.internal_var))
 
+    def _print_ListAppend(self, expr):
+        target = expr.list_obj
+        class_type = target.class_type
+        dtype = self.get_c_type(class_type)
path: pyccel/codegen/printing/ccode.py
user: login: EmilyBourne
body: Dtype is usually for the underlying datatype. Eg `int` in `list[int]` better to avoid this name for containers
created_at: 2024-09-04T19:43:55Z
author_association: MEMBER
diff_hunk: @@ -2353,6 +2362,13 @@ def _print_PythonImag(self, expr):
     def _print_PythonConjugate(self, expr):
         return 'conj({})'.format(self._print(expr.internal_var))
 
+    def _print_ListAppend(self, expr):
path: pyccel/codegen/printing/ccode.py
user: login: EmilyBourne
body: Append should be mentioned in the changelog. This probably also fixes extend 
created_at: 2024-09-04T19:44:35Z
author_association: MEMBER
diff_hunk: @@ -19,7 +19,7 @@
          #if f not in failing_files \
          #else pytest.param(os.path.join(path_dir,f), marks = pytest.mark.xfail(reason=failing_files[f])) \
          for f in files \
-         if f.endswith(".py") \
+         if f.endswith(".py") and f != 'ListComprehension.py' \
path: tests/codegen/fcode/test_fcode_codegen.py
user: login: EmilyBourne
body: Better to uncomment code above regarding xfails?
created_at: 2024-09-04T19:46:05Z
author_association: MEMBER
diff_hunk: @@ -2353,6 +2362,13 @@ def _print_PythonImag(self, expr):
     def _print_PythonConjugate(self, expr):
         return 'conj({})'.format(self._print(expr.internal_var))
 
+    def _print_ListAppend(self, expr):
path: pyccel/codegen/printing/ccode.py
user: login: EmilyBourne
body: Don't forget to activate the associated tests. Maybe this tiny function should be in its own PR ?
created_at: 2024-09-04T19:46:43Z
author_association: MEMBER
diff_hunk: @@ -104,6 +104,20 @@ def sympy_to_pyccel(expr, symbol_map):
         else:
             return MathCeil(arg)
 
+    elif isinstance(expr, sp.Min):
+        args = [sympy_to_pyccel(a, symbol_map) for a in expr.args]
+        result = args[0]
+        for a in args[1:]:
+            result = PythonMin(result, a)
+        return result
+
+    elif isinstance(expr, sp.Max):
+        args = [sympy_to_pyccel(a, symbol_map) for a in expr.args]
+        result = args[0]
+        for a in args[1:]:
+            result = PythonMax(result, a)
+        return result
+
path: pyccel/ast/sympy_helper.py
user: login: pyccel-bot[bot]
body: This code isn't tested. Please can you take a look
created_at: 2024-09-04T19:47:27Z
author_association: NONE
diff_hunk: @@ -3455,53 +3455,84 @@ def _visit_For(self, expr):
             for_expr.end_annotation = expr.end_annotation
         return for_expr
 
-
     def _visit_FunctionalFor(self, expr):
-        old_index   = expr.index
-        new_index   = self.scope.get_new_name()
-        expr.substitute(old_index, new_index)
+        """
+        Visit and transform a FunctionalFor AST node into an equivalent code block.
 
-        target  = expr.expr
-        index   = new_index
-        indices = [self.scope.get_expected_name(i) for i in expr.indices]
-        dims    = []
-        body    = expr.loops[1]
+        This method processes a `FunctionalFor` expression and transforms the loop structure
+        into a corresponding code block.
 
-        idx_subs = {}
-        #scope = self.create_new_loop_scope()
+        Parameters
+        ----------
+        expr : pyccel.ast.functionalexpr.FunctionalFor
+            The FunctionalFor AST node.
 
-        # The symbols created to represent unknown valued objects are temporary
+        Returns
+        -------
+        pyccel.ast.basic.CodeBlock
+            A code block containing the equivalent loops and necessary variable allocations for the given `FunctionalFor` expression.
+        """
+         
+        target  = expr.expr
+        indices = []
+        dims = []
+        idx_subs = {}
         tmp_used_names = self.scope.all_used_symbols.copy()
+        body = expr.loops[0]
         i = 0
         while isinstance(body, For):
-
-            stop  = None
+            stop = None
             start = LiteralInteger(0)
-            step  = LiteralInteger(1)
-            var   = indices[i]
-            i += 1
-            a     = self._visit(body.iterable)
-            if isinstance(a, PythonRange):
+            step = LiteralInteger(1)
+            vars = []
+            a = Iterable(self._visit(body.iterable))
+            if isinstance(a.iterable, PythonRange):
path: pyccel/parser/semantic.py
user: login: EmilyBourne
body: I don't remember the purpose of `var` here. For the rest is it not possible to use `a.to_range` to reduce the number of ifs?
created_at: 2024-09-04T19:51:31Z
author_association: MEMBER
diff_hunk: @@ -3455,53 +3455,84 @@ def _visit_For(self, expr):
             for_expr.end_annotation = expr.end_annotation
         return for_expr
 
-
     def _visit_FunctionalFor(self, expr):
-        old_index   = expr.index
-        new_index   = self.scope.get_new_name()
-        expr.substitute(old_index, new_index)
+        """
+        Visit and transform a FunctionalFor AST node into an equivalent code block.
 
-        target  = expr.expr
-        index   = new_index
-        indices = [self.scope.get_expected_name(i) for i in expr.indices]
-        dims    = []
-        body    = expr.loops[1]
+        This method processes a `FunctionalFor` expression and transforms the loop structure
+        into a corresponding code block.
 
-        idx_subs = {}
-        #scope = self.create_new_loop_scope()
+        Parameters
+        ----------
+        expr : pyccel.ast.functionalexpr.FunctionalFor
+            The FunctionalFor AST node.
 
-        # The symbols created to represent unknown valued objects are temporary
+        Returns
+        -------
+        pyccel.ast.basic.CodeBlock
+            A code block containing the equivalent loops and necessary variable allocations for the given `FunctionalFor` expression.
+        """
+         
+        target  = expr.expr
+        indices = []
+        dims = []
+        idx_subs = {}
         tmp_used_names = self.scope.all_used_symbols.copy()
+        body = expr.loops[0]
         i = 0
         while isinstance(body, For):
-
-            stop  = None
+            stop = None
             start = LiteralInteger(0)
-            step  = LiteralInteger(1)
-            var   = indices[i]
-            i += 1
-            a     = self._visit(body.iterable)
-            if isinstance(a, PythonRange):
+            step = LiteralInteger(1)
+            vars = []
+            a = Iterable(self._visit(body.iterable))
+            if isinstance(a.iterable, PythonRange):
+                var = self.scope.get_expected_name(expr.indices[i])
+                indices.append(var)
                 var   = self._create_variable(var, PythonNativeInt(), start, {})
                 dvar  = self._infer_type(var)
-                stop  = a.stop
-                start = a.start
-                step  = a.step
-
-            elif isinstance(a, (PythonZip, PythonEnumerate)):
-                dvar  = self._infer_type(a.element)
-                class_type = dvar.pop('class_type')
+                vars.append((var, dvar))
+                stop  = a.iterable.stop
+                start = a.iterable.start
+                step  = a.iterable.step
+            elif isinstance(a.iterable, PythonEnumerate):
+                var1 = self.scope.get_expected_name(expr.indices[i][0])
+                var2 = self.scope.get_expected_name(expr.indices[i][1])
+                indices.append(var1)
+                indices.append(var2)
+                dvar2 = self._infer_type(a.iterable.element)
+                class_type = dvar2.pop('class_type')
                 if class_type.rank > 0:
-                    class_type = class_type.switch_rank(class_type.rank-1)
-                    dvar['shape'] = (dvar['shape'])[1:]
+                    class_type = class_type.switch_rank(class_type.rank - 1)
+                    dvar2['shape'] = (dvar2['shape'])[1:]
                 if class_type.rank == 0:
-                    dvar['shape'] = None
-                    dvar['memory_handling'] = 'stack'
-                var  = Variable(class_type, var, **dvar)
-                stop = a.element.shape[0]
-
-            elif isinstance(a, Variable):
-                dvar  = self._infer_type(a)
+                    dvar2['shape'] = None
+                    dvar2['memory_handling'] = 'stack'
+                var2 = Variable(class_type, var2, **dvar2)
+                var1 = Variable(PythonNativeInt(), var1)
+                vars.append((var1, self._infer_type(var1)))
+                vars.append((var2, dvar2))
+                stop = a.iterable.element.shape[0]
+            elif isinstance(a.iterable, PythonZip):
+                for idx, arg in enumerate(a.iterable.args):
+                    var = self.scope.get_expected_name(expr.indices[i][idx])
+                    indices.append(var)
+                    dvar = self._infer_type(arg)
+                    class_type = dvar.pop('class_type')
+                    if class_type.rank > 0:
+                        class_type = class_type.switch_rank(class_type.rank-1)
+                        dvar['shape'] = (dvar['shape'])[1:]
+                    if class_type.rank == 0:
+                        dvar['shape'] = None
+                        dvar['memory_handling'] = 'stack'
+                    var  = Variable(class_type, var, **dvar)
path: pyccel/parser/semantic.py
user: login: EmilyBourne
body: Not ideal to create variables without using the vriable creation functions. This usually leads to duplicated type checks or forgotten tests (e.g setting target as necessary)
created_at: 2024-09-04T19:53:46Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,66 @@
+{
path: tests/pyccel/scripts/hope_benchmarks/test.json
user: login: EmilyBourne
body: Accidental commit?
created_at: 2024-09-04T19:56:08Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,66 @@
+{
path: tests/pyccel/scripts/test2.json
user: login: EmilyBourne
body: Accidental commit?
created_at: 2024-09-04T19:56:34Z
author_association: MEMBER
diff_hunk: @@ -61,6 +61,7 @@ All notable changes to this project will be documented in this file.
 -   #1892 : Fix implementation of list function when an iterable is passed as parameter.
 -   #1924 : Fix internal error arising in Duplicate or list comprehensions.
 -   #1970 : Fix missing `TypeError` for wrong type passed as optional argument.
+-   #1297 : Fix iteration over an enum, map or zip in a functional for
path: CHANGELOG.md
user: login: jalalium
body: Done [03b6f4a](https://github.com/pyccel/pyccel/pull/1977/commits/03b6f4ae39a811bf68b7f5a66e9f9a6b4524171a)
created_at: 2024-09-04T20:08:02Z
author_association: MEMBER
diff_hunk: @@ -1156,7 +1156,7 @@ def __init__(self, *args):
             if lengths:
                 self._length = min(lengths)
             else:
-                self._length = self.args[0].shape[0]
+                self._length = PythonMin([a.shape[0] for a in self.args])
path: pyccel/ast/builtins.py
user: login: jalalium
body: this case deals with when the shape is not known, I think if all the shapes are known, then it would fall under the first case
created_at: 2024-09-04T20:09:57Z
author_association: MEMBER
diff_hunk: @@ -1649,7 +1652,13 @@ def _print_Allocate(self, expr):
         free_code = ''
         variable = expr.variable
         if isinstance(variable.class_type, (HomogeneousListType, HomogeneousSetType, DictType)):
-            return ''
+            if len(variable.alloc_shape) > 0 and isinstance(variable.class_type, HomogeneousListType):
path: pyccel/codegen/printing/ccode.py
user: login: jalalium
body: Definitely not, I didn't give much thought to this part as i didn't consider all the cases that would require a reserve. I will think more about it
created_at: 2024-09-04T20:11:26Z
author_association: MEMBER
diff_hunk: @@ -1649,7 +1652,13 @@ def _print_Allocate(self, expr):
         free_code = ''
         variable = expr.variable
         if isinstance(variable.class_type, (HomogeneousListType, HomogeneousSetType, DictType)):
-            return ''
+            if len(variable.alloc_shape) > 0 and isinstance(variable.class_type, HomogeneousListType):
+                size = self._print(variable.alloc_shape[0])
+                variable_address = self._print(ObjectAddress(expr.variable))
+                container_type = self.get_c_type(expr.variable.class_type)
+                return f'{container_type}_reserve({variable_address}, {size});\n'
+            else:
+                return ''
         if variable.rank > 0:
             #free the array if its already allocated and checking if its not null if the status is unknown
path: pyccel/codegen/printing/ccode.py
user: login: jalalium
body: could you elaborate please?
created_at: 2024-09-04T20:12:04Z
author_association: MEMBER
diff_hunk: @@ -1649,7 +1652,13 @@ def _print_Allocate(self, expr):
         free_code = ''
         variable = expr.variable
         if isinstance(variable.class_type, (HomogeneousListType, HomogeneousSetType, DictType)):
-            return ''
+            if len(variable.alloc_shape) > 0 and isinstance(variable.class_type, HomogeneousListType):
path: pyccel/codegen/printing/ccode.py
user: login: EmilyBourne
body: I don't know what cases might need reserve either. I just don't see any particular reason to exclude set/dict if they have an equivalent method with a similar name. If they don't then this question becomes irrelevant 
created_at: 2024-09-04T20:42:24Z
author_association: MEMBER
diff_hunk: @@ -1649,7 +1652,13 @@ def _print_Allocate(self, expr):
         free_code = ''
         variable = expr.variable
         if isinstance(variable.class_type, (HomogeneousListType, HomogeneousSetType, DictType)):
-            return ''
+            if len(variable.alloc_shape) > 0 and isinstance(variable.class_type, HomogeneousListType):
+                size = self._print(variable.alloc_shape[0])
+                variable_address = self._print(ObjectAddress(expr.variable))
+                container_type = self.get_c_type(expr.variable.class_type)
+                return f'{container_type}_reserve({variable_address}, {size});\n'
+            else:
+                return ''
         if variable.rank > 0:
             #free the array if its already allocated and checking if its not null if the status is unknown
path: pyccel/codegen/printing/ccode.py
user: login: EmilyBourne
body: The comment at the bottom of the code section says that Deallocate is called for arrays if the status of the Allocate node is 'unknown'. The equivalent function for lists is probably clear. I was wondering if the Allocate status should be used here
created_at: 2024-09-04T20:46:28Z
author_association: MEMBER
diff_hunk: @@ -1649,7 +1652,13 @@ def _print_Allocate(self, expr):
         free_code = ''
         variable = expr.variable
         if isinstance(variable.class_type, (HomogeneousListType, HomogeneousSetType, DictType)):
-            return ''
+            if len(variable.alloc_shape) > 0 and isinstance(variable.class_type, HomogeneousListType):
path: pyccel/codegen/printing/ccode.py
user: login: jalalium
body: I excluded them because tests relative to variable annotations were failing.
```
if __name__ == '__main__':
    a : 'set[float]'
    a = {1.5, 2.5, 3.3, 4.3}
```
```
int main()
{
    hset_double a;
    hset_double_reserve(&a, INT64_C(4));
    a = c_init(hset_double, {1.5, 2.5, 3.3, 4.3});
    hset_double_drop(&a);
    return 0;
}
```
this would cause a double free because `c_init` doesn't work well with `reserve` in contrast to `append`. an `Allocate` might need to be treated differently depending on what will happen to the memory.
created_at: 2024-09-04T21:14:59Z
author_association: MEMBER
diff_hunk: @@ -1649,7 +1652,13 @@ def _print_Allocate(self, expr):
         free_code = ''
         variable = expr.variable
         if isinstance(variable.class_type, (HomogeneousListType, HomogeneousSetType, DictType)):
-            return ''
+            if len(variable.alloc_shape) > 0 and isinstance(variable.class_type, HomogeneousListType):
path: pyccel/codegen/printing/ccode.py
user: login: EmilyBourne
body: Is this not also true for:
```python
a = [1,2,3,4]
```
?
How do you avoid the reserve/init problem in this case?
created_at: 2024-09-05T07:08:26Z
author_association: MEMBER
diff_hunk: @@ -1649,7 +1652,13 @@ def _print_Allocate(self, expr):
         free_code = ''
         variable = expr.variable
         if isinstance(variable.class_type, (HomogeneousListType, HomogeneousSetType, DictType)):
-            return ''
+            if len(variable.alloc_shape) > 0 and isinstance(variable.class_type, HomogeneousListType):
path: pyccel/codegen/printing/ccode.py
user: login: jalalium
body: I think we should update `_assign_lhs_variable` to not add an `Allocate` node when the `rhs` is a `c_init` since it takes care of memory allocation.
created_at: 2024-09-06T11:55:02Z
author_association: MEMBER
diff_hunk: @@ -2353,6 +2362,13 @@ def _print_PythonImag(self, expr):
     def _print_PythonConjugate(self, expr):
         return 'conj({})'.format(self._print(expr.internal_var))
 
+    def _print_ListAppend(self, expr):
path: pyccel/codegen/printing/ccode.py
user: login: jalalium
body: done #1982, however the tests require C wrapping for lists as returns of functions as well as the ability to print lists
created_at: 2024-09-06T11:56:49Z
author_association: MEMBER
diff_hunk: @@ -1649,7 +1652,13 @@ def _print_Allocate(self, expr):
         free_code = ''
         variable = expr.variable
         if isinstance(variable.class_type, (HomogeneousListType, HomogeneousSetType, DictType)):
-            return ''
+            if len(variable.alloc_shape) > 0 and isinstance(variable.class_type, HomogeneousListType):
path: pyccel/codegen/printing/ccode.py
user: login: EmilyBourne
body: I'm not sure how you manage this. The semantic stage is language agnostic.  What happens if the Allocate node is needed for Fortran? You put it back in `FCodePrinter._print_PythonList`?

It would be nice if there was a way to make the init value available. Maybe we could add a property to `Allocate`?

Additionally if you have two c_init statements in a row then you will definitely still need the Allocate to know that you need to call c_drop first. (I assume if you can't call c_init on an initialised list then you can't call it twice without some kind of deallocation either).

Eg:
```python
a = [1, 2, 3, 4]
...
a = [6, 7, 8, 9,  10]
```
If `a` was an array instead of a list then this would give:
```fortran
allocate(a(0:3))
a(:) = (/ 1, 2, 3, 4 /)
deallocate(a)
allocate(a(0:4))
a(:) = (/ 6, 7, 8, 9 /)
```
The deallocation comes from the Allocate node
created_at: 2024-09-06T20:49:20Z
author_association: MEMBER
diff_hunk: @@ -2353,6 +2362,13 @@ def _print_PythonImag(self, expr):
     def _print_PythonConjugate(self, expr):
         return 'conj({})'.format(self._print(expr.internal_var))
 
+    def _print_ListAppend(self, expr):
path: pyccel/codegen/printing/ccode.py
user: login: EmilyBourne
body: Ah. So it's either blocked by the associated issue or the returns need changing to a len call and elements and then it's blocked by Farouk's issue
created_at: 2024-09-06T20:51:13Z
author_association: MEMBER
diff_hunk: @@ -2443,6 +2445,13 @@ def _print_PythonImag(self, expr):
     def _print_PythonConjugate(self, expr):
         return 'conj({})'.format(self._print(expr.internal_var))
 
+    def _print_ListAppend(self, expr):
+        target = expr.list_obj
+        class_type = target.class_type
+        dtype = self.get_c_type(class_type)
+        args = self._print(expr.args[0])
+        return f'{dtype}_push(&{target.name}, {args});\n'
+
path: pyccel/codegen/printing/ccode.py
user: login: pyccel-bot[bot]
body: This code isn't tested. Please can you take a look
created_at: 2024-12-04T15:14:09Z
author_association: NONE
diff_hunk: @@ -104,6 +104,20 @@ def sympy_to_pyccel(expr, symbol_map):
         else:
             return MathCeil(arg)
 
+    elif isinstance(expr, sp.Min):
+        args = [sympy_to_pyccel(a, symbol_map) for a in expr.args]
+        result = args[0]
+        for a in args[1:]:
+            result = PythonMin(result, a)
+        return result
+
+    elif isinstance(expr, sp.Max):
+        args = [sympy_to_pyccel(a, symbol_map) for a in expr.args]
+        result = args[0]
+        for a in args[1:]:
+            result = PythonMax(result, a)
+        return result
+
path: pyccel/ast/sympy_helper.py
user: login: jalalium
body: no way to test, only for completeness
created_at: 2024-12-04T15:29:32Z
author_association: MEMBER
diff_hunk: @@ -179,6 +193,14 @@ def pyccel_to_sympy(expr, symbol_map, used_names):
     elif isinstance(expr, MathCeil):
         return sp.ceiling(pyccel_to_sympy(expr.args[0], symbol_map, used_names))
 
+    elif isinstance(expr, PythonMin):
+        args = [pyccel_to_sympy(ee, symbol_map, used_names) for e in expr.args for ee in e]
+        return sp.Min(*args)
+
+    elif isinstance(expr, PythonMax):
+        args = [pyccel_to_sympy(ee, symbol_map, used_names) for e in expr.args for ee in e]
+        return sp.Max(*args)
+
path: pyccel/ast/sympy_helper.py
user: login: jalalium
body: no way to test, only for completeness
created_at: 2024-12-04T15:29:37Z
author_association: MEMBER
diff_hunk: @@ -2443,6 +2445,13 @@ def _print_PythonImag(self, expr):
     def _print_PythonConjugate(self, expr):
         return 'conj({})'.format(self._print(expr.internal_var))
 
+    def _print_ListAppend(self, expr):
+        target = expr.list_obj
+        class_type = target.class_type
+        dtype = self.get_c_type(class_type)
+        args = self._print(expr.args[0])
+        return f'{dtype}_push(&{target.name}, {args});\n'
+
path: pyccel/codegen/printing/ccode.py
user: login: pyccel-bot[bot]
body: This seems to be fixed now! Thank you for looking into this. Please resolve this conversation.

created_at: 2024-12-04T15:52:23Z
author_association: NONE
diff_hunk: @@ -104,6 +104,20 @@ def sympy_to_pyccel(expr, symbol_map):
         else:
             return MathCeil(arg)
 
+    elif isinstance(expr, sp.Min):
+        args = [sympy_to_pyccel(a, symbol_map) for a in expr.args]
+        result = args[0]
+        for a in args[1:]:
+            result = PythonMin(result, a)
+        return result
+
+    elif isinstance(expr, sp.Max):
+        args = [sympy_to_pyccel(a, symbol_map) for a in expr.args]
+        result = args[0]
+        for a in args[1:]:
+            result = PythonMax(result, a)
+        return result
+
path: pyccel/ast/sympy_helper.py
user: login: pyccel-bot[bot]
body: This seems to be fixed now! Thank you for looking into this. Please resolve this conversation.

created_at: 2024-12-04T18:30:34Z
author_association: NONE
diff_hunk: @@ -179,6 +193,14 @@ def pyccel_to_sympy(expr, symbol_map, used_names):
     elif isinstance(expr, MathCeil):
         return sp.ceiling(pyccel_to_sympy(expr.args[0], symbol_map, used_names))
 
+    elif isinstance(expr, PythonMin):
+        args = [pyccel_to_sympy(ee, symbol_map, used_names) for e in expr.args for ee in e]
+        return sp.Min(*args)
+
+    elif isinstance(expr, PythonMax):
+        args = [pyccel_to_sympy(ee, symbol_map, used_names) for e in expr.args for ee in e]
+        return sp.Max(*args)
+
path: pyccel/ast/sympy_helper.py
user: login: pyccel-bot[bot]
body: This seems to be fixed now! Thank you for looking into this. Please resolve this conversation.

created_at: 2024-12-04T18:30:35Z
author_association: NONE
diff_hunk: @@ -104,6 +104,20 @@ def sympy_to_pyccel(expr, symbol_map):
         else:
             return MathCeil(arg)
 
+    elif isinstance(expr, sp.Min):
+        args = [sympy_to_pyccel(a, symbol_map) for a in expr.args]
+        result = args[0]
+        for a in args[1:]:
+            result = PythonMin(result, a)
+        return result
+
+    elif isinstance(expr, sp.Max):
+        args = [sympy_to_pyccel(a, symbol_map) for a in expr.args]
+        result = args[0]
+        for a in args[1:]:
+            result = PythonMax(result, a)
+        return result
+
path: pyccel/ast/sympy_helper.py
user: login: EmilyBourne
body: @jalalium I agree with your initial decision to include this for completeness. Can you put it back and follow the bot's instructions to get it to ignore the lack of coverage.
The comment needs to address the bot with the keywords described in the bot's comment. The comment that you left above is for me.
created_at: 2024-12-05T08:28:09Z
author_association: MEMBER
diff_hunk: @@ -119,4 +119,6 @@ indexable
 traceback
 STC
 gFTL
-CMake
\ No newline at end of file
+enum
+zip
path: .dict_custom.txt
user: login: EmilyBourne
body: I am surprised that the dictionary doesn't recognise `zip`, but I agree with it that `enum` is not a word. It is an abbreviation of `enumerate`. You should abbreviations in documentation and quote code keywords to avoid these problems
created_at: 2024-12-05T08:31:41Z
author_association: MEMBER
diff_hunk: @@ -24,8 +24,7 @@
          if f not in failing_files \
          else pytest.param(os.path.join(path_dir,f), marks = pytest.mark.xfail(reason=failing_files[f])) \
          for f in files \
-         if f.endswith(".py") \
-        ]
+         if f.endswith(".py")]
path: tests/codegen/fcode/test_fcode_codegen.py
user: login: EmilyBourne
body: Any reason for this change?
created_at: 2024-12-05T08:32:05Z
author_association: MEMBER
diff_hunk: @@ -78,6 +78,9 @@ All notable changes to this project will be documented in this file.
 -   #1913 : Fix function calls to renamed functions.
 -   #1930 : Preserve ordering of import targets.
 -   #1892 : Fix implementation of list function when an iterable is passed as parameter.
+-   #1924 : Fix internal error arising in Duplicate or list comprehensions.
path: CHANGELOG.md
user: login: EmilyBourne
body: #1924 is already closed. Are you sure that this issue is relevant to this PR?
created_at: 2024-12-05T08:33:03Z
author_association: MEMBER
diff_hunk: @@ -78,6 +78,9 @@ All notable changes to this project will be documented in this file.
 -   #1913 : Fix function calls to renamed functions.
 -   #1930 : Preserve ordering of import targets.
 -   #1892 : Fix implementation of list function when an iterable is passed as parameter.
+-   #1924 : Fix internal error arising in Duplicate or list comprehensions.
+-   #1970 : Fix missing `TypeError` for wrong type passed as optional argument.
path: CHANGELOG.md
user: login: EmilyBourne
body: https://github.com/pyccel/pyccel/issues/1970 is already closed. Are you sure that this issue is relevant to this PR?
Maybe bad merge?
created_at: 2024-12-05T08:33:28Z
author_association: MEMBER
diff_hunk: @@ -78,6 +78,9 @@ All notable changes to this project will be documented in this file.
 -   #1913 : Fix function calls to renamed functions.
 -   #1930 : Preserve ordering of import targets.
 -   #1892 : Fix implementation of list function when an iterable is passed as parameter.
+-   #1924 : Fix internal error arising in Duplicate or list comprehensions.
+-   #1970 : Fix missing `TypeError` for wrong type passed as optional argument.
+-   #1297 : Fix iteration over an enum, map or zip in a list comprehension.
path: CHANGELOG.md
user: login: EmilyBourne
body: ```suggestion
-   #1297 : Fix iteration over an `enumerate`, `map` or `zip` in a list comprehension.
```
created_at: 2024-12-05T08:33:54Z
author_association: MEMBER
diff_hunk: @@ -103,6 +104,13 @@ def sympy_to_pyccel(expr, symbol_map):
         else:
             return MathCeil(arg)
 
+    elif isinstance(expr, sp.Min):
+        args = [sympy_to_pyccel(a, symbol_map) for a in expr.args]
+        result = args[0]
+        for a in args[1:]:
+            result = PythonMin(result, a)
+        return result
path: pyccel/ast/sympy_helper.py
user: login: EmilyBourne
body: When unpacking you do not limit yourself to 2 arguments. Is there a reason why you cannot do:
```suggestion
    elif isinstance(expr, sp.Min):
        args = [sympy_to_pyccel(a, symbol_map) for a in expr.args]
        return PythonMin(*args)
```
?
created_at: 2024-12-05T08:35:16Z
author_association: MEMBER
diff_hunk: @@ -1706,13 +1706,15 @@ def _print_Allocate(self, expr):
         free_code = ''
         variable = expr.variable
         if isinstance(variable.class_type, (HomogeneousListType, HomogeneousSetType, DictType)):
-            if expr.status in ('allocated', 'unknown'):
-                free_code = f'{self._print(Deallocate(variable))}\n'
             if expr.shape[0] is None:
                 return free_code
+            if expr.status in ('allocated', 'unknown'):
+                free_code = f'{self._print(Deallocate(variable))}\n'
             size = self._print(expr.shape[0])
             variable_address = self._print(ObjectAddress(expr.variable))
             container_type = self.get_c_type(expr.variable.class_type)
+            if expr.alloc_type == 'reserve':
+                free_code = f'{container_type}_clear({variable_address});\n'
path: pyccel/codegen/printing/ccode.py
user: login: EmilyBourne
body: If `expr.status in ('allocated', 'unknown')` and `expr.alloc_type == 'reserve'` then you overwrite `free_code`. Is this intentional? Maybe it would be better to do:
```suggestion
            size = self._print(expr.shape[0])
            variable_address = self._print(ObjectAddress(expr.variable))
            container_type = self.get_c_type(expr.variable.class_type)
            if expr.alloc_type == 'reserve':
                free_code = f'{container_type}_clear({variable_address});\n'
            elif expr.status in ('allocated', 'unknown'):
                free_code = f'{self._print(Deallocate(variable))}\n'
```
to avoid unnecessary calls to `self._print`?
created_at: 2024-12-05T08:37:40Z
author_association: MEMBER
diff_hunk: @@ -622,15 +626,23 @@ def _print_For(self, expr):
     def _print_FunctionalFor(self, expr):
         body, iterators = self._find_functional_expr_and_iterables(expr)
         lhs = self._print(expr.lhs)
-        body = self._print(body.rhs)
+        condition = ''
+        if isinstance(body, Assign):
+            body = self._print(body.rhs)
+        elif isinstance(body, ListAppend):
+            body = self._print(body.args[0])
+        else:
+            condition = 'if ' + self._print(body.blocks[0].condition)
+            body = self._print(body.blocks[0].body.body[0].args[0])
         for_loops = ' '.join(['for {} in {}'.format(self._print(idx), self._print(iters))
                         for idx, iters in zip(expr.indices, iterators)])
 
-        name = self._aliases.get(type(expr),'array')
-        if name == 'array':
+        name = expr.target_type
+        if 'array' in str(name):
path: pyccel/codegen/printing/pycode.py
user: login: EmilyBourne
body: Will this break if I do:
```python
from numpy import array as chunk

a = chunk([1,2,3,4])
```
?
created_at: 2024-12-05T08:42:06Z
author_association: MEMBER
diff_hunk: @@ -3659,88 +3659,123 @@ def _visit_For(self, expr):
             for_expr.end_annotation = expr.end_annotation
         return for_expr
 
-
     def _visit_FunctionalFor(self, expr):
-        old_index   = expr.index
-        new_index   = self.scope.get_new_name()
-        expr.substitute(old_index, new_index)
+        """
+        Visit and transform a FunctionalFor AST node into an equivalent code block.
 
-        target  = expr.expr
-        index   = new_index
-        indices = [self.scope.get_expected_name(i) for i in expr.indices]
-        dims    = []
-        body    = expr.loops[1]
+        This method processes a `FunctionalFor` expression and transforms the loop structure
+        into a corresponding code block.
 
-        idx_subs = {}
-        #scope = self.create_new_loop_scope()
+        Parameters
+        ----------
+        expr : pyccel.ast.functionalexpr.FunctionalFor
+            The FunctionalFor AST node.
+
+        Returns
+        -------
+        pyccel.ast.basic.CodeBlock
+            A code block containing the equivalent loops and necessary variable allocations for the given `FunctionalFor` expression.
+        """
+        if expr.target_type != 'list':
+            old_index   = expr.index
+            new_index   = self.scope.get_new_name()
+            expr.substitute(old_index, new_index)
+            index = new_index
path: pyccel/parser/semantic.py
user: login: EmilyBourne
body: Why is this necessary?
created_at: 2024-12-05T08:43:06Z
author_association: MEMBER
diff_hunk: @@ -1182,25 +1198,48 @@ def _visit_ListComp(self, stmt):
                 lhs = lhs[0]
             else:
                 raise NotImplementedError("A list comprehension cannot be unpacked")
-
         parent = self._context[-2]
         if isinstance(parent, ast.Call):
             output_type = self._visit(parent.func)
         else:
             output_type = 'list'
-
-        index = PyccelSymbol('_', is_temp=True)
-
-        args = [index]
-        target = IndexedElement(lhs, *args)
-        target = Assign(target, result)
-        assign1 = Assign(index, LiteralInteger(0))
-        assign1.set_current_ast(stmt)
-        target.set_current_ast(stmt)
-        generators[-1].insert2body(target)
-        assign2 = Assign(index, PyccelAdd(index, LiteralInteger(1)))
-        assign2.set_current_ast(stmt)
-        generators[-1].insert2body(assign2)
+        operations = []
+        if output_type != 'list':
+            index = PyccelSymbol('_', is_temp=True)
+            args = [index]
+            target = IndexedElement(lhs, *args)
+            target = Assign(target, result)
+            assign1 = Assign(index, LiteralInteger(0))
+            assign1.set_current_ast(stmt)
+            target.set_current_ast(stmt)
+            operations.append(target)
+            assign2 = Assign(index, PyccelAdd(index, LiteralInteger(1)))
+            assign2.set_current_ast(stmt)
+            operations.append(assign2)
+        else:
+            operations.append(DottedName(lhs, FunctionCall('append', [FunctionCallArgument(result)])))
path: pyccel/parser/syntactic.py
user: login: EmilyBourne
body: Ideally calculations like this should be left for the semantic stage to ensure the user has not done something stupid like create a function called `list`. Is there a reason why this needs to be done in the syntactic stage?
created_at: 2024-12-05T08:48:03Z
author_association: MEMBER
diff_hunk: @@ -1182,25 +1198,48 @@ def _visit_ListComp(self, stmt):
                 lhs = lhs[0]
             else:
                 raise NotImplementedError("A list comprehension cannot be unpacked")
-
         parent = self._context[-2]
         if isinstance(parent, ast.Call):
             output_type = self._visit(parent.func)
         else:
             output_type = 'list'
-
-        index = PyccelSymbol('_', is_temp=True)
-
-        args = [index]
-        target = IndexedElement(lhs, *args)
-        target = Assign(target, result)
-        assign1 = Assign(index, LiteralInteger(0))
-        assign1.set_current_ast(stmt)
-        target.set_current_ast(stmt)
-        generators[-1].insert2body(target)
-        assign2 = Assign(index, PyccelAdd(index, LiteralInteger(1)))
-        assign2.set_current_ast(stmt)
-        generators[-1].insert2body(assign2)
+        operations = []
+        if output_type != 'list':
+            index = PyccelSymbol('_', is_temp=True)
+            args = [index]
+            target = IndexedElement(lhs, *args)
+            target = Assign(target, result)
+            assign1 = Assign(index, LiteralInteger(0))
+            assign1.set_current_ast(stmt)
+            target.set_current_ast(stmt)
+            operations.append(target)
+            assign2 = Assign(index, PyccelAdd(index, LiteralInteger(1)))
+            assign2.set_current_ast(stmt)
+            operations.append(assign2)
+        else:
+            operations.append(DottedName(lhs, FunctionCall('append', [FunctionCallArgument(result)])))
+
+        conditions = []
+        for generator in generators:
+            if generator.body.body:
+                assert isinstance(generator.body.body[0], If)
+                for ifSection in generator.body.body[0].blocks:
+                    conditions.append(ifSection.condition)
+                generator._body = CodeBlock([]) # pylint: disable=protected-access
path: pyccel/parser/syntactic.py
user: login: EmilyBourne
body: I agree with codacy. This is a code smell. Is there no other way to do this?

This will also cause bugs in the AST tree.
created_at: 2024-12-05T09:01:10Z
author_association: MEMBER
diff_hunk: @@ -1182,25 +1198,48 @@ def _visit_ListComp(self, stmt):
                 lhs = lhs[0]
             else:
                 raise NotImplementedError("A list comprehension cannot be unpacked")
-
         parent = self._context[-2]
         if isinstance(parent, ast.Call):
             output_type = self._visit(parent.func)
         else:
             output_type = 'list'
-
-        index = PyccelSymbol('_', is_temp=True)
-
-        args = [index]
-        target = IndexedElement(lhs, *args)
-        target = Assign(target, result)
-        assign1 = Assign(index, LiteralInteger(0))
-        assign1.set_current_ast(stmt)
-        target.set_current_ast(stmt)
-        generators[-1].insert2body(target)
-        assign2 = Assign(index, PyccelAdd(index, LiteralInteger(1)))
-        assign2.set_current_ast(stmt)
-        generators[-1].insert2body(assign2)
+        operations = []
+        if output_type != 'list':
+            index = PyccelSymbol('_', is_temp=True)
+            args = [index]
+            target = IndexedElement(lhs, *args)
+            target = Assign(target, result)
+            assign1 = Assign(index, LiteralInteger(0))
+            assign1.set_current_ast(stmt)
+            target.set_current_ast(stmt)
+            operations.append(target)
+            assign2 = Assign(index, PyccelAdd(index, LiteralInteger(1)))
+            assign2.set_current_ast(stmt)
+            operations.append(assign2)
+        else:
+            operations.append(DottedName(lhs, FunctionCall('append', [FunctionCallArgument(result)])))
+
+        conditions = []
+        for generator in generators:
+            if generator.body.body:
+                assert isinstance(generator.body.body[0], If)
+                for ifSection in generator.body.body[0].blocks:
+                    conditions.append(ifSection.condition)
+                generator._body = CodeBlock([]) # pylint: disable=protected-access
path: pyccel/parser/syntactic.py
user: login: EmilyBourne
body: If the body is not used after this does it matter if it contains ifs?
created_at: 2024-12-05T09:01:40Z
author_association: MEMBER
diff_hunk: @@ -1182,25 +1198,48 @@ def _visit_ListComp(self, stmt):
                 lhs = lhs[0]
             else:
                 raise NotImplementedError("A list comprehension cannot be unpacked")
-
         parent = self._context[-2]
         if isinstance(parent, ast.Call):
             output_type = self._visit(parent.func)
         else:
             output_type = 'list'
-
-        index = PyccelSymbol('_', is_temp=True)
-
-        args = [index]
-        target = IndexedElement(lhs, *args)
-        target = Assign(target, result)
-        assign1 = Assign(index, LiteralInteger(0))
-        assign1.set_current_ast(stmt)
-        target.set_current_ast(stmt)
-        generators[-1].insert2body(target)
-        assign2 = Assign(index, PyccelAdd(index, LiteralInteger(1)))
-        assign2.set_current_ast(stmt)
-        generators[-1].insert2body(assign2)
+        operations = []
+        if output_type != 'list':
+            index = PyccelSymbol('_', is_temp=True)
+            args = [index]
+            target = IndexedElement(lhs, *args)
+            target = Assign(target, result)
+            assign1 = Assign(index, LiteralInteger(0))
+            assign1.set_current_ast(stmt)
+            target.set_current_ast(stmt)
+            operations.append(target)
+            assign2 = Assign(index, PyccelAdd(index, LiteralInteger(1)))
+            assign2.set_current_ast(stmt)
+            operations.append(assign2)
+        else:
+            operations.append(DottedName(lhs, FunctionCall('append', [FunctionCallArgument(result)])))
+
+        conditions = []
+        for generator in generators:
+            if generator.body.body:
+                assert isinstance(generator.body.body[0], If)
+                for ifSection in generator.body.body[0].blocks:
+                    conditions.append(ifSection.condition)
+                generator._body = CodeBlock([]) # pylint: disable=protected-access
+
+        if conditions:
+            if output_type != 'list':
+                errors.report("Cannot handle if statements in list comprehensions if lhs is a numpy array.\
+                              List length cannot be calculated.\n" + PYCCEL_RESTRICTION_TODO,
+                               symbol=stmt, severity='error')
path: pyccel/parser/syntactic.py
user: login: EmilyBourne
body: Errors related to the type should not appear in the syntactic stage which contains no information about typing
created_at: 2024-12-05T09:02:23Z
author_association: MEMBER
diff_hunk: @@ -4049,6 +4049,7 @@ def test_array_float_nested_C_array_initialization_3(language):
         pytest.param("python", marks = pytest.mark.python)
     )
 )
+@pytest.mark.skipif(lambda lang: lang == 'fortran', reason="List comprehension is not supported in Fortran")
path: tests/epyccel/test_arrays.py
user: login: EmilyBourne
body: The missing C support seemed very minimal. Is there any reason not to make the same minimal changes to fix Fortran?
created_at: 2024-12-05T09:04:12Z
author_association: MEMBER
diff_hunk: @@ -16,6 +17,7 @@ def compare_epyccel(f, language, *args):
 def test_functional_for_1d_range(language):
     compare_epyccel(functionals.functional_for_1d_range, language)
 
+@pytest.mark.skipif(lambda lang: lang == 'c', reason="Skipping for C due to array redefinition issue. See #1979")
path: tests/epyccel/test_functionals.py
user: login: EmilyBourne
body: #1979 has been closed
created_at: 2024-12-05T09:05:30Z
author_association: MEMBER
diff_hunk: @@ -150,6 +151,7 @@ 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) )
 
+
path: tests/epyccel/test_loops.py
user: login: EmilyBourne
body: Unnecessary changes in this file.
created_at: 2024-12-05T09:05:44Z
author_association: MEMBER
diff_hunk: @@ -3659,88 +3659,123 @@ def _visit_For(self, expr):
             for_expr.end_annotation = expr.end_annotation
         return for_expr
 
-
     def _visit_FunctionalFor(self, expr):
-        old_index   = expr.index
-        new_index   = self.scope.get_new_name()
-        expr.substitute(old_index, new_index)
+        """
+        Visit and transform a FunctionalFor AST node into an equivalent code block.
 
-        target  = expr.expr
-        index   = new_index
-        indices = [self.scope.get_expected_name(i) for i in expr.indices]
-        dims    = []
-        body    = expr.loops[1]
+        This method processes a `FunctionalFor` expression and transforms the loop structure
+        into a corresponding code block.
 
-        idx_subs = {}
-        #scope = self.create_new_loop_scope()
+        Parameters
+        ----------
+        expr : pyccel.ast.functionalexpr.FunctionalFor
+            The FunctionalFor AST node.
+
+        Returns
+        -------
+        pyccel.ast.basic.CodeBlock
+            A code block containing the equivalent loops and necessary variable allocations for the given `FunctionalFor` expression.
+        """
+        if expr.target_type != 'list':
+            old_index   = expr.index
+            new_index   = self.scope.get_new_name()
+            expr.substitute(old_index, new_index)
+            index = new_index
 
-        # The symbols created to represent unknown valued objects are temporary
+        target  = expr.expr
+        indices = []
+        dims = []
+        idx_subs = {}
         tmp_used_names = self.scope.all_used_symbols.copy()
+        body = expr.loops[0 if expr.target_type == 'list' else 1]
         i = 0
-        while isinstance(body, For):
 
-            stop  = None
+        # Inner function to handle PythonNativeInt variables
+        def handle_int_loop_variable(var_name):
+            indices.append(var_name)
+            var = self._create_variable(var_name, PythonNativeInt(), None, {})
+            dvar = self._infer_type(var)
+            variables.append((var, dvar))
+
+        # Inner function to handle iterable variables
+        def handle_iterable_variable(var_name, element):
+            indices.append(var_name)
+            dvar = self._infer_type(element)
+            class_type = dvar.pop('class_type')
+            if class_type.rank > 0:
+                class_type = class_type.switch_rank(class_type.rank - 1)
+                dvar['shape'] = dvar['shape'][1:]
+            if class_type.rank == 0:
+                dvar['shape'] = None
+                dvar['memory_handling'] = 'stack'
+            var = self._create_variable(var_name, class_type, None, dvar)
+            dvar['class_type'] = class_type
+            variables.append((var, dvar))
path: pyccel/parser/semantic.py
user: login: EmilyBourne
body: Why is this specific to `FunctionalFor`? Is there nothing from the `For` handling that would let you do something similar?
created_at: 2024-12-05T09:08:21Z
author_association: MEMBER
diff_hunk: @@ -3659,88 +3659,123 @@ def _visit_For(self, expr):
             for_expr.end_annotation = expr.end_annotation
         return for_expr
 
-
     def _visit_FunctionalFor(self, expr):
-        old_index   = expr.index
-        new_index   = self.scope.get_new_name()
-        expr.substitute(old_index, new_index)
+        """
+        Visit and transform a FunctionalFor AST node into an equivalent code block.
 
-        target  = expr.expr
-        index   = new_index
-        indices = [self.scope.get_expected_name(i) for i in expr.indices]
-        dims    = []
-        body    = expr.loops[1]
+        This method processes a `FunctionalFor` expression and transforms the loop structure
+        into a corresponding code block.
 
-        idx_subs = {}
-        #scope = self.create_new_loop_scope()
+        Parameters
+        ----------
+        expr : pyccel.ast.functionalexpr.FunctionalFor
+            The FunctionalFor AST node.
+
+        Returns
+        -------
+        pyccel.ast.basic.CodeBlock
+            A code block containing the equivalent loops and necessary variable allocations for the given `FunctionalFor` expression.
+        """
+        if expr.target_type != 'list':
+            old_index   = expr.index
+            new_index   = self.scope.get_new_name()
+            expr.substitute(old_index, new_index)
+            index = new_index
 
-        # The symbols created to represent unknown valued objects are temporary
+        target  = expr.expr
+        indices = []
+        dims = []
+        idx_subs = {}
         tmp_used_names = self.scope.all_used_symbols.copy()
+        body = expr.loops[0 if expr.target_type == 'list' else 1]
         i = 0
-        while isinstance(body, For):
 
-            stop  = None
+        # Inner function to handle PythonNativeInt variables
+        def handle_int_loop_variable(var_name):
+            indices.append(var_name)
+            var = self._create_variable(var_name, PythonNativeInt(), None, {})
+            dvar = self._infer_type(var)
+            variables.append((var, dvar))
+
+        # Inner function to handle iterable variables
+        def handle_iterable_variable(var_name, element):
+            indices.append(var_name)
+            dvar = self._infer_type(element)
+            class_type = dvar.pop('class_type')
+            if class_type.rank > 0:
+                class_type = class_type.switch_rank(class_type.rank - 1)
+                dvar['shape'] = dvar['shape'][1:]
+            if class_type.rank == 0:
+                dvar['shape'] = None
+                dvar['memory_handling'] = 'stack'
+            var = self._create_variable(var_name, class_type, None, dvar)
+            dvar['class_type'] = class_type
+            variables.append((var, dvar))
+
+        while isinstance(body, For):
+            stop = None
             start = LiteralInteger(0)
-            step  = LiteralInteger(1)
-            var   = indices[i]
-            i += 1
-            a     = self._visit(body.iterable)
+            step = LiteralInteger(1)
+            variables = []
+            a = self._get_iterable(self._visit(body.iterable))
             if isinstance(a, PythonRange):
-                var   = self._create_variable(var, PythonNativeInt(), start, {})
-                dvar  = self._infer_type(var)
-                stop  = a.stop
+                var_name = self.scope.get_expected_name(expr.indices[i])
+                handle_int_loop_variable(var_name)
                 start = a.start
+                stop  = a.stop
                 step  = a.step
 
-            elif isinstance(a, (PythonZip, PythonEnumerate)):
-                dvar  = self._infer_type(a.element)
-                class_type = dvar.pop('class_type')
-                if class_type.rank > 0:
-                    class_type = class_type.switch_rank(class_type.rank-1)
-                    dvar['shape'] = (dvar['shape'])[1:]
-                if class_type.rank == 0:
-                    dvar['shape'] = None
-                    dvar['memory_handling'] = 'stack'
-                var  = Variable(class_type, var, **dvar)
+            elif isinstance(a, PythonEnumerate):
+                var_name1 = self.scope.get_expected_name(expr.indices[i][0])
+                var_name2 = self.scope.get_expected_name(expr.indices[i][1])
+                handle_int_loop_variable(var_name1)
+                handle_iterable_variable(var_name2, a.element)
                 stop = a.element.shape[0]
 
-            elif isinstance(a, Variable):
-                dvar  = self._infer_type(a)
-                class_type = dvar.pop('class_type')
-                if class_type.rank > 0:
-                    class_type = class_type.switch_rank(class_type.rank-1)
-                    dvar['shape'] = (dvar['shape'])[1:]
-                if class_type.rank == 0:
-                    dvar['shape'] = None
-                    dvar['memory_handling'] = 'stack'
-                var  = Variable(class_type, var, **dvar)
-                stop = a.shape[0]
+            elif isinstance(a, PythonZip):
+                for idx, arg in enumerate(a.args):
+                    var = self.scope.get_expected_name(expr.indices[i][idx])
+                    handle_iterable_variable(var, arg)
+                stop = a.get_range().stop
+
+            elif isinstance(a, VariableIterator):
+                var = self.scope.get_expected_name(expr.indices[i])
+                handle_iterable_variable(var, a.variable)
+                stop = a.variable.shape[0]
 
             else:
                 errors.report(PYCCEL_RESTRICTION_TODO,
                               bounding_box=(self.current_ast_node.lineno, self.current_ast_node.col_offset),
                               severity='fatal')
-            existing_var = self.scope.find(var.name, 'variables')
-            if existing_var:
-                if self._infer_type(existing_var) != dvar:
-                    errors.report(f"Variable {var} already exists with different type",
+
+            for var, dvar in variables:
+                existing_var = self.scope.find(var.name, 'variables')
+                if var.name == expr.lhs:
+                    return errors.report(f"Variable {var} has the same name as the left hand side",
                             symbol = expr, severity='error')
path: pyccel/parser/semantic.py
user: login: EmilyBourne
body: Do you have an example of where this can arise?
created_at: 2024-12-05T09:16:51Z
author_association: MEMBER
diff_hunk: @@ -3857,31 +3886,37 @@ def _visit_FunctionalFor(self, expr):
         # TODO [YG, 30.10.2020]:
         #  - Check if we should allow the possibility that is_stack_array=True
         # ...
-        lhs_symbol = expr.lhs.base
+        lhs_symbol = expr.lhs
         ne = []
         lhs = self._assign_lhs_variable(lhs_symbol, d_var, rhs=expr, new_expressions=ne)
         lhs_alloc = ne[0]
 
         if isinstance(target, PythonTuple) and not target.is_homogeneous:
-            errors.report(LIST_OF_TUPLES, symbol=expr, severity='error')
+            errors.report(LIST_OF_TUPLES, symbol=expr, severity='fatal')
path: pyccel/parser/semantic.py
user: login: EmilyBourne
body: Why does the `error` now cause crashes?
created_at: 2024-12-05T09:18:24Z
author_association: MEMBER
diff_hunk: @@ -3857,31 +3886,37 @@ def _visit_FunctionalFor(self, expr):
         # TODO [YG, 30.10.2020]:
         #  - Check if we should allow the possibility that is_stack_array=True
         # ...
-        lhs_symbol = expr.lhs.base
+        lhs_symbol = expr.lhs
         ne = []
         lhs = self._assign_lhs_variable(lhs_symbol, d_var, rhs=expr, new_expressions=ne)
         lhs_alloc = ne[0]
 
         if isinstance(target, PythonTuple) and not target.is_homogeneous:
-            errors.report(LIST_OF_TUPLES, symbol=expr, severity='error')
+            errors.report(LIST_OF_TUPLES, symbol=expr, severity='fatal')
 
         target.invalidate_node()
 
         loops = [self._visit(i) for i in expr.loops]
-        index = self._visit(index)
+        if expr.index:
+            index = self._visit(index)
 
         l = loops[-1]
+        cnt = 0
         for idx in indices:
             assert isinstance(l, For)
             # Sub in indices as defined here for coherent naming
             if idx.is_temp:
                 self.scope.remove_variable(l.target[0])
                 l.substitute(l.target[0], idx_subs[idx])
-            l = l.body.body[-1]
+            cnt += 1
+            if cnt == len(l.target):
+                l = l.body.body[-1]
+                cnt = 0
path: pyccel/parser/semantic.py
user: login: EmilyBourne
body: I am surprised that this is not:
```python
            if idx.is_temp:
                self.scope.remove_variable(l.target[cnt])
                l.substitute(l.target[v], idx_subs[idx])
            cnt += 1
            if cnt == len(l.target):
                l = l.body.body[-1]
                cnt = 0
```
Why do you not care about the other targets?
created_at: 2024-12-05T09:26:31Z
author_association: MEMBER
diff_hunk: @@ -3659,88 +3659,123 @@ def _visit_For(self, expr):
             for_expr.end_annotation = expr.end_annotation
         return for_expr
 
-
     def _visit_FunctionalFor(self, expr):
-        old_index   = expr.index
-        new_index   = self.scope.get_new_name()
-        expr.substitute(old_index, new_index)
+        """
+        Visit and transform a FunctionalFor AST node into an equivalent code block.
 
-        target  = expr.expr
-        index   = new_index
-        indices = [self.scope.get_expected_name(i) for i in expr.indices]
-        dims    = []
-        body    = expr.loops[1]
+        This method processes a `FunctionalFor` expression and transforms the loop structure
+        into a corresponding code block.
 
-        idx_subs = {}
-        #scope = self.create_new_loop_scope()
+        Parameters
+        ----------
+        expr : pyccel.ast.functionalexpr.FunctionalFor
+            The FunctionalFor AST node.
+
+        Returns
+        -------
+        pyccel.ast.basic.CodeBlock
+            A code block containing the equivalent loops and necessary variable allocations for the given `FunctionalFor` expression.
+        """
+        if expr.target_type != 'list':
+            old_index   = expr.index
+            new_index   = self.scope.get_new_name()
+            expr.substitute(old_index, new_index)
+            index = new_index
 
-        # The symbols created to represent unknown valued objects are temporary
+        target  = expr.expr
+        indices = []
+        dims = []
+        idx_subs = {}
         tmp_used_names = self.scope.all_used_symbols.copy()
+        body = expr.loops[0 if expr.target_type == 'list' else 1]
         i = 0
-        while isinstance(body, For):
 
-            stop  = None
+        # Inner function to handle PythonNativeInt variables
+        def handle_int_loop_variable(var_name):
+            indices.append(var_name)
+            var = self._create_variable(var_name, PythonNativeInt(), None, {})
+            dvar = self._infer_type(var)
+            variables.append((var, dvar))
+
+        # Inner function to handle iterable variables
+        def handle_iterable_variable(var_name, element):
+            indices.append(var_name)
+            dvar = self._infer_type(element)
+            class_type = dvar.pop('class_type')
+            if class_type.rank > 0:
+                class_type = class_type.switch_rank(class_type.rank - 1)
+                dvar['shape'] = dvar['shape'][1:]
+            if class_type.rank == 0:
+                dvar['shape'] = None
+                dvar['memory_handling'] = 'stack'
+            var = self._create_variable(var_name, class_type, None, dvar)
+            dvar['class_type'] = class_type
+            variables.append((var, dvar))
+
+        while isinstance(body, For):
+            stop = None
             start = LiteralInteger(0)
-            step  = LiteralInteger(1)
-            var   = indices[i]
-            i += 1
-            a     = self._visit(body.iterable)
+            step = LiteralInteger(1)
+            variables = []
+            a = self._get_iterable(self._visit(body.iterable))
             if isinstance(a, PythonRange):
-                var   = self._create_variable(var, PythonNativeInt(), start, {})
-                dvar  = self._infer_type(var)
-                stop  = a.stop
+                var_name = self.scope.get_expected_name(expr.indices[i])
+                handle_int_loop_variable(var_name)
                 start = a.start
+                stop  = a.stop
                 step  = a.step
 
-            elif isinstance(a, (PythonZip, PythonEnumerate)):
-                dvar  = self._infer_type(a.element)
-                class_type = dvar.pop('class_type')
-                if class_type.rank > 0:
-                    class_type = class_type.switch_rank(class_type.rank-1)
-                    dvar['shape'] = (dvar['shape'])[1:]
-                if class_type.rank == 0:
-                    dvar['shape'] = None
-                    dvar['memory_handling'] = 'stack'
-                var  = Variable(class_type, var, **dvar)
+            elif isinstance(a, PythonEnumerate):
+                var_name1 = self.scope.get_expected_name(expr.indices[i][0])
+                var_name2 = self.scope.get_expected_name(expr.indices[i][1])
+                handle_int_loop_variable(var_name1)
+                handle_iterable_variable(var_name2, a.element)
                 stop = a.element.shape[0]
 
-            elif isinstance(a, Variable):
-                dvar  = self._infer_type(a)
-                class_type = dvar.pop('class_type')
-                if class_type.rank > 0:
-                    class_type = class_type.switch_rank(class_type.rank-1)
-                    dvar['shape'] = (dvar['shape'])[1:]
-                if class_type.rank == 0:
-                    dvar['shape'] = None
-                    dvar['memory_handling'] = 'stack'
-                var  = Variable(class_type, var, **dvar)
-                stop = a.shape[0]
+            elif isinstance(a, PythonZip):
+                for idx, arg in enumerate(a.args):
+                    var = self.scope.get_expected_name(expr.indices[i][idx])
+                    handle_iterable_variable(var, arg)
+                stop = a.get_range().stop
+
+            elif isinstance(a, VariableIterator):
+                var = self.scope.get_expected_name(expr.indices[i])
+                handle_iterable_variable(var, a.variable)
+                stop = a.variable.shape[0]
 
             else:
                 errors.report(PYCCEL_RESTRICTION_TODO,
                               bounding_box=(self.current_ast_node.lineno, self.current_ast_node.col_offset),
                               severity='fatal')
-            existing_var = self.scope.find(var.name, 'variables')
-            if existing_var:
-                if self._infer_type(existing_var) != dvar:
-                    errors.report(f"Variable {var} already exists with different type",
+
+            for var, dvar in variables:
+                existing_var = self.scope.find(var.name, 'variables')
+                if var.name == expr.lhs:
+                    return errors.report(f"Variable {var} has the same name as the left hand side",
                             symbol = expr, severity='error')
path: pyccel/parser/semantic.py
user: login: jalalium
body: Yes.
```python
a = [a for a in range(10)]
```
Could you please point out where to add tests that should raise errors so I can add this kind of examples?
created_at: 2024-12-05T11:12:22Z
author_association: MEMBER
diff_hunk: @@ -622,15 +626,23 @@ def _print_For(self, expr):
     def _print_FunctionalFor(self, expr):
         body, iterators = self._find_functional_expr_and_iterables(expr)
         lhs = self._print(expr.lhs)
-        body = self._print(body.rhs)
+        condition = ''
+        if isinstance(body, Assign):
+            body = self._print(body.rhs)
+        elif isinstance(body, ListAppend):
+            body = self._print(body.args[0])
+        else:
+            condition = 'if ' + self._print(body.blocks[0].condition)
+            body = self._print(body.blocks[0].body.body[0].args[0])
         for_loops = ' '.join(['for {} in {}'.format(self._print(idx), self._print(iters))
                         for idx, iters in zip(expr.indices, iterators)])
 
-        name = self._aliases.get(type(expr),'array')
-        if name == 'array':
+        name = expr.target_type
+        if 'array' in str(name):
path: pyccel/codegen/printing/pycode.py
user: login: jalalium
body: You mean something like this:
```python
from numpy import array as chunk

if __name__ == '__main__':
    a = chunk([i for i in range(10)])
```

Yes, it works just fine:
```python
from numpy import array as chunk

if __name__ == "__main__":
    a = chunk([i for i in range(0, 10, 1) ])
```
created_at: 2024-12-05T11:18:03Z
author_association: MEMBER
diff_hunk: @@ -3659,88 +3659,123 @@ def _visit_For(self, expr):
             for_expr.end_annotation = expr.end_annotation
         return for_expr
 
-
     def _visit_FunctionalFor(self, expr):
-        old_index   = expr.index
-        new_index   = self.scope.get_new_name()
-        expr.substitute(old_index, new_index)
+        """
+        Visit and transform a FunctionalFor AST node into an equivalent code block.
 
-        target  = expr.expr
-        index   = new_index
-        indices = [self.scope.get_expected_name(i) for i in expr.indices]
-        dims    = []
-        body    = expr.loops[1]
+        This method processes a `FunctionalFor` expression and transforms the loop structure
+        into a corresponding code block.
 
-        idx_subs = {}
-        #scope = self.create_new_loop_scope()
+        Parameters
+        ----------
+        expr : pyccel.ast.functionalexpr.FunctionalFor
+            The FunctionalFor AST node.
+
+        Returns
+        -------
+        pyccel.ast.basic.CodeBlock
+            A code block containing the equivalent loops and necessary variable allocations for the given `FunctionalFor` expression.
+        """
+        if expr.target_type != 'list':
+            old_index   = expr.index
+            new_index   = self.scope.get_new_name()
+            expr.substitute(old_index, new_index)
+            index = new_index
path: pyccel/parser/semantic.py
user: login: jalalium
body: Because list comprehensions are populated one of two ways, either through appending for lists, in which case this is irrelevant, or through indexedElement assignment for numpy arrays. This is necessary for the latter.
created_at: 2024-12-05T11:19:52Z
author_association: MEMBER
diff_hunk: @@ -3857,31 +3886,37 @@ def _visit_FunctionalFor(self, expr):
         # TODO [YG, 30.10.2020]:
         #  - Check if we should allow the possibility that is_stack_array=True
         # ...
-        lhs_symbol = expr.lhs.base
+        lhs_symbol = expr.lhs
         ne = []
         lhs = self._assign_lhs_variable(lhs_symbol, d_var, rhs=expr, new_expressions=ne)
         lhs_alloc = ne[0]
 
         if isinstance(target, PythonTuple) and not target.is_homogeneous:
-            errors.report(LIST_OF_TUPLES, symbol=expr, severity='error')
+            errors.report(LIST_OF_TUPLES, symbol=expr, severity='fatal')
 
         target.invalidate_node()
 
         loops = [self._visit(i) for i in expr.loops]
-        index = self._visit(index)
+        if expr.index:
+            index = self._visit(index)
 
         l = loops[-1]
+        cnt = 0
         for idx in indices:
             assert isinstance(l, For)
             # Sub in indices as defined here for coherent naming
             if idx.is_temp:
                 self.scope.remove_variable(l.target[0])
                 l.substitute(l.target[0], idx_subs[idx])
-            l = l.body.body[-1]
+            cnt += 1
+            if cnt == len(l.target):
+                l = l.body.body[-1]
+                cnt = 0
path: pyccel/parser/semantic.py
user: login: jalalium
body: Good catch! However, I don't know when this condition applies. I think this is vestigial code back from when `functionalFor` created loop scopes. Could you please come up with an example when idx is temp?
created_at: 2024-12-05T11:28:36Z
author_association: MEMBER
diff_hunk: @@ -3857,31 +3886,37 @@ def _visit_FunctionalFor(self, expr):
         # TODO [YG, 30.10.2020]:
         #  - Check if we should allow the possibility that is_stack_array=True
         # ...
-        lhs_symbol = expr.lhs.base
+        lhs_symbol = expr.lhs
         ne = []
         lhs = self._assign_lhs_variable(lhs_symbol, d_var, rhs=expr, new_expressions=ne)
         lhs_alloc = ne[0]
 
         if isinstance(target, PythonTuple) and not target.is_homogeneous:
-            errors.report(LIST_OF_TUPLES, symbol=expr, severity='error')
+            errors.report(LIST_OF_TUPLES, symbol=expr, severity='fatal')
 
         target.invalidate_node()
 
         loops = [self._visit(i) for i in expr.loops]
-        index = self._visit(index)
+        if expr.index:
+            index = self._visit(index)
 
         l = loops[-1]
+        cnt = 0
         for idx in indices:
             assert isinstance(l, For)
             # Sub in indices as defined here for coherent naming
             if idx.is_temp:
                 self.scope.remove_variable(l.target[0])
                 l.substitute(l.target[0], idx_subs[idx])
-            l = l.body.body[-1]
+            cnt += 1
+            if cnt == len(l.target):
+                l = l.body.body[-1]
+                cnt = 0
path: pyccel/parser/semantic.py
user: login: EmilyBourne
body: `idx` is temp for almost everything except `range` and `enumerate`. The difficulty is finding an example where there are multiple targets. Try this:
```python
import numpy as np

if __name__ == '__main__':
    a = np.array([1,2,3,4])
    b = np.array([5,6,7,8])
    c = [ai*bi for ai, bi in product(a, b)]
```
created_at: 2024-12-05T12:52:13Z
author_association: MEMBER
diff_hunk: @@ -3659,88 +3659,123 @@ def _visit_For(self, expr):
             for_expr.end_annotation = expr.end_annotation
         return for_expr
 
-
     def _visit_FunctionalFor(self, expr):
-        old_index   = expr.index
-        new_index   = self.scope.get_new_name()
-        expr.substitute(old_index, new_index)
+        """
+        Visit and transform a FunctionalFor AST node into an equivalent code block.
 
-        target  = expr.expr
-        index   = new_index
-        indices = [self.scope.get_expected_name(i) for i in expr.indices]
-        dims    = []
-        body    = expr.loops[1]
+        This method processes a `FunctionalFor` expression and transforms the loop structure
+        into a corresponding code block.
 
-        idx_subs = {}
-        #scope = self.create_new_loop_scope()
+        Parameters
+        ----------
+        expr : pyccel.ast.functionalexpr.FunctionalFor
+            The FunctionalFor AST node.
+
+        Returns
+        -------
+        pyccel.ast.basic.CodeBlock
+            A code block containing the equivalent loops and necessary variable allocations for the given `FunctionalFor` expression.
+        """
+        if expr.target_type != 'list':
+            old_index   = expr.index
+            new_index   = self.scope.get_new_name()
+            expr.substitute(old_index, new_index)
+            index = new_index
 
-        # The symbols created to represent unknown valued objects are temporary
+        target  = expr.expr
+        indices = []
+        dims = []
+        idx_subs = {}
         tmp_used_names = self.scope.all_used_symbols.copy()
+        body = expr.loops[0 if expr.target_type == 'list' else 1]
         i = 0
-        while isinstance(body, For):
 
-            stop  = None
+        # Inner function to handle PythonNativeInt variables
+        def handle_int_loop_variable(var_name):
+            indices.append(var_name)
+            var = self._create_variable(var_name, PythonNativeInt(), None, {})
+            dvar = self._infer_type(var)
+            variables.append((var, dvar))
+
+        # Inner function to handle iterable variables
+        def handle_iterable_variable(var_name, element):
+            indices.append(var_name)
+            dvar = self._infer_type(element)
+            class_type = dvar.pop('class_type')
+            if class_type.rank > 0:
+                class_type = class_type.switch_rank(class_type.rank - 1)
+                dvar['shape'] = dvar['shape'][1:]
+            if class_type.rank == 0:
+                dvar['shape'] = None
+                dvar['memory_handling'] = 'stack'
+            var = self._create_variable(var_name, class_type, None, dvar)
+            dvar['class_type'] = class_type
+            variables.append((var, dvar))
+
+        while isinstance(body, For):
+            stop = None
             start = LiteralInteger(0)
-            step  = LiteralInteger(1)
-            var   = indices[i]
-            i += 1
-            a     = self._visit(body.iterable)
+            step = LiteralInteger(1)
+            variables = []
+            a = self._get_iterable(self._visit(body.iterable))
             if isinstance(a, PythonRange):
-                var   = self._create_variable(var, PythonNativeInt(), start, {})
-                dvar  = self._infer_type(var)
-                stop  = a.stop
+                var_name = self.scope.get_expected_name(expr.indices[i])
+                handle_int_loop_variable(var_name)
                 start = a.start
+                stop  = a.stop
                 step  = a.step
 
-            elif isinstance(a, (PythonZip, PythonEnumerate)):
-                dvar  = self._infer_type(a.element)
-                class_type = dvar.pop('class_type')
-                if class_type.rank > 0:
-                    class_type = class_type.switch_rank(class_type.rank-1)
-                    dvar['shape'] = (dvar['shape'])[1:]
-                if class_type.rank == 0:
-                    dvar['shape'] = None
-                    dvar['memory_handling'] = 'stack'
-                var  = Variable(class_type, var, **dvar)
+            elif isinstance(a, PythonEnumerate):
+                var_name1 = self.scope.get_expected_name(expr.indices[i][0])
+                var_name2 = self.scope.get_expected_name(expr.indices[i][1])
+                handle_int_loop_variable(var_name1)
+                handle_iterable_variable(var_name2, a.element)
                 stop = a.element.shape[0]
 
-            elif isinstance(a, Variable):
-                dvar  = self._infer_type(a)
-                class_type = dvar.pop('class_type')
-                if class_type.rank > 0:
-                    class_type = class_type.switch_rank(class_type.rank-1)
-                    dvar['shape'] = (dvar['shape'])[1:]
-                if class_type.rank == 0:
-                    dvar['shape'] = None
-                    dvar['memory_handling'] = 'stack'
-                var  = Variable(class_type, var, **dvar)
-                stop = a.shape[0]
+            elif isinstance(a, PythonZip):
+                for idx, arg in enumerate(a.args):
+                    var = self.scope.get_expected_name(expr.indices[i][idx])
+                    handle_iterable_variable(var, arg)
+                stop = a.get_range().stop
+
+            elif isinstance(a, VariableIterator):
+                var = self.scope.get_expected_name(expr.indices[i])
+                handle_iterable_variable(var, a.variable)
+                stop = a.variable.shape[0]
 
             else:
                 errors.report(PYCCEL_RESTRICTION_TODO,
                               bounding_box=(self.current_ast_node.lineno, self.current_ast_node.col_offset),
                               severity='fatal')
-            existing_var = self.scope.find(var.name, 'variables')
-            if existing_var:
-                if self._infer_type(existing_var) != dvar:
-                    errors.report(f"Variable {var} already exists with different type",
+
+            for var, dvar in variables:
+                existing_var = self.scope.find(var.name, 'variables')
+                if var.name == expr.lhs:
+                    return errors.report(f"Variable {var} has the same name as the left hand side",
                             symbol = expr, severity='error')
path: pyccel/parser/semantic.py
user: login: EmilyBourne
body: https://github.com/pyccel/pyccel/tree/devel/tests/errors
created_at: 2024-12-05T12:52:27Z
author_association: MEMBER
diff_hunk: @@ -622,15 +626,23 @@ def _print_For(self, expr):
     def _print_FunctionalFor(self, expr):
         body, iterators = self._find_functional_expr_and_iterables(expr)
         lhs = self._print(expr.lhs)
-        body = self._print(body.rhs)
+        condition = ''
+        if isinstance(body, Assign):
+            body = self._print(body.rhs)
+        elif isinstance(body, ListAppend):
+            body = self._print(body.args[0])
+        else:
+            condition = 'if ' + self._print(body.blocks[0].condition)
+            body = self._print(body.blocks[0].body.body[0].args[0])
         for_loops = ' '.join(['for {} in {}'.format(self._print(idx), self._print(iters))
                         for idx, iters in zip(expr.indices, iterators)])
 
-        name = self._aliases.get(type(expr),'array')
-        if name == 'array':
+        name = expr.target_type
+        if 'array' in str(name):
path: pyccel/codegen/printing/pycode.py
user: login: EmilyBourne
body: Nice! How does that work? Is `target_type` set to `array` instead of `chunk` in the semantic stage?
created_at: 2024-12-05T12:55:32Z
author_association: MEMBER
diff_hunk: @@ -622,15 +628,23 @@ def _print_For(self, expr):
     def _print_FunctionalFor(self, expr):
         body, iterators = self._find_functional_expr_and_iterables(expr)
         lhs = self._print(expr.lhs)
-        body = self._print(body.rhs)
-        for_loops = ' '.join(['for {} in {}'.format(self._print(idx), self._print(iters))
-                        for idx, iters in zip(expr.indices, iterators)])
+        condition = ''
+        if isinstance(body, Assign):
+            body = self._print(body.rhs)
+        elif isinstance(body, ListAppend):
+            body = self._print(body.args[0])
+        else:
+            condition = 'if ' + self._print(body.blocks[0].condition)
+            body = self._print(body.blocks[0].body.body[0].args[0])
path: pyccel/codegen/printing/pycode.py
user: login: pyccel-bot[bot]
body: This code isn't tested. Please can you take a look
created_at: 2025-01-06T10:57:29Z
author_association: NONE
diff_hunk: @@ -1844,11 +1844,17 @@
         index   = Variable(PythonNativeInt(),self.scope.get_new_name('to_delete'), is_temp=True)
         self.scope.insert_variable(index)
         new_expr = []
-        while isinstance(loop, For):
+        while isinstance(loop, (For, If)):
+
+            if isinstance(loop, If):
+                loop = loop.blocks[0].body.body[0]
+                continue
path: pyccel/parser/semantic.py
user: login: pyccel-bot[bot]
body: This code isn't tested. Please can you take a look
created_at: 2025-01-06T10:57:30Z
author_association: NONE
diff_hunk: @@ -1760,7 +1760,9 @@ def _print_PyccelArrayShapeElement(self, expr):
     def _print_Allocate(self, expr):
         free_code = ''
         variable = expr.variable
-        if isinstance(variable.class_type, (HomogeneousListType, HomogeneousSetType, DictType)):
+        if isinstance(variable.class_type, (HomogeneousListType, HomogeneousSetType, DictType, StringType)):
+            if expr.status in ('allocated', 'unknown'):
+                free_code = f'{self._print(Deallocate(variable))}'
             if expr.shape[0] is None:
                 return free_code
             if expr.status in ('allocated', 'unknown'):
path: pyccel/codegen/printing/ccode.py
user: login: EmilyBourne
body: Careful it looks like you have duplicated `if expr.status in ('allocated', 'unknown'):` in this merge
created_at: 2025-01-13T13:15:58Z
author_association: MEMBER
diff_hunk: @@ -244,4 +244,12 @@ def f():
         return v
     
     f_epyc = epyccel(f, language = language)
+
+def test_sum_with_two_variables(language):
+    def f():
+        x = sum(i-j for i in range(10) for j in range(7))
+        return x
+
+    f_epyc = epyccel(f, language=language)
+
     assert f() == f_epyc()
path: tests/epyccel/test_epyccel_generators.py
user: login: EmilyBourne
body: You seem to have lost your assert
```suggestion
    f_epyc = epyccel(f, language = language)

    assert f() == f_epyc()

def test_sum_with_two_variables(language):
    def f():
        x = sum(i-j for i in range(10) for j in range(7))
        return x

    f_epyc = epyccel(f, language=language)

    assert f() == f_epyc()
```
created_at: 2025-01-13T13:16:41Z
author_association: MEMBER
diff_hunk: @@ -103,6 +104,13 @@ def sympy_to_pyccel(expr, symbol_map):
         else:
             return MathCeil(arg)
 
+    elif isinstance(expr, sp.Min):
+        args = [sympy_to_pyccel(a, symbol_map) for a in expr.args]
+        result = args[0]
+        for a in args[1:]:
+            result = PythonMin(result, a)
+        return result
path: pyccel/ast/sympy_helper.py
user: login: jalalium
body: Good point!
created_at: 2025-01-14T14:58:19Z
author_association: MEMBER
diff_hunk: @@ -3857,31 +3886,37 @@ def _visit_FunctionalFor(self, expr):
         # TODO [YG, 30.10.2020]:
         #  - Check if we should allow the possibility that is_stack_array=True
         # ...
-        lhs_symbol = expr.lhs.base
+        lhs_symbol = expr.lhs
         ne = []
         lhs = self._assign_lhs_variable(lhs_symbol, d_var, rhs=expr, new_expressions=ne)
         lhs_alloc = ne[0]
 
         if isinstance(target, PythonTuple) and not target.is_homogeneous:
-            errors.report(LIST_OF_TUPLES, symbol=expr, severity='error')
+            errors.report(LIST_OF_TUPLES, symbol=expr, severity='fatal')
path: pyccel/parser/semantic.py
user: login: jalalium
body: sorry, unintentional change.
created_at: 2025-01-14T17:18:17Z
author_association: MEMBER
diff_hunk: @@ -24,8 +24,7 @@
          if f not in failing_files \
          else pytest.param(os.path.join(path_dir,f), marks = pytest.mark.xfail(reason=failing_files[f])) \
          for f in files \
-         if f.endswith(".py") \
-        ]
+         if f.endswith(".py")]
path: tests/codegen/fcode/test_fcode_codegen.py
user: login: jalalium
body: nope
created_at: 2025-01-14T17:18:43Z
author_association: MEMBER
diff_hunk: @@ -16,6 +17,7 @@ def compare_epyccel(f, language, *args):
 def test_functional_for_1d_range(language):
     compare_epyccel(functionals.functional_for_1d_range, language)
 
+@pytest.mark.skipif(lambda lang: lang == 'c', reason="Skipping for C due to array redefinition issue. See #1979")
path: tests/epyccel/test_functionals.py
user: login: jalalium
body: This test is not supported in C. I don't see what you are referring to.
created_at: 2025-01-14T17:29:22Z
author_association: MEMBER
diff_hunk: @@ -622,15 +626,23 @@ def _print_For(self, expr):
     def _print_FunctionalFor(self, expr):
         body, iterators = self._find_functional_expr_and_iterables(expr)
         lhs = self._print(expr.lhs)
-        body = self._print(body.rhs)
+        condition = ''
+        if isinstance(body, Assign):
+            body = self._print(body.rhs)
+        elif isinstance(body, ListAppend):
+            body = self._print(body.args[0])
+        else:
+            condition = 'if ' + self._print(body.blocks[0].condition)
+            body = self._print(body.blocks[0].body.body[0].args[0])
         for_loops = ' '.join(['for {} in {}'.format(self._print(idx), self._print(iters))
                         for idx, iters in zip(expr.indices, iterators)])
 
-        name = self._aliases.get(type(expr),'array')
-        if name == 'array':
+        name = expr.target_type
+        if 'array' in str(name):
path: pyccel/codegen/printing/pycode.py
user: login: jalalium
body: Now we wait till the semantic stage to deduce the target_type.
created_at: 2025-01-14T17:37:10Z
author_association: MEMBER
diff_hunk: @@ -3857,31 +3886,37 @@ def _visit_FunctionalFor(self, expr):
         # TODO [YG, 30.10.2020]:
         #  - Check if we should allow the possibility that is_stack_array=True
         # ...
-        lhs_symbol = expr.lhs.base
+        lhs_symbol = expr.lhs
         ne = []
         lhs = self._assign_lhs_variable(lhs_symbol, d_var, rhs=expr, new_expressions=ne)
         lhs_alloc = ne[0]
 
         if isinstance(target, PythonTuple) and not target.is_homogeneous:
-            errors.report(LIST_OF_TUPLES, symbol=expr, severity='error')
+            errors.report(LIST_OF_TUPLES, symbol=expr, severity='fatal')
path: pyccel/parser/semantic.py
user: login: jalalium
body: @EmilyBourne 
In fact, it is in the `semantic/blocking` folder, it needs to be `fatal` for tests to pass. I wonder how this happened.
created_at: 2025-01-14T19:03:43Z
author_association: MEMBER
diff_hunk: @@ -622,15 +628,23 @@ def _print_For(self, expr):
     def _print_FunctionalFor(self, expr):
         body, iterators = self._find_functional_expr_and_iterables(expr)
         lhs = self._print(expr.lhs)
-        body = self._print(body.rhs)
-        for_loops = ' '.join(['for {} in {}'.format(self._print(idx), self._print(iters))
-                        for idx, iters in zip(expr.indices, iterators)])
+        condition = ''
+        if isinstance(body, Assign):
+            body = self._print(body.rhs)
+        elif isinstance(body, ListAppend):
+            body = self._print(body.args[0])
+        else:
+            condition = 'if ' + self._print(body.blocks[0].condition)
+            body = self._print(body.blocks[0].body.body[0].args[0])
path: pyccel/codegen/printing/pycode.py
user: login: pyccel-bot[bot]
body: This seems to be fixed now! Thank you for looking into this. Please resolve this conversation.

created_at: 2025-01-14T19:20:07Z
author_association: NONE
diff_hunk: @@ -1844,11 +1844,17 @@
         index   = Variable(PythonNativeInt(),self.scope.get_new_name('to_delete'), is_temp=True)
         self.scope.insert_variable(index)
         new_expr = []
-        while isinstance(loop, For):
+        while isinstance(loop, (For, If)):
+
+            if isinstance(loop, If):
+                loop = loop.blocks[0].body.body[0]
+                continue
path: pyccel/parser/semantic.py
user: login: pyccel-bot[bot]
body: This seems to be fixed now! Thank you for looking into this. Please resolve this conversation.

created_at: 2025-01-14T19:20:08Z
author_association: NONE
diff_hunk: @@ -103,6 +104,14 @@ def sympy_to_pyccel(expr, symbol_map):
         else:
             return MathCeil(arg)
 
+    elif isinstance(expr, sp.Min):
+        args = [sympy_to_pyccel(a, symbol_map) for a in expr.args]
+        return PythonMin(*args)
+
+    elif isinstance(expr, sp.Max):
+        args = [sympy_to_pyccel(a, symbol_map) for a in expr.args]
+        return PythonMax(*args)
+
path: pyccel/ast/sympy_helper.py
user: login: jalalium
body: /bot accept
this is added for completeness
created_at: 2025-01-14T19:28:56Z
author_association: MEMBER
diff_hunk: @@ -179,6 +193,14 @@ def pyccel_to_sympy(expr, symbol_map, used_names):
     elif isinstance(expr, MathCeil):
         return sp.ceiling(pyccel_to_sympy(expr.args[0], symbol_map, used_names))
 
+    elif isinstance(expr, PythonMin):
+        args = [pyccel_to_sympy(ee, symbol_map, used_names) for e in expr.args for ee in e]
+        return sp.Min(*args)
+
+    elif isinstance(expr, PythonMax):
+        args = [pyccel_to_sympy(ee, symbol_map, used_names) for e in expr.args for ee in e]
+        return sp.Max(*args)
+
path: pyccel/ast/sympy_helper.py
user: login: jalalium
body: /bot accept
created_at: 2025-01-15T16:00:31Z
author_association: MEMBER
diff_hunk: @@ -179,6 +193,14 @@ def pyccel_to_sympy(expr, symbol_map, used_names):
     elif isinstance(expr, MathCeil):
         return sp.ceiling(pyccel_to_sympy(expr.args[0], symbol_map, used_names))
 
+    elif isinstance(expr, PythonMin):
+        args = [pyccel_to_sympy(ee, symbol_map, used_names) for e in expr.args for ee in e]
+        return sp.Min(*args)
+
+    elif isinstance(expr, PythonMax):
+        args = [pyccel_to_sympy(ee, symbol_map, used_names) for e in expr.args for ee in e]
+        return sp.Max(*args)
+
path: pyccel/ast/sympy_helper.py
user: login: jalalium
body: /bot accept
created_at: 2025-01-20T08:37:38Z
author_association: MEMBER
diff_hunk: @@ -104,6 +104,20 @@ def sympy_to_pyccel(expr, symbol_map):
         else:
             return MathCeil(arg)
 
+    elif isinstance(expr, sp.Min):
+        args = [sympy_to_pyccel(a, symbol_map) for a in expr.args]
+        result = args[0]
+        for a in args[1:]:
+            result = PythonMin(result, a)
+        return result
+
+    elif isinstance(expr, sp.Max):
+        args = [sympy_to_pyccel(a, symbol_map) for a in expr.args]
+        result = args[0]
+        for a in args[1:]:
+            result = PythonMax(result, a)
+        return result
+
path: pyccel/ast/sympy_helper.py
user: login: jalalium
body: /bot accept
created_at: 2025-01-20T14:26:27Z
author_association: MEMBER
diff_hunk: @@ -78,6 +78,9 @@ All notable changes to this project will be documented in this file.
 -   #1913 : Fix function calls to renamed functions.
 -   #1930 : Preserve ordering of import targets.
 -   #1892 : Fix implementation of list function when an iterable is passed as parameter.
+-   #1924 : Fix internal error arising in Duplicate or list comprehensions.
path: CHANGELOG.md
user: login: EmilyBourne
body: This comment is still relevant
created_at: 2025-01-20T14:55:57Z
author_association: MEMBER
diff_hunk: @@ -78,6 +78,9 @@ All notable changes to this project will be documented in this file.
 -   #1913 : Fix function calls to renamed functions.
 -   #1930 : Preserve ordering of import targets.
 -   #1892 : Fix implementation of list function when an iterable is passed as parameter.
+-   #1924 : Fix internal error arising in Duplicate or list comprehensions.
+-   #1970 : Fix missing `TypeError` for wrong type passed as optional argument.
path: CHANGELOG.md
user: login: EmilyBourne
body: This comment is still relevant
created_at: 2025-01-20T14:56:00Z
author_association: MEMBER
diff_hunk: @@ -56,9 +56,16 @@ class FunctionalFor(TypedAstNode):
     target_type : PyccelSymbol, optional
         The type of the result of the functional for. This is useful at
         the syntactic stage to pass along the final type of the lhs (list/set/array/etc).
+    conditions : list of If or None, optional
path: pyccel/ast/functionalexpr.py
user: login: EmilyBourne
body: Does this mean `list[If|None]` or `list[If] | None` ?
If the latter then you don't need to specify `None` it is already specified by `optional`
created_at: 2025-01-20T14:57:02Z
author_association: MEMBER
diff_hunk: @@ -102,6 +113,26 @@ def indices(self):
     def index(self):
         return self._index
 
+    @property
+    def operations(self):
+        """
+        A dictionary mapping each type of comprehension to the operation used for populating it.
+
+        For example, for list comprehensions we might use 
+        ``{'list': 'append'}``, and for cspans (the equivalent of NumPy arrays, 
+        which require a fixed size at compile time), we might use 
+        ``{'cspans': 'indexed_element_assignment'}``. 
+        This mapping allows the code generator to select the appropriate operation 
+        when building the final data structure from the comprehension.
path: pyccel/ast/functionalexpr.py
user: login: EmilyBourne
body: Not sure I understand why the code generator needs this information. Won't it be included in the AST describing the for loops anyway?
created_at: 2025-01-20T14:58:23Z
author_association: MEMBER
diff_hunk: @@ -112,6 +143,24 @@ def target_type(self):
         """
         return self._target_type
 
+    @property
+    def conditions(self):
+        """
+        A list of filter conditions for each loop in the comprehension.
+
+        If not `None`, each element of this list is either an `If` instance 
+        that describes the filtering condition for that loop, or `None` if 
+        no condition is applied. These conditions collectively determine
+        whether each item produced by the loops is included in the final result.
+
+        Returns
+        -------
+        list of If or None, or None
path: pyccel/ast/functionalexpr.py
user: login: EmilyBourne
body: Strange type. How do you end up with a list of nones?
created_at: 2025-01-20T14:59:01Z
author_association: MEMBER
diff_hunk: @@ -1790,6 +1790,10 @@ def _print_Allocate(self, expr):
             size = self._print(expr.shape[0])
             variable_address = self._print(ObjectAddress(expr.variable))
             container_type = self.get_c_type(expr.variable.class_type)
+            if expr.alloc_type == 'reserve':
+                free_code = f'{container_type}_clear({variable_address});\n'
+            elif expr.status in ('allocated', 'unknown'):
+                free_code = f'{self._print(Deallocate(variable))}\n'
path: pyccel/codegen/printing/ccode.py
user: login: EmilyBourne
body: This condition is also found 10 lines above. This looks like a merge problem
created_at: 2025-01-20T15:00:19Z
author_association: MEMBER
diff_hunk: @@ -1790,6 +1790,10 @@ def _print_Allocate(self, expr):
             size = self._print(expr.shape[0])
             variable_address = self._print(ObjectAddress(expr.variable))
             container_type = self.get_c_type(expr.variable.class_type)
+            if expr.alloc_type == 'reserve':
+                free_code = f'{container_type}_clear({variable_address});\n'
+            elif expr.status in ('allocated', 'unknown'):
+                free_code = f'{self._print(Deallocate(variable))}\n'
             if expr.alloc_type == 'reserve':
                 return free_code + f'{container_type}_reserve({variable_address}, {size});\n'
path: pyccel/codegen/printing/ccode.py
user: login: EmilyBourne
body: Why are there 2 `if expr.alloc_type == 'reserve'` conditions?

For the first why is `clear` called systematically even if the status is "unallocated"?
created_at: 2025-01-20T15:01:06Z
author_association: MEMBER
diff_hunk: @@ -1148,26 +1148,70 @@ def _visit_For(self, stmt):
     def _visit_comprehension(self, stmt):
 
         scope = self.create_new_loop_scope()
+        condition = None
 
         self._in_lhs_assign = True
         iterator = self._visit(stmt.target)
         self._in_lhs_assign = False
         iterable = self._visit(stmt.iter)
 
-        self.exit_loop_scope()
-
         if stmt.ifs:
-            errors.report("Cannot handle if statements in list comprehensions. List length cannot be calculated.\n" + PYCCEL_RESTRICTION_TODO,
-                    symbol=stmt, severity='error')
+            from functools import reduce
path: pyccel/parser/syntactic.py
user: login: EmilyBourne
body: Imports should be at the top of the file
created_at: 2025-01-20T15:02:19Z
author_association: MEMBER
diff_hunk: @@ -1148,26 +1148,70 @@ def _visit_For(self, stmt):
     def _visit_comprehension(self, stmt):
 
         scope = self.create_new_loop_scope()
+        condition = None
 
         self._in_lhs_assign = True
         iterator = self._visit(stmt.target)
         self._in_lhs_assign = False
         iterable = self._visit(stmt.iter)
 
-        self.exit_loop_scope()
-
         if stmt.ifs:
-            errors.report("Cannot handle if statements in list comprehensions. List length cannot be calculated.\n" + PYCCEL_RESTRICTION_TODO,
-                    symbol=stmt, severity='error')
+            from functools import reduce
+            cond = reduce(PyccelAnd, [self._visit(if_cond)
+                                      for if_cond in stmt.ifs])
path: pyccel/parser/syntactic.py
user: login: EmilyBourne
body: `PyccelAnd` is not a binary operator. In theory you should be able to do:
```suggestion
            cond = PyccelAnd(*[self._visit(if_cond)
                                      for if_cond in stmt.ifs])
```
created_at: 2025-01-20T15:02:54Z
author_association: MEMBER
diff_hunk: @@ -1148,26 +1148,70 @@ def _visit_For(self, stmt):
     def _visit_comprehension(self, stmt):
 
         scope = self.create_new_loop_scope()
+        condition = None
 
         self._in_lhs_assign = True
         iterator = self._visit(stmt.target)
         self._in_lhs_assign = False
         iterable = self._visit(stmt.iter)
 
-        self.exit_loop_scope()
-
         if stmt.ifs:
-            errors.report("Cannot handle if statements in list comprehensions. List length cannot be calculated.\n" + PYCCEL_RESTRICTION_TODO,
-                    symbol=stmt, severity='error')
+            from functools import reduce
+            cond = reduce(PyccelAnd, [self._visit(if_cond)
+                                      for if_cond in stmt.ifs])
+            condition = If(IfSection(cond, CodeBlock([])))
 
-        expr = For(iterator, iterable, [], scope=scope)
+        self.exit_loop_scope()
+
+        expr = (For(iterator, iterable, [], scope=scope), condition)
         return expr
 
     def _visit_ListComp(self, stmt):
+        """
+        Converts a list comprehension statement into a `FunctionalFor` AST object.
+
+        This method translates the list comprehension into an equivalent `FunctionalFor`
+        
+        Parameters
+        ----------
+        stmt : ast.stmt
+            Object to visit of type X.
+
+        Returns
+        -------
+        pyccel.ast.functionalexpr.FunctionalFor
+            AST object which is the syntactic equivalent of the list comprehension.
+        """
+
+        def create_target_operations():
+            operations = {'list' : [], 'numpy_array' : []}
+            index = PyccelSymbol('_', is_temp=True)
+            args = [index]
+            target = IndexedElement(lhs, *args)
+            target = Assign(target, result)
+            assign1 = Assign(index, LiteralInteger(0))
+            assign1.set_current_ast(stmt)
+            operations['numpy_array'].append(assign1)
+            target.set_current_ast(stmt)
+            operations['numpy_array'].append(target)
+            assign2 = Assign(index, PyccelAdd(index, LiteralInteger(1)))
+            assign2.set_current_ast(stmt)
+            operations['numpy_array'].append(assign2)
+            operations['list'].append(DottedName(lhs, FunctionCall('append', [FunctionCallArgument(result)])))
+
+            return index, operations
+
 
         result = self._visit(stmt.elt)
+        output_type = None
 
-        generators = list(self._visit(stmt.generators))
+        comprehensions = list(self._visit(stmt.generators))
path: pyccel/parser/syntactic.py
user: login: EmilyBourne
body: What type is `stmt.generators`? Do you know what this list unpacking is doing? I thought `self._visit(stmt.generators)` would return a `GeneratorComprehension` but that class doesn't seem to implement `__iter__` so I'm not sure what's going on here
created_at: 2025-01-20T15:06:06Z
author_association: MEMBER
diff_hunk: @@ -1148,26 +1148,70 @@ def _visit_For(self, stmt):
     def _visit_comprehension(self, stmt):
 
         scope = self.create_new_loop_scope()
+        condition = None
 
         self._in_lhs_assign = True
         iterator = self._visit(stmt.target)
         self._in_lhs_assign = False
         iterable = self._visit(stmt.iter)
 
-        self.exit_loop_scope()
-
         if stmt.ifs:
-            errors.report("Cannot handle if statements in list comprehensions. List length cannot be calculated.\n" + PYCCEL_RESTRICTION_TODO,
-                    symbol=stmt, severity='error')
+            from functools import reduce
+            cond = reduce(PyccelAnd, [self._visit(if_cond)
+                                      for if_cond in stmt.ifs])
+            condition = If(IfSection(cond, CodeBlock([])))
 
-        expr = For(iterator, iterable, [], scope=scope)
+        self.exit_loop_scope()
+
+        expr = (For(iterator, iterable, [], scope=scope), condition)
         return expr
 
     def _visit_ListComp(self, stmt):
+        """
+        Converts a list comprehension statement into a `FunctionalFor` AST object.
+
+        This method translates the list comprehension into an equivalent `FunctionalFor`
+        
+        Parameters
+        ----------
+        stmt : ast.stmt
+            Object to visit of type X.
+
+        Returns
+        -------
+        pyccel.ast.functionalexpr.FunctionalFor
+            AST object which is the syntactic equivalent of the list comprehension.
+        """
+
+        def create_target_operations():
+            operations = {'list' : [], 'numpy_array' : []}
+            index = PyccelSymbol('_', is_temp=True)
+            args = [index]
+            target = IndexedElement(lhs, *args)
+            target = Assign(target, result)
+            assign1 = Assign(index, LiteralInteger(0))
+            assign1.set_current_ast(stmt)
+            operations['numpy_array'].append(assign1)
+            target.set_current_ast(stmt)
+            operations['numpy_array'].append(target)
+            assign2 = Assign(index, PyccelAdd(index, LiteralInteger(1)))
+            assign2.set_current_ast(stmt)
+            operations['numpy_array'].append(assign2)
+            operations['list'].append(DottedName(lhs, FunctionCall('append', [FunctionCallArgument(result)])))
+
+            return index, operations
+
 
         result = self._visit(stmt.elt)
+        output_type = None
 
-        generators = list(self._visit(stmt.generators))
+        comprehensions = list(self._visit(stmt.generators))
+        generators, conditions = zip(*comprehensions)
+        generators, conditions = map(list, [generators, conditions])
path: pyccel/parser/syntactic.py
user: login: EmilyBourne
body: Is this equivalent to:
```suggestion
        generators = [comprehensions[0][0], comprehensions[1][0]]
        conditions = [comprehensions[0][1], comprehensions[1][1]]
```
?
created_at: 2025-01-20T15:09:19Z
author_association: MEMBER
diff_hunk: @@ -1148,26 +1148,70 @@ def _visit_For(self, stmt):
     def _visit_comprehension(self, stmt):
 
         scope = self.create_new_loop_scope()
+        condition = None
 
         self._in_lhs_assign = True
         iterator = self._visit(stmt.target)
         self._in_lhs_assign = False
         iterable = self._visit(stmt.iter)
 
-        self.exit_loop_scope()
-
         if stmt.ifs:
-            errors.report("Cannot handle if statements in list comprehensions. List length cannot be calculated.\n" + PYCCEL_RESTRICTION_TODO,
-                    symbol=stmt, severity='error')
+            from functools import reduce
+            cond = reduce(PyccelAnd, [self._visit(if_cond)
+                                      for if_cond in stmt.ifs])
+            condition = If(IfSection(cond, CodeBlock([])))
 
-        expr = For(iterator, iterable, [], scope=scope)
+        self.exit_loop_scope()
+
+        expr = (For(iterator, iterable, [], scope=scope), condition)
         return expr
 
     def _visit_ListComp(self, stmt):
+        """
+        Converts a list comprehension statement into a `FunctionalFor` AST object.
+
+        This method translates the list comprehension into an equivalent `FunctionalFor`
+        
+        Parameters
+        ----------
+        stmt : ast.stmt
+            Object to visit of type X.
+
+        Returns
+        -------
+        pyccel.ast.functionalexpr.FunctionalFor
+            AST object which is the syntactic equivalent of the list comprehension.
+        """
+
+        def create_target_operations():
+            operations = {'list' : [], 'numpy_array' : []}
+            index = PyccelSymbol('_', is_temp=True)
+            args = [index]
+            target = IndexedElement(lhs, *args)
+            target = Assign(target, result)
+            assign1 = Assign(index, LiteralInteger(0))
+            assign1.set_current_ast(stmt)
+            operations['numpy_array'].append(assign1)
+            target.set_current_ast(stmt)
+            operations['numpy_array'].append(target)
+            assign2 = Assign(index, PyccelAdd(index, LiteralInteger(1)))
+            assign2.set_current_ast(stmt)
+            operations['numpy_array'].append(assign2)
+            operations['list'].append(DottedName(lhs, FunctionCall('append', [FunctionCallArgument(result)])))
+
+            return index, operations
path: pyccel/parser/syntactic.py
user: login: EmilyBourne
body: Is there any reason this needs to be in the syntactic stage? It seems that you could create semantic objects directly and you wouldn't need to create a version for lists and another for arrays if you waited for the semantic stage where you should know what you are creating.
created_at: 2025-01-20T15:11:33Z
author_association: MEMBER
diff_hunk: @@ -1243,20 +1272,34 @@ def _visit_GeneratorExp(self, stmt):
             body = Assign(lhs, body)
 
         body.set_current_ast(parent)
-        indices = []
-        generators = list(generators)
-        while len(generators) > 0:
-            indices.append(generators[-1].target)
+
+        for loop, condition in zip(generators, conditions):
+            if condition:
+                loop.insert2body(condition)
+
+        if generators[-1].body.body:
+            generators[-1].body.body[0].blocks[0].body.insert2body(body)
+        else:
             generators[-1].insert2body(body)
-            body = generators.pop()
+
+        while(len(generators)) > 1:
path: pyccel/parser/syntactic.py
user: login: EmilyBourne
body: ```suggestion
        while len(generators) > 1:
```
created_at: 2025-01-20T15:16:31Z
author_association: MEMBER
diff_hunk: @@ -1243,20 +1272,34 @@ def _visit_GeneratorExp(self, stmt):
             body = Assign(lhs, body)
 
         body.set_current_ast(parent)
-        indices = []
-        generators = list(generators)
-        while len(generators) > 0:
-            indices.append(generators[-1].target)
+
+        for loop, condition in zip(generators, conditions):
+            if condition:
+                loop.insert2body(condition)
+
+        if generators[-1].body.body:
+            generators[-1].body.body[0].blocks[0].body.insert2body(body)
+        else:
             generators[-1].insert2body(body)
-            body = generators.pop()
+
+        while(len(generators)) > 1:
+            indices.append(generators[-1].target)
+            outter_loop = generators.pop()
+            inserted_into = generators[-1]
+            if inserted_into.body.body:
+                inserted_into.body.body[0].blocks[0].body.insert2body(outter_loop)
+            else:
+                inserted_into.insert2body(outter_loop)
path: pyccel/parser/syntactic.py
user: login: EmilyBourne
body: Typo:
```suggestion
            outer_loop = generators.pop()
            inserted_into = generators[-1]
            if inserted_into.body.body:
                inserted_into.body.body[0].blocks[0].body.insert2body(outer_loop)
            else:
                inserted_into.insert2body(outer_loop)
```
created_at: 2025-01-20T15:17:28Z
author_association: MEMBER
diff_hunk: @@ -1243,20 +1272,34 @@ def _visit_GeneratorExp(self, stmt):
             body = Assign(lhs, body)
 
         body.set_current_ast(parent)
-        indices = []
-        generators = list(generators)
-        while len(generators) > 0:
-            indices.append(generators[-1].target)
+
+        for loop, condition in zip(generators, conditions):
+            if condition:
+                loop.insert2body(condition)
+
+        if generators[-1].body.body:
+            generators[-1].body.body[0].blocks[0].body.insert2body(body)
+        else:
             generators[-1].insert2body(body)
-            body = generators.pop()
+
+        while(len(generators)) > 1:
+            indices.append(generators[-1].target)
+            outter_loop = generators.pop()
+            inserted_into = generators[-1]
+            if inserted_into.body.body:
+                inserted_into.body.body[0].blocks[0].body.insert2body(outter_loop)
+            else:
+                inserted_into.insert2body(outter_loop)
path: pyccel/parser/syntactic.py
user: login: EmilyBourne
body: The if block before the while loop seems to be the same as the one inside the while loop. Can you not do something like:
```python
         outer_loop = body
         while generators:
             inserted_into = generators[-1]
￼            if inserted_into.body.body:
￼                inserted_into.body.body[0].blocks[0].body.insert2body(outer_loop)
￼            else:
￼                inserted_into.insert2body(outer_loop)
￼            indices.append(generators[-1].target)
￼            outer_loop = generators.pop()

        expr = FunctionalX(outer_loop, result, lhs, indices)
```
to reduce code duplication?
created_at: 2025-01-20T15:22:39Z
author_association: MEMBER
diff_hunk: @@ -1243,20 +1272,34 @@ def _visit_GeneratorExp(self, stmt):
             body = Assign(lhs, body)
 
         body.set_current_ast(parent)
-        indices = []
-        generators = list(generators)
-        while len(generators) > 0:
-            indices.append(generators[-1].target)
+
+        for loop, condition in zip(generators, conditions):
+            if condition:
+                loop.insert2body(condition)
+
+        if generators[-1].body.body:
+            generators[-1].body.body[0].blocks[0].body.insert2body(body)
path: pyccel/parser/syntactic.py
user: login: EmilyBourne
body: Can we add some intermediate variables or comments to make this easier to follow?
created_at: 2025-01-20T15:23:21Z
author_association: MEMBER
diff_hunk: @@ -16,6 +17,7 @@ def compare_epyccel(f, language, *args):
 def test_functional_for_1d_range(language):
     compare_epyccel(functionals.functional_for_1d_range, language)
 
+@pytest.mark.skipif(lambda lang: lang == 'c', reason="Skipping for C due to array redefinition issue. See #1979")
path: tests/epyccel/test_functionals.py
user: login: EmilyBourne
body: The reason that you have added in this PR says "Skipping for C due to array redefinition issue. See #1979". #1979 has been closed as fixed so either this comment is the result of a bad merge or you need to update the issue that you refer to
created_at: 2025-01-20T15:32:45Z
author_association: MEMBER
diff_hunk: @@ -199,6 +199,58 @@ def f(a0 : 'int[:]'):
 
     assert f(x) == f_epyc(x)
 
+def test_sum_with_condition(language):
+    def f():
+        v = sum(i for i in range(20) if i % 2 == 1)
+        return v
+
+    f_epyc = epyccel(f, language = language)
+    assert f() == f_epyc()
+
+def test_sum_with_multiple_conditions(language):
+    def f():
+        v = sum(i - j for i in range(20) if i % 2 == 1 for j in range(30) if j % 3 == 1)
+        return v
+
+    f_epyc = epyccel(f, language = language)
+    assert f() == f_epyc()
+
+@pytest.mark.xfail(sys.platform == "darwin", reason="Constant('inf') printing")
+def test_max_with_condition(language):
+    def f():
+        v = max(i for i in range(20) if i % 2 == 1)
+        return v
+
+    f_epyc = epyccel(f, language = language)
+    assert f() == f_epyc()
+
+@pytest.mark.xfail(sys.platform == "darwin", reason="Constant('inf') printing")
+def test_max_with_multiple_conditions(language):
+    def f():
+        v = max(i - j for i in range(20) if i % 2 == 1 for j in range(30) if j % 3 == 1)
+        return v
+
+    f_epyc = epyccel(f, language = language)
+    assert f() == f_epyc()
+
+@pytest.mark.xfail(sys.platform == "darwin", reason="Constant('inf') printing")
+def test_min_with_condition(language):
+    def f():
+        v = min(i for i in range(20) if i % 2 == 1)
+        return v
+
+    f_epyc = epyccel(f, language = language)
+    assert f() == f_epyc()
+
+@pytest.mark.xfail(sys.platform == "darwin", reason="Constant('inf') printing")
path: tests/epyccel/test_epyccel_generators.py
user: login: EmilyBourne
body: Please reference an issue that describes this issue so it is easy to locate these xfails when the issue is fixed
created_at: 2025-01-20T15:33:24Z
author_association: MEMBER
diff_hunk: @@ -4049,6 +4049,7 @@ def test_array_float_nested_C_array_initialization_3(language):
         pytest.param("python", marks = pytest.mark.python)
     )
 )
+@pytest.mark.skipif(lambda lang: lang == 'fortran', reason="List comprehension is not supported in Fortran")
path: tests/epyccel/test_arrays.py
user: login: EmilyBourne
body: This comment is still relevant
created_at: 2025-01-20T15:34:04Z
author_association: MEMBER
diff_hunk: @@ -78,6 +78,9 @@ All notable changes to this project will be documented in this file.
 -   #1913 : Fix function calls to renamed functions.
 -   #1930 : Preserve ordering of import targets.
 -   #1892 : Fix implementation of list function when an iterable is passed as parameter.
+-   #1924 : Fix internal error arising in Duplicate or list comprehensions.
path: CHANGELOG.md
user: login: jalalium
body: fixed [fc9b4731](https://github.com/pyccel/pyccel/pull/1977/commits/fc9b47310eb6c74b060ca6d048b6ad438a930551)
created_at: 2025-02-04T11:10:47Z
author_association: MEMBER
diff_hunk: @@ -78,6 +78,9 @@ All notable changes to this project will be documented in this file.
 -   #1913 : Fix function calls to renamed functions.
 -   #1930 : Preserve ordering of import targets.
 -   #1892 : Fix implementation of list function when an iterable is passed as parameter.
+-   #1924 : Fix internal error arising in Duplicate or list comprehensions.
+-   #1970 : Fix missing `TypeError` for wrong type passed as optional argument.
path: CHANGELOG.md
user: login: jalalium
body: fixed [fc9b4731](https://github.com/pyccel/pyccel/pull/1977/commits/fc9b47310eb6c74b060ca6d048b6ad438a930551)
created_at: 2025-02-04T11:11:04Z
author_association: MEMBER
diff_hunk: @@ -56,9 +56,16 @@ class FunctionalFor(TypedAstNode):
     target_type : PyccelSymbol, optional
         The type of the result of the functional for. This is useful at
         the syntactic stage to pass along the final type of the lhs (list/set/array/etc).
+    conditions : list of If or None, optional
path: pyccel/ast/functionalexpr.py
user: login: jalalium
body: It is indeed a `list[If|None]`,`conditions` is either `None` if not specified or a list of the same length as the number of loops. For each loop, we either store its conditions or `None` if no condition is specified.
fixed in [f64bcde9](https://github.com/pyccel/pyccel/pull/1977/commits/f64bcde9d2c12c505b127a905798e5ec7a21035c)
created_at: 2025-02-04T11:16:43Z
author_association: MEMBER
diff_hunk: @@ -102,6 +113,26 @@ def indices(self):
     def index(self):
         return self._index
 
+    @property
+    def operations(self):
+        """
+        A dictionary mapping each type of comprehension to the operation used for populating it.
+
+        For example, for list comprehensions we might use 
+        ``{'list': 'append'}``, and for cspans (the equivalent of NumPy arrays, 
+        which require a fixed size at compile time), we might use 
+        ``{'cspans': 'indexed_element_assignment'}``. 
+        This mapping allows the code generator to select the appropriate operation 
+        when building the final data structure from the comprehension.
path: pyccel/ast/functionalexpr.py
user: login: jalalium
body: This information is relevant at the semantic stage but needs to be created during the syntactic stage where we still can't tell precisely which operation to use, as per our discussion about calling numpy arrays lists. In addition, this is intended to handle `dict` and `set` comprehensions.
created_at: 2025-02-04T11:18:59Z
author_association: MEMBER
diff_hunk: @@ -112,6 +143,24 @@ def target_type(self):
         """
         return self._target_type
 
+    @property
+    def conditions(self):
+        """
+        A list of filter conditions for each loop in the comprehension.
+
+        If not `None`, each element of this list is either an `If` instance 
+        that describes the filtering condition for that loop, or `None` if 
+        no condition is applied. These conditions collectively determine
+        whether each item produced by the loops is included in the final result.
+
+        Returns
+        -------
+        list of If or None, or None
path: pyccel/ast/functionalexpr.py
user: login: jalalium
body: changed to `List[If|None]`. We would end up with a list of nones if no loop has conditions. This seems awkward but i think it is better that using more complex data structures to tell which loops have conditions.
created_at: 2025-02-04T11:21:13Z
author_association: MEMBER
diff_hunk: @@ -1790,6 +1790,10 @@ def _print_Allocate(self, expr):
             size = self._print(expr.shape[0])
             variable_address = self._print(ObjectAddress(expr.variable))
             container_type = self.get_c_type(expr.variable.class_type)
+            if expr.alloc_type == 'reserve':
+                free_code = f'{container_type}_clear({variable_address});\n'
+            elif expr.status in ('allocated', 'unknown'):
+                free_code = f'{self._print(Deallocate(variable))}\n'
path: pyccel/codegen/printing/ccode.py
user: login: jalalium
body: Sorry bad merge.
Fixed [1915f2b5](https://github.com/pyccel/pyccel/pull/1977/commits/1915f2b5346fa56504827b5a7c269d2b8b673f33)
created_at: 2025-02-04T11:43:06Z
author_association: MEMBER
diff_hunk: @@ -1790,6 +1790,10 @@ def _print_Allocate(self, expr):
             size = self._print(expr.shape[0])
             variable_address = self._print(ObjectAddress(expr.variable))
             container_type = self.get_c_type(expr.variable.class_type)
+            if expr.alloc_type == 'reserve':
+                free_code = f'{container_type}_clear({variable_address});\n'
+            elif expr.status in ('allocated', 'unknown'):
+                free_code = f'{self._print(Deallocate(variable))}\n'
             if expr.alloc_type == 'reserve':
                 return free_code + f'{container_type}_reserve({variable_address}, {size});\n'
path: pyccel/codegen/printing/ccode.py
user: login: jalalium
body: Sorry bad merge.
Fixed [1915f2b5](https://github.com/pyccel/pyccel/pull/1977/commits/1915f2b5346fa56504827b5a7c269d2b8b673f33)
created_at: 2025-02-04T11:43:16Z
author_association: MEMBER
diff_hunk: @@ -1148,26 +1148,70 @@ def _visit_For(self, stmt):
     def _visit_comprehension(self, stmt):
 
         scope = self.create_new_loop_scope()
+        condition = None
 
         self._in_lhs_assign = True
         iterator = self._visit(stmt.target)
         self._in_lhs_assign = False
         iterable = self._visit(stmt.iter)
 
-        self.exit_loop_scope()
-
         if stmt.ifs:
-            errors.report("Cannot handle if statements in list comprehensions. List length cannot be calculated.\n" + PYCCEL_RESTRICTION_TODO,
-                    symbol=stmt, severity='error')
+            from functools import reduce
+            cond = reduce(PyccelAnd, [self._visit(if_cond)
+                                      for if_cond in stmt.ifs])
+            condition = If(IfSection(cond, CodeBlock([])))
 
-        expr = For(iterator, iterable, [], scope=scope)
+        self.exit_loop_scope()
+
+        expr = (For(iterator, iterable, [], scope=scope), condition)
         return expr
 
     def _visit_ListComp(self, stmt):
+        """
+        Converts a list comprehension statement into a `FunctionalFor` AST object.
+
+        This method translates the list comprehension into an equivalent `FunctionalFor`
+        
+        Parameters
+        ----------
+        stmt : ast.stmt
+            Object to visit of type X.
+
+        Returns
+        -------
+        pyccel.ast.functionalexpr.FunctionalFor
+            AST object which is the syntactic equivalent of the list comprehension.
+        """
+
+        def create_target_operations():
+            operations = {'list' : [], 'numpy_array' : []}
+            index = PyccelSymbol('_', is_temp=True)
+            args = [index]
+            target = IndexedElement(lhs, *args)
+            target = Assign(target, result)
+            assign1 = Assign(index, LiteralInteger(0))
+            assign1.set_current_ast(stmt)
+            operations['numpy_array'].append(assign1)
+            target.set_current_ast(stmt)
+            operations['numpy_array'].append(target)
+            assign2 = Assign(index, PyccelAdd(index, LiteralInteger(1)))
+            assign2.set_current_ast(stmt)
+            operations['numpy_array'].append(assign2)
+            operations['list'].append(DottedName(lhs, FunctionCall('append', [FunctionCallArgument(result)])))
+
+            return index, operations
path: pyccel/parser/syntactic.py
user: login: jalalium
body: That's true, but without this. I had to set the stage to `syntactic` during the semantic stage to be able to declare the dummy index
created_at: 2025-02-04T12:03:36Z
author_association: MEMBER
diff_hunk: @@ -1148,26 +1148,70 @@ def _visit_For(self, stmt):
     def _visit_comprehension(self, stmt):
 
         scope = self.create_new_loop_scope()
+        condition = None
 
         self._in_lhs_assign = True
         iterator = self._visit(stmt.target)
         self._in_lhs_assign = False
         iterable = self._visit(stmt.iter)
 
-        self.exit_loop_scope()
-
         if stmt.ifs:
-            errors.report("Cannot handle if statements in list comprehensions. List length cannot be calculated.\n" + PYCCEL_RESTRICTION_TODO,
-                    symbol=stmt, severity='error')
+            from functools import reduce
+            cond = reduce(PyccelAnd, [self._visit(if_cond)
+                                      for if_cond in stmt.ifs])
+            condition = If(IfSection(cond, CodeBlock([])))
 
-        expr = For(iterator, iterable, [], scope=scope)
+        self.exit_loop_scope()
+
+        expr = (For(iterator, iterable, [], scope=scope), condition)
         return expr
 
     def _visit_ListComp(self, stmt):
+        """
+        Converts a list comprehension statement into a `FunctionalFor` AST object.
+
+        This method translates the list comprehension into an equivalent `FunctionalFor`
+        
+        Parameters
+        ----------
+        stmt : ast.stmt
+            Object to visit of type X.
+
+        Returns
+        -------
+        pyccel.ast.functionalexpr.FunctionalFor
+            AST object which is the syntactic equivalent of the list comprehension.
+        """
+
+        def create_target_operations():
+            operations = {'list' : [], 'numpy_array' : []}
+            index = PyccelSymbol('_', is_temp=True)
+            args = [index]
+            target = IndexedElement(lhs, *args)
+            target = Assign(target, result)
+            assign1 = Assign(index, LiteralInteger(0))
+            assign1.set_current_ast(stmt)
+            operations['numpy_array'].append(assign1)
+            target.set_current_ast(stmt)
+            operations['numpy_array'].append(target)
+            assign2 = Assign(index, PyccelAdd(index, LiteralInteger(1)))
+            assign2.set_current_ast(stmt)
+            operations['numpy_array'].append(assign2)
+            operations['list'].append(DottedName(lhs, FunctionCall('append', [FunctionCallArgument(result)])))
+
+            return index, operations
+
 
         result = self._visit(stmt.elt)
+        output_type = None
 
-        generators = list(self._visit(stmt.generators))
+        comprehensions = list(self._visit(stmt.generators))
path: pyccel/parser/syntactic.py
user: login: jalalium
body: `stmt.generators` is a regular python list. calling `self._visit` on it calls:
```Python
    def _visit_list(self, stmt):
        return list(self._treat_iterable(stmt))
```
which visits each of `comprehension` in the list.
created_at: 2025-02-04T12:08:06Z
author_association: MEMBER
diff_hunk: @@ -1148,26 +1148,70 @@ def _visit_For(self, stmt):
     def _visit_comprehension(self, stmt):
 
         scope = self.create_new_loop_scope()
+        condition = None
 
         self._in_lhs_assign = True
         iterator = self._visit(stmt.target)
         self._in_lhs_assign = False
         iterable = self._visit(stmt.iter)
 
-        self.exit_loop_scope()
-
         if stmt.ifs:
-            errors.report("Cannot handle if statements in list comprehensions. List length cannot be calculated.\n" + PYCCEL_RESTRICTION_TODO,
-                    symbol=stmt, severity='error')
+            from functools import reduce
+            cond = reduce(PyccelAnd, [self._visit(if_cond)
+                                      for if_cond in stmt.ifs])
+            condition = If(IfSection(cond, CodeBlock([])))
 
-        expr = For(iterator, iterable, [], scope=scope)
+        self.exit_loop_scope()
+
+        expr = (For(iterator, iterable, [], scope=scope), condition)
         return expr
 
     def _visit_ListComp(self, stmt):
+        """
+        Converts a list comprehension statement into a `FunctionalFor` AST object.
+
+        This method translates the list comprehension into an equivalent `FunctionalFor`
+        
+        Parameters
+        ----------
+        stmt : ast.stmt
+            Object to visit of type X.
+
+        Returns
+        -------
+        pyccel.ast.functionalexpr.FunctionalFor
+            AST object which is the syntactic equivalent of the list comprehension.
+        """
+
+        def create_target_operations():
+            operations = {'list' : [], 'numpy_array' : []}
+            index = PyccelSymbol('_', is_temp=True)
+            args = [index]
+            target = IndexedElement(lhs, *args)
+            target = Assign(target, result)
+            assign1 = Assign(index, LiteralInteger(0))
+            assign1.set_current_ast(stmt)
+            operations['numpy_array'].append(assign1)
+            target.set_current_ast(stmt)
+            operations['numpy_array'].append(target)
+            assign2 = Assign(index, PyccelAdd(index, LiteralInteger(1)))
+            assign2.set_current_ast(stmt)
+            operations['numpy_array'].append(assign2)
+            operations['list'].append(DottedName(lhs, FunctionCall('append', [FunctionCallArgument(result)])))
+
+            return index, operations
+
 
         result = self._visit(stmt.elt)
+        output_type = None
 
-        generators = list(self._visit(stmt.generators))
+        comprehensions = list(self._visit(stmt.generators))
+        generators, conditions = zip(*comprehensions)
+        generators, conditions = map(list, [generators, conditions])
path: pyccel/parser/syntactic.py
user: login: jalalium
body: I don't think so. `comprehensions` is  a list of tuples where each tuple is a (comprehension, condition). I think the `zip` and `map` are necessary. Your suggestion would work if we only had a 2 loops.
created_at: 2025-02-04T12:11:42Z
author_association: MEMBER
diff_hunk: @@ -1148,26 +1148,70 @@ def _visit_For(self, stmt):
     def _visit_comprehension(self, stmt):
 
         scope = self.create_new_loop_scope()
+        condition = None
 
         self._in_lhs_assign = True
         iterator = self._visit(stmt.target)
         self._in_lhs_assign = False
         iterable = self._visit(stmt.iter)
 
-        self.exit_loop_scope()
-
         if stmt.ifs:
-            errors.report("Cannot handle if statements in list comprehensions. List length cannot be calculated.\n" + PYCCEL_RESTRICTION_TODO,
-                    symbol=stmt, severity='error')
+            from functools import reduce
+            cond = reduce(PyccelAnd, [self._visit(if_cond)
+                                      for if_cond in stmt.ifs])
path: pyccel/parser/syntactic.py
user: login: jalalium
body: fixed [39a2edb7](https://github.com/pyccel/pyccel/pull/1977/commits/39a2edb75d601dcee7bb904c2b2d132228f6515b)
created_at: 2025-02-04T12:15:56Z
author_association: MEMBER
diff_hunk: @@ -1243,20 +1272,34 @@ def _visit_GeneratorExp(self, stmt):
             body = Assign(lhs, body)
 
         body.set_current_ast(parent)
-        indices = []
-        generators = list(generators)
-        while len(generators) > 0:
-            indices.append(generators[-1].target)
+
+        for loop, condition in zip(generators, conditions):
+            if condition:
+                loop.insert2body(condition)
+
+        if generators[-1].body.body:
+            generators[-1].body.body[0].blocks[0].body.insert2body(body)
path: pyccel/parser/syntactic.py
user: login: jalalium
body: fixed [39a2edb7](https://github.com/pyccel/pyccel/pull/1977/commits/39a2edb75d601dcee7bb904c2b2d132228f6515b)
created_at: 2025-02-04T12:16:11Z
author_association: MEMBER
diff_hunk: @@ -1243,20 +1272,34 @@ def _visit_GeneratorExp(self, stmt):
             body = Assign(lhs, body)
 
         body.set_current_ast(parent)
-        indices = []
-        generators = list(generators)
-        while len(generators) > 0:
-            indices.append(generators[-1].target)
+
+        for loop, condition in zip(generators, conditions):
+            if condition:
+                loop.insert2body(condition)
+
+        if generators[-1].body.body:
+            generators[-1].body.body[0].blocks[0].body.insert2body(body)
+        else:
             generators[-1].insert2body(body)
-            body = generators.pop()
+
+        while(len(generators)) > 1:
path: pyccel/parser/syntactic.py
user: login: jalalium
body: fixed [39a2edb7](https://github.com/pyccel/pyccel/pull/1977/commits/39a2edb75d601dcee7bb904c2b2d132228f6515b)
created_at: 2025-02-04T12:16:18Z
author_association: MEMBER
diff_hunk: @@ -1243,20 +1272,34 @@ def _visit_GeneratorExp(self, stmt):
             body = Assign(lhs, body)
 
         body.set_current_ast(parent)
-        indices = []
-        generators = list(generators)
-        while len(generators) > 0:
-            indices.append(generators[-1].target)
+
+        for loop, condition in zip(generators, conditions):
+            if condition:
+                loop.insert2body(condition)
+
+        if generators[-1].body.body:
+            generators[-1].body.body[0].blocks[0].body.insert2body(body)
+        else:
             generators[-1].insert2body(body)
-            body = generators.pop()
+
+        while(len(generators)) > 1:
+            indices.append(generators[-1].target)
+            outter_loop = generators.pop()
+            inserted_into = generators[-1]
+            if inserted_into.body.body:
+                inserted_into.body.body[0].blocks[0].body.insert2body(outter_loop)
+            else:
+                inserted_into.insert2body(outter_loop)
path: pyccel/parser/syntactic.py
user: login: jalalium
body: fixed [625671bb](https://github.com/pyccel/pyccel/pull/1977/commits/625671bbb588d8f9d6dad171c00d459bec67840d)
created_at: 2025-02-04T12:31:00Z
author_association: MEMBER
diff_hunk: @@ -106,6 +107,14 @@ def sympy_to_pyccel(expr, symbol_map):
         else:
             return MathCeil(arg)
 
+    elif isinstance(expr, sp.Min):
+        args = [sympy_to_pyccel(a, symbol_map) for a in expr.args]
+        return PythonMin(*args)
+
+    elif isinstance(expr, sp.Max):
+        args = [sympy_to_pyccel(a, symbol_map) for a in expr.args]
+        return PythonMax(*args)
+
path: pyccel/ast/sympy_helper.py
user: login: pyccel-bot[bot]
body: This code isn't tested. Please can you take a look
created_at: 2025-03-06T13:42:17Z
author_association: NONE
diff_hunk: @@ -217,6 +226,14 @@ def pyccel_to_sympy(expr, symbol_map, used_names):
     elif isinstance(expr, MathCeil):
         return sp.ceiling(pyccel_to_sympy(expr.args[0], symbol_map, used_names))
 
+    elif isinstance(expr, PythonMin):
+        args = [pyccel_to_sympy(ee, symbol_map, used_names) for e in expr.args for ee in e]
+        return sp.Min(*args)
+
+    elif isinstance(expr, PythonMax):
+        args = [pyccel_to_sympy(ee, symbol_map, used_names) for e in expr.args for ee in e]
+        return sp.Max(*args)
+
path: pyccel/ast/sympy_helper.py
user: login: pyccel-bot[bot]
body: This code isn't tested. Please can you take a look
created_at: 2025-03-06T13:42:17Z
author_association: NONE
diff_hunk: @@ -106,6 +107,14 @@ def sympy_to_pyccel(expr, symbol_map):
         else:
             return MathCeil(arg)
 
+    elif isinstance(expr, sp.Min):
+        args = [sympy_to_pyccel(a, symbol_map) for a in expr.args]
+        return PythonMin(*args)
+
+    elif isinstance(expr, sp.Max):
+        args = [sympy_to_pyccel(a, symbol_map) for a in expr.args]
+        return PythonMax(*args)
+
path: pyccel/ast/sympy_helper.py
user: login: jalalium
body: /bot accept
added for completeness
created_at: 2025-03-06T14:11:29Z
author_association: MEMBER
diff_hunk: @@ -217,6 +226,14 @@ def pyccel_to_sympy(expr, symbol_map, used_names):
     elif isinstance(expr, MathCeil):
         return sp.ceiling(pyccel_to_sympy(expr.args[0], symbol_map, used_names))
 
+    elif isinstance(expr, PythonMin):
+        args = [pyccel_to_sympy(ee, symbol_map, used_names) for e in expr.args for ee in e]
+        return sp.Min(*args)
+
+    elif isinstance(expr, PythonMax):
+        args = [pyccel_to_sympy(ee, symbol_map, used_names) for e in expr.args for ee in e]
+        return sp.Max(*args)
+
path: pyccel/ast/sympy_helper.py
user: login: jalalium
body: /bot accept
added for completeness
created_at: 2025-03-06T14:11:36Z
author_association: MEMBER
diff_hunk: @@ -106,6 +107,14 @@ def sympy_to_pyccel(expr, symbol_map):
         else:
             return MathCeil(arg)
 
+    elif isinstance(expr, sp.Min):
+        args = [sympy_to_pyccel(a, symbol_map) for a in expr.args]
+        return PythonMin(*args)
+
+    elif isinstance(expr, sp.Max):
+        args = [sympy_to_pyccel(a, symbol_map) for a in expr.args]
+        return PythonMax(*args)
+
path: pyccel/ast/sympy_helper.py
user: login: EmilyBourne
body: @jalalium any way to add a short test for this or do we just need to tell the bot to ignore it?
created_at: 2025-03-13T15:34:46Z
author_association: MEMBER
diff_hunk: @@ -106,6 +107,14 @@ def sympy_to_pyccel(expr, symbol_map):
         else:
             return MathCeil(arg)
 
+    elif isinstance(expr, sp.Min):
+        args = [sympy_to_pyccel(a, symbol_map) for a in expr.args]
+        return PythonMin(*args)
+
+    elif isinstance(expr, sp.Max):
+        args = [sympy_to_pyccel(a, symbol_map) for a in expr.args]
+        return PythonMax(*args)
+
path: pyccel/ast/sympy_helper.py
user: login: EmilyBourne
body: Never mind you mentioned this above:
https://github.com/pyccel/pyccel/pull/1977#discussion_r1869791247
created_at: 2025-03-13T15:35:49Z
author_association: MEMBER
diff_hunk: @@ -217,6 +226,14 @@ def pyccel_to_sympy(expr, symbol_map, used_names):
     elif isinstance(expr, MathCeil):
         return sp.ceiling(pyccel_to_sympy(expr.args[0], symbol_map, used_names))
 
+    elif isinstance(expr, PythonMin):
+        args = [pyccel_to_sympy(ee, symbol_map, used_names) for e in expr.args for ee in e]
+        return sp.Min(*args)
+
+    elif isinstance(expr, PythonMax):
+        args = [pyccel_to_sympy(ee, symbol_map, used_names) for e in expr.args for ee in e]
+        return sp.Max(*args)
+
path: pyccel/ast/sympy_helper.py
user: login: EmilyBourne
body: /bot accept
Included for completeness
created_at: 2025-03-13T15:35:53Z
author_association: MEMBER
diff_hunk: @@ -106,6 +107,14 @@ def sympy_to_pyccel(expr, symbol_map):
         else:
             return MathCeil(arg)
 
+    elif isinstance(expr, sp.Min):
+        args = [sympy_to_pyccel(a, symbol_map) for a in expr.args]
+        return PythonMin(*args)
+
+    elif isinstance(expr, sp.Max):
+        args = [sympy_to_pyccel(a, symbol_map) for a in expr.args]
+        return PythonMax(*args)
+
path: pyccel/ast/sympy_helper.py
user: login: EmilyBourne
body: /bot accept
Included for completeness
created_at: 2025-03-13T15:36:02Z
author_association: MEMBER
diff_hunk: @@ -56,9 +56,16 @@ class FunctionalFor(TypedAstNode):
     target_type : PyccelSymbol, optional
         The type of the result of the functional for. This is useful at
         the syntactic stage to pass along the final type of the lhs (list/set/array/etc).
+    conditions : list[If|None], optional
+        A list of filter conditions corresponding to each for-loop in the comprehension.
+        Each element of this list is either an `If` instance that describes the filtering
+        condition for that loop, or `None` if no condition is applied in that loop.
path: pyccel/ast/functionalexpr.py
user: login: EmilyBourne
body: Does this fix #1410 ? It should be mentioned in the CHANGELOG
created_at: 2025-03-13T16:13:22Z
author_association: MEMBER
diff_hunk: @@ -56,9 +56,16 @@ class FunctionalFor(TypedAstNode):
     target_type : PyccelSymbol, optional
         The type of the result of the functional for. This is useful at
         the syntactic stage to pass along the final type of the lhs (list/set/array/etc).
+    conditions : list[If|None], optional
+        A list of filter conditions corresponding to each for-loop in the comprehension.
+        Each element of this list is either an `If` instance that describes the filtering
+        condition for that loop, or `None` if no condition is applied in that loop.
+    operations : dict
+        A dictionary mapping each type of comprehension (e.g. list, array, etc.)
+        to the operation used for populating it.
path: pyccel/ast/functionalexpr.py
user: login: EmilyBourne
body: ```suggestion
    operations : dict, optional
        A dictionary mapping each type of comprehension (e.g. list, array, etc.)
        to the operation used for populating it.
```
Is this always optional or does it depend on the stage?
created_at: 2025-03-13T16:14:49Z
author_association: MEMBER
diff_hunk: @@ -2311,7 +2311,7 @@ def _print_Allocate(self, expr):
             elif expr.alloc_type == 'reserve':
                 var_code = self._print(expr.variable)
                 size_code = self._print(expr.shape[0])
-                return f'call {var_code} % reserve({size_code})\n'
+                return f'call {var_code} % clear()\n' + f'call {var_code} % reserve({size_code})\n'
path: pyccel/codegen/printing/fcode.py
user: login: EmilyBourne
body: The `+` seems unnecessary but I guess you introduced it for readability. What about:
```suggestion
                return (f'call {var_code} % clear()\n'
                        f'call {var_code} % reserve({size_code})\n')
```
?
created_at: 2025-03-13T16:16:53Z
author_association: MEMBER
diff_hunk: @@ -660,15 +666,22 @@ def _print_For(self, expr):
     def _print_FunctionalFor(self, expr):
         body, iterators = self._find_functional_expr_and_iterables(expr)
         lhs = self._print(expr.lhs)
-        body = self._print(body.rhs)
-        for_loops = ' '.join(['for {} in {}'.format(self._print(idx), self._print(iters))
-                        for idx, iters in zip(expr.indices, iterators)])
+        condition = ''
+        if isinstance(body, Assign):
+            body = self._print(body.rhs)
+        else:
+            assert isinstance(body, ListAppend)
+            body = self._print(body.args[0])
+
+        for_loops = ' '.join([f'for {self._print(idx)} in {self._print(iters)}{" if " + self._print(condition.blocks[0].condition) if condition else ""}'
+                             for idx, iters, condition in zip(expr.indices, iterators, expr.conditions)])
path: pyccel/codegen/printing/pycode.py
user: login: EmilyBourne
body: ```suggestion
        for_loops = ' '.join(f'for {self._print(idx)} in {self._print(iters)}{" if " + self._print(condition.blocks[0].condition) if condition else ""}'
                             for idx, iters, condition in zip(expr.indices, iterators, expr.conditions))
```
created_at: 2025-03-13T16:19:42Z
author_association: MEMBER
diff_hunk: @@ -660,15 +666,22 @@ def _print_For(self, expr):
     def _print_FunctionalFor(self, expr):
         body, iterators = self._find_functional_expr_and_iterables(expr)
         lhs = self._print(expr.lhs)
-        body = self._print(body.rhs)
-        for_loops = ' '.join(['for {} in {}'.format(self._print(idx), self._print(iters))
-                        for idx, iters in zip(expr.indices, iterators)])
+        condition = ''
+        if isinstance(body, Assign):
+            body = self._print(body.rhs)
+        else:
+            assert isinstance(body, ListAppend)
+            body = self._print(body.args[0])
+
+        for_loops = ' '.join([f'for {self._print(idx)} in {self._print(iters)}{" if " + self._print(condition.blocks[0].condition) if condition else ""}'
+                             for idx, iters, condition in zip(expr.indices, iterators, expr.conditions)])
 
-        name = self._aliases.get(type(expr),'array')
-        if name == 'array':
+        name = expr.target_type
+        if 'array' in str(name):
path: pyccel/codegen/printing/pycode.py
user: login: EmilyBourne
body: Why:
`if 'array' in str(name):`
not:
`if 'array' == str(name):`
?

What additional case are you catching here?
created_at: 2025-03-13T16:22:27Z
author_association: MEMBER
diff_hunk: @@ -684,8 +697,8 @@ def _print_GeneratorComprehension(self, expr):
                     rhs = type(body.rhs)(*args)
 
         body = self._print(rhs)
-        for_loops = ' '.join(['for {} in {}'.format(self._print(idx), self._print(iters))
-                        for idx, iters in zip(expr.indices, iterators)])
+        for_loops = ' '.join([f'for {self._print(idx)} in {self._print(iters)}{" if " + self._print(condition.blocks[0].condition) if condition else ""}'
+                             for idx, iters, condition in zip(expr.indices, iterators, expr.conditions)])
path: pyccel/codegen/printing/pycode.py
user: login: EmilyBourne
body: ```suggestion
        for_loops = ' '.join(f'for {self._print(idx)} in {self._print(iters)}{" if " + self._print(condition.blocks[0].condition) if condition else ""}'
                             for idx, iters, condition in zip(expr.indices, iterators, expr.conditions))
```
created_at: 2025-03-13T16:22:44Z
author_association: MEMBER
diff_hunk: @@ -1966,9 +1969,13 @@ def _assign_GeneratorComprehension(self, lhs_name, expr):
         elif isinstance(expr, FunctionalMin):
             val = math_constants['inf']
             d_var = self._infer_type(result)
+            if d_var['class_type'] is PythonNativeInt():
+                val = LiteralInteger(2**63-1)
path: pyccel/parser/semantic.py
user: login: EmilyBourne
body: Did you create an issue about these values?
created_at: 2025-03-13T16:24:07Z
author_association: MEMBER
diff_hunk: @@ -3970,90 +3977,140 @@ def _visit_For(self, expr):
             for_expr.end_annotation = expr.end_annotation
         return for_expr
 
-
     def _visit_FunctionalFor(self, expr):
-        old_index   = expr.index
-        new_index   = self.scope.get_new_name()
-        # Identity comparison is required to only find duplicates of the index caused
-        # by the construction of FunctionalFor.
-        expr.substitute(old_index, new_index, is_equivalent = lambda x,y: x is y)
+        """
+        Visit and transform a FunctionalFor AST node into an equivalent code block.
 
-        target  = expr.expr
-        index   = new_index
-        indices = [self.scope.get_expected_name(i) for i in expr.indices]
-        dims    = []
-        body    = expr.loops[1]
+        This method processes a `FunctionalFor` expression and transforms the loop structure
+        into a corresponding code block.
 
-        idx_subs = {}
-        #scope = self.create_new_loop_scope()
+        Parameters
+        ----------
+        expr : pyccel.ast.functionalexpr.FunctionalFor
+            The FunctionalFor AST node.
 
-        # The symbols created to represent unknown valued objects are temporary
+        Returns
+        -------
+        pyccel.ast.basic.CodeBlock
+            A code block containing the equivalent loops and necessary variable allocations for the given `FunctionalFor` expression.
+        """
+        target  = expr.expr
+        indices = []
+        dims = []
+        idx_subs = {}
         tmp_used_names = self.scope.all_used_symbols.copy()
         i = 0
-        while isinstance(body, For):
+        loops = list(expr.loops)
+
+        # Inner function to handle PythonNativeInt variables
+        def handle_int_loop_variable(var_name, var_scope):
+            indices.append(var_name)
+            var = self._create_variable(var_name, PythonNativeInt(), None, {},insertion_scope=var_scope)
path: pyccel/parser/semantic.py
user: login: EmilyBourne
body: ```suggestion
            var = self._create_variable(var_name, PythonNativeInt(), None, {}, insertion_scope=var_scope)
```
created_at: 2025-03-13T16:24:40Z
author_association: MEMBER
diff_hunk: @@ -3970,90 +3977,140 @@ def _visit_For(self, expr):
             for_expr.end_annotation = expr.end_annotation
         return for_expr
 
-
     def _visit_FunctionalFor(self, expr):
-        old_index   = expr.index
-        new_index   = self.scope.get_new_name()
-        # Identity comparison is required to only find duplicates of the index caused
-        # by the construction of FunctionalFor.
-        expr.substitute(old_index, new_index, is_equivalent = lambda x,y: x is y)
+        """
+        Visit and transform a FunctionalFor AST node into an equivalent code block.
 
-        target  = expr.expr
-        index   = new_index
-        indices = [self.scope.get_expected_name(i) for i in expr.indices]
-        dims    = []
-        body    = expr.loops[1]
+        This method processes a `FunctionalFor` expression and transforms the loop structure
+        into a corresponding code block.
 
-        idx_subs = {}
-        #scope = self.create_new_loop_scope()
+        Parameters
+        ----------
+        expr : pyccel.ast.functionalexpr.FunctionalFor
+            The FunctionalFor AST node.
 
-        # The symbols created to represent unknown valued objects are temporary
+        Returns
+        -------
+        pyccel.ast.basic.CodeBlock
+            A code block containing the equivalent loops and necessary variable allocations for the given `FunctionalFor` expression.
+        """
+        target  = expr.expr
+        indices = []
+        dims = []
+        idx_subs = {}
         tmp_used_names = self.scope.all_used_symbols.copy()
         i = 0
-        while isinstance(body, For):
+        loops = list(expr.loops)
+
+        # Inner function to handle PythonNativeInt variables
+        def handle_int_loop_variable(var_name, var_scope):
+            indices.append(var_name)
+            var = self._create_variable(var_name, PythonNativeInt(), None, {},insertion_scope=var_scope)
+            dvar = self._infer_type(var)
+            variables.append((var, dvar))
path: pyccel/parser/semantic.py
user: login: EmilyBourne
body: I am not a fan of filling `variables` as a global variable. Especially when the definition of `variables` is so much later in the code. It makes it hard to check/be sure what it is
created_at: 2025-03-13T16:27:06Z
author_association: MEMBER
diff_hunk: @@ -3970,90 +3977,140 @@ def _visit_For(self, expr):
             for_expr.end_annotation = expr.end_annotation
         return for_expr
 
-
     def _visit_FunctionalFor(self, expr):
-        old_index   = expr.index
-        new_index   = self.scope.get_new_name()
-        # Identity comparison is required to only find duplicates of the index caused
-        # by the construction of FunctionalFor.
-        expr.substitute(old_index, new_index, is_equivalent = lambda x,y: x is y)
+        """
+        Visit and transform a FunctionalFor AST node into an equivalent code block.
 
-        target  = expr.expr
-        index   = new_index
-        indices = [self.scope.get_expected_name(i) for i in expr.indices]
-        dims    = []
-        body    = expr.loops[1]
+        This method processes a `FunctionalFor` expression and transforms the loop structure
+        into a corresponding code block.
 
-        idx_subs = {}
-        #scope = self.create_new_loop_scope()
+        Parameters
+        ----------
+        expr : pyccel.ast.functionalexpr.FunctionalFor
+            The FunctionalFor AST node.
 
-        # The symbols created to represent unknown valued objects are temporary
+        Returns
+        -------
+        pyccel.ast.basic.CodeBlock
+            A code block containing the equivalent loops and necessary variable allocations for the given `FunctionalFor` expression.
+        """
+        target  = expr.expr
+        indices = []
+        dims = []
+        idx_subs = {}
         tmp_used_names = self.scope.all_used_symbols.copy()
         i = 0
-        while isinstance(body, For):
+        loops = list(expr.loops)
+
+        # Inner function to handle PythonNativeInt variables
+        def handle_int_loop_variable(var_name, var_scope):
+            indices.append(var_name)
+            var = self._create_variable(var_name, PythonNativeInt(), None, {},insertion_scope=var_scope)
+            dvar = self._infer_type(var)
+            variables.append((var, dvar))
+
+        # Inner function to handle iterable variables
+        def handle_iterable_variable(var_name, element, var_scope):
+            indices.append(var_name)
+            dvar = self._infer_type(element)
+            class_type = dvar.pop('class_type')
+            if class_type.rank > 0:
+                class_type = class_type.switch_rank(class_type.rank - 1)
+                dvar['shape'] = dvar['shape'][1:]
+            if class_type.rank == 0:
+                dvar['shape'] = None
+                dvar['memory_handling'] = 'stack'
+            var = self._create_variable(var_name, class_type, None, dvar, insertion_scope=var_scope)
+            dvar['class_type'] = class_type
+            variables.append((var, dvar))
+
+        def create_target_operations(target):
+            # this can be used to add opertations for set/dict comprehension
+            assert target in expr.operations
+            return expr.operations[target]
+
+        for loop, condition in zip(loops, expr.conditions):
+            if condition:
+                loop.insert2body(condition)
+
+        while len(loops) > 1:
+            outter_loop = loops.pop()
+            inserted_into = loops[-1]
+            if inserted_into.body.body:
+                inserted_into.body.body[0].blocks[0].body.insert2body(outter_loop)
+            else:
+                inserted_into.insert2body(outter_loop)
+
+        body = loops[0]
 
-            stop  = None
+        while isinstance(body, (For, If)):
+
+            if isinstance(body, If):
+                body = None if not body.blocks[0].body.body else body.blocks[0].body.body[0]
+                continue
+
+            stop = None
             start = LiteralInteger(0)
-            step  = LiteralInteger(1)
-            var   = indices[i]
-            i += 1
-            a     = self._visit(body.iterable)
+            step = LiteralInteger(1)
+            variables = []
+            a = self._get_iterable(self._visit(body.iterable))
             if isinstance(a, PythonRange):
-                var   = self._create_variable(var, PythonNativeInt(), start, {})
-                dvar  = self._infer_type(var)
-                stop  = a.stop
+                var_name = self.scope.get_expected_name(expr.indices[i])
+                handle_int_loop_variable(var_name, body.scope)
                 start = a.start
+                stop  = a.stop
                 step  = a.step
 
-            elif isinstance(a, (PythonZip, PythonEnumerate)):
-                dvar  = self._infer_type(a.element)
-                class_type = dvar.pop('class_type')
-                if class_type.rank > 0:
-                    class_type = class_type.switch_rank(class_type.rank-1)
-                    dvar['shape'] = (dvar['shape'])[1:]
-                if class_type.rank == 0:
-                    dvar['shape'] = None
-                    dvar['memory_handling'] = 'stack'
-                var  = Variable(class_type, var, **dvar)
+            elif isinstance(a, PythonEnumerate):
+                var_name1 = self.scope.get_expected_name(expr.indices[i][0])
+                var_name2 = self.scope.get_expected_name(expr.indices[i][1])
+                handle_int_loop_variable(var_name1, body.scope)
+                handle_iterable_variable(var_name2, a.element, body.scope)
                 stop = a.element.shape[0]
 
-            elif isinstance(a, Variable):
-                dvar  = self._infer_type(a)
-                class_type = dvar.pop('class_type')
-                if class_type.rank > 0:
-                    class_type = class_type.switch_rank(class_type.rank-1)
-                    dvar['shape'] = (dvar['shape'])[1:]
-                if class_type.rank == 0:
-                    dvar['shape'] = None
-                    dvar['memory_handling'] = 'stack'
-                var  = Variable(class_type, var, **dvar)
-                stop = a.shape[0]
+            elif isinstance(a, PythonZip):
+                for idx, arg in enumerate(a.args):
+                    var = self.scope.get_expected_name(expr.indices[i][idx])
+                    handle_iterable_variable(var, arg, body.scope)
+                stop = a.get_range().stop
+
+            elif isinstance(a, VariableIterator):
+                var = self.scope.get_expected_name(expr.indices[i])
+                handle_iterable_variable(var, a.variable, body.scope)
+                stop = a.variable.shape[0]
 
             else:
                 errors.report(PYCCEL_RESTRICTION_TODO,
                               bounding_box=(self.current_ast_node.lineno, self.current_ast_node.col_offset),
                               severity='fatal')
-            existing_var = self.scope.find(var.name, 'variables')
-            if existing_var:
-                if self._infer_type(existing_var) != dvar:
-                    errors.report(f"Variable {var} already exists with different type",
-                            symbol = expr, severity='error')
-            else:
-                self.scope.insert_variable(var)
-            step.invalidate_node()
+
+            for var, dvar in variables:
+                existing_var = self.scope.find(var.name, 'variables')
+                if var.name == expr.lhs:
+                    errors.report(f"Variable {var} has the same name as the left hand side",
+                            symbol = expr, severity='fatal')
+                if existing_var or var.name ==  expr.lhs:
+                    if self._infer_type(existing_var)['class_type'] != dvar['class_type']:
path: pyccel/parser/semantic.py
user: login: EmilyBourne
body: Why do you need to save `dvar`? Can you not simply do:
```suggestion
            for var, _ in variables:
                existing_var = self.scope.find(var.name, 'variables')
                if var.name == expr.lhs:
                    errors.report(f"Variable {var} has the same name as the left hand side",
                            symbol = expr, severity='fatal')
                if existing_var or var.name ==  expr.lhs:
                    if self._infer_type(existing_var)['class_type'] != var.class_type:
```
?
created_at: 2025-03-13T16:29:27Z
author_association: MEMBER
diff_hunk: @@ -3970,90 +3977,140 @@ def _visit_For(self, expr):
             for_expr.end_annotation = expr.end_annotation
         return for_expr
 
-
     def _visit_FunctionalFor(self, expr):
-        old_index   = expr.index
-        new_index   = self.scope.get_new_name()
-        # Identity comparison is required to only find duplicates of the index caused
-        # by the construction of FunctionalFor.
-        expr.substitute(old_index, new_index, is_equivalent = lambda x,y: x is y)
+        """
+        Visit and transform a FunctionalFor AST node into an equivalent code block.
 
-        target  = expr.expr
-        index   = new_index
-        indices = [self.scope.get_expected_name(i) for i in expr.indices]
-        dims    = []
-        body    = expr.loops[1]
+        This method processes a `FunctionalFor` expression and transforms the loop structure
+        into a corresponding code block.
 
-        idx_subs = {}
-        #scope = self.create_new_loop_scope()
+        Parameters
+        ----------
+        expr : pyccel.ast.functionalexpr.FunctionalFor
+            The FunctionalFor AST node.
 
-        # The symbols created to represent unknown valued objects are temporary
+        Returns
+        -------
+        pyccel.ast.basic.CodeBlock
+            A code block containing the equivalent loops and necessary variable allocations for the given `FunctionalFor` expression.
+        """
+        target  = expr.expr
+        indices = []
+        dims = []
+        idx_subs = {}
         tmp_used_names = self.scope.all_used_symbols.copy()
         i = 0
-        while isinstance(body, For):
+        loops = list(expr.loops)
+
+        # Inner function to handle PythonNativeInt variables
+        def handle_int_loop_variable(var_name, var_scope):
+            indices.append(var_name)
+            var = self._create_variable(var_name, PythonNativeInt(), None, {},insertion_scope=var_scope)
+            dvar = self._infer_type(var)
+            variables.append((var, dvar))
+
+        # Inner function to handle iterable variables
+        def handle_iterable_variable(var_name, element, var_scope):
+            indices.append(var_name)
+            dvar = self._infer_type(element)
+            class_type = dvar.pop('class_type')
+            if class_type.rank > 0:
+                class_type = class_type.switch_rank(class_type.rank - 1)
+                dvar['shape'] = dvar['shape'][1:]
+            if class_type.rank == 0:
+                dvar['shape'] = None
+                dvar['memory_handling'] = 'stack'
+            var = self._create_variable(var_name, class_type, None, dvar, insertion_scope=var_scope)
+            dvar['class_type'] = class_type
+            variables.append((var, dvar))
+
+        def create_target_operations(target):
+            # this can be used to add opertations for set/dict comprehension
+            assert target in expr.operations
+            return expr.operations[target]
+
+        for loop, condition in zip(loops, expr.conditions):
+            if condition:
+                loop.insert2body(condition)
+
+        while len(loops) > 1:
+            outter_loop = loops.pop()
+            inserted_into = loops[-1]
+            if inserted_into.body.body:
+                inserted_into.body.body[0].blocks[0].body.insert2body(outter_loop)
+            else:
+                inserted_into.insert2body(outter_loop)
+
+        body = loops[0]
 
-            stop  = None
+        while isinstance(body, (For, If)):
+
+            if isinstance(body, If):
+                body = None if not body.blocks[0].body.body else body.blocks[0].body.body[0]
+                continue
+
+            stop = None
             start = LiteralInteger(0)
-            step  = LiteralInteger(1)
-            var   = indices[i]
-            i += 1
-            a     = self._visit(body.iterable)
+            step = LiteralInteger(1)
+            variables = []
+            a = self._get_iterable(self._visit(body.iterable))
             if isinstance(a, PythonRange):
-                var   = self._create_variable(var, PythonNativeInt(), start, {})
-                dvar  = self._infer_type(var)
-                stop  = a.stop
+                var_name = self.scope.get_expected_name(expr.indices[i])
+                handle_int_loop_variable(var_name, body.scope)
                 start = a.start
+                stop  = a.stop
                 step  = a.step
 
-            elif isinstance(a, (PythonZip, PythonEnumerate)):
-                dvar  = self._infer_type(a.element)
-                class_type = dvar.pop('class_type')
-                if class_type.rank > 0:
-                    class_type = class_type.switch_rank(class_type.rank-1)
-                    dvar['shape'] = (dvar['shape'])[1:]
-                if class_type.rank == 0:
-                    dvar['shape'] = None
-                    dvar['memory_handling'] = 'stack'
-                var  = Variable(class_type, var, **dvar)
+            elif isinstance(a, PythonEnumerate):
+                var_name1 = self.scope.get_expected_name(expr.indices[i][0])
+                var_name2 = self.scope.get_expected_name(expr.indices[i][1])
+                handle_int_loop_variable(var_name1, body.scope)
+                handle_iterable_variable(var_name2, a.element, body.scope)
                 stop = a.element.shape[0]
 
-            elif isinstance(a, Variable):
-                dvar  = self._infer_type(a)
-                class_type = dvar.pop('class_type')
-                if class_type.rank > 0:
-                    class_type = class_type.switch_rank(class_type.rank-1)
-                    dvar['shape'] = (dvar['shape'])[1:]
-                if class_type.rank == 0:
-                    dvar['shape'] = None
-                    dvar['memory_handling'] = 'stack'
-                var  = Variable(class_type, var, **dvar)
-                stop = a.shape[0]
+            elif isinstance(a, PythonZip):
+                for idx, arg in enumerate(a.args):
+                    var = self.scope.get_expected_name(expr.indices[i][idx])
+                    handle_iterable_variable(var, arg, body.scope)
+                stop = a.get_range().stop
+
+            elif isinstance(a, VariableIterator):
+                var = self.scope.get_expected_name(expr.indices[i])
+                handle_iterable_variable(var, a.variable, body.scope)
+                stop = a.variable.shape[0]
 
             else:
                 errors.report(PYCCEL_RESTRICTION_TODO,
                               bounding_box=(self.current_ast_node.lineno, self.current_ast_node.col_offset),
                               severity='fatal')
-            existing_var = self.scope.find(var.name, 'variables')
-            if existing_var:
-                if self._infer_type(existing_var) != dvar:
-                    errors.report(f"Variable {var} already exists with different type",
-                            symbol = expr, severity='error')
-            else:
-                self.scope.insert_variable(var)
-            step.invalidate_node()
+
+            for var, dvar in variables:
+                existing_var = self.scope.find(var.name, 'variables')
+                if var.name == expr.lhs:
+                    errors.report(f"Variable {var} has the same name as the left hand side",
+                            symbol = expr, severity='fatal')
+                if existing_var or var.name ==  expr.lhs:
+                    if self._infer_type(existing_var)['class_type'] != dvar['class_type']:
+                        return errors.report(f"Variable {var} already exists with different type",
+                                symbol = expr, severity='error')
path: pyccel/parser/semantic.py
user: login: EmilyBourne
body: Have you checked that you can raise this error? Given that you use `_create_variable` to create the variables, I would expect errors to be raised before you reach here.
created_at: 2025-03-13T16:30:10Z
author_association: MEMBER
diff_hunk: @@ -3970,90 +3977,140 @@ def _visit_For(self, expr):
             for_expr.end_annotation = expr.end_annotation
         return for_expr
 
-
     def _visit_FunctionalFor(self, expr):
-        old_index   = expr.index
-        new_index   = self.scope.get_new_name()
-        # Identity comparison is required to only find duplicates of the index caused
-        # by the construction of FunctionalFor.
-        expr.substitute(old_index, new_index, is_equivalent = lambda x,y: x is y)
+        """
+        Visit and transform a FunctionalFor AST node into an equivalent code block.
 
-        target  = expr.expr
-        index   = new_index
-        indices = [self.scope.get_expected_name(i) for i in expr.indices]
-        dims    = []
-        body    = expr.loops[1]
+        This method processes a `FunctionalFor` expression and transforms the loop structure
+        into a corresponding code block.
 
-        idx_subs = {}
-        #scope = self.create_new_loop_scope()
+        Parameters
+        ----------
+        expr : pyccel.ast.functionalexpr.FunctionalFor
+            The FunctionalFor AST node.
 
-        # The symbols created to represent unknown valued objects are temporary
+        Returns
+        -------
+        pyccel.ast.basic.CodeBlock
+            A code block containing the equivalent loops and necessary variable allocations for the given `FunctionalFor` expression.
+        """
+        target  = expr.expr
+        indices = []
+        dims = []
+        idx_subs = {}
         tmp_used_names = self.scope.all_used_symbols.copy()
         i = 0
-        while isinstance(body, For):
+        loops = list(expr.loops)
+
+        # Inner function to handle PythonNativeInt variables
+        def handle_int_loop_variable(var_name, var_scope):
+            indices.append(var_name)
+            var = self._create_variable(var_name, PythonNativeInt(), None, {},insertion_scope=var_scope)
+            dvar = self._infer_type(var)
+            variables.append((var, dvar))
+
+        # Inner function to handle iterable variables
+        def handle_iterable_variable(var_name, element, var_scope):
+            indices.append(var_name)
+            dvar = self._infer_type(element)
+            class_type = dvar.pop('class_type')
+            if class_type.rank > 0:
+                class_type = class_type.switch_rank(class_type.rank - 1)
+                dvar['shape'] = dvar['shape'][1:]
+            if class_type.rank == 0:
+                dvar['shape'] = None
+                dvar['memory_handling'] = 'stack'
+            var = self._create_variable(var_name, class_type, None, dvar, insertion_scope=var_scope)
+            dvar['class_type'] = class_type
+            variables.append((var, dvar))
+
+        def create_target_operations(target):
+            # this can be used to add opertations for set/dict comprehension
+            assert target in expr.operations
+            return expr.operations[target]
path: pyccel/parser/semantic.py
user: login: EmilyBourne
body: Why is this function needed?
I think `expr.operations[target]` would achieve the same result as Python asserts the presence anyway
created_at: 2025-03-13T16:31:08Z
author_association: MEMBER
diff_hunk: @@ -3970,90 +3977,140 @@ def _visit_For(self, expr):
             for_expr.end_annotation = expr.end_annotation
         return for_expr
 
-
     def _visit_FunctionalFor(self, expr):
-        old_index   = expr.index
-        new_index   = self.scope.get_new_name()
-        # Identity comparison is required to only find duplicates of the index caused
-        # by the construction of FunctionalFor.
-        expr.substitute(old_index, new_index, is_equivalent = lambda x,y: x is y)
+        """
+        Visit and transform a FunctionalFor AST node into an equivalent code block.
 
-        target  = expr.expr
-        index   = new_index
-        indices = [self.scope.get_expected_name(i) for i in expr.indices]
-        dims    = []
-        body    = expr.loops[1]
+        This method processes a `FunctionalFor` expression and transforms the loop structure
+        into a corresponding code block.
 
-        idx_subs = {}
-        #scope = self.create_new_loop_scope()
+        Parameters
+        ----------
+        expr : pyccel.ast.functionalexpr.FunctionalFor
+            The FunctionalFor AST node.
 
-        # The symbols created to represent unknown valued objects are temporary
+        Returns
+        -------
+        pyccel.ast.basic.CodeBlock
+            A code block containing the equivalent loops and necessary variable allocations for the given `FunctionalFor` expression.
+        """
+        target  = expr.expr
+        indices = []
+        dims = []
+        idx_subs = {}
         tmp_used_names = self.scope.all_used_symbols.copy()
         i = 0
-        while isinstance(body, For):
+        loops = list(expr.loops)
+
+        # Inner function to handle PythonNativeInt variables
+        def handle_int_loop_variable(var_name, var_scope):
+            indices.append(var_name)
+            var = self._create_variable(var_name, PythonNativeInt(), None, {},insertion_scope=var_scope)
+            dvar = self._infer_type(var)
+            variables.append((var, dvar))
+
+        # Inner function to handle iterable variables
+        def handle_iterable_variable(var_name, element, var_scope):
+            indices.append(var_name)
+            dvar = self._infer_type(element)
+            class_type = dvar.pop('class_type')
+            if class_type.rank > 0:
+                class_type = class_type.switch_rank(class_type.rank - 1)
+                dvar['shape'] = dvar['shape'][1:]
+            if class_type.rank == 0:
+                dvar['shape'] = None
+                dvar['memory_handling'] = 'stack'
+            var = self._create_variable(var_name, class_type, None, dvar, insertion_scope=var_scope)
+            dvar['class_type'] = class_type
+            variables.append((var, dvar))
+
+        def create_target_operations(target):
+            # this can be used to add opertations for set/dict comprehension
+            assert target in expr.operations
+            return expr.operations[target]
+
+        for loop, condition in zip(loops, expr.conditions):
+            if condition:
+                loop.insert2body(condition)
+
+        while len(loops) > 1:
+            outter_loop = loops.pop()
path: pyccel/parser/semantic.py
user: login: EmilyBourne
body: Typo `outter`->`outer`
created_at: 2025-03-13T16:31:35Z
author_association: MEMBER
diff_hunk: @@ -3970,90 +3977,140 @@ def _visit_For(self, expr):
             for_expr.end_annotation = expr.end_annotation
         return for_expr
 
-
     def _visit_FunctionalFor(self, expr):
-        old_index   = expr.index
-        new_index   = self.scope.get_new_name()
-        # Identity comparison is required to only find duplicates of the index caused
-        # by the construction of FunctionalFor.
-        expr.substitute(old_index, new_index, is_equivalent = lambda x,y: x is y)
+        """
+        Visit and transform a FunctionalFor AST node into an equivalent code block.
 
-        target  = expr.expr
-        index   = new_index
-        indices = [self.scope.get_expected_name(i) for i in expr.indices]
-        dims    = []
-        body    = expr.loops[1]
+        This method processes a `FunctionalFor` expression and transforms the loop structure
+        into a corresponding code block.
 
-        idx_subs = {}
-        #scope = self.create_new_loop_scope()
+        Parameters
+        ----------
+        expr : pyccel.ast.functionalexpr.FunctionalFor
+            The FunctionalFor AST node.
 
-        # The symbols created to represent unknown valued objects are temporary
+        Returns
+        -------
+        pyccel.ast.basic.CodeBlock
+            A code block containing the equivalent loops and necessary variable allocations for the given `FunctionalFor` expression.
+        """
+        target  = expr.expr
+        indices = []
+        dims = []
+        idx_subs = {}
         tmp_used_names = self.scope.all_used_symbols.copy()
         i = 0
-        while isinstance(body, For):
+        loops = list(expr.loops)
+
+        # Inner function to handle PythonNativeInt variables
+        def handle_int_loop_variable(var_name, var_scope):
+            indices.append(var_name)
+            var = self._create_variable(var_name, PythonNativeInt(), None, {},insertion_scope=var_scope)
+            dvar = self._infer_type(var)
+            variables.append((var, dvar))
+
+        # Inner function to handle iterable variables
+        def handle_iterable_variable(var_name, element, var_scope):
+            indices.append(var_name)
+            dvar = self._infer_type(element)
+            class_type = dvar.pop('class_type')
+            if class_type.rank > 0:
+                class_type = class_type.switch_rank(class_type.rank - 1)
+                dvar['shape'] = dvar['shape'][1:]
+            if class_type.rank == 0:
+                dvar['shape'] = None
+                dvar['memory_handling'] = 'stack'
+            var = self._create_variable(var_name, class_type, None, dvar, insertion_scope=var_scope)
+            dvar['class_type'] = class_type
+            variables.append((var, dvar))
path: pyccel/parser/semantic.py
user: login: EmilyBourne
body: Is this code specific to functionals or could/should it be shared with `_visit_For`?
created_at: 2025-03-13T16:33:10Z
author_association: MEMBER
diff_hunk: @@ -3970,90 +3977,140 @@ def _visit_For(self, expr):
             for_expr.end_annotation = expr.end_annotation
         return for_expr
 
-
     def _visit_FunctionalFor(self, expr):
-        old_index   = expr.index
-        new_index   = self.scope.get_new_name()
-        # Identity comparison is required to only find duplicates of the index caused
-        # by the construction of FunctionalFor.
-        expr.substitute(old_index, new_index, is_equivalent = lambda x,y: x is y)
+        """
+        Visit and transform a FunctionalFor AST node into an equivalent code block.
 
-        target  = expr.expr
-        index   = new_index
-        indices = [self.scope.get_expected_name(i) for i in expr.indices]
-        dims    = []
-        body    = expr.loops[1]
+        This method processes a `FunctionalFor` expression and transforms the loop structure
+        into a corresponding code block.
 
-        idx_subs = {}
-        #scope = self.create_new_loop_scope()
+        Parameters
+        ----------
+        expr : pyccel.ast.functionalexpr.FunctionalFor
+            The FunctionalFor AST node.
 
-        # The symbols created to represent unknown valued objects are temporary
+        Returns
+        -------
+        pyccel.ast.basic.CodeBlock
+            A code block containing the equivalent loops and necessary variable allocations for the given `FunctionalFor` expression.
+        """
+        target  = expr.expr
+        indices = []
+        dims = []
+        idx_subs = {}
         tmp_used_names = self.scope.all_used_symbols.copy()
         i = 0
-        while isinstance(body, For):
+        loops = list(expr.loops)
+
+        # Inner function to handle PythonNativeInt variables
+        def handle_int_loop_variable(var_name, var_scope):
+            indices.append(var_name)
+            var = self._create_variable(var_name, PythonNativeInt(), None, {},insertion_scope=var_scope)
+            dvar = self._infer_type(var)
+            variables.append((var, dvar))
+
+        # Inner function to handle iterable variables
+        def handle_iterable_variable(var_name, element, var_scope):
+            indices.append(var_name)
+            dvar = self._infer_type(element)
+            class_type = dvar.pop('class_type')
+            if class_type.rank > 0:
+                class_type = class_type.switch_rank(class_type.rank - 1)
+                dvar['shape'] = dvar['shape'][1:]
+            if class_type.rank == 0:
+                dvar['shape'] = None
+                dvar['memory_handling'] = 'stack'
+            var = self._create_variable(var_name, class_type, None, dvar, insertion_scope=var_scope)
+            dvar['class_type'] = class_type
+            variables.append((var, dvar))
+
+        def create_target_operations(target):
+            # this can be used to add opertations for set/dict comprehension
+            assert target in expr.operations
+            return expr.operations[target]
+
+        for loop, condition in zip(loops, expr.conditions):
+            if condition:
+                loop.insert2body(condition)
+
+        while len(loops) > 1:
+            outter_loop = loops.pop()
+            inserted_into = loops[-1]
+            if inserted_into.body.body:
+                inserted_into.body.body[0].blocks[0].body.insert2body(outter_loop)
+            else:
+                inserted_into.insert2body(outter_loop)
+
+        body = loops[0]
 
-            stop  = None
+        while isinstance(body, (For, If)):
+
+            if isinstance(body, If):
+                body = None if not body.blocks[0].body.body else body.blocks[0].body.body[0]
+                continue
+
+            stop = None
             start = LiteralInteger(0)
-            step  = LiteralInteger(1)
-            var   = indices[i]
-            i += 1
-            a     = self._visit(body.iterable)
+            step = LiteralInteger(1)
+            variables = []
+            a = self._get_iterable(self._visit(body.iterable))
             if isinstance(a, PythonRange):
-                var   = self._create_variable(var, PythonNativeInt(), start, {})
-                dvar  = self._infer_type(var)
-                stop  = a.stop
+                var_name = self.scope.get_expected_name(expr.indices[i])
+                handle_int_loop_variable(var_name, body.scope)
                 start = a.start
+                stop  = a.stop
                 step  = a.step
 
-            elif isinstance(a, (PythonZip, PythonEnumerate)):
-                dvar  = self._infer_type(a.element)
-                class_type = dvar.pop('class_type')
-                if class_type.rank > 0:
-                    class_type = class_type.switch_rank(class_type.rank-1)
-                    dvar['shape'] = (dvar['shape'])[1:]
-                if class_type.rank == 0:
-                    dvar['shape'] = None
-                    dvar['memory_handling'] = 'stack'
-                var  = Variable(class_type, var, **dvar)
+            elif isinstance(a, PythonEnumerate):
+                var_name1 = self.scope.get_expected_name(expr.indices[i][0])
+                var_name2 = self.scope.get_expected_name(expr.indices[i][1])
+                handle_int_loop_variable(var_name1, body.scope)
+                handle_iterable_variable(var_name2, a.element, body.scope)
                 stop = a.element.shape[0]
 
-            elif isinstance(a, Variable):
-                dvar  = self._infer_type(a)
-                class_type = dvar.pop('class_type')
-                if class_type.rank > 0:
-                    class_type = class_type.switch_rank(class_type.rank-1)
-                    dvar['shape'] = (dvar['shape'])[1:]
-                if class_type.rank == 0:
-                    dvar['shape'] = None
-                    dvar['memory_handling'] = 'stack'
-                var  = Variable(class_type, var, **dvar)
-                stop = a.shape[0]
+            elif isinstance(a, PythonZip):
+                for idx, arg in enumerate(a.args):
+                    var = self.scope.get_expected_name(expr.indices[i][idx])
+                    handle_iterable_variable(var, arg, body.scope)
+                stop = a.get_range().stop
+
+            elif isinstance(a, VariableIterator):
+                var = self.scope.get_expected_name(expr.indices[i])
+                handle_iterable_variable(var, a.variable, body.scope)
+                stop = a.variable.shape[0]
 
             else:
                 errors.report(PYCCEL_RESTRICTION_TODO,
                               bounding_box=(self.current_ast_node.lineno, self.current_ast_node.col_offset),
                               severity='fatal')
-            existing_var = self.scope.find(var.name, 'variables')
-            if existing_var:
-                if self._infer_type(existing_var) != dvar:
-                    errors.report(f"Variable {var} already exists with different type",
-                            symbol = expr, severity='error')
-            else:
-                self.scope.insert_variable(var)
-            step.invalidate_node()
+
+            for var, dvar in variables:
+                existing_var = self.scope.find(var.name, 'variables')
+                if var.name == expr.lhs:
+                    errors.report(f"Variable {var} has the same name as the left hand side",
+                            symbol = expr, severity='fatal')
+                if existing_var or var.name ==  expr.lhs:
+                    if self._infer_type(existing_var)['class_type'] != dvar['class_type']:
+                        return errors.report(f"Variable {var} already exists with different type",
+                                symbol = expr, severity='error')
+                else:
+                    self.scope.insert_variable(var)
+
+            if isinstance(step, LiteralInteger):
+                step.invalidate_node()
             step  = pyccel_to_sympy(step , idx_subs, tmp_used_names)
-            start.invalidate_node()
+            if isinstance(start, LiteralInteger):
+                start.invalidate_node()
path: pyccel/parser/semantic.py
user: login: EmilyBourne
body: Are you sure about this change? It seems to me that if start is a LiteralInteger then the invalidation is useless.
created_at: 2025-03-13T16:34:27Z
author_association: MEMBER
diff_hunk: @@ -3857,31 +3886,37 @@ def _visit_FunctionalFor(self, expr):
         # TODO [YG, 30.10.2020]:
         #  - Check if we should allow the possibility that is_stack_array=True
         # ...
-        lhs_symbol = expr.lhs.base
+        lhs_symbol = expr.lhs
         ne = []
         lhs = self._assign_lhs_variable(lhs_symbol, d_var, rhs=expr, new_expressions=ne)
         lhs_alloc = ne[0]
 
         if isinstance(target, PythonTuple) and not target.is_homogeneous:
-            errors.report(LIST_OF_TUPLES, symbol=expr, severity='error')
+            errors.report(LIST_OF_TUPLES, symbol=expr, severity='fatal')
path: pyccel/parser/semantic.py
user: login: EmilyBourne
body: This has not been reverted
created_at: 2025-03-13T16:35:10Z
author_association: MEMBER
diff_hunk: @@ -4167,31 +4221,64 @@ def _visit_FunctionalFor(self, expr):
         # TODO [YG, 30.10.2020]:
         #  - Check if we should allow the possibility that is_stack_array=True
         # ...
-        lhs_symbol = expr.lhs.base
+        lhs_symbol = expr.lhs
         ne = []
         lhs = self._assign_lhs_variable(lhs_symbol, d_var, rhs=expr, new_expressions=ne)
         lhs_alloc = ne[0]
 
         if isinstance(target, PythonTuple) and not target.is_homogeneous:
-            errors.report(LIST_OF_TUPLES, symbol=expr, severity='error')
+            errors.report(LIST_OF_TUPLES, symbol=expr, severity='fatal')
 
         target.invalidate_node()
+        operations = []
+        assign = None
+        if target_type_name != 'list':
+            old_index   = expr.index
+            new_index   = self.scope.get_new_name()
+            expr.substitute(old_index, new_index)
path: pyccel/parser/semantic.py
user: login: EmilyBourne
body: What is wrong with the old index?
created_at: 2025-03-13T16:37:59Z
author_association: MEMBER
diff_hunk: @@ -4167,31 +4221,64 @@ def _visit_FunctionalFor(self, expr):
         # TODO [YG, 30.10.2020]:
         #  - Check if we should allow the possibility that is_stack_array=True
         # ...
-        lhs_symbol = expr.lhs.base
+        lhs_symbol = expr.lhs
         ne = []
         lhs = self._assign_lhs_variable(lhs_symbol, d_var, rhs=expr, new_expressions=ne)
         lhs_alloc = ne[0]
 
         if isinstance(target, PythonTuple) and not target.is_homogeneous:
-            errors.report(LIST_OF_TUPLES, symbol=expr, severity='error')
+            errors.report(LIST_OF_TUPLES, symbol=expr, severity='fatal')
 
         target.invalidate_node()
+        operations = []
+        assign = None
+        if target_type_name != 'list':
+            old_index   = expr.index
+            new_index   = self.scope.get_new_name()
+            expr.substitute(old_index, new_index)
path: pyccel/parser/semantic.py
user: login: EmilyBourne
body: Why do you need to substitute in the syntactic object?
created_at: 2025-03-13T16:38:18Z
author_association: MEMBER
diff_hunk: @@ -4167,31 +4221,64 @@ def _visit_FunctionalFor(self, expr):
         # TODO [YG, 30.10.2020]:
         #  - Check if we should allow the possibility that is_stack_array=True
         # ...
-        lhs_symbol = expr.lhs.base
+        lhs_symbol = expr.lhs
         ne = []
         lhs = self._assign_lhs_variable(lhs_symbol, d_var, rhs=expr, new_expressions=ne)
         lhs_alloc = ne[0]
 
         if isinstance(target, PythonTuple) and not target.is_homogeneous:
-            errors.report(LIST_OF_TUPLES, symbol=expr, severity='error')
+            errors.report(LIST_OF_TUPLES, symbol=expr, severity='fatal')
 
         target.invalidate_node()
+        operations = []
+        assign = None
+        if target_type_name != 'list':
+            old_index   = expr.index
+            new_index   = self.scope.get_new_name()
+            expr.substitute(old_index, new_index)
+            array_ops = create_target_operations('numpy_array')
+            assign = array_ops[0]
+            assign.substitute(old_index, new_index)
+            assign = self._visit(array_ops[0])
+            array_ops = array_ops[1:]
+            for operation in array_ops:
+                operation.substitute(old_index, new_index)
+                operations.append(operation)
+            index = new_index
+            index = self._visit(index)
+        else:
+            index = None
+            operations.extend(create_target_operations('list'))
path: pyccel/parser/semantic.py
user: login: EmilyBourne
body: Maybe better to add an `elif` and an `else` that will catch new targets to ensure we don't miss anything when adding new things?
created_at: 2025-03-13T16:39:22Z
author_association: MEMBER
diff_hunk: @@ -1148,26 +1148,70 @@ def _visit_For(self, stmt):
     def _visit_comprehension(self, stmt):
 
         scope = self.create_new_loop_scope()
+        condition = None
 
         self._in_lhs_assign = True
         iterator = self._visit(stmt.target)
         self._in_lhs_assign = False
         iterable = self._visit(stmt.iter)
 
-        self.exit_loop_scope()
-
         if stmt.ifs:
-            errors.report("Cannot handle if statements in list comprehensions. List length cannot be calculated.\n" + PYCCEL_RESTRICTION_TODO,
-                    symbol=stmt, severity='error')
+            from functools import reduce
+            cond = reduce(PyccelAnd, [self._visit(if_cond)
+                                      for if_cond in stmt.ifs])
+            condition = If(IfSection(cond, CodeBlock([])))
 
-        expr = For(iterator, iterable, [], scope=scope)
+        self.exit_loop_scope()
+
+        expr = (For(iterator, iterable, [], scope=scope), condition)
         return expr
 
     def _visit_ListComp(self, stmt):
+        """
+        Converts a list comprehension statement into a `FunctionalFor` AST object.
+
+        This method translates the list comprehension into an equivalent `FunctionalFor`
+        
+        Parameters
+        ----------
+        stmt : ast.stmt
+            Object to visit of type X.
+
+        Returns
+        -------
+        pyccel.ast.functionalexpr.FunctionalFor
+            AST object which is the syntactic equivalent of the list comprehension.
+        """
+
+        def create_target_operations():
+            operations = {'list' : [], 'numpy_array' : []}
+            index = PyccelSymbol('_', is_temp=True)
+            args = [index]
+            target = IndexedElement(lhs, *args)
+            target = Assign(target, result)
+            assign1 = Assign(index, LiteralInteger(0))
+            assign1.set_current_ast(stmt)
+            operations['numpy_array'].append(assign1)
+            target.set_current_ast(stmt)
+            operations['numpy_array'].append(target)
+            assign2 = Assign(index, PyccelAdd(index, LiteralInteger(1)))
+            assign2.set_current_ast(stmt)
+            operations['numpy_array'].append(assign2)
+            operations['list'].append(DottedName(lhs, FunctionCall('append', [FunctionCallArgument(result)])))
+
+            return index, operations
+
 
         result = self._visit(stmt.elt)
+        output_type = None
 
-        generators = list(self._visit(stmt.generators))
+        comprehensions = list(self._visit(stmt.generators))
path: pyccel/parser/syntactic.py
user: login: EmilyBourne
body: Question still relevant
created_at: 2025-03-13T16:42:54Z
author_association: MEMBER
diff_hunk: @@ -1148,26 +1148,70 @@ def _visit_For(self, stmt):
     def _visit_comprehension(self, stmt):
 
         scope = self.create_new_loop_scope()
+        condition = None
 
         self._in_lhs_assign = True
         iterator = self._visit(stmt.target)
         self._in_lhs_assign = False
         iterable = self._visit(stmt.iter)
 
-        self.exit_loop_scope()
-
         if stmt.ifs:
-            errors.report("Cannot handle if statements in list comprehensions. List length cannot be calculated.\n" + PYCCEL_RESTRICTION_TODO,
-                    symbol=stmt, severity='error')
+            from functools import reduce
+            cond = reduce(PyccelAnd, [self._visit(if_cond)
+                                      for if_cond in stmt.ifs])
+            condition = If(IfSection(cond, CodeBlock([])))
 
-        expr = For(iterator, iterable, [], scope=scope)
+        self.exit_loop_scope()
+
+        expr = (For(iterator, iterable, [], scope=scope), condition)
         return expr
 
     def _visit_ListComp(self, stmt):
+        """
+        Converts a list comprehension statement into a `FunctionalFor` AST object.
+
+        This method translates the list comprehension into an equivalent `FunctionalFor`
+        
+        Parameters
+        ----------
+        stmt : ast.stmt
+            Object to visit of type X.
+
+        Returns
+        -------
+        pyccel.ast.functionalexpr.FunctionalFor
+            AST object which is the syntactic equivalent of the list comprehension.
+        """
+
+        def create_target_operations():
+            operations = {'list' : [], 'numpy_array' : []}
+            index = PyccelSymbol('_', is_temp=True)
+            args = [index]
+            target = IndexedElement(lhs, *args)
+            target = Assign(target, result)
+            assign1 = Assign(index, LiteralInteger(0))
+            assign1.set_current_ast(stmt)
+            operations['numpy_array'].append(assign1)
+            target.set_current_ast(stmt)
+            operations['numpy_array'].append(target)
+            assign2 = Assign(index, PyccelAdd(index, LiteralInteger(1)))
+            assign2.set_current_ast(stmt)
+            operations['numpy_array'].append(assign2)
+            operations['list'].append(DottedName(lhs, FunctionCall('append', [FunctionCallArgument(result)])))
+
+            return index, operations
+
 
         result = self._visit(stmt.elt)
+        output_type = None
 
-        generators = list(self._visit(stmt.generators))
+        comprehensions = list(self._visit(stmt.generators))
+        generators, conditions = zip(*comprehensions)
+        generators, conditions = map(list, [generators, conditions])
path: pyccel/parser/syntactic.py
user: login: EmilyBourne
body: Question still relevant
created_at: 2025-03-13T16:43:08Z
author_association: MEMBER
diff_hunk: @@ -4167,31 +4221,64 @@ def _visit_FunctionalFor(self, expr):
         # TODO [YG, 30.10.2020]:
         #  - Check if we should allow the possibility that is_stack_array=True
         # ...
-        lhs_symbol = expr.lhs.base
+        lhs_symbol = expr.lhs
         ne = []
         lhs = self._assign_lhs_variable(lhs_symbol, d_var, rhs=expr, new_expressions=ne)
         lhs_alloc = ne[0]
 
         if isinstance(target, PythonTuple) and not target.is_homogeneous:
-            errors.report(LIST_OF_TUPLES, symbol=expr, severity='error')
+            errors.report(LIST_OF_TUPLES, symbol=expr, severity='fatal')
 
         target.invalidate_node()
+        operations = []
+        assign = None
+        if target_type_name != 'list':
+            old_index   = expr.index
+            new_index   = self.scope.get_new_name()
+            expr.substitute(old_index, new_index)
path: pyccel/parser/semantic.py
user: login: EmilyBourne
body: It seems that the old index is defined as : `index = PyccelSymbol('_', is_temp=True)`

Beware of the `is_equivalent` argument added in #2107 
created_at: 2025-03-13T16:45:13Z
author_association: MEMBER
diff_hunk: @@ -1222,40 +1264,25 @@ def _visit_ListComp(self, stmt):
             else:
                 raise NotImplementedError("A list comprehension cannot be unpacked")
 
-        parent = self._context[-2]
-        if isinstance(parent, ast.Call):
-            output_type = self._visit(parent.func)
-        else:
-            output_type = 'list'
-
-        index = PyccelSymbol('_', is_temp=True)
-
-        args = [index]
-        target = IndexedElement(lhs, *args)
-        target = Assign(target, result)
-        assign1 = Assign(index, LiteralInteger(0))
-        assign1.set_current_ast(stmt)
-        target.set_current_ast(stmt)
-        generators[-1].insert2body(target)
-        assign2 = Assign(index, PyccelAdd(index, LiteralInteger(1)))
-        assign2.set_current_ast(stmt)
-        generators[-1].insert2body(assign2)
-
-        indices = [generators[-1].target]
-        while len(generators) > 1:
-            F = generators.pop()
-            generators[-1].insert2body(F)
-            indices.append(generators[-1].target)
-        indices = indices[::-1]
+        indices = [generator.target for generator in generators]
+        index, operations = create_target_operations()
 
-        return FunctionalFor([assign1, generators[-1]],target.rhs, target.lhs,
-                             indices, index, target_type = output_type)
+        return FunctionalFor(generators, result, lhs,indices,index=index,
+                             conditions=conditions,
+                             target_type=output_type, operations=operations)
 
     def _visit_GeneratorExp(self, stmt):
+        conditions = []
+        generators = []
+        indices = []
+        condition = None
 
         result = self._visit(stmt.elt)
 
-        generators = self._visit(stmt.generators)
+        comprehensions = list(self._visit(stmt.generators))
+        generators, conditions = zip(*comprehensions)
+        generators, conditions = map(list, [generators, conditions])
path: pyccel/parser/syntactic.py
user: login: EmilyBourne
body: Same question as above. I am not 100% sure that I understand what these lines are doing
created_at: 2025-03-13T16:46:22Z
author_association: MEMBER
diff_hunk: @@ -96,6 +96,7 @@ All notable changes to this project will be documented in this file.
 -   #1913 : Fix function calls to renamed functions.
 -   #1930 : Preserve ordering of import targets.
 -   #1892 : Fix implementation of list function when an iterable is passed as parameter.
+-   #1297 : Fix iteration over an `enumerate`, `map` or `zip` in a list comprehension.
path: CHANGELOG.md
user: login: EmilyBourne
body: This PR closes 3 issues but only 1 seems to be mentioned here
created_at: 2025-03-13T16:49:02Z
author_association: MEMBER
diff_hunk: @@ -56,9 +56,16 @@ class FunctionalFor(TypedAstNode):
     target_type : PyccelSymbol, optional
         The type of the result of the functional for. This is useful at
         the syntactic stage to pass along the final type of the lhs (list/set/array/etc).
+    conditions : list[If|None], optional
+        A list of filter conditions corresponding to each for-loop in the comprehension.
+        Each element of this list is either an `If` instance that describes the filtering
+        condition for that loop, or `None` if no condition is applied in that loop.
path: pyccel/ast/functionalexpr.py
user: login: jalalium
body: Yes, it does. Added!
created_at: 2025-03-16T13:31:36Z
author_association: MEMBER
diff_hunk: @@ -56,9 +56,16 @@ class FunctionalFor(TypedAstNode):
     target_type : PyccelSymbol, optional
         The type of the result of the functional for. This is useful at
         the syntactic stage to pass along the final type of the lhs (list/set/array/etc).
+    conditions : list[If|None], optional
+        A list of filter conditions corresponding to each for-loop in the comprehension.
+        Each element of this list is either an `If` instance that describes the filtering
+        condition for that loop, or `None` if no condition is applied in that loop.
+    operations : dict
+        A dictionary mapping each type of comprehension (e.g. list, array, etc.)
+        to the operation used for populating it.
path: pyccel/ast/functionalexpr.py
user: login: jalalium
body: It depends on the stage. It needs to be passed from the syntactic stage to the semantic. but afterwards it is not required anymore
created_at: 2025-03-16T13:35:56Z
author_association: MEMBER
diff_hunk: @@ -3970,90 +3977,140 @@ def _visit_For(self, expr):
             for_expr.end_annotation = expr.end_annotation
         return for_expr
 
-
     def _visit_FunctionalFor(self, expr):
-        old_index   = expr.index
-        new_index   = self.scope.get_new_name()
-        # Identity comparison is required to only find duplicates of the index caused
-        # by the construction of FunctionalFor.
-        expr.substitute(old_index, new_index, is_equivalent = lambda x,y: x is y)
+        """
+        Visit and transform a FunctionalFor AST node into an equivalent code block.
 
-        target  = expr.expr
-        index   = new_index
-        indices = [self.scope.get_expected_name(i) for i in expr.indices]
-        dims    = []
-        body    = expr.loops[1]
+        This method processes a `FunctionalFor` expression and transforms the loop structure
+        into a corresponding code block.
 
-        idx_subs = {}
-        #scope = self.create_new_loop_scope()
+        Parameters
+        ----------
+        expr : pyccel.ast.functionalexpr.FunctionalFor
+            The FunctionalFor AST node.
 
-        # The symbols created to represent unknown valued objects are temporary
+        Returns
+        -------
+        pyccel.ast.basic.CodeBlock
+            A code block containing the equivalent loops and necessary variable allocations for the given `FunctionalFor` expression.
+        """
+        target  = expr.expr
+        indices = []
+        dims = []
+        idx_subs = {}
         tmp_used_names = self.scope.all_used_symbols.copy()
         i = 0
-        while isinstance(body, For):
+        loops = list(expr.loops)
+
+        # Inner function to handle PythonNativeInt variables
+        def handle_int_loop_variable(var_name, var_scope):
+            indices.append(var_name)
+            var = self._create_variable(var_name, PythonNativeInt(), None, {},insertion_scope=var_scope)
+            dvar = self._infer_type(var)
+            variables.append((var, dvar))
+
+        # Inner function to handle iterable variables
+        def handle_iterable_variable(var_name, element, var_scope):
+            indices.append(var_name)
+            dvar = self._infer_type(element)
+            class_type = dvar.pop('class_type')
+            if class_type.rank > 0:
+                class_type = class_type.switch_rank(class_type.rank - 1)
+                dvar['shape'] = dvar['shape'][1:]
+            if class_type.rank == 0:
+                dvar['shape'] = None
+                dvar['memory_handling'] = 'stack'
+            var = self._create_variable(var_name, class_type, None, dvar, insertion_scope=var_scope)
+            dvar['class_type'] = class_type
+            variables.append((var, dvar))
path: pyccel/parser/semantic.py
user: login: jalalium
body: I don't so, `For` handles iterable variables differently
created_at: 2025-03-16T13:53:50Z
author_association: MEMBER
diff_hunk: @@ -3970,90 +3977,140 @@ def _visit_For(self, expr):
             for_expr.end_annotation = expr.end_annotation
         return for_expr
 
-
     def _visit_FunctionalFor(self, expr):
-        old_index   = expr.index
-        new_index   = self.scope.get_new_name()
-        # Identity comparison is required to only find duplicates of the index caused
-        # by the construction of FunctionalFor.
-        expr.substitute(old_index, new_index, is_equivalent = lambda x,y: x is y)
+        """
+        Visit and transform a FunctionalFor AST node into an equivalent code block.
 
-        target  = expr.expr
-        index   = new_index
-        indices = [self.scope.get_expected_name(i) for i in expr.indices]
-        dims    = []
-        body    = expr.loops[1]
+        This method processes a `FunctionalFor` expression and transforms the loop structure
+        into a corresponding code block.
 
-        idx_subs = {}
-        #scope = self.create_new_loop_scope()
+        Parameters
+        ----------
+        expr : pyccel.ast.functionalexpr.FunctionalFor
+            The FunctionalFor AST node.
 
-        # The symbols created to represent unknown valued objects are temporary
+        Returns
+        -------
+        pyccel.ast.basic.CodeBlock
+            A code block containing the equivalent loops and necessary variable allocations for the given `FunctionalFor` expression.
+        """
+        target  = expr.expr
+        indices = []
+        dims = []
+        idx_subs = {}
         tmp_used_names = self.scope.all_used_symbols.copy()
         i = 0
-        while isinstance(body, For):
+        loops = list(expr.loops)
+
+        # Inner function to handle PythonNativeInt variables
+        def handle_int_loop_variable(var_name, var_scope):
+            indices.append(var_name)
+            var = self._create_variable(var_name, PythonNativeInt(), None, {},insertion_scope=var_scope)
+            dvar = self._infer_type(var)
+            variables.append((var, dvar))
+
+        # Inner function to handle iterable variables
+        def handle_iterable_variable(var_name, element, var_scope):
+            indices.append(var_name)
+            dvar = self._infer_type(element)
+            class_type = dvar.pop('class_type')
+            if class_type.rank > 0:
+                class_type = class_type.switch_rank(class_type.rank - 1)
+                dvar['shape'] = dvar['shape'][1:]
+            if class_type.rank == 0:
+                dvar['shape'] = None
+                dvar['memory_handling'] = 'stack'
+            var = self._create_variable(var_name, class_type, None, dvar, insertion_scope=var_scope)
+            dvar['class_type'] = class_type
+            variables.append((var, dvar))
+
+        def create_target_operations(target):
+            # this can be used to add opertations for set/dict comprehension
+            assert target in expr.operations
+            return expr.operations[target]
path: pyccel/parser/semantic.py
user: login: jalalium
body: You are right, i was thinking of using this assert as a reminder to where to add operations for the other containers.
created_at: 2025-03-16T13:56:15Z
author_association: MEMBER
diff_hunk: @@ -4167,31 +4221,64 @@ def _visit_FunctionalFor(self, expr):
         # TODO [YG, 30.10.2020]:
         #  - Check if we should allow the possibility that is_stack_array=True
         # ...
-        lhs_symbol = expr.lhs.base
+        lhs_symbol = expr.lhs
         ne = []
         lhs = self._assign_lhs_variable(lhs_symbol, d_var, rhs=expr, new_expressions=ne)
         lhs_alloc = ne[0]
 
         if isinstance(target, PythonTuple) and not target.is_homogeneous:
-            errors.report(LIST_OF_TUPLES, symbol=expr, severity='error')
+            errors.report(LIST_OF_TUPLES, symbol=expr, severity='fatal')
 
         target.invalidate_node()
+        operations = []
+        assign = None
+        if target_type_name != 'list':
+            old_index   = expr.index
+            new_index   = self.scope.get_new_name()
+            expr.substitute(old_index, new_index)
path: pyccel/parser/semantic.py
user: login: jalalium
body: because it gives me this error:
```bash
 Variables named '_' are assumed to be throwaways so their value cannot be used
```
created_at: 2025-03-16T14:07:00Z
author_association: MEMBER
diff_hunk: @@ -3857,31 +3886,37 @@ def _visit_FunctionalFor(self, expr):
         # TODO [YG, 30.10.2020]:
         #  - Check if we should allow the possibility that is_stack_array=True
         # ...
-        lhs_symbol = expr.lhs.base
+        lhs_symbol = expr.lhs
         ne = []
         lhs = self._assign_lhs_variable(lhs_symbol, d_var, rhs=expr, new_expressions=ne)
         lhs_alloc = ne[0]
 
         if isinstance(target, PythonTuple) and not target.is_homogeneous:
-            errors.report(LIST_OF_TUPLES, symbol=expr, severity='error')
+            errors.report(LIST_OF_TUPLES, symbol=expr, severity='fatal')
path: pyccel/parser/semantic.py
user: login: EmilyBourne
body: Hmm, you are right. The test is not passing anyway as a different error (`AttributeError`) is being raised. I guess if you had to change this it is because you somehow fixed that error. I would rather you move the test to the right folder instead of changing the error level. I will see if I can create a quick fix PR to correct the `AttributeError` explicitly
created_at: 2025-03-16T14:26:55Z
author_association: MEMBER
diff_hunk: @@ -56,9 +56,16 @@ class FunctionalFor(TypedAstNode):
     target_type : PyccelSymbol, optional
         The type of the result of the functional for. This is useful at
         the syntactic stage to pass along the final type of the lhs (list/set/array/etc).
+    conditions : list of If or None, optional
path: pyccel/ast/functionalexpr.py
user: login: EmilyBourne
body: Just to confirm sometimes `conditions = [None]`? `list[If|None]` means a list containing either `None` or `If` instances. From you description it seems that the type should simply be `list[If], optional`
created_at: 2025-03-16T14:28:45Z
author_association: MEMBER
diff_hunk: @@ -56,9 +56,16 @@ class FunctionalFor(TypedAstNode):
     target_type : PyccelSymbol, optional
         The type of the result of the functional for. This is useful at
         the syntactic stage to pass along the final type of the lhs (list/set/array/etc).
+    conditions : list of If or None, optional
path: pyccel/ast/functionalexpr.py
user: login: EmilyBourne
body: I guess this is answered by the discussion 100 lines below
created_at: 2025-03-16T14:29:45Z
author_association: MEMBER
diff_hunk: @@ -1148,26 +1148,70 @@ def _visit_For(self, stmt):
     def _visit_comprehension(self, stmt):
 
         scope = self.create_new_loop_scope()
+        condition = None
 
         self._in_lhs_assign = True
         iterator = self._visit(stmt.target)
         self._in_lhs_assign = False
         iterable = self._visit(stmt.iter)
 
-        self.exit_loop_scope()
-
         if stmt.ifs:
-            errors.report("Cannot handle if statements in list comprehensions. List length cannot be calculated.\n" + PYCCEL_RESTRICTION_TODO,
-                    symbol=stmt, severity='error')
+            from functools import reduce
+            cond = reduce(PyccelAnd, [self._visit(if_cond)
+                                      for if_cond in stmt.ifs])
+            condition = If(IfSection(cond, CodeBlock([])))
 
-        expr = For(iterator, iterable, [], scope=scope)
+        self.exit_loop_scope()
+
+        expr = (For(iterator, iterable, [], scope=scope), condition)
         return expr
 
     def _visit_ListComp(self, stmt):
+        """
+        Converts a list comprehension statement into a `FunctionalFor` AST object.
+
+        This method translates the list comprehension into an equivalent `FunctionalFor`
+        
+        Parameters
+        ----------
+        stmt : ast.stmt
+            Object to visit of type X.
+
+        Returns
+        -------
+        pyccel.ast.functionalexpr.FunctionalFor
+            AST object which is the syntactic equivalent of the list comprehension.
+        """
+
+        def create_target_operations():
+            operations = {'list' : [], 'numpy_array' : []}
+            index = PyccelSymbol('_', is_temp=True)
+            args = [index]
+            target = IndexedElement(lhs, *args)
+            target = Assign(target, result)
+            assign1 = Assign(index, LiteralInteger(0))
+            assign1.set_current_ast(stmt)
+            operations['numpy_array'].append(assign1)
+            target.set_current_ast(stmt)
+            operations['numpy_array'].append(target)
+            assign2 = Assign(index, PyccelAdd(index, LiteralInteger(1)))
+            assign2.set_current_ast(stmt)
+            operations['numpy_array'].append(assign2)
+            operations['list'].append(DottedName(lhs, FunctionCall('append', [FunctionCallArgument(result)])))
+
+            return index, operations
+
 
         result = self._visit(stmt.elt)
+        output_type = None
 
-        generators = list(self._visit(stmt.generators))
+        comprehensions = list(self._visit(stmt.generators))
+        generators, conditions = zip(*comprehensions)
+        generators, conditions = map(list, [generators, conditions])
path: pyccel/parser/syntactic.py
user: login: EmilyBourne
body: > Your suggestion would work if we only had a 2 loops.

This is true. So what about:
```python
generators = [c[0] for c in comprehensions]
conditions = [c[1] for c in comprehensions]
```
?

From testing it seems that `zip(*comprehensions)` extracts splits a list of N tuples into a list of 2 tuples of size N. While `map(list, [generators, conditions])` simply changes the tuples to lists.

I find the above suggestion significantly more readable that the map/list/zip solution
created_at: 2025-03-16T14:37:29Z
author_association: MEMBER
diff_hunk: @@ -56,9 +56,16 @@ class FunctionalFor(TypedAstNode):
     target_type : PyccelSymbol, optional
         The type of the result of the functional for. This is useful at
         the syntactic stage to pass along the final type of the lhs (list/set/array/etc).
+    conditions : list[If|None], optional
+        A list of filter conditions corresponding to each for-loop in the comprehension.
+        Each element of this list is either an `If` instance that describes the filtering
+        condition for that loop, or `None` if no condition is applied in that loop.
+    operations : dict
+        A dictionary mapping each type of comprehension (e.g. list, array, etc.)
+        to the operation used for populating it.
path: pyccel/ast/functionalexpr.py
user: login: EmilyBourne
body: In that case I recommend mentioning this in the docstring and maybe adding an assertion. E.g:
```python
assert pyccel_stage != 'syntactic' || operations is not None
```
created_at: 2025-03-16T14:42:17Z
author_association: MEMBER
diff_hunk: @@ -3970,90 +3977,140 @@ def _visit_For(self, expr):
             for_expr.end_annotation = expr.end_annotation
         return for_expr
 
-
     def _visit_FunctionalFor(self, expr):
-        old_index   = expr.index
-        new_index   = self.scope.get_new_name()
-        # Identity comparison is required to only find duplicates of the index caused
-        # by the construction of FunctionalFor.
-        expr.substitute(old_index, new_index, is_equivalent = lambda x,y: x is y)
+        """
+        Visit and transform a FunctionalFor AST node into an equivalent code block.
 
-        target  = expr.expr
-        index   = new_index
-        indices = [self.scope.get_expected_name(i) for i in expr.indices]
-        dims    = []
-        body    = expr.loops[1]
+        This method processes a `FunctionalFor` expression and transforms the loop structure
+        into a corresponding code block.
 
-        idx_subs = {}
-        #scope = self.create_new_loop_scope()
+        Parameters
+        ----------
+        expr : pyccel.ast.functionalexpr.FunctionalFor
+            The FunctionalFor AST node.
 
-        # The symbols created to represent unknown valued objects are temporary
+        Returns
+        -------
+        pyccel.ast.basic.CodeBlock
+            A code block containing the equivalent loops and necessary variable allocations for the given `FunctionalFor` expression.
+        """
+        target  = expr.expr
+        indices = []
+        dims = []
+        idx_subs = {}
         tmp_used_names = self.scope.all_used_symbols.copy()
         i = 0
-        while isinstance(body, For):
+        loops = list(expr.loops)
+
+        # Inner function to handle PythonNativeInt variables
+        def handle_int_loop_variable(var_name, var_scope):
+            indices.append(var_name)
+            var = self._create_variable(var_name, PythonNativeInt(), None, {},insertion_scope=var_scope)
+            dvar = self._infer_type(var)
+            variables.append((var, dvar))
+
+        # Inner function to handle iterable variables
+        def handle_iterable_variable(var_name, element, var_scope):
+            indices.append(var_name)
+            dvar = self._infer_type(element)
+            class_type = dvar.pop('class_type')
+            if class_type.rank > 0:
+                class_type = class_type.switch_rank(class_type.rank - 1)
+                dvar['shape'] = dvar['shape'][1:]
+            if class_type.rank == 0:
+                dvar['shape'] = None
+                dvar['memory_handling'] = 'stack'
+            var = self._create_variable(var_name, class_type, None, dvar, insertion_scope=var_scope)
+            dvar['class_type'] = class_type
+            variables.append((var, dvar))
+
+        def create_target_operations(target):
+            # this can be used to add opertations for set/dict comprehension
+            assert target in expr.operations
+            return expr.operations[target]
path: pyccel/parser/semantic.py
user: login: EmilyBourne
body: Maybe just document it in the `FunctionalFor` class
created_at: 2025-03-16T14:43:24Z
author_association: MEMBER
diff_hunk: @@ -4167,31 +4221,64 @@ def _visit_FunctionalFor(self, expr):
         # TODO [YG, 30.10.2020]:
         #  - Check if we should allow the possibility that is_stack_array=True
         # ...
-        lhs_symbol = expr.lhs.base
+        lhs_symbol = expr.lhs
         ne = []
         lhs = self._assign_lhs_variable(lhs_symbol, d_var, rhs=expr, new_expressions=ne)
         lhs_alloc = ne[0]
 
         if isinstance(target, PythonTuple) and not target.is_homogeneous:
-            errors.report(LIST_OF_TUPLES, symbol=expr, severity='error')
+            errors.report(LIST_OF_TUPLES, symbol=expr, severity='fatal')
 
         target.invalidate_node()
+        operations = []
+        assign = None
+        if target_type_name != 'list':
+            old_index   = expr.index
+            new_index   = self.scope.get_new_name()
+            expr.substitute(old_index, new_index)
path: pyccel/parser/semantic.py
user: login: EmilyBourne
body: Yes see my second comment :+1:

Did you check #2107 ? 

It could cause problems if the user code contains `_` e.g. you should test:
```python
def functional_with_enumerate():
    a = [x + 1 for x in range(10)]
    b = [i for i,_ in enumerate(a)]
    return len(b), b[0], b[1], b[2]
```
created_at: 2025-03-16T14:45:34Z
author_association: MEMBER
diff_hunk: @@ -3857,31 +3886,37 @@ def _visit_FunctionalFor(self, expr):
         # TODO [YG, 30.10.2020]:
         #  - Check if we should allow the possibility that is_stack_array=True
         # ...
-        lhs_symbol = expr.lhs.base
+        lhs_symbol = expr.lhs
         ne = []
         lhs = self._assign_lhs_variable(lhs_symbol, d_var, rhs=expr, new_expressions=ne)
         lhs_alloc = ne[0]
 
         if isinstance(target, PythonTuple) and not target.is_homogeneous:
-            errors.report(LIST_OF_TUPLES, symbol=expr, severity='error')
+            errors.report(LIST_OF_TUPLES, symbol=expr, severity='fatal')
path: pyccel/parser/semantic.py
user: login: EmilyBourne
body: https://github.com/pyccel/pyccel/pull/2245
created_at: 2025-03-16T14:58:11Z
author_association: MEMBER
diff_hunk: @@ -660,15 +666,22 @@ def _print_For(self, expr):
     def _print_FunctionalFor(self, expr):
         body, iterators = self._find_functional_expr_and_iterables(expr)
         lhs = self._print(expr.lhs)
-        body = self._print(body.rhs)
-        for_loops = ' '.join(['for {} in {}'.format(self._print(idx), self._print(iters))
-                        for idx, iters in zip(expr.indices, iterators)])
+        condition = ''
+        if isinstance(body, Assign):
+            body = self._print(body.rhs)
+        else:
+            assert isinstance(body, ListAppend)
+            body = self._print(body.args[0])
+
+        for_loops = ' '.join([f'for {self._print(idx)} in {self._print(iters)}{" if " + self._print(condition.blocks[0].condition) if condition else ""}'
+                             for idx, iters, condition in zip(expr.indices, iterators, expr.conditions)])
 
-        name = self._aliases.get(type(expr),'array')
-        if name == 'array':
+        name = expr.target_type
+        if 'array' in str(name):
path: pyccel/codegen/printing/pycode.py
user: login: jalalium
body: Fixed! I need to check if it is a list or a numpy array
created_at: 2025-03-16T15:27:22Z
author_association: MEMBER
diff_hunk: @@ -3970,90 +3977,140 @@ def _visit_For(self, expr):
             for_expr.end_annotation = expr.end_annotation
         return for_expr
 
-
     def _visit_FunctionalFor(self, expr):
-        old_index   = expr.index
-        new_index   = self.scope.get_new_name()
-        # Identity comparison is required to only find duplicates of the index caused
-        # by the construction of FunctionalFor.
-        expr.substitute(old_index, new_index, is_equivalent = lambda x,y: x is y)
+        """
+        Visit and transform a FunctionalFor AST node into an equivalent code block.
 
-        target  = expr.expr
-        index   = new_index
-        indices = [self.scope.get_expected_name(i) for i in expr.indices]
-        dims    = []
-        body    = expr.loops[1]
+        This method processes a `FunctionalFor` expression and transforms the loop structure
+        into a corresponding code block.
 
-        idx_subs = {}
-        #scope = self.create_new_loop_scope()
+        Parameters
+        ----------
+        expr : pyccel.ast.functionalexpr.FunctionalFor
+            The FunctionalFor AST node.
 
-        # The symbols created to represent unknown valued objects are temporary
+        Returns
+        -------
+        pyccel.ast.basic.CodeBlock
+            A code block containing the equivalent loops and necessary variable allocations for the given `FunctionalFor` expression.
+        """
+        target  = expr.expr
+        indices = []
+        dims = []
+        idx_subs = {}
         tmp_used_names = self.scope.all_used_symbols.copy()
         i = 0
-        while isinstance(body, For):
+        loops = list(expr.loops)
+
+        # Inner function to handle PythonNativeInt variables
+        def handle_int_loop_variable(var_name, var_scope):
+            indices.append(var_name)
+            var = self._create_variable(var_name, PythonNativeInt(), None, {},insertion_scope=var_scope)
+            dvar = self._infer_type(var)
+            variables.append((var, dvar))
+
+        # Inner function to handle iterable variables
+        def handle_iterable_variable(var_name, element, var_scope):
+            indices.append(var_name)
+            dvar = self._infer_type(element)
+            class_type = dvar.pop('class_type')
+            if class_type.rank > 0:
+                class_type = class_type.switch_rank(class_type.rank - 1)
+                dvar['shape'] = dvar['shape'][1:]
+            if class_type.rank == 0:
+                dvar['shape'] = None
+                dvar['memory_handling'] = 'stack'
+            var = self._create_variable(var_name, class_type, None, dvar, insertion_scope=var_scope)
+            dvar['class_type'] = class_type
+            variables.append((var, dvar))
+
+        def create_target_operations(target):
+            # this can be used to add opertations for set/dict comprehension
+            assert target in expr.operations
+            return expr.operations[target]
+
+        for loop, condition in zip(loops, expr.conditions):
+            if condition:
+                loop.insert2body(condition)
+
+        while len(loops) > 1:
+            outter_loop = loops.pop()
path: pyccel/parser/semantic.py
user: login: jalalium
body: fixed
created_at: 2025-03-16T15:28:10Z
author_association: MEMBER
diff_hunk: @@ -3970,90 +3977,140 @@ def _visit_For(self, expr):
             for_expr.end_annotation = expr.end_annotation
         return for_expr
 
-
     def _visit_FunctionalFor(self, expr):
-        old_index   = expr.index
-        new_index   = self.scope.get_new_name()
-        # Identity comparison is required to only find duplicates of the index caused
-        # by the construction of FunctionalFor.
-        expr.substitute(old_index, new_index, is_equivalent = lambda x,y: x is y)
+        """
+        Visit and transform a FunctionalFor AST node into an equivalent code block.
 
-        target  = expr.expr
-        index   = new_index
-        indices = [self.scope.get_expected_name(i) for i in expr.indices]
-        dims    = []
-        body    = expr.loops[1]
+        This method processes a `FunctionalFor` expression and transforms the loop structure
+        into a corresponding code block.
 
-        idx_subs = {}
-        #scope = self.create_new_loop_scope()
+        Parameters
+        ----------
+        expr : pyccel.ast.functionalexpr.FunctionalFor
+            The FunctionalFor AST node.
 
-        # The symbols created to represent unknown valued objects are temporary
+        Returns
+        -------
+        pyccel.ast.basic.CodeBlock
+            A code block containing the equivalent loops and necessary variable allocations for the given `FunctionalFor` expression.
+        """
+        target  = expr.expr
+        indices = []
+        dims = []
+        idx_subs = {}
         tmp_used_names = self.scope.all_used_symbols.copy()
         i = 0
-        while isinstance(body, For):
+        loops = list(expr.loops)
+
+        # Inner function to handle PythonNativeInt variables
+        def handle_int_loop_variable(var_name, var_scope):
+            indices.append(var_name)
+            var = self._create_variable(var_name, PythonNativeInt(), None, {},insertion_scope=var_scope)
+            dvar = self._infer_type(var)
+            variables.append((var, dvar))
+
+        # Inner function to handle iterable variables
+        def handle_iterable_variable(var_name, element, var_scope):
+            indices.append(var_name)
+            dvar = self._infer_type(element)
+            class_type = dvar.pop('class_type')
+            if class_type.rank > 0:
+                class_type = class_type.switch_rank(class_type.rank - 1)
+                dvar['shape'] = dvar['shape'][1:]
+            if class_type.rank == 0:
+                dvar['shape'] = None
+                dvar['memory_handling'] = 'stack'
+            var = self._create_variable(var_name, class_type, None, dvar, insertion_scope=var_scope)
+            dvar['class_type'] = class_type
+            variables.append((var, dvar))
+
+        def create_target_operations(target):
+            # this can be used to add opertations for set/dict comprehension
+            assert target in expr.operations
+            return expr.operations[target]
+
+        for loop, condition in zip(loops, expr.conditions):
+            if condition:
+                loop.insert2body(condition)
+
+        while len(loops) > 1:
+            outter_loop = loops.pop()
+            inserted_into = loops[-1]
+            if inserted_into.body.body:
+                inserted_into.body.body[0].blocks[0].body.insert2body(outter_loop)
+            else:
+                inserted_into.insert2body(outter_loop)
+
+        body = loops[0]
 
-            stop  = None
+        while isinstance(body, (For, If)):
+
+            if isinstance(body, If):
+                body = None if not body.blocks[0].body.body else body.blocks[0].body.body[0]
+                continue
+
+            stop = None
             start = LiteralInteger(0)
-            step  = LiteralInteger(1)
-            var   = indices[i]
-            i += 1
-            a     = self._visit(body.iterable)
+            step = LiteralInteger(1)
+            variables = []
+            a = self._get_iterable(self._visit(body.iterable))
             if isinstance(a, PythonRange):
-                var   = self._create_variable(var, PythonNativeInt(), start, {})
-                dvar  = self._infer_type(var)
-                stop  = a.stop
+                var_name = self.scope.get_expected_name(expr.indices[i])
+                handle_int_loop_variable(var_name, body.scope)
                 start = a.start
+                stop  = a.stop
                 step  = a.step
 
-            elif isinstance(a, (PythonZip, PythonEnumerate)):
-                dvar  = self._infer_type(a.element)
-                class_type = dvar.pop('class_type')
-                if class_type.rank > 0:
-                    class_type = class_type.switch_rank(class_type.rank-1)
-                    dvar['shape'] = (dvar['shape'])[1:]
-                if class_type.rank == 0:
-                    dvar['shape'] = None
-                    dvar['memory_handling'] = 'stack'
-                var  = Variable(class_type, var, **dvar)
+            elif isinstance(a, PythonEnumerate):
+                var_name1 = self.scope.get_expected_name(expr.indices[i][0])
+                var_name2 = self.scope.get_expected_name(expr.indices[i][1])
+                handle_int_loop_variable(var_name1, body.scope)
+                handle_iterable_variable(var_name2, a.element, body.scope)
                 stop = a.element.shape[0]
 
-            elif isinstance(a, Variable):
-                dvar  = self._infer_type(a)
-                class_type = dvar.pop('class_type')
-                if class_type.rank > 0:
-                    class_type = class_type.switch_rank(class_type.rank-1)
-                    dvar['shape'] = (dvar['shape'])[1:]
-                if class_type.rank == 0:
-                    dvar['shape'] = None
-                    dvar['memory_handling'] = 'stack'
-                var  = Variable(class_type, var, **dvar)
-                stop = a.shape[0]
+            elif isinstance(a, PythonZip):
+                for idx, arg in enumerate(a.args):
+                    var = self.scope.get_expected_name(expr.indices[i][idx])
+                    handle_iterable_variable(var, arg, body.scope)
+                stop = a.get_range().stop
+
+            elif isinstance(a, VariableIterator):
+                var = self.scope.get_expected_name(expr.indices[i])
+                handle_iterable_variable(var, a.variable, body.scope)
+                stop = a.variable.shape[0]
 
             else:
                 errors.report(PYCCEL_RESTRICTION_TODO,
                               bounding_box=(self.current_ast_node.lineno, self.current_ast_node.col_offset),
                               severity='fatal')
-            existing_var = self.scope.find(var.name, 'variables')
-            if existing_var:
-                if self._infer_type(existing_var) != dvar:
-                    errors.report(f"Variable {var} already exists with different type",
-                            symbol = expr, severity='error')
-            else:
-                self.scope.insert_variable(var)
-            step.invalidate_node()
+
+            for var, dvar in variables:
+                existing_var = self.scope.find(var.name, 'variables')
+                if var.name == expr.lhs:
+                    errors.report(f"Variable {var} has the same name as the left hand side",
+                            symbol = expr, severity='fatal')
+                if existing_var or var.name ==  expr.lhs:
+                    if self._infer_type(existing_var)['class_type'] != dvar['class_type']:
+                        return errors.report(f"Variable {var} already exists with different type",
+                                symbol = expr, severity='error')
+                else:
+                    self.scope.insert_variable(var)
+
+            if isinstance(step, LiteralInteger):
+                step.invalidate_node()
             step  = pyccel_to_sympy(step , idx_subs, tmp_used_names)
-            start.invalidate_node()
+            if isinstance(start, LiteralInteger):
+                start.invalidate_node()
path: pyccel/parser/semantic.py
user: login: jalalium
body: You are right! I wasn't sure about these lines. they were here before
created_at: 2025-03-16T15:31:02Z
author_association: MEMBER
diff_hunk: @@ -4167,31 +4221,64 @@ def _visit_FunctionalFor(self, expr):
         # TODO [YG, 30.10.2020]:
         #  - Check if we should allow the possibility that is_stack_array=True
         # ...
-        lhs_symbol = expr.lhs.base
+        lhs_symbol = expr.lhs
         ne = []
         lhs = self._assign_lhs_variable(lhs_symbol, d_var, rhs=expr, new_expressions=ne)
         lhs_alloc = ne[0]
 
         if isinstance(target, PythonTuple) and not target.is_homogeneous:
-            errors.report(LIST_OF_TUPLES, symbol=expr, severity='error')
+            errors.report(LIST_OF_TUPLES, symbol=expr, severity='fatal')
 
         target.invalidate_node()
+        operations = []
+        assign = None
+        if target_type_name != 'list':
+            old_index   = expr.index
+            new_index   = self.scope.get_new_name()
+            expr.substitute(old_index, new_index)
+            array_ops = create_target_operations('numpy_array')
+            assign = array_ops[0]
+            assign.substitute(old_index, new_index)
+            assign = self._visit(array_ops[0])
+            array_ops = array_ops[1:]
+            for operation in array_ops:
+                operation.substitute(old_index, new_index)
+                operations.append(operation)
+            index = new_index
+            index = self._visit(index)
+        else:
+            index = None
+            operations.extend(create_target_operations('list'))
path: pyccel/parser/semantic.py
user: login: jalalium
body: This is because I dont have a way to check for arrays as in the following example:
```
from numpy import array as xx

if __name__ == '__main__':
    x = xx([i-3 for i in range(10)])
 ```
 We discussed this before
created_at: 2025-03-16T15:37:36Z
author_association: MEMBER
diff_hunk: @@ -3970,90 +3977,140 @@ def _visit_For(self, expr):
             for_expr.end_annotation = expr.end_annotation
         return for_expr
 
-
     def _visit_FunctionalFor(self, expr):
-        old_index   = expr.index
-        new_index   = self.scope.get_new_name()
-        # Identity comparison is required to only find duplicates of the index caused
-        # by the construction of FunctionalFor.
-        expr.substitute(old_index, new_index, is_equivalent = lambda x,y: x is y)
+        """
+        Visit and transform a FunctionalFor AST node into an equivalent code block.
 
-        target  = expr.expr
-        index   = new_index
-        indices = [self.scope.get_expected_name(i) for i in expr.indices]
-        dims    = []
-        body    = expr.loops[1]
+        This method processes a `FunctionalFor` expression and transforms the loop structure
+        into a corresponding code block.
 
-        idx_subs = {}
-        #scope = self.create_new_loop_scope()
+        Parameters
+        ----------
+        expr : pyccel.ast.functionalexpr.FunctionalFor
+            The FunctionalFor AST node.
 
-        # The symbols created to represent unknown valued objects are temporary
+        Returns
+        -------
+        pyccel.ast.basic.CodeBlock
+            A code block containing the equivalent loops and necessary variable allocations for the given `FunctionalFor` expression.
+        """
+        target  = expr.expr
+        indices = []
+        dims = []
+        idx_subs = {}
         tmp_used_names = self.scope.all_used_symbols.copy()
         i = 0
-        while isinstance(body, For):
+        loops = list(expr.loops)
+
+        # Inner function to handle PythonNativeInt variables
+        def handle_int_loop_variable(var_name, var_scope):
+            indices.append(var_name)
+            var = self._create_variable(var_name, PythonNativeInt(), None, {},insertion_scope=var_scope)
+            dvar = self._infer_type(var)
+            variables.append((var, dvar))
+
+        # Inner function to handle iterable variables
+        def handle_iterable_variable(var_name, element, var_scope):
+            indices.append(var_name)
+            dvar = self._infer_type(element)
+            class_type = dvar.pop('class_type')
+            if class_type.rank > 0:
+                class_type = class_type.switch_rank(class_type.rank - 1)
+                dvar['shape'] = dvar['shape'][1:]
+            if class_type.rank == 0:
+                dvar['shape'] = None
+                dvar['memory_handling'] = 'stack'
+            var = self._create_variable(var_name, class_type, None, dvar, insertion_scope=var_scope)
+            dvar['class_type'] = class_type
+            variables.append((var, dvar))
+
+        def create_target_operations(target):
+            # this can be used to add opertations for set/dict comprehension
+            assert target in expr.operations
+            return expr.operations[target]
+
+        for loop, condition in zip(loops, expr.conditions):
+            if condition:
+                loop.insert2body(condition)
+
+        while len(loops) > 1:
+            outter_loop = loops.pop()
+            inserted_into = loops[-1]
+            if inserted_into.body.body:
+                inserted_into.body.body[0].blocks[0].body.insert2body(outter_loop)
+            else:
+                inserted_into.insert2body(outter_loop)
+
+        body = loops[0]
 
-            stop  = None
+        while isinstance(body, (For, If)):
+
+            if isinstance(body, If):
+                body = None if not body.blocks[0].body.body else body.blocks[0].body.body[0]
+                continue
+
+            stop = None
             start = LiteralInteger(0)
-            step  = LiteralInteger(1)
-            var   = indices[i]
-            i += 1
-            a     = self._visit(body.iterable)
+            step = LiteralInteger(1)
+            variables = []
+            a = self._get_iterable(self._visit(body.iterable))
             if isinstance(a, PythonRange):
-                var   = self._create_variable(var, PythonNativeInt(), start, {})
-                dvar  = self._infer_type(var)
-                stop  = a.stop
+                var_name = self.scope.get_expected_name(expr.indices[i])
+                handle_int_loop_variable(var_name, body.scope)
                 start = a.start
+                stop  = a.stop
                 step  = a.step
 
-            elif isinstance(a, (PythonZip, PythonEnumerate)):
-                dvar  = self._infer_type(a.element)
-                class_type = dvar.pop('class_type')
-                if class_type.rank > 0:
-                    class_type = class_type.switch_rank(class_type.rank-1)
-                    dvar['shape'] = (dvar['shape'])[1:]
-                if class_type.rank == 0:
-                    dvar['shape'] = None
-                    dvar['memory_handling'] = 'stack'
-                var  = Variable(class_type, var, **dvar)
+            elif isinstance(a, PythonEnumerate):
+                var_name1 = self.scope.get_expected_name(expr.indices[i][0])
+                var_name2 = self.scope.get_expected_name(expr.indices[i][1])
+                handle_int_loop_variable(var_name1, body.scope)
+                handle_iterable_variable(var_name2, a.element, body.scope)
                 stop = a.element.shape[0]
 
-            elif isinstance(a, Variable):
-                dvar  = self._infer_type(a)
-                class_type = dvar.pop('class_type')
-                if class_type.rank > 0:
-                    class_type = class_type.switch_rank(class_type.rank-1)
-                    dvar['shape'] = (dvar['shape'])[1:]
-                if class_type.rank == 0:
-                    dvar['shape'] = None
-                    dvar['memory_handling'] = 'stack'
-                var  = Variable(class_type, var, **dvar)
-                stop = a.shape[0]
+            elif isinstance(a, PythonZip):
+                for idx, arg in enumerate(a.args):
+                    var = self.scope.get_expected_name(expr.indices[i][idx])
+                    handle_iterable_variable(var, arg, body.scope)
+                stop = a.get_range().stop
+
+            elif isinstance(a, VariableIterator):
+                var = self.scope.get_expected_name(expr.indices[i])
+                handle_iterable_variable(var, a.variable, body.scope)
+                stop = a.variable.shape[0]
 
             else:
                 errors.report(PYCCEL_RESTRICTION_TODO,
                               bounding_box=(self.current_ast_node.lineno, self.current_ast_node.col_offset),
                               severity='fatal')
-            existing_var = self.scope.find(var.name, 'variables')
-            if existing_var:
-                if self._infer_type(existing_var) != dvar:
-                    errors.report(f"Variable {var} already exists with different type",
-                            symbol = expr, severity='error')
-            else:
-                self.scope.insert_variable(var)
-            step.invalidate_node()
+
+            for var, dvar in variables:
+                existing_var = self.scope.find(var.name, 'variables')
+                if var.name == expr.lhs:
+                    errors.report(f"Variable {var} has the same name as the left hand side",
+                            symbol = expr, severity='fatal')
+                if existing_var or var.name ==  expr.lhs:
+                    if self._infer_type(existing_var)['class_type'] != dvar['class_type']:
+                        return errors.report(f"Variable {var} already exists with different type",
+                                symbol = expr, severity='error')
+                else:
+                    self.scope.insert_variable(var)
+
+            if isinstance(step, LiteralInteger):
+                step.invalidate_node()
             step  = pyccel_to_sympy(step , idx_subs, tmp_used_names)
-            start.invalidate_node()
+            if isinstance(start, LiteralInteger):
+                start.invalidate_node()
path: pyccel/parser/semantic.py
user: login: EmilyBourne
body: I think I added them in order to ensure the AST tree was correct. At the time these objects were discarded here I think. If all the tests pass then I think you can remove them. I can always put it back later with a test in `tests/ast` if I notice an issue
created_at: 2025-03-16T15:42:28Z
author_association: MEMBER
diff_hunk: @@ -4167,31 +4221,64 @@ def _visit_FunctionalFor(self, expr):
         # TODO [YG, 30.10.2020]:
         #  - Check if we should allow the possibility that is_stack_array=True
         # ...
-        lhs_symbol = expr.lhs.base
+        lhs_symbol = expr.lhs
         ne = []
         lhs = self._assign_lhs_variable(lhs_symbol, d_var, rhs=expr, new_expressions=ne)
         lhs_alloc = ne[0]
 
         if isinstance(target, PythonTuple) and not target.is_homogeneous:
-            errors.report(LIST_OF_TUPLES, symbol=expr, severity='error')
+            errors.report(LIST_OF_TUPLES, symbol=expr, severity='fatal')
 
         target.invalidate_node()
+        operations = []
+        assign = None
+        if target_type_name != 'list':
+            old_index   = expr.index
+            new_index   = self.scope.get_new_name()
+            expr.substitute(old_index, new_index)
+            array_ops = create_target_operations('numpy_array')
+            assign = array_ops[0]
+            assign.substitute(old_index, new_index)
+            assign = self._visit(array_ops[0])
+            array_ops = array_ops[1:]
+            for operation in array_ops:
+                operation.substitute(old_index, new_index)
+                operations.append(operation)
+            index = new_index
+            index = self._visit(index)
+        else:
+            index = None
+            operations.extend(create_target_operations('list'))
path: pyccel/parser/semantic.py
user: login: EmilyBourne
body: When we last discussed this you were doing this in the syntactic stage. Now you are in the semantic stage shouldn't it be possible with:
```python
target_conversion_func = self._visit(target_type_name)
if isinstance(target_conversion_func, PyccelFunctionDef) and target_conversion_func.cls_base is NumpyArray:
```
created_at: 2025-03-16T15:49:16Z
author_association: MEMBER
diff_hunk: @@ -56,9 +56,16 @@ class FunctionalFor(TypedAstNode):
     target_type : PyccelSymbol, optional
         The type of the result of the functional for. This is useful at
         the syntactic stage to pass along the final type of the lhs (list/set/array/etc).
+    conditions : list[If|None], optional
+        A list of filter conditions corresponding to each for-loop in the comprehension.
+        Each element of this list is either an `If` instance that describes the filtering
+        condition for that loop, or `None` if no condition is applied in that loop.
+    operations : dict
+        A dictionary mapping each type of comprehension (e.g. list, array, etc.)
+        to the operation used for populating it.
path: pyccel/ast/functionalexpr.py
user: login: jalalium
body: Actually, it does not depend only on the stage
created_at: 2025-03-16T17:01:15Z
author_association: MEMBER
diff_hunk: @@ -1148,26 +1148,70 @@ def _visit_For(self, stmt):
     def _visit_comprehension(self, stmt):
 
         scope = self.create_new_loop_scope()
+        condition = None
 
         self._in_lhs_assign = True
         iterator = self._visit(stmt.target)
         self._in_lhs_assign = False
         iterable = self._visit(stmt.iter)
 
-        self.exit_loop_scope()
-
         if stmt.ifs:
-            errors.report("Cannot handle if statements in list comprehensions. List length cannot be calculated.\n" + PYCCEL_RESTRICTION_TODO,
-                    symbol=stmt, severity='error')
+            from functools import reduce
+            cond = reduce(PyccelAnd, [self._visit(if_cond)
+                                      for if_cond in stmt.ifs])
+            condition = If(IfSection(cond, CodeBlock([])))
 
-        expr = For(iterator, iterable, [], scope=scope)
+        self.exit_loop_scope()
+
+        expr = (For(iterator, iterable, [], scope=scope), condition)
         return expr
 
     def _visit_ListComp(self, stmt):
+        """
+        Converts a list comprehension statement into a `FunctionalFor` AST object.
+
+        This method translates the list comprehension into an equivalent `FunctionalFor`
+        
+        Parameters
+        ----------
+        stmt : ast.stmt
+            Object to visit of type X.
+
+        Returns
+        -------
+        pyccel.ast.functionalexpr.FunctionalFor
+            AST object which is the syntactic equivalent of the list comprehension.
+        """
+
+        def create_target_operations():
+            operations = {'list' : [], 'numpy_array' : []}
+            index = PyccelSymbol('_', is_temp=True)
+            args = [index]
+            target = IndexedElement(lhs, *args)
+            target = Assign(target, result)
+            assign1 = Assign(index, LiteralInteger(0))
+            assign1.set_current_ast(stmt)
+            operations['numpy_array'].append(assign1)
+            target.set_current_ast(stmt)
+            operations['numpy_array'].append(target)
+            assign2 = Assign(index, PyccelAdd(index, LiteralInteger(1)))
+            assign2.set_current_ast(stmt)
+            operations['numpy_array'].append(assign2)
+            operations['list'].append(DottedName(lhs, FunctionCall('append', [FunctionCallArgument(result)])))
+
+            return index, operations
+
 
         result = self._visit(stmt.elt)
+        output_type = None
 
-        generators = list(self._visit(stmt.generators))
+        comprehensions = list(self._visit(stmt.generators))
+        generators, conditions = zip(*comprehensions)
+        generators, conditions = map(list, [generators, conditions])
path: pyccel/parser/syntactic.py
user: login: jalalium
body: fixed!
created_at: 2025-03-16T17:01:40Z
author_association: MEMBER
diff_hunk: @@ -1222,40 +1264,25 @@ def _visit_ListComp(self, stmt):
             else:
                 raise NotImplementedError("A list comprehension cannot be unpacked")
 
-        parent = self._context[-2]
-        if isinstance(parent, ast.Call):
-            output_type = self._visit(parent.func)
-        else:
-            output_type = 'list'
-
-        index = PyccelSymbol('_', is_temp=True)
-
-        args = [index]
-        target = IndexedElement(lhs, *args)
-        target = Assign(target, result)
-        assign1 = Assign(index, LiteralInteger(0))
-        assign1.set_current_ast(stmt)
-        target.set_current_ast(stmt)
-        generators[-1].insert2body(target)
-        assign2 = Assign(index, PyccelAdd(index, LiteralInteger(1)))
-        assign2.set_current_ast(stmt)
-        generators[-1].insert2body(assign2)
-
-        indices = [generators[-1].target]
-        while len(generators) > 1:
-            F = generators.pop()
-            generators[-1].insert2body(F)
-            indices.append(generators[-1].target)
-        indices = indices[::-1]
+        indices = [generator.target for generator in generators]
+        index, operations = create_target_operations()
 
-        return FunctionalFor([assign1, generators[-1]],target.rhs, target.lhs,
-                             indices, index, target_type = output_type)
+        return FunctionalFor(generators, result, lhs,indices,index=index,
+                             conditions=conditions,
+                             target_type=output_type, operations=operations)
 
     def _visit_GeneratorExp(self, stmt):
+        conditions = []
+        generators = []
+        indices = []
+        condition = None
 
         result = self._visit(stmt.elt)
 
-        generators = self._visit(stmt.generators)
+        comprehensions = list(self._visit(stmt.generators))
+        generators, conditions = zip(*comprehensions)
+        generators, conditions = map(list, [generators, conditions])
path: pyccel/parser/syntactic.py
user: login: jalalium
body: fixed!
created_at: 2025-03-16T17:01:59Z
author_association: MEMBER
diff_hunk: @@ -4167,31 +4221,64 @@ def _visit_FunctionalFor(self, expr):
         # TODO [YG, 30.10.2020]:
         #  - Check if we should allow the possibility that is_stack_array=True
         # ...
-        lhs_symbol = expr.lhs.base
+        lhs_symbol = expr.lhs
         ne = []
         lhs = self._assign_lhs_variable(lhs_symbol, d_var, rhs=expr, new_expressions=ne)
         lhs_alloc = ne[0]
 
         if isinstance(target, PythonTuple) and not target.is_homogeneous:
-            errors.report(LIST_OF_TUPLES, symbol=expr, severity='error')
+            errors.report(LIST_OF_TUPLES, symbol=expr, severity='fatal')
 
         target.invalidate_node()
+        operations = []
+        assign = None
+        if target_type_name != 'list':
+            old_index   = expr.index
+            new_index   = self.scope.get_new_name()
+            expr.substitute(old_index, new_index)
path: pyccel/parser/semantic.py
user: login: jalalium
body: I checked #2107 but i don't follow, sorry. I also read your second comment.
You are right! `b = [_ for _, i in enumerate(a)]` wouldn't work if that's what you meant.
created_at: 2025-03-16T17:11:21Z
author_association: MEMBER
diff_hunk: @@ -4167,31 +4221,64 @@ def _visit_FunctionalFor(self, expr):
         # TODO [YG, 30.10.2020]:
         #  - Check if we should allow the possibility that is_stack_array=True
         # ...
-        lhs_symbol = expr.lhs.base
+        lhs_symbol = expr.lhs
         ne = []
         lhs = self._assign_lhs_variable(lhs_symbol, d_var, rhs=expr, new_expressions=ne)
         lhs_alloc = ne[0]
 
         if isinstance(target, PythonTuple) and not target.is_homogeneous:
-            errors.report(LIST_OF_TUPLES, symbol=expr, severity='error')
+            errors.report(LIST_OF_TUPLES, symbol=expr, severity='fatal')
 
         target.invalidate_node()
+        operations = []
+        assign = None
+        if target_type_name != 'list':
+            old_index   = expr.index
+            new_index   = self.scope.get_new_name()
+            expr.substitute(old_index, new_index)
path: pyccel/parser/semantic.py
user: login: EmilyBourne
body: > You are right! b = [_ for _, i in enumerate(a)] wouldn't work if that's what you meant.

No that is not what I meant. Maybe this is not the best example. I was trying to think of a case where a user has used `_` **and** you have `index = PyccelSymbol('_', is_temp=True)`. I think that this case would fail currently but would pass if you do:
```suggestion
            old_index   = expr.index
            new_index   = self.scope.get_new_name()
            expr.substitute(old_index, new_index, is_equivalent = lambda x,y: x is y)
```
created_at: 2025-03-16T17:20:40Z
author_association: MEMBER
diff_hunk: @@ -4167,31 +4221,64 @@ def _visit_FunctionalFor(self, expr):
         # TODO [YG, 30.10.2020]:
         #  - Check if we should allow the possibility that is_stack_array=True
         # ...
-        lhs_symbol = expr.lhs.base
+        lhs_symbol = expr.lhs
         ne = []
         lhs = self._assign_lhs_variable(lhs_symbol, d_var, rhs=expr, new_expressions=ne)
         lhs_alloc = ne[0]
 
         if isinstance(target, PythonTuple) and not target.is_homogeneous:
-            errors.report(LIST_OF_TUPLES, symbol=expr, severity='error')
+            errors.report(LIST_OF_TUPLES, symbol=expr, severity='fatal')
 
         target.invalidate_node()
+        operations = []
+        assign = None
+        if target_type_name != 'list':
+            old_index   = expr.index
+            new_index   = self.scope.get_new_name()
+            expr.substitute(old_index, new_index)
path: pyccel/parser/semantic.py
user: login: EmilyBourne
body: See the necessary change here:
https://github.com/pyccel/pyccel/pull/2107/files#diff-9ba0c438140113a543a41e16000b939989e841d4fc6a6a4112b5c8e5d8807524L3910-R3963
created_at: 2025-03-16T17:21:15Z
author_association: MEMBER
diff_hunk: @@ -4167,31 +4221,64 @@ def _visit_FunctionalFor(self, expr):
         # TODO [YG, 30.10.2020]:
         #  - Check if we should allow the possibility that is_stack_array=True
         # ...
-        lhs_symbol = expr.lhs.base
+        lhs_symbol = expr.lhs
         ne = []
         lhs = self._assign_lhs_variable(lhs_symbol, d_var, rhs=expr, new_expressions=ne)
         lhs_alloc = ne[0]
 
         if isinstance(target, PythonTuple) and not target.is_homogeneous:
-            errors.report(LIST_OF_TUPLES, symbol=expr, severity='error')
+            errors.report(LIST_OF_TUPLES, symbol=expr, severity='fatal')
 
         target.invalidate_node()
+        operations = []
+        assign = None
+        if target_type_name != 'list':
+            old_index   = expr.index
+            new_index   = self.scope.get_new_name()
+            expr.substitute(old_index, new_index)
path: pyccel/parser/semantic.py
user: login: EmilyBourne
body: If you use the default `is_equivalent` function then your substitution will substitute all PyccelSymbol('_'), if you use an `is` comparison then your substitution will substitute only the symbols which have exactly the same id as `old_index` (and are therefore sure to represent the same thing)
created_at: 2025-03-16T17:22:38Z
author_association: MEMBER
diff_hunk: @@ -4167,31 +4221,64 @@ def _visit_FunctionalFor(self, expr):
         # TODO [YG, 30.10.2020]:
         #  - Check if we should allow the possibility that is_stack_array=True
         # ...
-        lhs_symbol = expr.lhs.base
+        lhs_symbol = expr.lhs
         ne = []
         lhs = self._assign_lhs_variable(lhs_symbol, d_var, rhs=expr, new_expressions=ne)
         lhs_alloc = ne[0]
 
         if isinstance(target, PythonTuple) and not target.is_homogeneous:
-            errors.report(LIST_OF_TUPLES, symbol=expr, severity='error')
+            errors.report(LIST_OF_TUPLES, symbol=expr, severity='fatal')
 
         target.invalidate_node()
+        operations = []
+        assign = None
+        if target_type_name != 'list':
+            old_index   = expr.index
+            new_index   = self.scope.get_new_name()
+            expr.substitute(old_index, new_index)
path: pyccel/parser/semantic.py
user: login: EmilyBourne
body: Here is an example:
```python
import numpy as np

def f(b : 'float[:]'):
    a = np.array([i for i,_ in enumerate(b)])
```
On your branch this fails with:
```
|error [semantic]: tmp.py [4,17]| Object has already been defined with type 'int'. Type 'numpy.float64' in assignment is incompatible ('Dummy_0001=b[i]')
```
but with the suggested change it translates correctly
created_at: 2025-03-16T17:54:27Z
author_association: MEMBER
diff_hunk: @@ -1966,9 +1969,13 @@ def _assign_GeneratorComprehension(self, lhs_name, expr):
         elif isinstance(expr, FunctionalMin):
             val = math_constants['inf']
             d_var = self._infer_type(result)
+            if d_var['class_type'] is PythonNativeInt():
+                val = LiteralInteger(2**63-1)
path: pyccel/parser/semantic.py
user: login: jalalium
body: fixed
created_at: 2025-03-17T06:00:58Z
author_association: MEMBER
diff_hunk: @@ -4167,31 +4221,64 @@ def _visit_FunctionalFor(self, expr):
         # TODO [YG, 30.10.2020]:
         #  - Check if we should allow the possibility that is_stack_array=True
         # ...
-        lhs_symbol = expr.lhs.base
+        lhs_symbol = expr.lhs
         ne = []
         lhs = self._assign_lhs_variable(lhs_symbol, d_var, rhs=expr, new_expressions=ne)
         lhs_alloc = ne[0]
 
         if isinstance(target, PythonTuple) and not target.is_homogeneous:
-            errors.report(LIST_OF_TUPLES, symbol=expr, severity='error')
+            errors.report(LIST_OF_TUPLES, symbol=expr, severity='fatal')
 
         target.invalidate_node()
+        operations = []
+        assign = None
+        if target_type_name != 'list':
+            old_index   = expr.index
+            new_index   = self.scope.get_new_name()
+            expr.substitute(old_index, new_index)
path: pyccel/parser/semantic.py
user: login: jalalium
body: fixed
created_at: 2025-03-17T06:01:17Z
author_association: MEMBER
diff_hunk: @@ -4167,31 +4221,64 @@ def _visit_FunctionalFor(self, expr):
         # TODO [YG, 30.10.2020]:
         #  - Check if we should allow the possibility that is_stack_array=True
         # ...
-        lhs_symbol = expr.lhs.base
+        lhs_symbol = expr.lhs
         ne = []
         lhs = self._assign_lhs_variable(lhs_symbol, d_var, rhs=expr, new_expressions=ne)
         lhs_alloc = ne[0]
 
         if isinstance(target, PythonTuple) and not target.is_homogeneous:
-            errors.report(LIST_OF_TUPLES, symbol=expr, severity='error')
+            errors.report(LIST_OF_TUPLES, symbol=expr, severity='fatal')
 
         target.invalidate_node()
+        operations = []
+        assign = None
+        if target_type_name != 'list':
+            old_index   = expr.index
+            new_index   = self.scope.get_new_name()
+            expr.substitute(old_index, new_index)
+            array_ops = create_target_operations('numpy_array')
+            assign = array_ops[0]
+            assign.substitute(old_index, new_index)
+            assign = self._visit(array_ops[0])
+            array_ops = array_ops[1:]
+            for operation in array_ops:
+                operation.substitute(old_index, new_index)
+                operations.append(operation)
+            index = new_index
+            index = self._visit(index)
+        else:
+            index = None
+            operations.extend(create_target_operations('list'))
path: pyccel/parser/semantic.py
user: login: jalalium
body: fixed
created_at: 2025-03-17T06:01:28Z
author_association: MEMBER
diff_hunk: @@ -3970,90 +3977,140 @@ def _visit_For(self, expr):
             for_expr.end_annotation = expr.end_annotation
         return for_expr
 
-
     def _visit_FunctionalFor(self, expr):
-        old_index   = expr.index
-        new_index   = self.scope.get_new_name()
-        # Identity comparison is required to only find duplicates of the index caused
-        # by the construction of FunctionalFor.
-        expr.substitute(old_index, new_index, is_equivalent = lambda x,y: x is y)
+        """
+        Visit and transform a FunctionalFor AST node into an equivalent code block.
 
-        target  = expr.expr
-        index   = new_index
-        indices = [self.scope.get_expected_name(i) for i in expr.indices]
-        dims    = []
-        body    = expr.loops[1]
+        This method processes a `FunctionalFor` expression and transforms the loop structure
+        into a corresponding code block.
 
-        idx_subs = {}
-        #scope = self.create_new_loop_scope()
+        Parameters
+        ----------
+        expr : pyccel.ast.functionalexpr.FunctionalFor
+            The FunctionalFor AST node.
 
-        # The symbols created to represent unknown valued objects are temporary
+        Returns
+        -------
+        pyccel.ast.basic.CodeBlock
+            A code block containing the equivalent loops and necessary variable allocations for the given `FunctionalFor` expression.
+        """
+        target  = expr.expr
+        indices = []
+        dims = []
+        idx_subs = {}
         tmp_used_names = self.scope.all_used_symbols.copy()
         i = 0
-        while isinstance(body, For):
+        loops = list(expr.loops)
+
+        # Inner function to handle PythonNativeInt variables
+        def handle_int_loop_variable(var_name, var_scope):
+            indices.append(var_name)
+            var = self._create_variable(var_name, PythonNativeInt(), None, {},insertion_scope=var_scope)
+            dvar = self._infer_type(var)
+            variables.append((var, dvar))
path: pyccel/parser/semantic.py
user: login: jalalium
body: fixed!
created_at: 2025-03-17T06:07:38Z
author_association: MEMBER
diff_hunk: @@ -3970,90 +3977,140 @@ def _visit_For(self, expr):
             for_expr.end_annotation = expr.end_annotation
         return for_expr
 
-
     def _visit_FunctionalFor(self, expr):
-        old_index   = expr.index
-        new_index   = self.scope.get_new_name()
-        # Identity comparison is required to only find duplicates of the index caused
-        # by the construction of FunctionalFor.
-        expr.substitute(old_index, new_index, is_equivalent = lambda x,y: x is y)
+        """
+        Visit and transform a FunctionalFor AST node into an equivalent code block.
 
-        target  = expr.expr
-        index   = new_index
-        indices = [self.scope.get_expected_name(i) for i in expr.indices]
-        dims    = []
-        body    = expr.loops[1]
+        This method processes a `FunctionalFor` expression and transforms the loop structure
+        into a corresponding code block.
 
-        idx_subs = {}
-        #scope = self.create_new_loop_scope()
+        Parameters
+        ----------
+        expr : pyccel.ast.functionalexpr.FunctionalFor
+            The FunctionalFor AST node.
 
-        # The symbols created to represent unknown valued objects are temporary
+        Returns
+        -------
+        pyccel.ast.basic.CodeBlock
+            A code block containing the equivalent loops and necessary variable allocations for the given `FunctionalFor` expression.
+        """
+        target  = expr.expr
+        indices = []
+        dims = []
+        idx_subs = {}
         tmp_used_names = self.scope.all_used_symbols.copy()
         i = 0
-        while isinstance(body, For):
+        loops = list(expr.loops)
+
+        # Inner function to handle PythonNativeInt variables
+        def handle_int_loop_variable(var_name, var_scope):
+            indices.append(var_name)
+            var = self._create_variable(var_name, PythonNativeInt(), None, {},insertion_scope=var_scope)
+            dvar = self._infer_type(var)
+            variables.append((var, dvar))
+
+        # Inner function to handle iterable variables
+        def handle_iterable_variable(var_name, element, var_scope):
+            indices.append(var_name)
+            dvar = self._infer_type(element)
+            class_type = dvar.pop('class_type')
+            if class_type.rank > 0:
+                class_type = class_type.switch_rank(class_type.rank - 1)
+                dvar['shape'] = dvar['shape'][1:]
+            if class_type.rank == 0:
+                dvar['shape'] = None
+                dvar['memory_handling'] = 'stack'
+            var = self._create_variable(var_name, class_type, None, dvar, insertion_scope=var_scope)
+            dvar['class_type'] = class_type
+            variables.append((var, dvar))
+
+        def create_target_operations(target):
+            # this can be used to add opertations for set/dict comprehension
+            assert target in expr.operations
+            return expr.operations[target]
path: pyccel/parser/semantic.py
user: login: jalalium
body: fixed through the `FunctionalFor` class
created_at: 2025-03-17T06:08:17Z
author_association: MEMBER
diff_hunk: @@ -3970,90 +3977,140 @@ def _visit_For(self, expr):
             for_expr.end_annotation = expr.end_annotation
         return for_expr
 
-
     def _visit_FunctionalFor(self, expr):
-        old_index   = expr.index
-        new_index   = self.scope.get_new_name()
-        # Identity comparison is required to only find duplicates of the index caused
-        # by the construction of FunctionalFor.
-        expr.substitute(old_index, new_index, is_equivalent = lambda x,y: x is y)
+        """
+        Visit and transform a FunctionalFor AST node into an equivalent code block.
 
-        target  = expr.expr
-        index   = new_index
-        indices = [self.scope.get_expected_name(i) for i in expr.indices]
-        dims    = []
-        body    = expr.loops[1]
+        This method processes a `FunctionalFor` expression and transforms the loop structure
+        into a corresponding code block.
 
-        idx_subs = {}
-        #scope = self.create_new_loop_scope()
+        Parameters
+        ----------
+        expr : pyccel.ast.functionalexpr.FunctionalFor
+            The FunctionalFor AST node.
 
-        # The symbols created to represent unknown valued objects are temporary
+        Returns
+        -------
+        pyccel.ast.basic.CodeBlock
+            A code block containing the equivalent loops and necessary variable allocations for the given `FunctionalFor` expression.
+        """
+        target  = expr.expr
+        indices = []
+        dims = []
+        idx_subs = {}
         tmp_used_names = self.scope.all_used_symbols.copy()
         i = 0
-        while isinstance(body, For):
+        loops = list(expr.loops)
+
+        # Inner function to handle PythonNativeInt variables
+        def handle_int_loop_variable(var_name, var_scope):
+            indices.append(var_name)
+            var = self._create_variable(var_name, PythonNativeInt(), None, {},insertion_scope=var_scope)
+            dvar = self._infer_type(var)
+            variables.append((var, dvar))
+
+        # Inner function to handle iterable variables
+        def handle_iterable_variable(var_name, element, var_scope):
+            indices.append(var_name)
+            dvar = self._infer_type(element)
+            class_type = dvar.pop('class_type')
+            if class_type.rank > 0:
+                class_type = class_type.switch_rank(class_type.rank - 1)
+                dvar['shape'] = dvar['shape'][1:]
+            if class_type.rank == 0:
+                dvar['shape'] = None
+                dvar['memory_handling'] = 'stack'
+            var = self._create_variable(var_name, class_type, None, dvar, insertion_scope=var_scope)
+            dvar['class_type'] = class_type
+            variables.append((var, dvar))
+
+        def create_target_operations(target):
+            # this can be used to add opertations for set/dict comprehension
+            assert target in expr.operations
+            return expr.operations[target]
+
+        for loop, condition in zip(loops, expr.conditions):
+            if condition:
+                loop.insert2body(condition)
+
+        while len(loops) > 1:
+            outter_loop = loops.pop()
+            inserted_into = loops[-1]
+            if inserted_into.body.body:
+                inserted_into.body.body[0].blocks[0].body.insert2body(outter_loop)
+            else:
+                inserted_into.insert2body(outter_loop)
+
+        body = loops[0]
 
-            stop  = None
+        while isinstance(body, (For, If)):
+
+            if isinstance(body, If):
+                body = None if not body.blocks[0].body.body else body.blocks[0].body.body[0]
+                continue
+
+            stop = None
             start = LiteralInteger(0)
-            step  = LiteralInteger(1)
-            var   = indices[i]
-            i += 1
-            a     = self._visit(body.iterable)
+            step = LiteralInteger(1)
+            variables = []
+            a = self._get_iterable(self._visit(body.iterable))
             if isinstance(a, PythonRange):
-                var   = self._create_variable(var, PythonNativeInt(), start, {})
-                dvar  = self._infer_type(var)
-                stop  = a.stop
+                var_name = self.scope.get_expected_name(expr.indices[i])
+                handle_int_loop_variable(var_name, body.scope)
                 start = a.start
+                stop  = a.stop
                 step  = a.step
 
-            elif isinstance(a, (PythonZip, PythonEnumerate)):
-                dvar  = self._infer_type(a.element)
-                class_type = dvar.pop('class_type')
-                if class_type.rank > 0:
-                    class_type = class_type.switch_rank(class_type.rank-1)
-                    dvar['shape'] = (dvar['shape'])[1:]
-                if class_type.rank == 0:
-                    dvar['shape'] = None
-                    dvar['memory_handling'] = 'stack'
-                var  = Variable(class_type, var, **dvar)
+            elif isinstance(a, PythonEnumerate):
+                var_name1 = self.scope.get_expected_name(expr.indices[i][0])
+                var_name2 = self.scope.get_expected_name(expr.indices[i][1])
+                handle_int_loop_variable(var_name1, body.scope)
+                handle_iterable_variable(var_name2, a.element, body.scope)
                 stop = a.element.shape[0]
 
-            elif isinstance(a, Variable):
-                dvar  = self._infer_type(a)
-                class_type = dvar.pop('class_type')
-                if class_type.rank > 0:
-                    class_type = class_type.switch_rank(class_type.rank-1)
-                    dvar['shape'] = (dvar['shape'])[1:]
-                if class_type.rank == 0:
-                    dvar['shape'] = None
-                    dvar['memory_handling'] = 'stack'
-                var  = Variable(class_type, var, **dvar)
-                stop = a.shape[0]
+            elif isinstance(a, PythonZip):
+                for idx, arg in enumerate(a.args):
+                    var = self.scope.get_expected_name(expr.indices[i][idx])
+                    handle_iterable_variable(var, arg, body.scope)
+                stop = a.get_range().stop
+
+            elif isinstance(a, VariableIterator):
+                var = self.scope.get_expected_name(expr.indices[i])
+                handle_iterable_variable(var, a.variable, body.scope)
+                stop = a.variable.shape[0]
 
             else:
                 errors.report(PYCCEL_RESTRICTION_TODO,
                               bounding_box=(self.current_ast_node.lineno, self.current_ast_node.col_offset),
                               severity='fatal')
-            existing_var = self.scope.find(var.name, 'variables')
-            if existing_var:
-                if self._infer_type(existing_var) != dvar:
-                    errors.report(f"Variable {var} already exists with different type",
-                            symbol = expr, severity='error')
-            else:
-                self.scope.insert_variable(var)
-            step.invalidate_node()
+
+            for var, dvar in variables:
+                existing_var = self.scope.find(var.name, 'variables')
+                if var.name == expr.lhs:
+                    errors.report(f"Variable {var} has the same name as the left hand side",
+                            symbol = expr, severity='fatal')
+                if existing_var or var.name ==  expr.lhs:
+                    if self._infer_type(existing_var)['class_type'] != dvar['class_type']:
+                        return errors.report(f"Variable {var} already exists with different type",
+                                symbol = expr, severity='error')
path: pyccel/parser/semantic.py
user: login: jalalium
body: Yes this is raised for examples like:
```Python
a = [a for a in range(10)]
```
created_at: 2025-03-17T06:09:54Z
author_association: MEMBER
diff_hunk: @@ -331,6 +331,38 @@ def __and__(self, other):
         else:
             return NotImplemented
 
+    @property
+    def min_value(self):
+        """
+        Get the minimum representable value for this datatype.
+
+        This property returns the smallest value that can be represented by
+        this numeric datatype. It is useful for validation, boundary checks,
+        and ensuring that generated code respects the limits of the type.
+
+        Returns
+        -------
+        bool
+            The minimum value that can be represented.
+        """
+        return False
+
path: pyccel/ast/datatypes.py
user: login: pyccel-bot[bot]
body: This code isn't tested. Please can you take a look
created_at: 2025-03-17T06:33:10Z
author_association: NONE
diff_hunk: @@ -331,6 +331,38 @@ def __and__(self, other):
         else:
             return NotImplemented
 
+    @property
+    def min_value(self):
+        """
+        Get the minimum representable value for this datatype.
+
+        This property returns the smallest value that can be represented by
+        this numeric datatype. It is useful for validation, boundary checks,
+        and ensuring that generated code respects the limits of the type.
+
+        Returns
+        -------
+        bool
+            The minimum value that can be represented.
+        """
+        return False
+
+    @property
+    def max_value(self):
+        """
+        Get the maximum representable value for this datatype.
+
+        This property returns the smallest value that can be represented by
+        this numeric datatype. It is useful for validation, boundary checks,
+        and ensuring that generated code respects the limits of the type.
+
+        Returns
+        -------
+        bool
+            The maximum value that can be represented.
+        """
+        return True
+
path: pyccel/ast/datatypes.py
user: login: pyccel-bot[bot]
body: This code isn't tested. Please can you take a look
created_at: 2025-03-17T06:33:10Z
author_association: NONE
diff_hunk: @@ -378,6 +443,39 @@ def __add__(self, other):
         else:
             return NotImplemented
 
+    @property
+    def min_value(self):
+        """
+        Get the minimum representable value for this datatype.
+
+        This property returns the smallest value that can be represented by
+        this numeric datatype. It is useful for validation, boundary checks,
+        and ensuring that generated code respects the limits of the type.
+
+        Returns
+        -------
+        float
+            The minimum value that can be represented.
+        """
+        return numpy.finfo(float).min
+
path: pyccel/ast/datatypes.py
user: login: pyccel-bot[bot]
body: This code isn't tested. Please can you take a look
created_at: 2025-03-17T06:33:10Z
author_association: NONE
diff_hunk: @@ -378,6 +443,39 @@ def __add__(self, other):
         else:
             return NotImplemented
 
+    @property
+    def min_value(self):
+        """
+        Get the minimum representable value for this datatype.
+
+        This property returns the smallest value that can be represented by
+        this numeric datatype. It is useful for validation, boundary checks,
+        and ensuring that generated code respects the limits of the type.
+
+        Returns
+        -------
+        float
+            The minimum value that can be represented.
+        """
+        return numpy.finfo(float).min
+
+    @property
+    def max_value(self):
+        """
+        Get the maximum representable value for this datatype.
+
+        This property returns the smallest value that can be represented by
+        this numeric datatype. It is useful for validation, boundary checks,
+        and ensuring that generated code respects the limits of the type.
+
+        Returns
+        -------
+        float
+            The maximum value that can be represented.
+        """
+        return numpy.finfo(float).max
+
+
path: pyccel/ast/datatypes.py
user: login: pyccel-bot[bot]
body: This code isn't tested. Please can you take a look
created_at: 2025-03-17T06:33:10Z
author_association: NONE
diff_hunk: @@ -118,6 +118,37 @@ class NumpyInt8Type(NumpyIntType):
     _name = 'numpy.int8'
     _precision = 1
 
+    @property
+    def min_value(self):
+        """
+        Get the minimum representable value for this datatype.
+
+        This property returns the smallest value that can be represented by
+        this numeric datatype. It is useful for validation, boundary checks,
+        and ensuring that generated code respects the limits of the type.
+
+        Returns
+        -------
+        int8
+            The minimum value that can be represented.
+        """
+        return np.iinfo(np.int8).min
+
path: pyccel/ast/numpytypes.py
user: login: pyccel-bot[bot]
body: This code isn't tested. Please can you take a look
created_at: 2025-03-17T06:33:10Z
author_association: NONE
diff_hunk: @@ -118,6 +118,37 @@ class NumpyInt8Type(NumpyIntType):
     _name = 'numpy.int8'
     _precision = 1
 
+    @property
+    def min_value(self):
+        """
+        Get the minimum representable value for this datatype.
+
+        This property returns the smallest value that can be represented by
+        this numeric datatype. It is useful for validation, boundary checks,
+        and ensuring that generated code respects the limits of the type.
+
+        Returns
+        -------
+        int8
+            The minimum value that can be represented.
+        """
+        return np.iinfo(np.int8).min
+
+    @property
+    def max_value(self):
+        """
+        Get the maximum representable value for this datatype.
+
+        This property returns the smallest value that can be represented by
+        this numeric datatype. It is useful for validation, boundary checks,
+        and ensuring that generated code respects the limits of the type.
+
+        Returns
+        -------
+        int8
+            The maximum value that can be represented.
+        """
+        return np.iinfo(np.int8).max
path: pyccel/ast/numpytypes.py
user: login: pyccel-bot[bot]
body: This code isn't tested. Please can you take a look
created_at: 2025-03-17T06:33:10Z
author_association: NONE
diff_hunk: @@ -129,6 +160,37 @@ class NumpyInt16Type(NumpyIntType):
     _name = 'numpy.int16'
     _precision = 2
 
+    @property
+    def min_value(self):
+        """
+        Get the minimum representable value for this datatype.
+
+        This property returns the smallest value that can be represented by
+        this numeric datatype. It is useful for validation, boundary checks,
+        and ensuring that generated code respects the limits of the type.
+
+        Returns
+        -------
+        int16
+            The minimum value that can be represented.
+        """
+        return np.iinfo(np.int16).min
+
path: pyccel/ast/numpytypes.py
user: login: pyccel-bot[bot]
body: This code isn't tested. Please can you take a look
created_at: 2025-03-17T06:33:10Z
author_association: NONE
diff_hunk: @@ -129,6 +160,37 @@ class NumpyInt16Type(NumpyIntType):
     _name = 'numpy.int16'
     _precision = 2
 
+    @property
+    def min_value(self):
+        """
+        Get the minimum representable value for this datatype.
+
+        This property returns the smallest value that can be represented by
+        this numeric datatype. It is useful for validation, boundary checks,
+        and ensuring that generated code respects the limits of the type.
+
+        Returns
+        -------
+        int16
+            The minimum value that can be represented.
+        """
+        return np.iinfo(np.int16).min
+
+    @property
+    def max_value(self):
+        """
+        Get the maximum representable value for this datatype.
+
+        This property returns the smallest value that can be represented by
+        this numeric datatype. It is useful for validation, boundary checks,
+        and ensuring that generated code respects the limits of the type.
+
+        Returns
+        -------
+        int16
+            The maximum value that can be represented.
+        """
+        return np.iinfo(np.int16).max
path: pyccel/ast/numpytypes.py
user: login: pyccel-bot[bot]
body: This code isn't tested. Please can you take a look
created_at: 2025-03-17T06:33:10Z
author_association: NONE
diff_hunk: @@ -140,6 +202,37 @@ class NumpyInt32Type(NumpyIntType):
     _name = 'numpy.int32'
     _precision = 4
 
+    @property
+    def min_value(self):
+        """
+        Get the minimum representable value for this datatype.
+
+        This property returns the smallest value that can be represented by
+        this numeric datatype. It is useful for validation, boundary checks,
+        and ensuring that generated code respects the limits of the type.
+
+        Returns
+        -------
+        int32
+            The minimum value that can be represented.
+        """
+        return np.iinfo(np.int32).min
+
path: pyccel/ast/numpytypes.py
user: login: pyccel-bot[bot]
body: This code isn't tested. Please can you take a look
created_at: 2025-03-17T06:33:10Z
author_association: NONE
diff_hunk: @@ -140,6 +202,37 @@ class NumpyInt32Type(NumpyIntType):
     _name = 'numpy.int32'
     _precision = 4
 
+    @property
+    def min_value(self):
+        """
+        Get the minimum representable value for this datatype.
+
+        This property returns the smallest value that can be represented by
+        this numeric datatype. It is useful for validation, boundary checks,
+        and ensuring that generated code respects the limits of the type.
+
+        Returns
+        -------
+        int32
+            The minimum value that can be represented.
+        """
+        return np.iinfo(np.int32).min
+
+    @property
+    def max_value(self):
+        """
+        Get the maximum representable value for this datatype.
+
+        This property returns the smallest value that can be represented by
+        this numeric datatype. It is useful for validation, boundary checks,
+        and ensuring that generated code respects the limits of the type.
+
+        Returns
+        -------
+        int32
+            The maximum value that can be represented.
+        """
+        return np.iinfo(np.int32).max
path: pyccel/ast/numpytypes.py
user: login: pyccel-bot[bot]
body: This code isn't tested. Please can you take a look
created_at: 2025-03-17T06:33:11Z
author_association: NONE
diff_hunk: @@ -151,6 +244,38 @@ class NumpyInt64Type(NumpyIntType):
     _name = 'numpy.int64'
     _precision = 8
 
+    @property
+    def min_value(self):
+        """
+        Get the minimum representable value for this datatype.
+
+        This property returns the smallest value that can be represented by
+        this numeric datatype. It is useful for validation, boundary checks,
+        and ensuring that generated code respects the limits of the type.
+
+        Returns
+        -------
+        int64
+            The minimum value that can be represented.
+        """
+        return np.iinfo(np.int64).min
+
path: pyccel/ast/numpytypes.py
user: login: pyccel-bot[bot]
body: This code isn't tested. Please can you take a look
created_at: 2025-03-17T06:33:11Z
author_association: NONE
diff_hunk: @@ -151,6 +244,38 @@ class NumpyInt64Type(NumpyIntType):
     _name = 'numpy.int64'
     _precision = 8
 
+    @property
+    def min_value(self):
+        """
+        Get the minimum representable value for this datatype.
+
+        This property returns the smallest value that can be represented by
+        this numeric datatype. It is useful for validation, boundary checks,
+        and ensuring that generated code respects the limits of the type.
+
+        Returns
+        -------
+        int64
+            The minimum value that can be represented.
+        """
+        return np.iinfo(np.int64).min
+
+    @property
+    def max_value(self):
+        """
+        Get the maximum representable value for this datatype.
+
+        This property returns the smallest value that can be represented by
+        this numeric datatype. It is useful for validation, boundary checks,
+        and ensuring that generated code respects the limits of the type.
+
+        Returns
+        -------
+        int64
+            The maximum value that can be represented.
+        """
+        return np.iinfo(np.int64).max
+
path: pyccel/ast/numpytypes.py
user: login: pyccel-bot[bot]
body: This code isn't tested. Please can you take a look
created_at: 2025-03-17T06:33:11Z
author_association: NONE
diff_hunk: @@ -164,6 +289,37 @@ class NumpyFloat32Type(NumpyNumericType):
     _primitive_type = PrimitiveFloatingPointType()
     _precision = 4
 
+    @property
+    def min_value(self):
+        """
+        Get the minimum representable value for this datatype.
+
+        This property returns the smallest value that can be represented by
+        this numeric datatype. It is useful for validation, boundary checks,
+        and ensuring that generated code respects the limits of the type.
+
+        Returns
+        -------
+        float32
+            The minimum value that can be represented.
+        """
+        return np.finfo(np.float32).min
+
path: pyccel/ast/numpytypes.py
user: login: pyccel-bot[bot]
body: This code isn't tested. Please can you take a look
created_at: 2025-03-17T06:33:11Z
author_association: NONE
diff_hunk: @@ -164,6 +289,37 @@ class NumpyFloat32Type(NumpyNumericType):
     _primitive_type = PrimitiveFloatingPointType()
     _precision = 4
 
+    @property
+    def min_value(self):
+        """
+        Get the minimum representable value for this datatype.
+
+        This property returns the smallest value that can be represented by
+        this numeric datatype. It is useful for validation, boundary checks,
+        and ensuring that generated code respects the limits of the type.
+
+        Returns
+        -------
+        float32
+            The minimum value that can be represented.
+        """
+        return np.finfo(np.float32).min
+
+    @property
+    def max_value(self):
+        """
+        Get the maximum representable value for this datatype.
+
+        This property returns the smallest value that can be represented by
+        this numeric datatype. It is useful for validation, boundary checks,
+        and ensuring that generated code respects the limits of the type.
+
+        Returns
+        -------
+        float32
+            The maximum value that can be represented.
+        """
+        return np.finfo(np.float32).max
path: pyccel/ast/numpytypes.py
user: login: pyccel-bot[bot]
body: This code isn't tested. Please can you take a look
created_at: 2025-03-17T06:33:11Z
author_association: NONE
diff_hunk: @@ -331,6 +331,38 @@ def __and__(self, other):
         else:
             return NotImplemented
 
+    @property
+    def min_value(self):
+        """
+        Get the minimum representable value for this datatype.
+
+        This property returns the smallest value that can be represented by
+        this numeric datatype. It is useful for validation, boundary checks,
+        and ensuring that generated code respects the limits of the type.
+
+        Returns
+        -------
+        bool
+            The minimum value that can be represented.
+        """
+        return False
+
path: pyccel/ast/datatypes.py
user: login: pyccel-bot[bot]
body: This seems to be fixed now! Thank you for looking into this. Please resolve this conversation.

created_at: 2025-03-17T07:44:40Z
author_association: NONE
diff_hunk: @@ -331,6 +331,38 @@ def __and__(self, other):
         else:
             return NotImplemented
 
+    @property
+    def min_value(self):
+        """
+        Get the minimum representable value for this datatype.
+
+        This property returns the smallest value that can be represented by
+        this numeric datatype. It is useful for validation, boundary checks,
+        and ensuring that generated code respects the limits of the type.
+
+        Returns
+        -------
+        bool
+            The minimum value that can be represented.
+        """
+        return False
+
+    @property
+    def max_value(self):
+        """
+        Get the maximum representable value for this datatype.
+
+        This property returns the smallest value that can be represented by
+        this numeric datatype. It is useful for validation, boundary checks,
+        and ensuring that generated code respects the limits of the type.
+
+        Returns
+        -------
+        bool
+            The maximum value that can be represented.
+        """
+        return True
+
path: pyccel/ast/datatypes.py
user: login: pyccel-bot[bot]
body: This seems to be fixed now! Thank you for looking into this. Please resolve this conversation.

created_at: 2025-03-17T07:44:41Z
author_association: NONE
diff_hunk: @@ -378,6 +443,39 @@ def __add__(self, other):
         else:
             return NotImplemented
 
+    @property
+    def min_value(self):
+        """
+        Get the minimum representable value for this datatype.
+
+        This property returns the smallest value that can be represented by
+        this numeric datatype. It is useful for validation, boundary checks,
+        and ensuring that generated code respects the limits of the type.
+
+        Returns
+        -------
+        float
+            The minimum value that can be represented.
+        """
+        return numpy.finfo(float).min
+
path: pyccel/ast/datatypes.py
user: login: pyccel-bot[bot]
body: This seems to be fixed now! Thank you for looking into this. Please resolve this conversation.

created_at: 2025-03-17T07:44:42Z
author_association: NONE
diff_hunk: @@ -378,6 +443,39 @@ def __add__(self, other):
         else:
             return NotImplemented
 
+    @property
+    def min_value(self):
+        """
+        Get the minimum representable value for this datatype.
+
+        This property returns the smallest value that can be represented by
+        this numeric datatype. It is useful for validation, boundary checks,
+        and ensuring that generated code respects the limits of the type.
+
+        Returns
+        -------
+        float
+            The minimum value that can be represented.
+        """
+        return numpy.finfo(float).min
+
+    @property
+    def max_value(self):
+        """
+        Get the maximum representable value for this datatype.
+
+        This property returns the smallest value that can be represented by
+        this numeric datatype. It is useful for validation, boundary checks,
+        and ensuring that generated code respects the limits of the type.
+
+        Returns
+        -------
+        float
+            The maximum value that can be represented.
+        """
+        return numpy.finfo(float).max
+
+
path: pyccel/ast/datatypes.py
user: login: pyccel-bot[bot]
body: This seems to be fixed now! Thank you for looking into this. Please resolve this conversation.

created_at: 2025-03-17T07:44:43Z
author_association: NONE
diff_hunk: @@ -358,6 +358,39 @@ def __and__(self, other):
         else:
             return NotImplemented
 
+    @property
+    def min_value(self):
+        """
+        Get the minimum representable value for this datatype.
+
+        This property returns the smallest value that can be represented by
+        this numeric datatype. It is useful for validation, boundary checks,
+        and ensuring that generated code respects the limits of the type.
+
+        Returns
+        -------
+        int
+            The minimum value that can be represented.
+        """
+        return numpy.iinfo(int).min
path: pyccel/ast/datatypes.py
user: login: EmilyBourne
body: This new property needs documenting here: https://github.com/pyccel/pyccel/blob/devel/developer_docs/type_inference.md#data-types-in-pyccel. Given the urgency of this PR I think it is ok if you create an issue and add the documentation in a second PR
created_at: 2025-03-17T08:01:22Z
author_association: MEMBER
diff_hunk: @@ -56,9 +56,16 @@ class FunctionalFor(TypedAstNode):
     target_type : PyccelSymbol, optional
         The type of the result of the functional for. This is useful at
         the syntactic stage to pass along the final type of the lhs (list/set/array/etc).
+    conditions : list[If|None], optional
+        A list of filter conditions corresponding to each for-loop in the comprehension.
+        Each element of this list is either an `If` instance that describes the filtering
+        condition for that loop, or `None` if no condition is applied in that loop.
path: pyccel/ast/functionalexpr.py
user: login: EmilyBourne
body: What happens if `conditions` is not provided? Should the code convert this to `[None]`?
created_at: 2025-03-17T08:03:38Z
author_association: MEMBER
diff_hunk: @@ -474,6 +474,8 @@ def convert_to_literal(value, dtype = None):
     if isinstance(primitive_type, PrimitiveIntegerType):
         if value >= 0:
             literal_val = LiteralInteger(value, dtype)
+        elif value == -9223372036854775808:
+            literal_val =  PyccelMinus(PyccelUnarySub(LiteralInteger(-value-1, dtype)), LiteralInteger(1))
path: pyccel/ast/literals.py
user: login: EmilyBourne
body: :vomiting_face: 
created_at: 2025-03-17T08:04:44Z
author_association: MEMBER
diff_hunk: @@ -474,6 +474,8 @@ def convert_to_literal(value, dtype = None):
     if isinstance(primitive_type, PrimitiveIntegerType):
         if value >= 0:
             literal_val = LiteralInteger(value, dtype)
+        elif value == -9223372036854775808:
+            literal_val =  PyccelMinus(PyccelUnarySub(LiteralInteger(-value-1, dtype)), LiteralInteger(1))
path: pyccel/ast/literals.py
user: login: EmilyBourne
body: Why is this necessary? Don't integers in Python adapt their precision to store any number?
created_at: 2025-03-17T08:06:00Z
author_association: MEMBER
diff_hunk: @@ -118,7 +118,6 @@ class NumpyInt8Type(NumpyIntType):
     _name = 'numpy.int8'
     _precision = 1
 
-
 class NumpyInt16Type(NumpyIntType):
path: pyccel/ast/numpytypes.py
user: login: EmilyBourne
body: Bad change. This is unnecessary for this PR and breaks PEP recommendations : https://peps.python.org/pep-0008/#blank-lines

> Surround top-level function and class definitions with two blank lines.
created_at: 2025-03-17T08:07:27Z
author_association: MEMBER
diff_hunk: @@ -176,6 +173,37 @@ class NumpyFloat64Type(NumpyNumericType):
     _primitive_type = PrimitiveFloatingPointType()
     _precision = 8
 
+    @property
+    def min_value(self):
+        """
+        Get the minimum representable value for this datatype.
+
+        This property returns the smallest value that can be represented by
+        this numeric datatype. It is useful for validation, boundary checks,
+        and ensuring that generated code respects the limits of the type.
+
+        Returns
+        -------
+        float64
+            The minimum value that can be represented.
+        """
+        return np.finfo(np.float64).min
path: pyccel/ast/numpytypes.py
user: login: EmilyBourne
body: Why is this only needed for Float64?

To be honest I think I would have preferred if these `min_value`, `max_value` methods were left for a second PR. I would have been happier with the hard coded values and an issue to say that this needs to be done cleanly
created_at: 2025-03-17T08:08:43Z
author_association: MEMBER
diff_hunk: @@ -2311,7 +2311,8 @@ def _print_Allocate(self, expr):
             elif expr.alloc_type == 'reserve':
                 var_code = self._print(expr.variable)
                 size_code = self._print(expr.shape[0])
-                return f'call {var_code} % reserve({size_code})\n'
+                return (f'call {var_code} % clear()\n'
+                        f'call {var_code} % reserve({size_code})\n')
path: pyccel/codegen/printing/fcode.py
user: login: EmilyBourne
body: Can the `clear` call be avoided if the status is `unallocated`?
created_at: 2025-03-17T08:09:21Z
author_association: MEMBER
diff_hunk: @@ -660,15 +666,20 @@ def _print_For(self, expr):
     def _print_FunctionalFor(self, expr):
         body, iterators = self._find_functional_expr_and_iterables(expr)
         lhs = self._print(expr.lhs)
-        body = self._print(body.rhs)
-        for_loops = ' '.join(['for {} in {}'.format(self._print(idx), self._print(iters))
-                        for idx, iters in zip(expr.indices, iterators)])
+        condition = ''
+        if isinstance(body, Assign):
+            body = self._print(body.rhs)
+        else:
+            assert isinstance(body, ListAppend)
+            body = self._print(body.args[0])
 
-        name = self._aliases.get(type(expr),'array')
-        if name == 'array':
-            self.add_import(Import('numpy', [AsName(NumpyArray, 'array')]))
+        for_loops = ' '.join(f'for {self._print(idx)} in {self._print(iters)}{" if " + self._print(condition.blocks[0].condition) if condition else ""}'
+                             for idx, iters, condition in zip(expr.indices, iterators, expr.conditions))
 
-        return '{} = {}([{} {}])\n'.format(lhs, name, body, for_loops)
+        if isinstance(expr.class_type, NumpyNDArrayType):
+            self.add_import(Import('numpy', [AsName(NumpyArray, 'array')]))
+            return f'{lhs} = array([{body} {for_loops} {condition}])\n'
path: pyccel/codegen/printing/pycode.py
user: login: EmilyBourne
body: Can `_get_numpy_name` be used here to ensure the expected name?
```suggestion
        if isinstance(expr.class_type, NumpyNDArrayType):
            array = self._get_numpy_name(NumpyArray)
            return f'{lhs} = array([{body} {for_loops} {condition}])\n'
```
created_at: 2025-03-17T08:12:05Z
author_association: MEMBER
diff_hunk: @@ -3970,90 +3973,129 @@ def _visit_For(self, expr):
             for_expr.end_annotation = expr.end_annotation
         return for_expr
 
-
     def _visit_FunctionalFor(self, expr):
-        old_index   = expr.index
-        new_index   = self.scope.get_new_name()
-        # Identity comparison is required to only find duplicates of the index caused
-        # by the construction of FunctionalFor.
-        expr.substitute(old_index, new_index, is_equivalent = lambda x,y: x is y)
+        """
+        Visit and transform a FunctionalFor AST node into an equivalent code block.
 
-        target  = expr.expr
-        index   = new_index
-        indices = [self.scope.get_expected_name(i) for i in expr.indices]
-        dims    = []
-        body    = expr.loops[1]
+        This method processes a `FunctionalFor` expression and transforms the loop structure
+        into a corresponding code block.
 
-        idx_subs = {}
-        #scope = self.create_new_loop_scope()
+        Parameters
+        ----------
+        expr : pyccel.ast.functionalexpr.FunctionalFor
+            The FunctionalFor AST node.
 
-        # The symbols created to represent unknown valued objects are temporary
+        Returns
+        -------
+        pyccel.ast.basic.CodeBlock
+            A code block containing the equivalent loops and necessary variable allocations for the given `FunctionalFor` expression.
+        """
+        target  = expr.expr
+        indices = []
+        dims = []
+        idx_subs = {}
         tmp_used_names = self.scope.all_used_symbols.copy()
         i = 0
-        while isinstance(body, For):
+        loops = list(expr.loops)
+
+        # Inner function to handle PythonNativeInt variables
+        def handle_int_loop_variable(var_name, var_scope):
+            indices.append(var_name)
+            var = self._create_variable(var_name, PythonNativeInt(), None, {}, insertion_scope=var_scope)
+            dvar = self._infer_type(var)
+            return var, dvar
+
+        # Inner function to handle iterable variables
+        def handle_iterable_variable(var_name, element, var_scope):
+            indices.append(var_name)
+            dvar = self._infer_type(element)
+            class_type = dvar.pop('class_type')
+            if class_type.rank > 0:
+                class_type = class_type.switch_rank(class_type.rank - 1)
+                dvar['shape'] = dvar['shape'][1:]
+            if class_type.rank == 0:
+                dvar['shape'] = None
+                dvar['memory_handling'] = 'stack'
+            var = self._create_variable(var_name, class_type, None, dvar, insertion_scope=var_scope)
+            dvar['class_type'] = class_type
+            return var, dvar
+
+        for loop, condition in zip(loops, expr.conditions):
+            if condition:
+                loop.insert2body(condition)
+
+        while len(loops) > 1:
+            outer_loop = loops.pop()
+            inserted_into = loops[-1]
+            if inserted_into.body.body:
+                inserted_into.body.body[0].blocks[0].body.insert2body(outer_loop)
+            else:
+                inserted_into.insert2body(outer_loop)
+
+        body = loops[0]
+
+        while isinstance(body, (For, If)):
 
-            stop  = None
+            if isinstance(body, If):
+                body = None if not body.blocks[0].body.body else body.blocks[0].body.body[0]
+                continue
+
+            stop = None
             start = LiteralInteger(0)
-            step  = LiteralInteger(1)
-            var   = indices[i]
-            i += 1
-            a     = self._visit(body.iterable)
+            step = LiteralInteger(1)
+            variables = []
+            a = self._get_iterable(self._visit(body.iterable))
             if isinstance(a, PythonRange):
-                var   = self._create_variable(var, PythonNativeInt(), start, {})
-                dvar  = self._infer_type(var)
-                stop  = a.stop
+                var_name = self.scope.get_expected_name(expr.indices[i])
+                variables.append(handle_int_loop_variable(var_name, body.scope))
                 start = a.start
+                stop  = a.stop
                 step  = a.step
 
-            elif isinstance(a, (PythonZip, PythonEnumerate)):
-                dvar  = self._infer_type(a.element)
-                class_type = dvar.pop('class_type')
-                if class_type.rank > 0:
-                    class_type = class_type.switch_rank(class_type.rank-1)
-                    dvar['shape'] = (dvar['shape'])[1:]
-                if class_type.rank == 0:
-                    dvar['shape'] = None
-                    dvar['memory_handling'] = 'stack'
-                var  = Variable(class_type, var, **dvar)
+            elif isinstance(a, PythonEnumerate):
+                var_name1 = self.scope.get_expected_name(expr.indices[i][0])
+                var_name2 = self.scope.get_expected_name(expr.indices[i][1])
+                variables.append(handle_int_loop_variable(var_name1, body.scope))
+                variables.append(handle_iterable_variable(var_name2, a.element, body.scope))
                 stop = a.element.shape[0]
 
-            elif isinstance(a, Variable):
-                dvar  = self._infer_type(a)
-                class_type = dvar.pop('class_type')
-                if class_type.rank > 0:
-                    class_type = class_type.switch_rank(class_type.rank-1)
-                    dvar['shape'] = (dvar['shape'])[1:]
-                if class_type.rank == 0:
-                    dvar['shape'] = None
-                    dvar['memory_handling'] = 'stack'
-                var  = Variable(class_type, var, **dvar)
-                stop = a.shape[0]
+            elif isinstance(a, PythonZip):
+                for idx, arg in enumerate(a.args):
+                    var = self.scope.get_expected_name(expr.indices[i][idx])
+                    variables.append(handle_iterable_variable(var, arg, body.scope))
+                stop = a.get_range().stop
+
+            elif isinstance(a, VariableIterator):
+                var = self.scope.get_expected_name(expr.indices[i])
+                variables.append(handle_iterable_variable(var, a.variable, body.scope))
+                stop = a.variable.shape[0]
 
             else:
                 errors.report(PYCCEL_RESTRICTION_TODO,
                               bounding_box=(self.current_ast_node.lineno, self.current_ast_node.col_offset),
                               severity='fatal')
-            existing_var = self.scope.find(var.name, 'variables')
-            if existing_var:
-                if self._infer_type(existing_var) != dvar:
-                    errors.report(f"Variable {var} already exists with different type",
-                            symbol = expr, severity='error')
-            else:
-                self.scope.insert_variable(var)
-            step.invalidate_node()
+
+            for var, _ in variables:
+                existing_var = self.scope.find(var.name, 'variables')
+                if var.name == expr.lhs:
+                    errors.report(f"Variable {var} has the same name as the left hand side",
+                            symbol = expr, severity='fatal')
+                if existing_var or var.name ==  expr.lhs:
+                    if self._infer_type(existing_var)['class_type'] != var.class_type:
+                        return errors.report(f"Variable {var} already exists with different type",
+                                symbol = expr, severity='error')
path: pyccel/parser/semantic.py
user: login: EmilyBourne
body: Strange to return an error. Should this be fatal or should it be:
```suggestion
                        errors.report(f"Variable {var} already exists with different type",
                                symbol = expr, severity='error')
```
?
created_at: 2025-03-17T08:17:57Z
author_association: MEMBER
diff_hunk: @@ -118,6 +118,37 @@ class NumpyInt8Type(NumpyIntType):
     _name = 'numpy.int8'
     _precision = 1
 
+    @property
+    def min_value(self):
+        """
+        Get the minimum representable value for this datatype.
+
+        This property returns the smallest value that can be represented by
+        this numeric datatype. It is useful for validation, boundary checks,
+        and ensuring that generated code respects the limits of the type.
+
+        Returns
+        -------
+        int8
+            The minimum value that can be represented.
+        """
+        return np.iinfo(np.int8).min
+
path: pyccel/ast/numpytypes.py
user: login: pyccel-bot[bot]
body: This seems to be fixed now! Thank you for looking into this. Please resolve this conversation.

created_at: 2025-03-17T08:19:27Z
author_association: NONE
diff_hunk: @@ -118,6 +118,37 @@ class NumpyInt8Type(NumpyIntType):
     _name = 'numpy.int8'
     _precision = 1
 
+    @property
+    def min_value(self):
+        """
+        Get the minimum representable value for this datatype.
+
+        This property returns the smallest value that can be represented by
+        this numeric datatype. It is useful for validation, boundary checks,
+        and ensuring that generated code respects the limits of the type.
+
+        Returns
+        -------
+        int8
+            The minimum value that can be represented.
+        """
+        return np.iinfo(np.int8).min
+
+    @property
+    def max_value(self):
+        """
+        Get the maximum representable value for this datatype.
+
+        This property returns the smallest value that can be represented by
+        this numeric datatype. It is useful for validation, boundary checks,
+        and ensuring that generated code respects the limits of the type.
+
+        Returns
+        -------
+        int8
+            The maximum value that can be represented.
+        """
+        return np.iinfo(np.int8).max
path: pyccel/ast/numpytypes.py
user: login: pyccel-bot[bot]
body: This seems to be fixed now! Thank you for looking into this. Please resolve this conversation.

created_at: 2025-03-17T08:19:28Z
author_association: NONE
diff_hunk: @@ -129,6 +160,37 @@ class NumpyInt16Type(NumpyIntType):
     _name = 'numpy.int16'
     _precision = 2
 
+    @property
+    def min_value(self):
+        """
+        Get the minimum representable value for this datatype.
+
+        This property returns the smallest value that can be represented by
+        this numeric datatype. It is useful for validation, boundary checks,
+        and ensuring that generated code respects the limits of the type.
+
+        Returns
+        -------
+        int16
+            The minimum value that can be represented.
+        """
+        return np.iinfo(np.int16).min
+
path: pyccel/ast/numpytypes.py
user: login: pyccel-bot[bot]
body: This seems to be fixed now! Thank you for looking into this. Please resolve this conversation.

created_at: 2025-03-17T08:19:29Z
author_association: NONE
diff_hunk: @@ -129,6 +160,37 @@ class NumpyInt16Type(NumpyIntType):
     _name = 'numpy.int16'
     _precision = 2
 
+    @property
+    def min_value(self):
+        """
+        Get the minimum representable value for this datatype.
+
+        This property returns the smallest value that can be represented by
+        this numeric datatype. It is useful for validation, boundary checks,
+        and ensuring that generated code respects the limits of the type.
+
+        Returns
+        -------
+        int16
+            The minimum value that can be represented.
+        """
+        return np.iinfo(np.int16).min
+
+    @property
+    def max_value(self):
+        """
+        Get the maximum representable value for this datatype.
+
+        This property returns the smallest value that can be represented by
+        this numeric datatype. It is useful for validation, boundary checks,
+        and ensuring that generated code respects the limits of the type.
+
+        Returns
+        -------
+        int16
+            The maximum value that can be represented.
+        """
+        return np.iinfo(np.int16).max
path: pyccel/ast/numpytypes.py
user: login: pyccel-bot[bot]
body: This seems to be fixed now! Thank you for looking into this. Please resolve this conversation.

created_at: 2025-03-17T08:19:30Z
author_association: NONE
diff_hunk: @@ -140,6 +202,37 @@ class NumpyInt32Type(NumpyIntType):
     _name = 'numpy.int32'
     _precision = 4
 
+    @property
+    def min_value(self):
+        """
+        Get the minimum representable value for this datatype.
+
+        This property returns the smallest value that can be represented by
+        this numeric datatype. It is useful for validation, boundary checks,
+        and ensuring that generated code respects the limits of the type.
+
+        Returns
+        -------
+        int32
+            The minimum value that can be represented.
+        """
+        return np.iinfo(np.int32).min
+
path: pyccel/ast/numpytypes.py
user: login: pyccel-bot[bot]
body: This seems to be fixed now! Thank you for looking into this. Please resolve this conversation.

created_at: 2025-03-17T08:19:31Z
author_association: NONE
diff_hunk: @@ -140,6 +202,37 @@ class NumpyInt32Type(NumpyIntType):
     _name = 'numpy.int32'
     _precision = 4
 
+    @property
+    def min_value(self):
+        """
+        Get the minimum representable value for this datatype.
+
+        This property returns the smallest value that can be represented by
+        this numeric datatype. It is useful for validation, boundary checks,
+        and ensuring that generated code respects the limits of the type.
+
+        Returns
+        -------
+        int32
+            The minimum value that can be represented.
+        """
+        return np.iinfo(np.int32).min
+
+    @property
+    def max_value(self):
+        """
+        Get the maximum representable value for this datatype.
+
+        This property returns the smallest value that can be represented by
+        this numeric datatype. It is useful for validation, boundary checks,
+        and ensuring that generated code respects the limits of the type.
+
+        Returns
+        -------
+        int32
+            The maximum value that can be represented.
+        """
+        return np.iinfo(np.int32).max
path: pyccel/ast/numpytypes.py
user: login: pyccel-bot[bot]
body: This seems to be fixed now! Thank you for looking into this. Please resolve this conversation.

created_at: 2025-03-17T08:19:32Z
author_association: NONE
diff_hunk: @@ -151,6 +244,38 @@ class NumpyInt64Type(NumpyIntType):
     _name = 'numpy.int64'
     _precision = 8
 
+    @property
+    def min_value(self):
+        """
+        Get the minimum representable value for this datatype.
+
+        This property returns the smallest value that can be represented by
+        this numeric datatype. It is useful for validation, boundary checks,
+        and ensuring that generated code respects the limits of the type.
+
+        Returns
+        -------
+        int64
+            The minimum value that can be represented.
+        """
+        return np.iinfo(np.int64).min
+
path: pyccel/ast/numpytypes.py
user: login: pyccel-bot[bot]
body: This seems to be fixed now! Thank you for looking into this. Please resolve this conversation.

created_at: 2025-03-17T08:19:33Z
author_association: NONE
diff_hunk: @@ -151,6 +244,38 @@ class NumpyInt64Type(NumpyIntType):
     _name = 'numpy.int64'
     _precision = 8
 
+    @property
+    def min_value(self):
+        """
+        Get the minimum representable value for this datatype.
+
+        This property returns the smallest value that can be represented by
+        this numeric datatype. It is useful for validation, boundary checks,
+        and ensuring that generated code respects the limits of the type.
+
+        Returns
+        -------
+        int64
+            The minimum value that can be represented.
+        """
+        return np.iinfo(np.int64).min
+
+    @property
+    def max_value(self):
+        """
+        Get the maximum representable value for this datatype.
+
+        This property returns the smallest value that can be represented by
+        this numeric datatype. It is useful for validation, boundary checks,
+        and ensuring that generated code respects the limits of the type.
+
+        Returns
+        -------
+        int64
+            The maximum value that can be represented.
+        """
+        return np.iinfo(np.int64).max
+
path: pyccel/ast/numpytypes.py
user: login: pyccel-bot[bot]
body: This seems to be fixed now! Thank you for looking into this. Please resolve this conversation.

created_at: 2025-03-17T08:19:33Z
author_association: NONE
diff_hunk: @@ -164,6 +289,37 @@ class NumpyFloat32Type(NumpyNumericType):
     _primitive_type = PrimitiveFloatingPointType()
     _precision = 4
 
+    @property
+    def min_value(self):
+        """
+        Get the minimum representable value for this datatype.
+
+        This property returns the smallest value that can be represented by
+        this numeric datatype. It is useful for validation, boundary checks,
+        and ensuring that generated code respects the limits of the type.
+
+        Returns
+        -------
+        float32
+            The minimum value that can be represented.
+        """
+        return np.finfo(np.float32).min
+
path: pyccel/ast/numpytypes.py
user: login: pyccel-bot[bot]
body: This seems to be fixed now! Thank you for looking into this. Please resolve this conversation.

created_at: 2025-03-17T08:19:34Z
author_association: NONE
diff_hunk: @@ -164,6 +289,37 @@ class NumpyFloat32Type(NumpyNumericType):
     _primitive_type = PrimitiveFloatingPointType()
     _precision = 4
 
+    @property
+    def min_value(self):
+        """
+        Get the minimum representable value for this datatype.
+
+        This property returns the smallest value that can be represented by
+        this numeric datatype. It is useful for validation, boundary checks,
+        and ensuring that generated code respects the limits of the type.
+
+        Returns
+        -------
+        float32
+            The minimum value that can be represented.
+        """
+        return np.finfo(np.float32).min
+
+    @property
+    def max_value(self):
+        """
+        Get the maximum representable value for this datatype.
+
+        This property returns the smallest value that can be represented by
+        this numeric datatype. It is useful for validation, boundary checks,
+        and ensuring that generated code respects the limits of the type.
+
+        Returns
+        -------
+        float32
+            The maximum value that can be represented.
+        """
+        return np.finfo(np.float32).max
path: pyccel/ast/numpytypes.py
user: login: pyccel-bot[bot]
body: This seems to be fixed now! Thank you for looking into this. Please resolve this conversation.

created_at: 2025-03-17T08:19:35Z
author_association: NONE
diff_hunk: @@ -4149,7 +4185,10 @@ def _visit_FunctionalFor(self, expr):
             errors.report("Unrecognised output type from functional for.\n"+PYCCEL_RESTRICTION_TODO,
                           symbol=expr,
                           severity='fatal')
-
+        if target_type_name != 'list' and any(cond is not None for cond in  expr.conditions):
path: pyccel/parser/semantic.py
user: login: EmilyBourne
body: According to its docstring `conditions` can be `None`. This line will raise an error in this case. As mentioned above I think conditions should either be non-optional or `None` should be converted to `[None]*depth` in the constructor
created_at: 2025-03-17T08:20:33Z
author_association: MEMBER
diff_hunk: @@ -4176,22 +4215,61 @@ def _visit_FunctionalFor(self, expr):
             errors.report(LIST_OF_TUPLES, symbol=expr, severity='error')
 
         target.invalidate_node()
+        operations = []
+        assign = None
+        target_conversion_func = self._visit(target_type_name)
+        if (isinstance(target_conversion_func, PyccelFunctionDef)
+            and target_conversion_func.cls_name is NumpyArray):
path: pyccel/parser/semantic.py
user: login: EmilyBourne
body: For readability so the condition does not look like the start of the code
```suggestion
        if (isinstance(target_conversion_func, PyccelFunctionDef)
                and target_conversion_func.cls_name is NumpyArray):
```
created_at: 2025-03-17T08:21:21Z
author_association: MEMBER
diff_hunk: @@ -4176,22 +4215,61 @@ def _visit_FunctionalFor(self, expr):
             errors.report(LIST_OF_TUPLES, symbol=expr, severity='error')
 
         target.invalidate_node()
+        operations = []
+        assign = None
+        target_conversion_func = self._visit(target_type_name)
+        if (isinstance(target_conversion_func, PyccelFunctionDef)
+            and target_conversion_func.cls_name is NumpyArray):
+            old_index   = expr.index
+            new_index   = self.scope.get_new_name()
+            expr.substitute(old_index, new_index, is_equivalent = lambda x,y: x is y)
+            array_ops = expr.operations['numpy_array']
+            assign = array_ops[0]
+            assign.substitute(old_index, new_index)
+            assign = self._visit(array_ops[0])
+            array_ops = array_ops[1:]
+            for operation in array_ops:
+                operation.substitute(old_index, new_index)
+                operations.append(operation)
path: pyccel/parser/semantic.py
user: login: EmilyBourne
body: ```suggestion
            expr.substitute(old_index, new_index, is_equivalent = lambda x,y: x is y)
            array_ops = expr.operations['numpy_array']
            for operation in array_ops:
                operation.substitute(old_index, new_index, is_equivalent = lambda x,y: x is y)
            assign = array_ops[0]
            assign = self._visit(array_ops[0])
            operations.extend(array_ops[1:])
```
created_at: 2025-03-17T08:23:57Z
author_association: MEMBER
diff_hunk: @@ -176,6 +173,37 @@ class NumpyFloat64Type(NumpyNumericType):
     _primitive_type = PrimitiveFloatingPointType()
     _precision = 8
 
+    @property
+    def min_value(self):
+        """
+        Get the minimum representable value for this datatype.
+
+        This property returns the smallest value that can be represented by
+        this numeric datatype. It is useful for validation, boundary checks,
+        and ensuring that generated code respects the limits of the type.
+
+        Returns
+        -------
+        float64
+            The minimum value that can be represented.
+        """
+        return np.finfo(np.float64).min
path: pyccel/ast/numpytypes.py
user: login: EmilyBourne
body: > Why is this only needed for Float64?

I.e. why is it not needed for Float32?
created_at: 2025-03-17T08:31:18Z
author_association: MEMBER
diff_hunk: @@ -474,6 +474,8 @@ def convert_to_literal(value, dtype = None):
     if isinstance(primitive_type, PrimitiveIntegerType):
         if value >= 0:
             literal_val = LiteralInteger(value, dtype)
+        elif value == -9223372036854775808:
+            literal_val =  PyccelMinus(PyccelUnarySub(LiteralInteger(-value-1, dtype)), LiteralInteger(1))
path: pyccel/ast/literals.py
user: login: jalalium
body: This removes the following warning:
```python
if __name__ == '__main__':
    x = -9223372036854775808
```
```bash
/home/jalal/Documents/pyccel_work/pyccel/codegen/compiling/compilers.py:505: UserWarning: /home/jalal/Documents/pyccel/__pyccel__/prog_boo.c: In function ‘main’:
/home/jalal/Documents/pyccel/__pyccel__/prog_boo.c:9:1: warning: integer constant is so large that it is unsigned
    9 |     x = -INT64_C(9223372036854775808);
      | ^   ~~~~~~~~~~~~~~~~

  warnings.warn(UserWarning(err))
  ```
  `9223372036854775808` should not be represented without a `-` sign
created_at: 2025-03-17T08:35:54Z
author_association: MEMBER
diff_hunk: @@ -358,6 +358,39 @@ def __and__(self, other):
         else:
             return NotImplemented
 
+    @property
+    def min_value(self):
+        """
+        Get the minimum representable value for this datatype.
+
+        This property returns the smallest value that can be represented by
+        this numeric datatype. It is useful for validation, boundary checks,
+        and ensuring that generated code respects the limits of the type.
+
+        Returns
+        -------
+        int
+            The minimum value that can be represented.
+        """
+        return numpy.iinfo(int).min
path: pyccel/ast/datatypes.py
user: login: jalalium
body: Done #2247 
created_at: 2025-03-17T08:40:12Z
author_association: MEMBER
diff_hunk: @@ -4149,7 +4185,10 @@ def _visit_FunctionalFor(self, expr):
             errors.report("Unrecognised output type from functional for.\n"+PYCCEL_RESTRICTION_TODO,
                           symbol=expr,
                           severity='fatal')
-
+        if target_type_name != 'list' and any(cond is not None for cond in  expr.conditions):
path: pyccel/parser/semantic.py
user: login: jalalium
body: You are right! I will update it!
created_at: 2025-03-17T08:46:33Z
author_association: MEMBER
diff_hunk: @@ -474,6 +474,8 @@ def convert_to_literal(value, dtype = None):
     if isinstance(primitive_type, PrimitiveIntegerType):
         if value >= 0:
             literal_val = LiteralInteger(value, dtype)
+        elif value == -9223372036854775808:
+            literal_val =  PyccelMinus(PyccelUnarySub(LiteralInteger(-value-1, dtype)), LiteralInteger(1))
path: pyccel/ast/literals.py
user: login: EmilyBourne
body: Ah interesting. I would expect this to be printed as:
```c
x = INT64_C(-9223372036854775808);
```
I will create an issue
created_at: 2025-03-17T08:49:14Z
author_association: MEMBER
diff_hunk: @@ -176,6 +173,37 @@ class NumpyFloat64Type(NumpyNumericType):
     _primitive_type = PrimitiveFloatingPointType()
     _precision = 8
 
+    @property
+    def min_value(self):
+        """
+        Get the minimum representable value for this datatype.
+
+        This property returns the smallest value that can be represented by
+        this numeric datatype. It is useful for validation, boundary checks,
+        and ensuring that generated code respects the limits of the type.
+
+        Returns
+        -------
+        float64
+            The minimum value that can be represented.
+        """
+        return np.finfo(np.float64).min
path: pyccel/ast/numpytypes.py
user: login: jalalium
body: It is needed for all numpy types i e
```python
a : 'float32[:]' ...
x = min( i+1 for i in a) 
```
this would require `min` and `max` values for `Float32`.

I need to add them in a separate PR and add tests for coverage.
created_at: 2025-03-17T08:50:27Z
author_association: MEMBER
diff_hunk: @@ -474,6 +474,8 @@ def convert_to_literal(value, dtype = None):
     if isinstance(primitive_type, PrimitiveIntegerType):
         if value >= 0:
             literal_val = LiteralInteger(value, dtype)
+        elif value == -9223372036854775808:
+            literal_val =  PyccelMinus(PyccelUnarySub(LiteralInteger(-value-1, dtype)), LiteralInteger(1))
path: pyccel/ast/literals.py
user: login: EmilyBourne
body: Actually moving `INT64_C` doesn't help:
```
/home/emily/Code/test/tmp/__pyccel__/prog_tmp_val.c:7:1: warning: integer constant is so large that it is unsigned
    7 |     x = INT64_C(-9223372036854775808);
      | ^   ~~~~~~~~~~~~~~~~
```
Fortran also raises a similar error:
```
/home/emily/Code/test/tmp/__pyccel__/prog_tmp_val.f90:11:30:

   11 |   x = -9223372036854775808_i64
      |                              1
Error: Integer too big for its kind at (1). This check can be disabled with the option ‘-fno-range-check’
```
created_at: 2025-03-17T08:52:01Z
author_association: MEMBER
diff_hunk: @@ -474,6 +474,8 @@ def convert_to_literal(value, dtype = None):
     if isinstance(primitive_type, PrimitiveIntegerType):
         if value >= 0:
             literal_val = LiteralInteger(value, dtype)
+        elif value == -9223372036854775808:
+            literal_val =  PyccelMinus(PyccelUnarySub(LiteralInteger(-value-1, dtype)), LiteralInteger(1))
path: pyccel/ast/literals.py
user: login: EmilyBourne
body: I guess it would make more sense to use : https://en.cppreference.com/w/c/types/limits
and `Huge` in fortran (see https://github.com/pyccel/pyccel/blob/b27a7a8eb3df2a65a1d0bb4004edbf3419884ac2/pyccel/codegen/printing/fcode.py#L2174-L2178)

Related to the comment below where I would rather have this in a separate PR
created_at: 2025-03-17T08:55:10Z
author_association: MEMBER
diff_hunk: @@ -2311,7 +2311,8 @@ def _print_Allocate(self, expr):
             elif expr.alloc_type == 'reserve':
                 var_code = self._print(expr.variable)
                 size_code = self._print(expr.shape[0])
-                return f'call {var_code} % reserve({size_code})\n'
+                return (f'call {var_code} % clear()\n'
+                        f'call {var_code} % reserve({size_code})\n')
path: pyccel/codegen/printing/fcode.py
user: login: jalalium
body: good catch!
created_at: 2025-03-17T09:08:43Z
author_association: MEMBER
diff_hunk: @@ -3970,90 +3973,129 @@ def _visit_For(self, expr):
             for_expr.end_annotation = expr.end_annotation
         return for_expr
 
-
     def _visit_FunctionalFor(self, expr):
-        old_index   = expr.index
-        new_index   = self.scope.get_new_name()
-        # Identity comparison is required to only find duplicates of the index caused
-        # by the construction of FunctionalFor.
-        expr.substitute(old_index, new_index, is_equivalent = lambda x,y: x is y)
+        """
+        Visit and transform a FunctionalFor AST node into an equivalent code block.
 
-        target  = expr.expr
-        index   = new_index
-        indices = [self.scope.get_expected_name(i) for i in expr.indices]
-        dims    = []
-        body    = expr.loops[1]
+        This method processes a `FunctionalFor` expression and transforms the loop structure
+        into a corresponding code block.
 
-        idx_subs = {}
-        #scope = self.create_new_loop_scope()
+        Parameters
+        ----------
+        expr : pyccel.ast.functionalexpr.FunctionalFor
+            The FunctionalFor AST node.
 
-        # The symbols created to represent unknown valued objects are temporary
+        Returns
+        -------
+        pyccel.ast.basic.CodeBlock
+            A code block containing the equivalent loops and necessary variable allocations for the given `FunctionalFor` expression.
+        """
+        target  = expr.expr
+        indices = []
+        dims = []
+        idx_subs = {}
         tmp_used_names = self.scope.all_used_symbols.copy()
         i = 0
-        while isinstance(body, For):
+        loops = list(expr.loops)
+
+        # Inner function to handle PythonNativeInt variables
+        def handle_int_loop_variable(var_name, var_scope):
+            indices.append(var_name)
+            var = self._create_variable(var_name, PythonNativeInt(), None, {}, insertion_scope=var_scope)
+            dvar = self._infer_type(var)
+            return var, dvar
+
+        # Inner function to handle iterable variables
+        def handle_iterable_variable(var_name, element, var_scope):
+            indices.append(var_name)
+            dvar = self._infer_type(element)
+            class_type = dvar.pop('class_type')
+            if class_type.rank > 0:
+                class_type = class_type.switch_rank(class_type.rank - 1)
+                dvar['shape'] = dvar['shape'][1:]
+            if class_type.rank == 0:
+                dvar['shape'] = None
+                dvar['memory_handling'] = 'stack'
+            var = self._create_variable(var_name, class_type, None, dvar, insertion_scope=var_scope)
+            dvar['class_type'] = class_type
+            return var, dvar
+
+        for loop, condition in zip(loops, expr.conditions):
+            if condition:
+                loop.insert2body(condition)
+
+        while len(loops) > 1:
+            outer_loop = loops.pop()
+            inserted_into = loops[-1]
+            if inserted_into.body.body:
+                inserted_into.body.body[0].blocks[0].body.insert2body(outer_loop)
+            else:
+                inserted_into.insert2body(outer_loop)
+
+        body = loops[0]
+
+        while isinstance(body, (For, If)):
 
-            stop  = None
+            if isinstance(body, If):
+                body = None if not body.blocks[0].body.body else body.blocks[0].body.body[0]
+                continue
+
+            stop = None
             start = LiteralInteger(0)
-            step  = LiteralInteger(1)
-            var   = indices[i]
-            i += 1
-            a     = self._visit(body.iterable)
+            step = LiteralInteger(1)
+            variables = []
+            a = self._get_iterable(self._visit(body.iterable))
             if isinstance(a, PythonRange):
-                var   = self._create_variable(var, PythonNativeInt(), start, {})
-                dvar  = self._infer_type(var)
-                stop  = a.stop
+                var_name = self.scope.get_expected_name(expr.indices[i])
+                variables.append(handle_int_loop_variable(var_name, body.scope))
                 start = a.start
+                stop  = a.stop
                 step  = a.step
 
-            elif isinstance(a, (PythonZip, PythonEnumerate)):
-                dvar  = self._infer_type(a.element)
-                class_type = dvar.pop('class_type')
-                if class_type.rank > 0:
-                    class_type = class_type.switch_rank(class_type.rank-1)
-                    dvar['shape'] = (dvar['shape'])[1:]
-                if class_type.rank == 0:
-                    dvar['shape'] = None
-                    dvar['memory_handling'] = 'stack'
-                var  = Variable(class_type, var, **dvar)
+            elif isinstance(a, PythonEnumerate):
+                var_name1 = self.scope.get_expected_name(expr.indices[i][0])
+                var_name2 = self.scope.get_expected_name(expr.indices[i][1])
+                variables.append(handle_int_loop_variable(var_name1, body.scope))
+                variables.append(handle_iterable_variable(var_name2, a.element, body.scope))
                 stop = a.element.shape[0]
 
-            elif isinstance(a, Variable):
-                dvar  = self._infer_type(a)
-                class_type = dvar.pop('class_type')
-                if class_type.rank > 0:
-                    class_type = class_type.switch_rank(class_type.rank-1)
-                    dvar['shape'] = (dvar['shape'])[1:]
-                if class_type.rank == 0:
-                    dvar['shape'] = None
-                    dvar['memory_handling'] = 'stack'
-                var  = Variable(class_type, var, **dvar)
-                stop = a.shape[0]
+            elif isinstance(a, PythonZip):
+                for idx, arg in enumerate(a.args):
+                    var = self.scope.get_expected_name(expr.indices[i][idx])
+                    variables.append(handle_iterable_variable(var, arg, body.scope))
+                stop = a.get_range().stop
+
+            elif isinstance(a, VariableIterator):
+                var = self.scope.get_expected_name(expr.indices[i])
+                variables.append(handle_iterable_variable(var, a.variable, body.scope))
+                stop = a.variable.shape[0]
 
             else:
                 errors.report(PYCCEL_RESTRICTION_TODO,
                               bounding_box=(self.current_ast_node.lineno, self.current_ast_node.col_offset),
                               severity='fatal')
-            existing_var = self.scope.find(var.name, 'variables')
-            if existing_var:
-                if self._infer_type(existing_var) != dvar:
-                    errors.report(f"Variable {var} already exists with different type",
-                            symbol = expr, severity='error')
-            else:
-                self.scope.insert_variable(var)
-            step.invalidate_node()
+
+            for var, _ in variables:
+                existing_var = self.scope.find(var.name, 'variables')
+                if var.name == expr.lhs:
+                    errors.report(f"Variable {var} has the same name as the left hand side",
+                            symbol = expr, severity='fatal')
+                if existing_var or var.name ==  expr.lhs:
+                    if self._infer_type(existing_var)['class_type'] != var.class_type:
+                        return errors.report(f"Variable {var} already exists with different type",
+                                symbol = expr, severity='error')
path: pyccel/parser/semantic.py
user: login: jalalium
body: You are right! it should be fatal! like in this case:
```python
    i = 5
    a = [3.4]
    b = [i-1 for i in a]
```
created_at: 2025-03-17T09:21:50Z
author_association: MEMBER
diff_hunk: @@ -660,15 +666,20 @@ def _print_For(self, expr):
     def _print_FunctionalFor(self, expr):
         body, iterators = self._find_functional_expr_and_iterables(expr)
         lhs = self._print(expr.lhs)
-        body = self._print(body.rhs)
-        for_loops = ' '.join(['for {} in {}'.format(self._print(idx), self._print(iters))
-                        for idx, iters in zip(expr.indices, iterators)])
+        condition = ''
+        if isinstance(body, Assign):
+            body = self._print(body.rhs)
+        else:
+            assert isinstance(body, ListAppend)
+            body = self._print(body.args[0])
 
-        name = self._aliases.get(type(expr),'array')
-        if name == 'array':
-            self.add_import(Import('numpy', [AsName(NumpyArray, 'array')]))
+        for_loops = ' '.join(f'for {self._print(idx)} in {self._print(iters)}{" if " + self._print(condition.blocks[0].condition) if condition else ""}'
+                             for idx, iters, condition in zip(expr.indices, iterators, expr.conditions))
 
-        return '{} = {}([{} {}])\n'.format(lhs, name, body, for_loops)
+        if isinstance(expr.class_type, NumpyNDArrayType):
+            self.add_import(Import('numpy', [AsName(NumpyArray, 'array')]))
+            return f'{lhs} = array([{body} {for_loops} {condition}])\n'
path: pyccel/codegen/printing/pycode.py
user: login: jalalium
body: fixed
created_at: 2025-03-17T09:26:03Z
author_association: MEMBER
diff_hunk: @@ -118,7 +118,6 @@ class NumpyInt8Type(NumpyIntType):
     _name = 'numpy.int8'
     _precision = 1
 
-
 class NumpyInt16Type(NumpyIntType):
path: pyccel/ast/numpytypes.py
user: login: jalalium
body: fixed
created_at: 2025-03-17T09:26:18Z
author_association: MEMBER
diff_hunk: @@ -474,6 +474,8 @@ def convert_to_literal(value, dtype = None):
     if isinstance(primitive_type, PrimitiveIntegerType):
         if value >= 0:
             literal_val = LiteralInteger(value, dtype)
+        elif value == -9223372036854775808:
+            literal_val =  PyccelMinus(PyccelUnarySub(LiteralInteger(-value-1, dtype)), LiteralInteger(1))
path: pyccel/ast/literals.py
user: login: jalalium
body: I will update this in a separate issue
created_at: 2025-03-17T09:28:35Z
author_association: MEMBER
diff_hunk: @@ -68,14 +75,19 @@ def __init__(
         lhs=None,
         indices=None,
         index=None,
-        target_type=None
+        target_type=None,
+        conditions=None,
+        operations=None
         ):
path: pyccel/ast/functionalexpr.py
user: login: EmilyBourne
body: Have you considered something like:
```suggestion
        *,
        target_type=None,
        conditions=None,
        operations
        ):
```
to ensure the argument is provided?
created_at: 2025-03-17T09:49:16Z
author_association: MEMBER
diff_hunk: @@ -68,14 +75,19 @@ def __init__(
         lhs=None,
         indices=None,
         index=None,
-        target_type=None
+        target_type=None,
+        conditions=None,
+        operations=None
         ):
path: pyccel/ast/functionalexpr.py
user: login: jalalium
body: You mean conditions right?
created_at: 2025-03-17T10:18:27Z
author_association: MEMBER
diff_hunk: @@ -68,14 +75,19 @@ def __init__(
         lhs=None,
         indices=None,
         index=None,
-        target_type=None
+        target_type=None,
+        conditions=None,
+        operations=None
         ):
path: pyccel/ast/functionalexpr.py
user: login: EmilyBourne
body: Ah possibly sorry. This wasn't a suggestion intended to be applied, it was just a suggestion to give you an idea of what I meant. Up to you to choose the appropriate location for `*` (the comment system wouldn't let me suggest it anywhere else) and remove `None` where there is no default
created_at: 2025-03-17T10:23:32Z
author_association: MEMBER
diff_hunk: @@ -102,6 +115,26 @@ def indices(self):
     def index(self):
         return self._index
 
+    @property
+    def operations(self):
+        """
+        A dictionary mapping each type of comprehension to the operation used for populating it.
+
+        For example, for list comprehensions we might use 
+        ``{'list': 'append'}``, and for cspans (the equivalent of NumPy arrays, 
+        which require a fixed size at compile time), we might use 
+        ``{'cspans': 'indexed_element_assignment'}``. 
+        This mapping allows the code generator to select the appropriate operation 
+        when building the final data structure from the comprehension.
+
+        Returns
+        -------
+        dict
+            A dictionary that maps comprehension types (e.g. 'list', 'cspans') to 
+            the corresponding operation ('append', 'indexed_element_assignment', etc.).
path: pyccel/ast/functionalexpr.py
user: login: EmilyBourne
body: ```suggestion
        ``{'list': 'append'}``, and for NumPy arrays, (which require a fixed size
        at compile time), we might use  ``{'numpy_array': [Assign_node]}``. 
        This mapping allows the code generator to select the appropriate operation 
        when building the final data structure from the comprehension.

        Returns
        -------
        dict
            A dictionary that maps comprehension types (e.g. 'list', 'numpy_array') to 
            the corresponding operation (append call, assign, etc.).
```
?
created_at: 2025-03-17T11:49:13Z
author_association: MEMBER
diff_hunk: @@ -1876,7 +1876,7 @@ def _print_Allocate(self, expr):
             variable_address = self._print(ObjectAddress(expr.variable))
             container_type = self.get_c_type(expr.variable.class_type)
             if expr.alloc_type == 'reserve':
-                return free_code + f'{container_type}_reserve({variable_address}, {size});\n'
+                free_code = f'{container_type}_clear({variable_address});\n'
path: pyccel/codegen/printing/ccode.py
user: login: EmilyBourne
body: What happened to the call to reserve?
created_at: 2025-03-17T11:54:28Z
author_association: MEMBER
diff_hunk: @@ -3970,90 +3973,129 @@ def _visit_For(self, expr):
             for_expr.end_annotation = expr.end_annotation
         return for_expr
 
-
     def _visit_FunctionalFor(self, expr):
-        old_index   = expr.index
-        new_index   = self.scope.get_new_name()
-        # Identity comparison is required to only find duplicates of the index caused
-        # by the construction of FunctionalFor.
-        expr.substitute(old_index, new_index, is_equivalent = lambda x,y: x is y)
+        """
+        Visit and transform a FunctionalFor AST node into an equivalent code block.
 
-        target  = expr.expr
-        index   = new_index
-        indices = [self.scope.get_expected_name(i) for i in expr.indices]
-        dims    = []
-        body    = expr.loops[1]
+        This method processes a `FunctionalFor` expression and transforms the loop structure
+        into a corresponding code block.
 
-        idx_subs = {}
-        #scope = self.create_new_loop_scope()
+        Parameters
+        ----------
+        expr : pyccel.ast.functionalexpr.FunctionalFor
+            The FunctionalFor AST node.
 
-        # The symbols created to represent unknown valued objects are temporary
+        Returns
+        -------
+        pyccel.ast.basic.CodeBlock
+            A code block containing the equivalent loops and necessary variable allocations for the given `FunctionalFor` expression.
+        """
+        target  = expr.expr
+        indices = []
+        dims = []
+        idx_subs = {}
         tmp_used_names = self.scope.all_used_symbols.copy()
         i = 0
-        while isinstance(body, For):
+        loops = list(expr.loops)
+
+        # Inner function to handle PythonNativeInt variables
+        def handle_int_loop_variable(var_name, var_scope):
+            indices.append(var_name)
+            var = self._create_variable(var_name, PythonNativeInt(), None, {}, insertion_scope=var_scope)
+            dvar = self._infer_type(var)
+            return var, dvar
+
+        # Inner function to handle iterable variables
+        def handle_iterable_variable(var_name, element, var_scope):
+            indices.append(var_name)
+            dvar = self._infer_type(element)
+            class_type = dvar.pop('class_type')
+            if class_type.rank > 0:
+                class_type = class_type.switch_rank(class_type.rank - 1)
+                dvar['shape'] = dvar['shape'][1:]
+            if class_type.rank == 0:
path: pyccel/parser/semantic.py
user: login: EmilyBourne
body: ```suggestion
            elif class_type.rank == 0:
```
created_at: 2025-03-17T11:58:46Z
author_association: MEMBER
diff_hunk: @@ -3970,90 +3973,129 @@ def _visit_For(self, expr):
             for_expr.end_annotation = expr.end_annotation
         return for_expr
 
-
     def _visit_FunctionalFor(self, expr):
-        old_index   = expr.index
-        new_index   = self.scope.get_new_name()
-        # Identity comparison is required to only find duplicates of the index caused
-        # by the construction of FunctionalFor.
-        expr.substitute(old_index, new_index, is_equivalent = lambda x,y: x is y)
+        """
+        Visit and transform a FunctionalFor AST node into an equivalent code block.
 
-        target  = expr.expr
-        index   = new_index
-        indices = [self.scope.get_expected_name(i) for i in expr.indices]
-        dims    = []
-        body    = expr.loops[1]
+        This method processes a `FunctionalFor` expression and transforms the loop structure
+        into a corresponding code block.
 
-        idx_subs = {}
-        #scope = self.create_new_loop_scope()
+        Parameters
+        ----------
+        expr : pyccel.ast.functionalexpr.FunctionalFor
+            The FunctionalFor AST node.
 
-        # The symbols created to represent unknown valued objects are temporary
+        Returns
+        -------
+        pyccel.ast.basic.CodeBlock
+            A code block containing the equivalent loops and necessary variable allocations for the given `FunctionalFor` expression.
+        """
+        target  = expr.expr
+        indices = []
+        dims = []
+        idx_subs = {}
         tmp_used_names = self.scope.all_used_symbols.copy()
         i = 0
-        while isinstance(body, For):
+        loops = list(expr.loops)
+
+        # Inner function to handle PythonNativeInt variables
+        def handle_int_loop_variable(var_name, var_scope):
+            indices.append(var_name)
+            var = self._create_variable(var_name, PythonNativeInt(), None, {}, insertion_scope=var_scope)
+            dvar = self._infer_type(var)
+            return var, dvar
+
+        # Inner function to handle iterable variables
+        def handle_iterable_variable(var_name, element, var_scope):
+            indices.append(var_name)
+            dvar = self._infer_type(element)
+            class_type = dvar.pop('class_type')
+            if class_type.rank > 0:
+                class_type = class_type.switch_rank(class_type.rank - 1)
+                dvar['shape'] = dvar['shape'][1:]
+            if class_type.rank == 0:
+                dvar['shape'] = None
+                dvar['memory_handling'] = 'stack'
+            var = self._create_variable(var_name, class_type, None, dvar, insertion_scope=var_scope)
+            dvar['class_type'] = class_type
+            return var, dvar
path: pyccel/parser/semantic.py
user: login: EmilyBourne
body: Where is `dvar` used once it is saved in `variables`? I cannot find it. Are you sure it needs returning?
created_at: 2025-03-17T12:00:21Z
author_association: MEMBER
diff_hunk: @@ -60,3 +60,39 @@ def functional_for_3d_range():
 def unknown_length_functional(x : 'int[:]'):
     a = [i*3 for i in range(len(x)*2)]
     return len(a), a[0], a[-1]
+
+def functional_with_enumerate():
+    a = [x + 1 for x in range(10)]
+    b = [i * j for i,j in enumerate(a)]
+    return len(b), b[0], b[1], b[2]
path: tests/epyccel/modules/functionals.py
user: login: EmilyBourne
body: No need to change but just a remark. You realise that you could simply do:
```suggestion
    return b
```
now right?
created_at: 2025-03-17T12:05:27Z
author_association: MEMBER
diff_hunk: @@ -60,3 +60,39 @@ def functional_for_3d_range():
 def unknown_length_functional(x : 'int[:]'):
     a = [i*3 for i in range(len(x)*2)]
     return len(a), a[0], a[-1]
+
+def functional_with_enumerate():
+    a = [x + 1 for x in range(10)]
+    b = [i * j for i,j in enumerate(a)]
+    return len(b), b[0], b[1], b[2]
path: tests/epyccel/modules/functionals.py
user: login: jalalium
body: Thank you! I think I wrote this before it was possible
created_at: 2025-03-17T13:49:07Z
author_association: MEMBER
diff_hunk: @@ -3970,90 +3973,129 @@ def _visit_For(self, expr):
             for_expr.end_annotation = expr.end_annotation
         return for_expr
 
-
     def _visit_FunctionalFor(self, expr):
-        old_index   = expr.index
-        new_index   = self.scope.get_new_name()
-        # Identity comparison is required to only find duplicates of the index caused
-        # by the construction of FunctionalFor.
-        expr.substitute(old_index, new_index, is_equivalent = lambda x,y: x is y)
+        """
+        Visit and transform a FunctionalFor AST node into an equivalent code block.
 
-        target  = expr.expr
-        index   = new_index
-        indices = [self.scope.get_expected_name(i) for i in expr.indices]
-        dims    = []
-        body    = expr.loops[1]
+        This method processes a `FunctionalFor` expression and transforms the loop structure
+        into a corresponding code block.
 
-        idx_subs = {}
-        #scope = self.create_new_loop_scope()
+        Parameters
+        ----------
+        expr : pyccel.ast.functionalexpr.FunctionalFor
+            The FunctionalFor AST node.
 
-        # The symbols created to represent unknown valued objects are temporary
+        Returns
+        -------
+        pyccel.ast.basic.CodeBlock
+            A code block containing the equivalent loops and necessary variable allocations for the given `FunctionalFor` expression.
+        """
+        target  = expr.expr
+        indices = []
+        dims = []
+        idx_subs = {}
         tmp_used_names = self.scope.all_used_symbols.copy()
         i = 0
-        while isinstance(body, For):
+        loops = list(expr.loops)
+
+        # Inner function to handle PythonNativeInt variables
+        def handle_int_loop_variable(var_name, var_scope):
+            indices.append(var_name)
+            var = self._create_variable(var_name, PythonNativeInt(), None, {}, insertion_scope=var_scope)
+            dvar = self._infer_type(var)
+            return var, dvar
+
+        # Inner function to handle iterable variables
+        def handle_iterable_variable(var_name, element, var_scope):
+            indices.append(var_name)
+            dvar = self._infer_type(element)
+            class_type = dvar.pop('class_type')
+            if class_type.rank > 0:
+                class_type = class_type.switch_rank(class_type.rank - 1)
+                dvar['shape'] = dvar['shape'][1:]
+            if class_type.rank == 0:
+                dvar['shape'] = None
+                dvar['memory_handling'] = 'stack'
+            var = self._create_variable(var_name, class_type, None, dvar, insertion_scope=var_scope)
+            dvar['class_type'] = class_type
+            return var, dvar
path: pyccel/parser/semantic.py
user: login: jalalium
body: Sorry, I stopped using it. refactored!
created_at: 2025-03-17T13:49:14Z
author_association: MEMBER
diff_hunk: @@ -3970,90 +3973,129 @@ def _visit_For(self, expr):
             for_expr.end_annotation = expr.end_annotation
         return for_expr
 
-
     def _visit_FunctionalFor(self, expr):
-        old_index   = expr.index
-        new_index   = self.scope.get_new_name()
-        # Identity comparison is required to only find duplicates of the index caused
-        # by the construction of FunctionalFor.
-        expr.substitute(old_index, new_index, is_equivalent = lambda x,y: x is y)
+        """
+        Visit and transform a FunctionalFor AST node into an equivalent code block.
 
-        target  = expr.expr
-        index   = new_index
-        indices = [self.scope.get_expected_name(i) for i in expr.indices]
-        dims    = []
-        body    = expr.loops[1]
+        This method processes a `FunctionalFor` expression and transforms the loop structure
+        into a corresponding code block.
 
-        idx_subs = {}
-        #scope = self.create_new_loop_scope()
+        Parameters
+        ----------
+        expr : pyccel.ast.functionalexpr.FunctionalFor
+            The FunctionalFor AST node.
 
-        # The symbols created to represent unknown valued objects are temporary
+        Returns
+        -------
+        pyccel.ast.basic.CodeBlock
+            A code block containing the equivalent loops and necessary variable allocations for the given `FunctionalFor` expression.
+        """
+        target  = expr.expr
+        indices = []
+        dims = []
+        idx_subs = {}
         tmp_used_names = self.scope.all_used_symbols.copy()
         i = 0
-        while isinstance(body, For):
+        loops = list(expr.loops)
+
+        # Inner function to handle PythonNativeInt variables
+        def handle_int_loop_variable(var_name, var_scope):
+            indices.append(var_name)
+            var = self._create_variable(var_name, PythonNativeInt(), None, {}, insertion_scope=var_scope)
+            dvar = self._infer_type(var)
+            return var, dvar
+
+        # Inner function to handle iterable variables
+        def handle_iterable_variable(var_name, element, var_scope):
+            indices.append(var_name)
+            dvar = self._infer_type(element)
+            class_type = dvar.pop('class_type')
+            if class_type.rank > 0:
+                class_type = class_type.switch_rank(class_type.rank - 1)
+                dvar['shape'] = dvar['shape'][1:]
+            if class_type.rank == 0:
path: pyccel/parser/semantic.py
user: login: jalalium
body: Actually no, the rank is reduced by one in the first condition. both conditions need to visited sometimes
created_at: 2025-03-17T13:49:20Z
author_association: MEMBER
diff_hunk: @@ -1876,7 +1876,7 @@ def _print_Allocate(self, expr):
             variable_address = self._print(ObjectAddress(expr.variable))
             container_type = self.get_c_type(expr.variable.class_type)
             if expr.alloc_type == 'reserve':
-                return free_code + f'{container_type}_reserve({variable_address}, {size});\n'
+                free_code = f'{container_type}_clear({variable_address});\n'
path: pyccel/codegen/printing/ccode.py
user: login: jalalium
body: Sorry! fixed!
created_at: 2025-03-17T13:49:34Z
author_association: MEMBER
diff_hunk: @@ -1876,7 +1876,10 @@ def _print_Allocate(self, expr):
             variable_address = self._print(ObjectAddress(expr.variable))
             container_type = self.get_c_type(expr.variable.class_type)
             if expr.alloc_type == 'reserve':
-                return free_code + f'{container_type}_reserve({variable_address}, {size});\n'
+                if expr.status == 'allocated':
+                    return (f'{container_type}_clear({variable_address});\n'
+                            f'{container_type}_reserve({variable_address}, {size});\n')
+                return f'{container_type}_reserve({variable_address}, {size});\n'
path: pyccel/codegen/printing/ccode.py
user: login: EmilyBourne
body: ```suggestion
                if expr.status != 'unallocated':
                    return (f'{container_type}_clear({variable_address});\n'
                            f'{container_type}_reserve({variable_address}, {size});\n')
                return f'{container_type}_reserve({variable_address}, {size});\n'
```
`clear` should also be called if the status is unknown (e.g maybe allocated in an if, maybe not)
created_at: 2025-03-17T14:48:16Z
author_association: MEMBER

PR comments:
user: login: pyccel-bot[bot]
created_at: 2024-09-02T10:29:54Z
body: Hello again! Thank you for this new pull request :star_struck:.

Please begin by requesting your checklist using the command `/bot checklist`

author_association: NONE
user: login: jalalium
created_at: 2024-09-02T10:30:13Z
body: Here is your checklist. Please tick items off when you have completed them or determined that they are not necessary for this pull request:
- [x] Write a clear PR description
- [x] Add tests to check your code works as expected
- [x] Update documentation if necessary
- [x] Update Changelog
- [x] Ensure any relevant issues are linked
- [x] Ensure new tests are passing

author_association: MEMBER
user: login: jalalium
created_at: 2024-09-02T11:05:41Z
body: /bot run linux
author_association: MEMBER
user: login: jalalium
created_at: 2024-09-02T14:42:27Z
body: /bot run linux
author_association: MEMBER
user: login: jalalium
created_at: 2024-09-04T16:05:34Z
body: /bot run linux
author_association: MEMBER
user: login: jalalium
created_at: 2024-09-04T16:22:57Z
body: /bot mark as ready
author_association: MEMBER
user: login: pyccel-bot[bot]
created_at: 2024-09-04T16:24:18Z
body: Unfortunately your PR is not passing the tests so it is not quite ready for review yet. Let me know when it is fixed with `/bot mark as ready`.

author_association: NONE
user: login: jalalium
created_at: 2024-09-04T19:09:05Z
body: /bot run linux
author_association: MEMBER
user: login: jalalium
created_at: 2024-09-04T19:46:35Z
body: /bot mark as ready
author_association: MEMBER
user: login: pyccel-bot[bot]
created_at: 2024-09-04T19:47:01Z
body: Unfortunately your PR is not passing the tests so it is not quite ready for review yet. Let me know when it is fixed with `/bot mark as ready`.

author_association: NONE
user: login: pyccel-bot[bot]
created_at: 2024-12-03T19:02:59Z
body: Unfortunately your PR is not passing the tests so it is not quite ready for review yet. Let me know when it is fixed with `/bot mark as ready`.

author_association: NONE
user: login: pyccel-bot[bot]
created_at: 2024-12-04T12:11:54Z
body: Unfortunately your PR is not passing the tests so it is not quite ready for review yet. Let me know when it is fixed with `/bot mark as ready`.

author_association: NONE
user: login: pyccel-bot[bot]
created_at: 2024-12-04T12:52:21Z
body: Unfortunately your PR is not passing the tests so it is not quite ready for review yet. Let me know when it is fixed with `/bot mark as ready`.

author_association: NONE
user: login: pyccel-bot[bot]
created_at: 2024-12-04T15:14:27Z
body: Unfortunately your PR is not passing the tests so it is not quite ready for review yet. Let me know when it is fixed with `/bot mark as ready`.

author_association: NONE
user: login: pyccel-bot[bot]
created_at: 2024-12-04T15:52:41Z
body: Unfortunately your PR is not passing the tests so it is not quite ready for review yet. Let me know when it is fixed with `/bot mark as ready`.

author_association: NONE
user: login: pyccel-bot[bot]
created_at: 2024-12-04T18:09:59Z
body: Unfortunately your PR is not passing the tests so it is not quite ready for review yet. Let me know when it is fixed with `/bot mark as ready`.

author_association: NONE
user: login: pyccel-bot[bot]
created_at: 2024-12-04T19:33:16Z
body: @EmilyBourne, @jalalium has been working hard and thinks that they have now replied to or fixed all your comments. Could you take another look at the PR and see if you can approve now?

author_association: NONE
user: login: pyccel-bot[bot]
created_at: 2024-12-05T09:34:55Z
body: @jalalium, @EmilyBourne has a few questions/comments about your code. Can you go through and see if you agree with them. If not go ahead and explain why. Once you've adressed all the comments let me know with `/bot mark as ready` and we will see if we can get approval.

author_association: NONE
user: login: pyccel-bot[bot]
created_at: 2024-12-25T11:55:04Z
body: Unfortunately your PR is not passing the tests so it is not quite ready for review yet. Let me know when it is fixed with `/bot mark as ready`.

author_association: NONE
user: login: pyccel-bot[bot]
created_at: 2025-01-02T16:43:04Z
body: Unfortunately your PR is not passing the tests so it is not quite ready for review yet. Let me know when it is fixed with `/bot mark as ready`.

author_association: NONE
user: login: pyccel-bot[bot]
created_at: 2025-01-06T10:37:15Z
body: Unfortunately your PR is not passing the tests so it is not quite ready for review yet. Let me know when it is fixed with `/bot mark as ready`.

author_association: NONE
user: login: pyccel-bot[bot]
created_at: 2025-01-14T14:53:47Z
body: Unfortunately your PR is not passing the tests so it is not quite ready for review yet. Let me know when it is fixed with `/bot mark as ready`.

author_association: NONE
user: login: pyccel-bot[bot]
created_at: 2025-01-14T15:02:21Z
body: Unfortunately your PR is not passing the tests so it is not quite ready for review yet. Let me know when it is fixed with `/bot mark as ready`.

author_association: NONE
user: login: pyccel-bot[bot]
created_at: 2025-01-14T17:57:44Z
body: Unfortunately your PR is not passing the tests so it is not quite ready for review yet. Let me know when it is fixed with `/bot mark as ready`.

author_association: NONE
user: login: pyccel-bot[bot]
created_at: 2025-01-14T18:32:26Z
body: Unfortunately your PR is not passing the tests so it is not quite ready for review yet. Let me know when it is fixed with `/bot mark as ready`.

author_association: NONE
user: login: pyccel-bot[bot]
created_at: 2025-01-14T19:20:25Z
body: Unfortunately your PR is not passing the tests so it is not quite ready for review yet. Let me know when it is fixed with `/bot mark as ready`.

author_association: NONE
user: login: pyccel-bot[bot]
created_at: 2025-01-14T20:28:08Z
body: Unfortunately your PR is not passing the tests so it is not quite ready for review yet. Let me know when it is fixed with `/bot mark as ready`.

author_association: NONE
user: login: pyccel-bot[bot]
created_at: 2025-01-20T15:34:46Z
body: @jalalium, @EmilyBourne has a few questions/comments about your code. Can you go through and see if you agree with them. If not go ahead and explain why. Once you've adressed all the comments let me know with `/bot mark as ready` and we will see if we can get approval.

author_association: NONE
user: login: jalalium
created_at: 2025-03-06T12:58:41Z
body: /bot run linux
author_association: MEMBER
user: login: pyccel-bot[bot]
created_at: 2025-03-06T13:42:32Z
body: Unfortunately your PR is not passing the tests so it is not quite ready for review yet. Let me know when it is fixed with `/bot mark as ready`.

author_association: NONE
user: login: pyccel-bot[bot]
created_at: 2025-03-13T16:49:22Z
body: @jalalium, @EmilyBourne has a few questions/comments about your code. Can you go through and see if you agree with them. If not go ahead and explain why. Once you've adressed all the comments let me know with `/bot mark as ready` and we will see if we can get approval.

author_association: NONE
user: login: jalalium
created_at: 2025-03-16T15:43:13Z
body: /bot run linux
author_association: MEMBER
user: login: jalalium
created_at: 2025-03-16T17:09:08Z
body: /bot run linux
author_association: MEMBER
user: login: jalalium
created_at: 2025-03-16T17:35:30Z
body: /bot run linux
author_association: MEMBER
user: login: jalalium
created_at: 2025-03-16T18:06:48Z
body: /bot run linux
author_association: MEMBER
user: login: jalalium
created_at: 2025-03-16T19:03:56Z
body: /bot run linux
author_association: MEMBER
user: login: jalalium
created_at: 2025-03-17T06:10:29Z
body: /bot run linux
author_association: MEMBER
user: login: pyccel-bot[bot]
created_at: 2025-03-17T06:33:26Z
body: Unfortunately your PR is not passing the tests so it is not quite ready for review yet. Let me know when it is fixed with `/bot mark as ready`.

author_association: NONE
user: login: jalalium
created_at: 2025-03-17T07:24:15Z
body: /bot run linux
author_association: MEMBER
user: login: pyccel-bot[bot]
created_at: 2025-03-17T07:44:58Z
body: Unfortunately your PR is not passing the tests so it is not quite ready for review yet. Let me know when it is fixed with `/bot mark as ready`.

author_association: NONE
user: login: pyccel-bot[bot]
created_at: 2025-03-17T08:26:43Z
body: @jalalium, @EmilyBourne has a few questions/comments about your code. Can you go through and see if you agree with them. If not go ahead and explain why. Once you've adressed all the comments let me know with `/bot mark as ready` and we will see if we can get approval.

author_association: NONE
user: login: jalalium
created_at: 2025-03-17T09:30:32Z
body: /bot run linux
author_association: MEMBER
user: login: pyccel-bot[bot]
created_at: 2025-03-17T10:14:13Z
body: Unfortunately your PR is not passing the tests so it is not quite ready for review yet. Let me know when it is fixed with `/bot mark as ready`.

author_association: NONE
user: login: jalalium
created_at: 2025-03-17T10:22:31Z
body: /bot run linux
author_association: MEMBER
user: login: pyccel-bot[bot]
created_at: 2025-03-17T10:54:14Z
body: Unfortunately your PR is not passing the tests so it is not quite ready for review yet. Let me know when it is fixed with `/bot mark as ready`.

author_association: NONE
user: login: pyccel-bot[bot]
created_at: 2025-03-17T11:23:24Z
body: @EmilyBourne, @jalalium has been working hard and thinks that they have now replied to or fixed all your comments. Could you take another look at the PR and see if you can approve now?

author_association: NONE
user: login: EmilyBourne
created_at: 2025-03-17T16:58:01Z
body: /Bot run pr_tests
author_association: MEMBER
user: login: EmilyBourne
created_at: 2025-03-17T17:35:58Z
body: /bot run pr_tests
author_association: MEMBER