Repository: pyccel/pyccel (2024-03-07 ~ 2024-03-18)

problem_statement: clear : Removes all the elements from the set
### Feature Description

**Add support for clear() method for sets in the Python translation.**
`clear()` : Removes all elements from the set, making it empty.

### Test Code

This Code Should Work 

```python
my_set = {1, 2, 3}
my_set.clear()
```

### Proposed Solution
See description in #1531 
date: 2024-03-07
patch: warning: 3 lines add whitespace errors.
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 322d037c..8ceaee09 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -5,6 +5,9 @@ All notable changes to this project will be documented in this file.
 
 ### Added
 
+-   #1739 : Add Python support for set method `clear()`.
+-   #1739 : Add abstract class `SetMethod` to handle calls to various set methods.
+
 ### Fixed
 
 ### Changed
diff --git a/pyccel/ast/builtin_methods/set_methods.py b/pyccel/ast/builtin_methods/set_methods.py
index b5b3a076..c6626df5 100644
--- a/pyccel/ast/builtin_methods/set_methods.py
+++ b/pyccel/ast/builtin_methods/set_methods.py
@@ -12,9 +12,39 @@ This module contains objects which describe these methods within Pyccel's AST.
 from pyccel.ast.datatypes import NativeVoid, NativeGeneric
 from pyccel.ast.internals import PyccelInternalFunction
 
-__all__ = ('SetAdd',)
+__all__ = ('SetAdd', 'SetClear', 'SetMethod')
 
-class SetAdd(PyccelInternalFunction) :
+class SetMethod(PyccelInternalFunction):
+    """
+    Abstract class for set method calls.
+
+    A subclass of this base class represents calls to a specific 
+    set method.
+
+    Parameters
+    ----------
+    set_variable : TypedAstNode
+        The set on which the method will operate.
+
+    *args : iterable
+        The arguments passed to the function call.
+    """
+    __slots__ = ('_set_variable',)
+    _attribute_nodes = ('_set_variable',)
+    def __init__(self,  set_variable, *args):
+        self._set_variable = set_variable
+        super().__init__(*args)
+
+    @property
+    def set_variable(self):
+        """
+        Get the variable representing the set.
+
+        Get the variable representing the set.
+        """
+        return self._set_variable
+
+class SetAdd(SetMethod) :
     """
     Represents a call to the .add() method.
     
@@ -25,13 +55,12 @@ class SetAdd(PyccelInternalFunction) :
     Parameters
     ----------
     set_variable : TypedAstNode
-        The name of the set.
+        The set on which the method will operate.
 
     new_elem : TypedAstNode
         The element that needs to be added to a set.
     """
-    __slots__ = ("_set_variable", "_add_arg")
-    _attribute_nodes = ("_set_variable", "_add_arg")
+    __slots__ = ()
     _dtype = NativeVoid()
     _shape = None
     _order = None
@@ -50,24 +79,28 @@ class SetAdd(PyccelInternalFunction) :
         )
         if not is_homogeneous:
             raise TypeError("Expecting an argument of the same type as the elements of the set")
-        self._set_variable = set_variable
-        self._add_arg = new_elem
-        super().__init__()
+        super().__init__(set_variable, new_elem)
 
-    @property
-    def set_variable(self):
-        """
-        Get the variable representing the set.
-
-        Get the variable representing the set.
-        """
-        return self._set_variable
+class SetClear(SetMethod):
+    """
+    Represents a call to the .clear() method.
+    
+    The method clear is used to remove all data from a set. 
+    This operation clears all elements from the set, leaving it empty.
 
-    @property
-    def add_argument(self):
-        """
-        Get the argument which is passed to add().
+    Parameters
+    ----------
+    set_variable : TypedAstNode
+        The set on which the method will operate.
+    """
+    __slots__ = ()
+    _dtype = NativeVoid()
+    _shape = None
+    _order = None
+    _rank = 0
+    _precision = None
+    _class_type = NativeVoid()
+    name = 'clear'
 
-        Get the argument which is passed to add().
-        """
-        return self._add_arg
+    def __init__(self, set_variable):
+        super().__init__(set_variable)
diff --git a/pyccel/ast/class_defs.py b/pyccel/ast/class_defs.py
index 250d4618..ecb09b2f 100644
--- a/pyccel/ast/class_defs.py
+++ b/pyccel/ast/class_defs.py
@@ -6,7 +6,7 @@
 This module contains all types which define a python class which is automatically recognised by pyccel
 """
 
-from pyccel.ast.builtin_methods.set_methods import SetAdd
+from pyccel.ast.builtin_methods.set_methods import SetAdd, SetClear
 from pyccel.ast.builtin_methods.list_methods import ListAppend, ListInsert, ListPop, ListClear
 
 
@@ -155,6 +155,8 @@ SetClass = ClassDef('set', class_type=NativeHomogeneousSet(),
         methods=[
             PyccelFunctionDef('add', func_class = SetAdd,
                 decorators = {}),
+            PyccelFunctionDef('clear', func_class = SetClear,
+                decorators={}),
         ])
 
 #=======================================================================================
diff --git a/pyccel/codegen/printing/pycode.py b/pyccel/codegen/printing/pycode.py
index b70d6727..fabdabc3 100644
--- a/pyccel/codegen/printing/pycode.py
+++ b/pyccel/codegen/printing/pycode.py
@@ -884,10 +884,12 @@ class PythonCodePrinter(CodePrinter):
                 stop  = stop,
                 step  = step)
 
-    def _print_SetAdd(self, expr):
-        name = self._print(expr.set_variable)
-        args = self._print(expr.add_argument)
-        return f"{name}.add({args})\n"
+    def _print_SetMethod(self, expr):
+        set_var = self._print(expr.set_variable)
+        name = expr.name
+        args = "" if len(expr.args) == 0 or expr.args[-1] is None \
+            else ', '.join(self._print(a) for a in expr.args)
+        return f"{set_var}.{name}({args})\n"
 
     def _print_Nil(self, expr):
         return 'None'

test_patch: warning: 1 line adds whitespace errors.
diff --git a/tests/epyccel/test_epyccel_sets.py b/tests/epyccel/test_epyccel_sets.py
index 78ff9057..4d838911 100644
--- a/tests/epyccel/test_epyccel_sets.py
+++ b/tests/epyccel/test_epyccel_sets.py
@@ -46,3 +46,34 @@ def test_add_variable_int(language):
     pyccel_result = epyc_add_element()
     python_result = add_element_range()
     assert python_result == pyccel_result
+
+def test_clear_int(language):
+    def clear_int():
+        se = {1,2,4,5}
+        se.clear()
+        return se
+    epyccel_clear = epyccel(clear_int, language = language)
+    pyccel_result = epyccel_clear()
+    python_result = clear_int()
+    assert python_result == pyccel_result
+
+def test_clear_float(language):
+    def clear_float():
+        se = {7.2, 2.1, 9.8, 6.4}
+        se.clear()
+        return se
+    epyccel_clear = epyccel(clear_float, language = language)
+    pyccel_result = epyccel_clear()
+    python_result = clear_float()
+    assert python_result == pyccel_result
+
+def test_clear_complex(language):
+    def clear_complex():
+        se = {3j, 6j, 2j}
+        se.clear()
+        return se
+    epyccel_clear = epyccel(clear_complex, language = language)
+    pyccel_result = epyccel_clear()
+    python_result = clear_complex()
+    assert python_result == pyccel_result
+


----

problem_statement: extend: adds each element of an iterable to the end of the list.
### Feature Description

Add support for extend() method for lists in the Python translation.

### Test Code

This Code Should Work 

```python
list1 = [1, 2, 3]
list2 = [4, 5, 6]

list1.extend(list2)

print(list1) 
>> [1, 2, 3, 4, 5, 6]
```


### Proposed Solution
See description in #1529
date: 2024-03-12
patch: diff --git a/pyccel/ast/builtin_methods/list_methods.py b/pyccel/ast/builtin_methods/list_methods.py
index 43f07e84..b093e17d 100644
--- a/pyccel/ast/builtin_methods/list_methods.py
+++ b/pyccel/ast/builtin_methods/list_methods.py
@@ -13,15 +13,48 @@ This module contains objects which describe these methods within Pyccel's AST.
 from pyccel.ast.datatypes import NativeVoid, NativeGeneric, NativeHomogeneousList
 from pyccel.ast.internals import PyccelInternalFunction
 
-
 __all__ = ('ListAppend',
            'ListClear',
+           'ListExtend',
            'ListInsert',
+           'ListMethod',
            'ListPop',
            )
 
 #==============================================================================
-class ListAppend(PyccelInternalFunction):
+class ListMethod(PyccelInternalFunction):
+    """
+    Abstract class for list method calls.
+
+    A subclass of this base class represents calls to a specific list
+    method.
+
+    Parameters
+    ----------
+    list_obj : TypedAstNode
+        The object which the method is called from.
+    
+    *args : TypedAstNode
+        The arguments passed to list methods.
+    """
+    __slots__ = ("_list_obj",)
+    _attribute_nodes = ("_list_obj",)
+    name = None
+    def __init__(self, list_obj, *args):
+        self._list_obj = list_obj
+        super().__init__(*args)
+
+    @property
+    def list_obj(self):
+        """
+        Get the object representing the list.
+
+        Get the object representing the list.
+        """
+        return self._list_obj
+
+#==============================================================================
+class ListAppend(ListMethod):
     """
     Represents a call to the .append() method.
 
@@ -36,14 +69,13 @@ class ListAppend(PyccelInternalFunction):
 
     Parameters
     ----------
-    list_variable : Variable
-        The variable representing the list.
+    list_obj : TypedAstNode
+        The list object which the method is called from.
     
     new_elem : TypedAstNode
         The argument passed to append() method.
     """
-    __slots__ = ("_list_variable", "_append_arg")
-    _attribute_nodes = ("_list_variable", "_append_arg")
+    __slots__ = ()
     _dtype = NativeVoid()
     _shape = None
     _order = None
@@ -52,40 +84,20 @@ class ListAppend(PyccelInternalFunction):
     _class_type = NativeVoid()
     name = 'append'
 
-    def __init__(self, list_variable, new_elem) -> None:
+    def __init__(self, list_obj, new_elem) -> None:
         is_homogeneous = (
             new_elem.dtype is not NativeGeneric() and
-            list_variable.dtype is not NativeGeneric() and
-            list_variable.dtype == new_elem.dtype and
-            list_variable.precision == new_elem.precision and
-            list_variable.rank - 1 == new_elem.rank
-        )
+            list_obj.dtype is not NativeGeneric() and
+            list_obj.dtype == new_elem.dtype and
+            list_obj.precision == new_elem.precision and
+            list_obj.rank - 1 == new_elem.rank
+            )
         if not is_homogeneous:
             raise TypeError("Expecting an argument of the same type as the elements of the list")
-        self._list_variable = list_variable
-        self._append_arg = new_elem
-        super().__init__()
-
-    @property
-    def list_variable(self):
-        """
-        Get the variable representing the list.
-
-        Get the variable representing the list.
-        """
-        return self._list_variable
-
-    @property
-    def append_argument(self):
-        """
-        Get the argument which is passed to append().
-
-        Get the argument which is passed to append().
-        """
-        return self._append_arg
+        super().__init__(list_obj, new_elem)
 
 #==============================================================================
-class ListPop(PyccelInternalFunction) :
+class ListPop(ListMethod) :
     """
     Represents a call to the .pop() method.
     
@@ -93,49 +105,31 @@ class ListPop(PyccelInternalFunction) :
     removes the item at the specified index. 
     The method also returns the removed item.
 
+    >>> [1, 2].pop()
+    2
+
     Parameters
     ----------
-    list_variable : TypedAstNode
-        The name of the list.
+    list_obj : TypedAstNode
+        The list object which the method is called from.
 
     index_element : TypedAstNode
         The current index value for the element to be popped.
     """
-    __slots__ = ('_dtype','_precision', '_index','_list_variable')
-    _attribute_nodes = ('_index','_list_variable')
-    _rank = 0
-    _order = None
-    _shape = None
+    __slots__ = ('_dtype','_precision', '_rank', '_shape', '_order')
     _class_type = NativeHomogeneousList()
     name = 'pop'
 
-    def __init__(self, list_variable, index_element=None):
-        self._index = index_element
-        self._list_variable = list_variable
-        self._dtype = list_variable.dtype
-        self._precision = list_variable.precision
-        super().__init__()
-
-    @property
-    def pop_index(self):
-        """
-        The current index value for the element to be popped.
-
-        The current index value for the element to be popped.
-        """
-        return self._index
-
-    @property
-    def list_variable(self):
-        """
-        Provide the name of the list as the return value.
-        
-        Provide the name of the list as the return value.
-        """
-        return self._list_variable
+    def __init__(self, list_obj, index_element=None) -> None:
+        self._rank = list_obj.rank - 1
+        self._dtype = list_obj.dtype
+        self._precision = list_obj.precision
+        self._shape = (None if len(list_obj.shape) == 1 else tuple(list_obj.shape[1:]))
+        self._order = (None if self._shape is None or len(self._shape) == 1 else list_obj.order)
+        super().__init__(list_obj, index_element)
 
 #==============================================================================
-class ListClear(PyccelInternalFunction) :
+class ListClear(ListMethod) :
     """
     Represents a call to the .clear() method.
     
@@ -143,13 +137,17 @@ class ListClear(PyccelInternalFunction) :
     effectively turning it into an empty list.
     Note that the .clear() method doesn't return any value.
 
+    >>> a = [1, 2]
+    >>> a.clear()
+    >>> print(a)
+    []
+
     Parameters
     ----------
-    list_variable : TypedAstNode
-        The name of the list.
+    list_obj : TypedAstNode
+        The list object which the method is called from.
     """
-    __slots__ = ('_list_variable',)
-    _attribute_nodes = ('_list_variable',)
+    __slots__ = ()
     _dtype = NativeVoid()
     _precision = -1
     _rank = 0
@@ -158,21 +156,11 @@ class ListClear(PyccelInternalFunction) :
     _class_type = NativeVoid()
     name = 'clear'
 
-    def __init__(self, list_variable):
-        self._list_variable = list_variable
-        super().__init__()
-
-    @property
-    def list_variable(self):
-        """
-        Provide the name of the list as the return value.
-
-        Provide the name of the list as the return value.
-        """
-        return self._list_variable
+    def __init__(self, list_obj) -> None:
+        super().__init__(list_obj)
 
 #==============================================================================
-class ListInsert(PyccelInternalFunction):
+class ListInsert(ListMethod):
     """
     Represents a call to the .insert() method.
 
@@ -188,8 +176,8 @@ class ListInsert(PyccelInternalFunction):
 
     Parameters
     ----------
-    list_variable : Variable
-        The variable representing the list.
+    list_obj : TypedAstNode
+        The list object which the method is called from.
 
     index : TypedAstNode
         The index value for the element to be added.
@@ -197,8 +185,7 @@ class ListInsert(PyccelInternalFunction):
     new_elem : TypedAstNode
         The argument passed to insert() method.
     """
-    __slots__ = ("_index", "_list_variable", "_insert_arg")
-    _attribute_nodes = ("_index", "_list_variable", "_insert_arg")
+    __slots__ = ()
     _dtype = NativeVoid()
     _shape = None
     _order = None
@@ -207,44 +194,49 @@ class ListInsert(PyccelInternalFunction):
     _class_type = NativeVoid()
     name = 'insert'
 
-    def __init__(self, list_variable, index, new_elem) -> None:
+    def __init__(self, list_obj, index, new_elem) -> None:
         is_homogeneous = (
             new_elem.dtype is not NativeGeneric() and
-            list_variable.dtype is not NativeGeneric() and
-            list_variable.dtype == new_elem.dtype and
-            list_variable.precision == new_elem.precision and
-            list_variable.rank - 1 == new_elem.rank
+            list_obj.dtype is not NativeGeneric() and
+            list_obj.dtype == new_elem.dtype and
+            list_obj.precision == new_elem.precision and
+            list_obj.rank - 1 == new_elem.rank
         )
         if not is_homogeneous:
             raise TypeError("Expecting an argument of the same type as the elements of the list")
-        self._index = index
-        self._list_variable = list_variable
-        self._insert_arg = new_elem
-        super().__init__()
-
-    @property
-    def index(self):
-        """
-        Index in which the element will be added.
-
-        Index in which the element will be added.
-        """
-        return self._index
+        super().__init__(list_obj, index, new_elem)
 
-    @property
-    def list_variable(self):
-        """
-        Get the variable representing the list.
+#==============================================================================
+class ListExtend(ListMethod):
+    """
+    Represents a call to the .extend() method.
+
+    Represents a call to the .extend() method of an object with a list type,
+    which adds items of an iterable (list, tuple, dictionary, etc) at the end
+    of a list.
+    This method is handled through the call to `_visit_ListExtend` in
+    the semantic stage. It then attempts to construct a `For` loop node with
+    a body that calls `append()`, or direct `append()` nodes depending on
+    the type of the iterable passed to `extend()`.
+    This class should never be instantiated; it's only purpose is to help
+    construct the annotation_method `_visit_ListExtend`. 
+    The extend method is called as follows:
+
+    >>> a = [1, 2, 3]
+    >>> a.extend(range(4, 8))
+    >>> print(a)
+    [1, 2, 3, 4, 5, 6, 7]
 
-        Get the variable representing the list.
-        """
-        return self._list_variable
+    Parameters
+    ----------
+    list_obj : TypedAstNode
+        The list object which the method is called from.
 
-    @property
-    def insert_argument(self):
-        """
-        Get the argument which is passed to insert().
+    iterable : TypedAstNode
+        The argument passed to extend() method.
+    """
+    __slots__ = ()
+    name = 'extend'
 
-        Get the argument which is passed to insert().
-        """
-        return self._insert_arg
+    def __init__(self, list_obj, iterable) -> None:
+        super().__init__(list_obj, iterable)
diff --git a/pyccel/ast/class_defs.py b/pyccel/ast/class_defs.py
index ecb09b2f..b9070f64 100644
--- a/pyccel/ast/class_defs.py
+++ b/pyccel/ast/class_defs.py
@@ -7,7 +7,7 @@ This module contains all types which define a python class which is automaticall
 """
 
 from pyccel.ast.builtin_methods.set_methods import SetAdd, SetClear
-from pyccel.ast.builtin_methods.list_methods import ListAppend, ListInsert, ListPop, ListClear
+from pyccel.ast.builtin_methods.list_methods import ListAppend, ListInsert, ListPop, ListClear, ListExtend
 
 
 from .builtins  import PythonImag, PythonReal, PythonConjugate
@@ -139,14 +139,11 @@ StringClass = ClassDef('string', class_type = NativeString(),
 
 ListClass = ClassDef('list', class_type = NativeHomogeneousList(),
         methods=[
-            PyccelFunctionDef('append', func_class = ListAppend,
-                decorators = {}),
-            PyccelFunctionDef('clear', func_class = ListClear,
-                decorators = {}),
-            PyccelFunctionDef('insert', func_class = ListInsert,
-                decorators = {}),
-            PyccelFunctionDef('pop', func_class = ListPop,
-                decorators = {}),
+            PyccelFunctionDef('append', func_class = ListAppend),
+            PyccelFunctionDef('clear', func_class = ListClear,),
+            PyccelFunctionDef('extend', func_class = ListExtend),
+            PyccelFunctionDef('insert', func_class = ListInsert),
+            PyccelFunctionDef('pop', func_class = ListPop),
         ])
 
 #=======================================================================================
diff --git a/pyccel/codegen/printing/pycode.py b/pyccel/codegen/printing/pycode.py
index fabdabc3..7fc07f7b 100644
--- a/pyccel/codegen/printing/pycode.py
+++ b/pyccel/codegen/printing/pycode.py
@@ -851,29 +851,15 @@ class PythonCodePrinter(CodePrinter):
 
         return "{}({})".format(name, arg)
 
-    def _print_ListAppend(self, expr):
+    def _print_ListMethod(self, expr):
         method_name = expr.name
-        list_var = self._print(expr.list_variable)
-        append_arg = self._print(expr.append_argument)
-
-        return f"{list_var}.{method_name}({append_arg})\n"
-
-    def _print_ListInsert(self, expr):
-        method_name = expr.name
-        index = self._print(expr.index)
-        list_var = self._print(expr.list_variable)
-        insert_arg = self._print(expr.insert_argument)
-
-        return f"{list_var}.{method_name}({index}, {insert_arg})\n"
-
-    def _print_ListPop(self, expr):
-        args = self._print(expr.pop_index) if expr.pop_index else ""
-        name = self._print(expr.list_variable)
-        return f"{name}.pop({args})"
+        list_obj = self._print(expr.list_obj)
+        if len(expr.args) == 0 or all(arg is None for arg in expr.args):
+            method_args = ''
+        else:
+            method_args = ', '.join(self._print(a) for a in expr.args)
 
-    def _print_ListClear(self, expr):
-        name = self._print(expr.list_variable)
-        return f"{name}.clear()\n"
+        return f"{list_obj}.{method_name}({method_args})\n"
 
     def _print_Slice(self, expr):
         start = self._print(expr.start) if expr.start else ''
diff --git a/pyccel/parser/semantic.py b/pyccel/parser/semantic.py
index 4d596d65..89a38e8a 100644
--- a/pyccel/parser/semantic.py
+++ b/pyccel/parser/semantic.py
@@ -30,6 +30,8 @@ from pyccel.ast.builtins import PythonList, PythonConjugate , PythonSet
 from pyccel.ast.builtins import (PythonRange, PythonZip, PythonEnumerate,
                                  PythonTuple, Lambda, PythonMap)
 
+from pyccel.ast.builtin_methods.list_methods import ListMethod, ListAppend
+
 from pyccel.ast.core import Comment, CommentBlock, Pass
 from pyccel.ast.core import If, IfSection
 from pyccel.ast.core import Allocate, Deallocate
@@ -2633,6 +2635,11 @@ class SemanticParser(BasicParser):
 
         # look for a class method
         if isinstance(rhs, FunctionCall):
+            method = cls_base.get_method(rhs_name)
+            if isinstance(method, PyccelFunctionDef):
+                annotation_method = '_visit_' + method.cls_name.__name__
+                if hasattr(self, annotation_method):
+                    return getattr(self, annotation_method)(expr)
             macro = self.scope.find(rhs_name, 'macros')
             if macro is not None:
                 master = macro.master
@@ -2643,7 +2650,6 @@ class SemanticParser(BasicParser):
                 return FunctionCall(master, args, self._current_function)
 
             args = [FunctionCallArgument(visited_lhs), *self._handle_function_args(rhs.args)]
-            method = cls_base.get_method(rhs_name)
             if cls_base.name == 'numpy.ndarray':
                 numpy_class = method.cls_name
                 self.insert_import('numpy', AsName(numpy_class, numpy_class.name))
@@ -2682,6 +2688,53 @@ class SemanticParser(BasicParser):
             bounding_box=(self.current_ast_node.lineno, self.current_ast_node.col_offset),
             severity='fatal')
 
+    def _visit_ListExtend(self, expr):
+        """
+        Method to navigate the syntactic DottedName node of an `extend()` call.
+
+        The purpose of this `_visit` method is to construct new nodes from a syntactic 
+        DottedName node. It checks the type of the iterable passed to `extend()`.
+        If the iterable is an instance of `PythonList` or `PythonTuple`, it constructs 
+        a CodeBlock node where its body consists of `ListAppend` objects with the 
+        elements of the iterable. If not, it attempts to construct a syntactic `For` 
+        loop to iterate over the iterable object and append its elements to the list 
+        object. Finally, it passes to a `_visit()` call for semantic parsing.
+
+        Parameters
+        ----------
+        expr : DottedName
+            The syntactic DottedName node that represent the call to `.extend()`
+
+        Returns
+        -------
+        PyccelAstNode
+            CodeBlock or For containing ListAppend objects.
+        """
+        iterable = expr.name[1].args[0].value
+
+        if isinstance(iterable, (PythonList, PythonTuple)):
+            list_variable = self._visit(expr.name[0])
+            added_list = self._visit(iterable)
+            try:
+                store = [ListAppend(list_variable, a) for a in added_list]
+            except TypeError as e:
+                msg = str(e)
+                errors.report(msg, symbol=expr, severity='fatal')
+            return CodeBlock(store)
+        else:
+            pyccel_stage.set_stage('syntactic')
+            for_target = self.scope.get_new_name('index')
+            arg = FunctionCallArgument(for_target)
+            func_call = FunctionCall('append', [arg])
+            dotted = DottedName(expr.name[0], func_call)
+            lhs = PyccelSymbol('_', is_temp=True)
+            assign = Assign(lhs, dotted)
+            assign.set_current_ast(expr.python_ast)
+            body = CodeBlock([assign])
+            for_obj = For(for_target, iterable, body)
+            pyccel_stage.set_stage('semantic')
+            return self._visit(for_obj)
+
     def _visit_PyccelOperator(self, expr):
         args     = [self._visit(a) for a in expr.args]
         return self._create_PyccelOperator(expr, args)
@@ -3071,6 +3124,9 @@ class SemanticParser(BasicParser):
             errors.report("Cannot assign a datatype to a variable.",
                     symbol=expr, severity='error')
 
+        # Checking for the result of _visit_ListExtend
+        if isinstance(rhs, For) or (isinstance(rhs, CodeBlock) and isinstance(rhs.body[0], ListMethod)):
+            return rhs
         if isinstance(rhs, ConstructorCall):
             return rhs
         elif isinstance(rhs, FunctionDef):

test_patch: warning: 5 lines add whitespace errors.
diff --git a/tests/epyccel/modules/list_class_attr.py b/tests/epyccel/modules/list_class_attr.py
new file mode 100644
index 00000000..ec57675c
--- /dev/null
+++ b/tests/epyccel/modules/list_class_attr.py
@@ -0,0 +1,22 @@
+# pylint: disable=missing-function-docstring, missing-module-docstring, missing-class-docstring
+
+class A:
+    def __init__(self, n : int):
+        self.ls = [n, n]
+
+class B:
+    def __init__(self, a : A):
+        self.a = a
+
+def fn():
+    a = (3, 4, 5)
+    my_a = A(2)
+    my_b = B(my_a)
+    my_b.a.ls.extend([3,4])
+    my_b.a.ls.extend((3,4))
+    my_b.a.ls.extend(range(0, 4))
+    my_b.a.ls.extend(a)
+    store = [1]
+    store.extend(my_b.a.ls)
+    return store
+
diff --git a/tests/epyccel/modules/list_user_defined_objs1.py b/tests/epyccel/modules/list_user_defined_objs1.py
new file mode 100644
index 00000000..fbbcda8a
--- /dev/null
+++ b/tests/epyccel/modules/list_user_defined_objs1.py
@@ -0,0 +1,16 @@
+# pylint: disable=missing-function-docstring, missing-module-docstring, missing-class-docstring
+
+class A:
+    def __init__(self, x : int):
+        self.x = x
+
+def fn():
+    a = A(1)
+    b = A(2)
+    c = A(3)
+    d = A(4)
+    e = A(5)
+    lst = [a, b, c]
+    lst.append(d)
+    lst.append(e, e)
+    return lst
diff --git a/tests/epyccel/modules/list_user_defined_objs2.py b/tests/epyccel/modules/list_user_defined_objs2.py
new file mode 100644
index 00000000..a489c9fd
--- /dev/null
+++ b/tests/epyccel/modules/list_user_defined_objs2.py
@@ -0,0 +1,16 @@
+# pylint: disable=missing-function-docstring, missing-module-docstring, missing-class-docstring
+
+class A:
+    def __init__(self, x : int):
+        self.x = x
+
+def fn():
+    a = A(1)
+    b = A(2)
+    c = A(3)
+    d = A(4)
+    e = A(5)
+    lst = [a, b, c]
+    lst2 = [d, e]
+    lst.extend(lst2)
+    return lst
diff --git a/tests/epyccel/test_epyccel_lists.py b/tests/epyccel/test_epyccel_lists.py
index 2ab40691..c8a463d3 100644
--- a/tests/epyccel/test_epyccel_lists.py
+++ b/tests/epyccel/test_epyccel_lists.py
@@ -1,5 +1,6 @@
 # pylint: disable=missing-function-docstring, missing-module-docstring
 import pytest
+import numpy as np
 from  pyccel.epyccel import epyccel
 
 @pytest.fixture( params=[
@@ -26,6 +27,70 @@ def test_pop_last_element(language) :
     assert isinstance(python_result, type(pyccel_result))
     assert python_result == pyccel_result
 
+def test_pop_list_bool(language) :
+    def pop_last_element():
+        a = [True, False, True]
+        return a.pop()
+    epyc_last_element = epyccel(pop_last_element, language = language)
+    pyccel_result = epyc_last_element()
+    python_result = pop_last_element()
+    assert isinstance(python_result, type(pyccel_result))
+    assert python_result == pyccel_result
+
+def test_pop_list_float(language) :
+    def pop_last_element():
+        a = [1.5 , 3.1, 4.5]
+        return a.pop()
+    epyc_last_element = epyccel(pop_last_element, language = language)
+    pyccel_result = epyc_last_element()
+    python_result = pop_last_element()
+    assert isinstance(python_result, type(pyccel_result))
+    assert python_result == pyccel_result
+
+def test_pop_list_of_lists(language) :
+    def pop_last_element():
+        a = [[4.6, 3.3], [4.2, 9.1], [2.3, 6.8]]
+        return a.pop()
+    epyc_last_element = epyccel(pop_last_element, language = language)
+    pyccel_result = epyc_last_element()
+    python_result = pop_last_element()
+    assert isinstance(python_result, type(pyccel_result))
+    assert python_result == pyccel_result
+
+def test_pop_list_of_tuples(language) :
+    def pop_last_element():
+        a = [(4.6, 3.3), (4.2, 9.1), (2.3, 6.8)]
+        return a.pop()
+    epyc_last_element = epyccel(pop_last_element, language = language)
+    pyccel_result = epyc_last_element()
+    python_result = pop_last_element()
+    assert isinstance(python_result, type(pyccel_result))
+    assert python_result == pyccel_result
+
+def test_pop_list_of_ndarrays(language) :
+    def pop_last_element():
+        from numpy import array
+
+        array1 = array([[1, 2, 3], [4, 5, 6]])
+        array2 = array([[7, 8, 9], [10, 11, 12]])
+        array3 = array([[13, 14, 15], [16, 17, 18]])
+        a = [array1, array2, array3]
+        return a.pop()
+    epyc_last_element = epyccel(pop_last_element, language = language)
+    pyccel_result = epyc_last_element()
+    python_result = pop_last_element()
+    assert isinstance(python_result, type(pyccel_result))
+    assert np.array_equal(python_result, pyccel_result)
+
+def test_pop_list_of_tuples_and_lists(language) :
+    def pop_last_element():
+        a = [(True, False), [True, False]]
+        return a.pop()
+    epyc_last_element = epyccel(pop_last_element, language = language)
+    pyccel_result = epyc_last_element()
+    python_result = pop_last_element()
+    assert isinstance(python_result, type(pyccel_result))
+    assert python_result == pyccel_result
 
 def test_pop_specific_index(language) :
     def pop_specific_index():
@@ -118,6 +183,68 @@ def test_append_range_tuple(language):
     epyc_f = epyccel(f, language=language)
     assert f() == epyc_f()
 
+def test_append_bool(language):
+    def f():
+        a = [True, True, True]
+        a.append(False)
+        a.append(False)
+        a.append(True)
+        return a
+
+    epyc_f = epyccel(f, language=language)
+    assert f() == epyc_f()
+
+def test_append_float(language):
+    def f():
+        a = [3.5, 2.2, 1.5]
+        a.append(3.0)
+        a.append(2.9)
+        a.append(1.1)
+        return a
+
+    epyc_f = epyccel(f, language=language)
+    assert f() == epyc_f()
+
+def test_append_complex(language):
+    def f():
+        a = [1 + 2j, 3 + 4j, 5 + 6j]
+        a.append(9j)
+        a.append(2 + 2j)
+        a.append(1j)
+        return a
+
+    epyc_f = epyccel(f, language=language)
+    assert f() == epyc_f()
+
+def test_append_ndarrays(language):
+    def f():
+        from numpy import array
+
+        array1 = array([[1, 2, 3], [4, 5, 6]])
+        array2 = array([[7, 8, 9], [10, 11, 12]])
+        array3 = array([[13, 14, 15], [16, 17, 18]])
+        array4 = array([[5, 4, 66], [69, 2, 180]])
+        array5 = array([[13, 14, 15], [6, 27, 0]])
+        array6 = array([[13, 1, 5], [16, 17, 18]])
+        a = [array1, array2, array3]
+        a.append(array4)
+        a.append(array5)
+        a.append(array6)
+        return a
+
+    epyc_f = epyccel(f, language=language)
+    assert np.array_equal(f(), epyc_f())
+
+def test_append_user_defined_objects(language):
+    import modules.list_user_defined_objs2 as mod
+
+    modnew = epyccel(mod, language=language)
+    python_list = mod.fn()
+    accelerated_list = modnew.fn()
+    assert len(python_list) == len(accelerated_list)
+    for python_elem, accelerated_elem in zip(python_list, accelerated_list):
+        assert python_elem.x == accelerated_elem.x
+
 def test_insert_basic(language):
     def f():
         a = [1, 2, 3]
@@ -127,6 +254,71 @@ def test_insert_basic(language):
     epyc_f = epyccel(f, language=language)
     assert f() == epyc_f()
 
+def test_insert_booleans(language):
+    def f():
+        a = [True, False, True]
+        a.insert(0, True)
+        a.insert(-100, True)
+        a.insert(1000, False)
+        a.insert(0, False)
+        a.insert(666, True)
+        a.insert(-1, True)
+        a.insert(-25, False)
+        return a
+
+    epyc_f = epyccel(f, language=language)
+    assert f() == epyc_f()
+
+def test_insert_complex(language):
+    def f():
+        a = [2j, 3 + 6j, 0 + 0j]
+        a.insert(0, 9j)
+        a.insert(-100, 1 - 1j)
+        a.insert(1000, -3j)
+        a.insert(0, 0j)
+        a.insert(666, 1j)
+        a.insert(-1, 1 + 0j)
+        a.insert(-25, 0 - 0j)
+        return a
+
+    epyc_f = epyccel(f, language=language)
+    assert f() == epyc_f()
+
+def test_insert_float(language):
+    def f():
+        a = [0.0, 3.6 , 0.5]
+        a.insert(0, 6.4)
+        a.insert(-100, 25.12)
+        a.insert(1000, 13.04)
+        a.insert(0, 19.99)
+        a.insert(666, 20.00)
+        a.insert(-1, 3.01)
+        a.insert(-25, 2.5)
+        return a
+
+    epyc_f = epyccel(f, language=language)
+    assert f() == epyc_f()
+
+def test_insert_ndarrays(language):
+    def f():
+        from numpy import array
+
+        array1 = array([[1, 2, 3], [4, 5, 6]])
+        array2 = array([[7, 8, 9], [10, 11, 12]])
+        array3 = array([[13, 14, 15], [16, 17, 18]])
+        array4 = array([[5, 4, 66], [69, 2, 180]])
+        array5 = array([[13, 14, 15], [6, 27, 0]])
+        array6 = array([[13, 1, 5], [16, 17, 18]])
+        a = [array1, array2]
+        a.insert(-100, array3)
+        a.insert(0, array4)
+        a.insert(100, array5)
+        a.insert(-3, array6)
+        return a
+
+    epyc_f = epyccel(f, language=language)
+    assert np.array_equal(f(), epyc_f())
+
 def test_insert_multiple(language):
     def f():
         a = [1, 2, 3]
@@ -222,3 +414,189 @@ def test_clear_3(language):
     pyccel_result = epyc_clear_3()
     python_result = clear_3()
     assert python_result == pyccel_result
+
+def test_extend_basic(language):
+    def f():
+        a = [1, 2, 3]
+        b = [4, 5, 6]
+        a.extend(b)
+        return a
+
+    epyc_f = epyccel(f, language=language)
+    assert f() == epyc_f()
+
+def test_extend_multiple(language):
+    def f():
+        a = [1, 2, 3]
+        a.extend([4, 5])
+        a.extend([6, 7, 8, 9])
+        a.extend([10])
+        return a
+
+    epyc_f = epyccel(f, language=language)
+    assert f() == epyc_f()
+
+def test_extend_nested_list(language):
+    def f():
+        a = [[1, 2, 3]]
+        a.extend([[4, 5, 6]])
+        return a
+
+    epyc_f = epyccel(f, language=language)
+    assert f() == epyc_f()
+
+def test_extend_tuple_with_list(language):
+    def f():
+        a = [1, 2, 3]
+        b = (4, 5, 6)
+        a.extend(b)
+        return a
+
+    epyc_f = epyccel(f, language=language)
+    assert f() == epyc_f()
+
+def test_extend_boolean_tuple(language):
+    def f():
+        a = [True, False, True]
+        b = (False, True, False)
+        a.extend(b)
+        return a
+
+    epyc_f = epyccel(f, language=language)
+    assert f() == epyc_f()
+
+def test_extend_float_tuple(language):
+    def f():
+        a = [3.4, 2.1, 3.9]
+        b = (4.1, 5.9, 0.3)
+        a.extend(b)
+        return a
+
+    epyc_f = epyccel(f, language=language)
+    assert f() == epyc_f()
+
+def test_extend_complex_list(language):
+    def f():
+        a = [1j, 2 + 3j, 0 + 0j]
+        b = [4j, 5j, 1 + 6j]
+        a.extend(b)
+        return a
+
+    epyc_f = epyccel(f, language=language)
+    assert f() == epyc_f()
+
+def test_extend_range(language):
+    def f():
+        a = [1, 2, 3]
+        a.extend(range(4, 9))
+        return a
+
+    epyc_f = epyccel(f, language=language)
+    assert f() == epyc_f()
+
+def test_extend_function_return(language):
+    def f():
+        def g():
+            a = [4, 5]
+            return a
+
+        lst = [1, 2, 3]
+        lst.extend(g())
+
+    epyc_f = epyccel(f, language=language)
+    assert f() == epyc_f()
+
+def test_extend_list_as_arg(language):
+    def f():
+        a = [1, 2, 3]
+        a.extend([4, 5, 6])
+        return a
+
+    epyc_f = epyccel(f, language=language)
+    assert f() == epyc_f()
+
+def test_extend_tuple_as_arg(language):
+    def f():
+        a = [1, 2, 3]
+        a.extend((4, 5, 6))
+        return a
+
+    epyc_f = epyccel(f, language=language)
+    assert f() == epyc_f()
+
+def test_extend_np_int(language):
+    def f():
+        from numpy import ones, int64
+
+        a = [int64(1),int64(2),int64(3)]
+        b = ones(3, dtype=int64)
+        a.extend(b)
+        return a
+
+    epyc_f = epyccel(f, language=language)
+    assert f() == epyc_f()
+
+def test_extend_np_float(language):
+    def f():
+        from numpy import ones, float64
+
+        a = [float64(1.0), float64(2.0), float64(3.0)]
+        b = ones(3, dtype=float64)
+        a.extend(b)
+        return a
+
+    epyc_f = epyccel(f, language=language)
+    assert f() == epyc_f()
+
+def test_extend_user_defined_objects(language):
+    import modules.list_user_defined_objs2 as mod
+
+    modnew = epyccel(mod, language=language)
+    python_list = mod.fn()
+    accelerated_list = modnew.fn()
+    assert len(python_list) == len(accelerated_list)
+    for python_elem, accelerated_elem in zip(python_list, accelerated_list):
+        assert python_elem.x == accelerated_elem.x
+
+def test_extend_list_class_attribute(language):
+    import modules.list_class_attr as mod
+
+    modnew = epyccel(mod, language=language)
+    python_list = mod.fn()
+    accelerated_list = modnew.fn()
+    assert python_list == accelerated_list
+
+def test_mixed_list_methods(language):
+    def f():
+        a = [(1, 4, 5), (33, 12, 5), (3, 5)]
+        a.append((0, 1, 2))
+        a.pop()
+        a.clear()
+        a.insert(-10, (2, 4, 3))
+        a.extend(((4, 5, 6), (3, 3)))
+        return a
+
+    epyc_f = epyccel(f, language=language)
+    assert f() == epyc_f()
+
+@pytest.mark.parametrize( 'language', [
+        pytest.param("c", marks = [
+            pytest.mark.skip(reason="List return not supported in c"),
+            pytest.mark.c]),
+        pytest.param("fortran", marks = [
+            pytest.mark.skip(reason="List return not supported in fortran"),
+            pytest.mark.fortran]),
+        pytest.param("python", marks = [
+            pytest.mark.xfail(reason="List return not implemented, related issue #337"),
+            pytest.mark.fortran]),
+    ]
+)
+def test_extend_returned_list(language):
+    def f():
+        def g():
+            return [4, 5, 6]
+        lst = [1, 2, 3]
+        lst.extend(g())
+    epyc_f = epyccel(f, language=language)
+    assert f() == epyc_f()
+
diff --git a/tests/errors/semantic/blocking/EXTEND_LIST_INCOMPATIBLE_TYPES_0.py b/tests/errors/semantic/blocking/EXTEND_LIST_INCOMPATIBLE_TYPES_0.py
new file mode 100644
index 00000000..95a907ba
--- /dev/null
+++ b/tests/errors/semantic/blocking/EXTEND_LIST_INCOMPATIBLE_TYPES_0.py
@@ -0,0 +1,7 @@
+# pylint: disable=missing-function-docstring, missing-module-docstring
+
+import numpy as np
+a = [1,2,3]
+b = np.array([4, 5, 6], dtype=np.int16)
+a.extend(b)
+
diff --git a/tests/errors/semantic/blocking/EXTEND_LIST_INCOMPATIBLE_TYPES_1.py b/tests/errors/semantic/blocking/EXTEND_LIST_INCOMPATIBLE_TYPES_1.py
new file mode 100644
index 00000000..3dd8f683
--- /dev/null
+++ b/tests/errors/semantic/blocking/EXTEND_LIST_INCOMPATIBLE_TYPES_1.py
@@ -0,0 +1,6 @@
+# pylint: disable=missing-function-docstring, missing-module-docstring
+
+
+a = [[1,2,3]]
+a.extend([4])
+
diff --git a/tests/errors/semantic/blocking/EXTEND_LIST_INCOMPATIBLE_TYPES_2.py b/tests/errors/semantic/blocking/EXTEND_LIST_INCOMPATIBLE_TYPES_2.py
new file mode 100644
index 00000000..7a237304
--- /dev/null
+++ b/tests/errors/semantic/blocking/EXTEND_LIST_INCOMPATIBLE_TYPES_2.py
@@ -0,0 +1,7 @@
+# pylint: disable=missing-function-docstring, missing-module-docstring
+
+a = [1,2,3]
+b = (((4,5), (3, 4)))
+a.extend(b)
+print(a)
+


----

problem_statement: remove: method removes the first occurrence of the element with the specified value.
### Feature Description
Add support for remove() method for lists in the Python translation.

### Test Code
This code should work when this issue is fixed:
```python

lis = [1, 2, 3]
lis.remove(2)
print(lis)
>> [1, 3]
```

### Proposed Solution
See description in https://github.com/pyccel/pyccel/issues/1529
date: 2024-03-18
patch: diff --git a/pyccel/ast/builtin_methods/list_methods.py b/pyccel/ast/builtin_methods/list_methods.py
index b093e17d..4ae3d45b 100644
--- a/pyccel/ast/builtin_methods/list_methods.py
+++ b/pyccel/ast/builtin_methods/list_methods.py
@@ -19,6 +19,7 @@ __all__ = ('ListAppend',
            'ListInsert',
            'ListMethod',
            'ListPop',
+           'ListRemove',
            )
 
 #==============================================================================
@@ -240,3 +241,50 @@ class ListExtend(ListMethod):
 
     def __init__(self, list_obj, iterable) -> None:
         super().__init__(list_obj, iterable)
+
+#==============================================================================
+class ListRemove(ListMethod) :
+    """
+    Represents a call to the .remove() method.
+    
+    Represents a call to the .remove() method which removes the first
+    occurrence of a given element from the list.
+    Note that the .remove() method doesn't return any value.
+
+    >>> a = [[1, 2], [3, 4]]
+    >>> a.remove([1, 2])
+    >>> print(a)
+    [[3, 4]]
+
+    Parameters
+    ----------
+    list_obj : TypedAstNode
+        The list object which the method is called from.
+
+    removed_obj : TypedAstNode
+        The object to be removed from the list.
+    """
+    __slots__ = ()
+    _dtype = NativeVoid()
+    _shape = None
+    _order = None
+    _rank = 0
+    _precision = -1
+    _shape = None
+    _order = None
+    _rank = 0
+    _class_type = NativeVoid()
+    name = 'remove'
+
+    def __init__(self, list_obj, removed_obj) -> None:
+        is_homogeneous = (
+            removed_obj.dtype is not NativeGeneric() and
+            list_obj.dtype is not NativeGeneric() and
+            list_obj.dtype == removed_obj.dtype and
+            list_obj.precision == removed_obj.precision and
+            list_obj.rank - 1 == removed_obj.rank
+        )
+        if not is_homogeneous:
+            raise TypeError(f"Can't remove an element of type {removed_obj.dtype} from list of {list_obj.dtype}")
+        super().__init__(list_obj, removed_obj)
+
diff --git a/pyccel/ast/class_defs.py b/pyccel/ast/class_defs.py
index b9070f64..73362b05 100644
--- a/pyccel/ast/class_defs.py
+++ b/pyccel/ast/class_defs.py
@@ -7,7 +7,7 @@ This module contains all types which define a python class which is automaticall
 """
 
 from pyccel.ast.builtin_methods.set_methods import SetAdd, SetClear
-from pyccel.ast.builtin_methods.list_methods import ListAppend, ListInsert, ListPop, ListClear, ListExtend
+from pyccel.ast.builtin_methods.list_methods import ListAppend, ListInsert, ListPop, ListClear, ListExtend, ListRemove
 
 
 from .builtins  import PythonImag, PythonReal, PythonConjugate
@@ -144,6 +144,7 @@ ListClass = ClassDef('list', class_type = NativeHomogeneousList(),
             PyccelFunctionDef('extend', func_class = ListExtend),
             PyccelFunctionDef('insert', func_class = ListInsert),
             PyccelFunctionDef('pop', func_class = ListPop),
+            PyccelFunctionDef('remove', func_class = ListRemove),
         ])
 
 #=======================================================================================

test_patch: Falling back to direct application...
diff --git a/tests/epyccel/test_epyccel_lists.py b/tests/epyccel/test_epyccel_lists.py
index c8a463d3..4d8d2ad9 100644
--- a/tests/epyccel/test_epyccel_lists.py
+++ b/tests/epyccel/test_epyccel_lists.py
@@ -558,6 +558,38 @@ def test_extend_user_defined_objects(language):
     for python_elem, accelerated_elem in zip(python_list, accelerated_list):
         assert python_elem.x == accelerated_elem.x
 
+def test_remove_basic(language):
+    def f():
+        lst = [1, 2, 3, 4]
+        lst.remove(3)
+        return lst
+    epyc_f = epyccel(f, language=language)
+    assert f() == epyc_f()
+
+def test_remove_float(language):
+    def f():
+        lst = [1.4, 2.3, 3.2, 4.4]
+        lst.remove(3.2)
+        return lst
+    epyc_f = epyccel(f, language=language)
+    assert f() == epyc_f()
+
+def test_remove_complex(language):
+    def f():
+        lst = [1j, 3j, 8j]
+        lst.remove(3j)
+        return lst
+    epyc_f = epyccel(f, language=language)
+    assert f() == epyc_f()
+
+def test_remove_list_from_list(language):
+    def f():
+        lst = [[True, False, True], [False, True]]
+        lst.remove([False, True])
+        return lst
+    epyc_f = epyccel(f, language=language)
+    assert f() == epyc_f()
+
 def test_extend_list_class_attribute(language):
     import modules.list_class_attr as mod