Repository: pyccel/pyccel (python)

Issue:
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

Pull request:
List support: extend method
Add support for the `extend` method of Python lists to the semantic stage.
Add handling of that method to the Python printer. This fixes #1694.

**Commit Summary**
- Create a superclass `ListMethod` inheriting from `PyccelInternalFunction`, representing the structure for all list methods.
- Create one dynamic printer `_print_ListMethod` for all list methods to prevent code duplication.
- Add the `ListExtend` class only to document the behavior of `extend()` and its Pyccel implementation.
- Create `_visit_ListExtend`, which, depending on the argument passed to `extend()`, tries to create semantic nodes of `append()` or a syntactic node of a `For` loop and then re-parse it.
- Add tests for multiple list methods involving many scenarios where the functions could be used.

Patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index d7e9a0e2..de6d46ba 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -5,6 +5,7 @@ All notable changes to this project will be documented in this file.
 
 ### Added
 
+-   #1694 : Add Python support for list method `extend()`.    
 -   #1739 : Add Python support for set method `clear()`.
 -   #1739 : Add abstract class `SetMethod` to handle calls to various set methods.
 -   #1740 : Add Python support for set method `copy()`.
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 f54a0aa9..69b2737a 100644
--- a/pyccel/ast/class_defs.py
+++ b/pyccel/ast/class_defs.py
@@ -6,32 +6,33 @@
 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, SetClear, SetCopy
-from pyccel.ast.builtin_methods.list_methods import ListAppend, ListInsert, ListPop, ListClear
+from pyccel.ast.builtin_methods.set_methods  import SetAdd, SetClear, SetCopy
+from pyccel.ast.builtin_methods.list_methods import ListAppend, ListInsert, ListPop, ListClear, ListExtend
 
-
-from .builtins  import PythonImag, PythonReal, PythonConjugate
-from .core      import ClassDef, PyccelFunctionDef
+from .builtins   import PythonImag, PythonReal, PythonConjugate
+from .core       import ClassDef, PyccelFunctionDef
 from .c_concepts import CStackArray
-from .datatypes import (NativeBool, NativeInteger, NativeFloat,
-                        NativeComplex, NativeString, NativeNumericTypes,
-                        NativeTuple, CustomDataType, NativeHomogeneousList, NativeHomogeneousSet)
-from .numpyext  import (NumpyShape, NumpySum, NumpyAmin, NumpyAmax,
-                        NumpyImag, NumpyReal, NumpyTranspose,
-                        NumpyConjugate, NumpySize, NumpyResultType,
-                        NumpyArray, NumpyNDArrayType)
-
-__all__ = ('BooleanClass',
-        'IntegerClass',
-        'FloatClass',
-        'ComplexClass',
-        'SetClass',
-        'StringClass',
-        'NumpyArrayClass',
-        'TupleClass',
-        'ListClass',
-        'literal_classes',
-        'get_cls_base')
+from .datatypes  import (NativeBool, NativeInteger, NativeFloat,
+                         NativeComplex, NativeString, NativeNumericTypes,
+                         NativeTuple, CustomDataType, NativeHomogeneousList, NativeHomogeneousSet)
+from .numpyext   import (NumpyShape, NumpySum, NumpyAmin, NumpyAmax,
+                         NumpyImag, NumpyReal, NumpyTranspose,
+                         NumpyConjugate, NumpySize, NumpyResultType,
+                         NumpyArray, NumpyNDArrayType)
+
+__all__ = (
+    'BooleanClass',
+    'IntegerClass',
+    'FloatClass',
+    'ComplexClass',
+    'SetClass',
+    'StringClass',
+    'NumpyArrayClass',
+    'TupleClass',
+    'ListClass',
+    'literal_classes',
+    'get_cls_base',
+)
 
 #=======================================================================================
 
@@ -139,14 +140,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:
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)
+