Repository: pyccel/pyccel (python)

Issue:
append: used for adding elements to the end of the list.
### Feature Description
Add support for append() method for lists in the Python translation.

### Test Code
This code should work when this issue is fixed:
```python
a = [1, 2]
a.append(1)
```

### Proposed Solution
See description in #1529

Pull request description:
List support: append method
Add support for the append() method of Python lists to the semantic stage. Add handling of that method to the Python printer. This fixes #1689.

**Commit Summary**
- Add a class representation for the `append()` method, inheriting from `PyccelInternalFunction`. The constructor ensures compatibility between the datatypes of the `append()` argument and the list for homogenous lists.
- Create `ClassDef` representing a `NativeHomogeneousList`. It includes a method `append()` implemented by the `ListAppend` class.`
- Update condition in `get_cls_base()` function to properly check on the classtype of the containers.
- Add the python printer `_print_ListAppend()` to generate the appropriate python representation of the `append()` method.
- Add check on the semantic to ensure that the correct class definition(`PythonList`) is assigned to variables created via expressions such as `a = [1]`
- Add tests for the `append()` method involving many scenarios where the function could be used.

Patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 6a32b883..686dd9dd 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -5,6 +5,8 @@ All notable changes to this project will be documented in this file.
 
 ### Added
 
+-   #1529 : Add Python support for list method `append()`
+
 ### Fixed
 
 ### Changed
diff --git a/pyccel/ast/builtin_methods/list_methods.py b/pyccel/ast/builtin_methods/list_methods.py
new file mode 100644
index 00000000..d9e3451d
--- /dev/null
+++ b/pyccel/ast/builtin_methods/list_methods.py
@@ -0,0 +1,81 @@
+# coding: utf-8
+#------------------------------------------------------------------------------------------#
+# This file is part of Pyccel which is released under MIT License. See the LICENSE file or #
+# go to https://github.com/pyccel/pyccel/blob/master/LICENSE for full license details.     #
+#------------------------------------------------------------------------------------------#
+"""
+The List container has a number of built-in methods that are 
+always available.
+
+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',)
+
+
+class ListAppend(PyccelInternalFunction):
+    """
+    Represents a call to the .append() method.
+
+    Represents a call to the .append() method of an object with a list type,
+    which adds an element to the end of the list. This method returns `None`.
+    The append method is called as follows:
+
+    >>> a = [1]
+    >>> a.append(2)
+    >>> print(a)
+    [1, 2]
+
+    Parameters
+    ----------
+    list_variable : Variable
+        The variable representing the list.
+    
+    new_elem : Variable
+        The argument passed to append() method.
+    """
+    __slots__ = ("_list_variable", "_append_arg")
+    _attribute_nodes = ("_list_variable", "_append_arg")
+    _dtype = NativeVoid()
+    _shape = None
+    _order = None
+    _rank = 0
+    _precision = -1
+    _class_type = NativeHomogeneousList()
+    name = 'append'
+
+    def __init__(self, list_variable, 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
+        )
+        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
diff --git a/pyccel/ast/class_defs.py b/pyccel/ast/class_defs.py
index 0a7de34c..693fc685 100644
--- a/pyccel/ast/class_defs.py
+++ b/pyccel/ast/class_defs.py
@@ -5,12 +5,16 @@
 """
 This module contains all types which define a python class which is automatically recognised by pyccel
 """
+
+from pyccel.ast.builtin_methods.list_methods import ListAppend
+
+
 from .builtins  import PythonImag, PythonReal, PythonConjugate
 from .core      import ClassDef, PyccelFunctionDef
 from .c_concepts import CStackArray
 from .datatypes import (NativeBool, NativeInteger, NativeFloat,
-                        NativeComplex, NativeString, NativeNumeric,
-                        NativeTuple, CustomDataType)
+                        NativeComplex, NativeString, NativeNumericTypes,
+                        NativeTuple, CustomDataType, NativeHomogeneousList)
 from .numpyext  import (NumpyShape, NumpySum, NumpyAmin, NumpyAmax,
                         NumpyImag, NumpyReal, NumpyTranspose,
                         NumpyConjugate, NumpySize, NumpyResultType,
@@ -23,6 +27,7 @@ __all__ = ('BooleanClass',
         'StringClass',
         'NumpyArrayClass',
         'TupleClass',
+        'ListClass',
         'literal_classes',
         'get_cls_base')
 
@@ -130,6 +135,14 @@ StringClass = ClassDef('string', class_type = NativeString(),
 
 #=======================================================================================
 
+ListClass = ClassDef('list', class_type = NativeHomogeneousList(),
+        methods=[
+            PyccelFunctionDef('append', func_class = ListAppend,
+                decorators = {}),
+        ])
+
+#=======================================================================================
+
 TupleClass = ClassDef('tuple', class_type = NativeTuple(),
         methods=[
             #index
@@ -217,10 +230,12 @@ def get_cls_base(dtype, precision, container_type):
         return None
     if precision in (-1, 0, None) and container_type is dtype:
         return literal_classes[dtype]
-    elif dtype in NativeNumeric or isinstance(container_type, NumpyNDArrayType):
+    elif isinstance(container_type, (*NativeNumericTypes, NumpyNDArrayType)):
         return NumpyArrayClass
     elif isinstance(container_type, NativeTuple):
         return TupleClass
+    elif isinstance(container_type, NativeHomogeneousList):
+        return ListClass
     else:
         if container_type:
             type_name = str(container_type)
diff --git a/pyccel/codegen/printing/pycode.py b/pyccel/codegen/printing/pycode.py
index 622e0402..f2e0546b 100644
--- a/pyccel/codegen/printing/pycode.py
+++ b/pyccel/codegen/printing/pycode.py
@@ -845,6 +845,13 @@ class PythonCodePrinter(CodePrinter):
 
         return "{}({})".format(name, arg)
 
+    def _print_ListAppend(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_Slice(self, expr):
         start = self._print(expr.start) if expr.start else ''
         stop  = self._print(expr.stop)  if expr.stop  else ''
diff --git a/pyccel/parser/semantic.py b/pyccel/parser/semantic.py
index e67467c9..3f08d9d2 100644
--- a/pyccel/parser/semantic.py
+++ b/pyccel/parser/semantic.py
@@ -1094,7 +1094,7 @@ class SemanticParser(BasicParser):
         func : FunctionDef instance, Interface instance or PyccelInternalFunction type
                The function being called.
 
-        args : tuple
+        args : iterable
                The arguments passed to the function.
 
         is_method : bool
@@ -1118,10 +1118,13 @@ class SemanticParser(BasicParser):
 
             try:
                 new_expr = func(*args, **kwargs)
-            except TypeError:
-                errors.report(UNRECOGNISED_FUNCTION_CALL,
-                        symbol = expr,
-                        severity = 'fatal')
+            except TypeError as e:
+                message = str(e)
+                if not message:
+                    message = UNRECOGNISED_FUNCTION_CALL
+                errors.report(message,
+                                symbol = expr,
+                                severity = 'fatal')
 
             return new_expr
         else:


Test patch:
diff --git a/tests/epyccel/test_epyccel_lists.py b/tests/epyccel/test_epyccel_lists.py
new file mode 100755
index 00000000..7147954e
--- /dev/null
+++ b/tests/epyccel/test_epyccel_lists.py
@@ -0,0 +1,81 @@
+# pylint: disable=missing-function-docstring, missing-module-docstring, missing-class-docstring
+# coding: utf-8
+""" Tests for list methods.
+"""
+
+import pytest
+from pyccel.epyccel import epyccel
+
+@pytest.fixture( params=[
+        pytest.param("fortran", marks = [
+            pytest.mark.skip(reason="list methods not implemented in fortran"),
+            pytest.mark.fortran]),
+        pytest.param("c", marks = [
+            pytest.mark.skip(reason="list methods not implemented in c"),
+            pytest.mark.c]),
+        pytest.param("python", marks = pytest.mark.python)
+    ],
+    scope = "module"
+)
+def language(request):
+    return request.param
+
+def test_append_basic(language):
+    def f():
+        a = [1, 2, 3]
+        a.append(4)
+        return a
+
+    epyc_f = epyccel(f, language=language)
+    assert f() == epyc_f()
+
+def test_append_multiple(language):
+    def f():
+        a = [1, 2, 3]
+        a.append(4)
+        a.append(5)
+        a.append(6)
+        return a
+
+    epyc_f = epyccel(f, language=language)
+    assert f() == epyc_f()
+
+def test_append_list(language):
+    def f():
+        a = [[1, 2, 3]]
+        a.append([4, 5, 6])
+        return a
+
+    epyc_f = epyccel(f, language=language)
+    assert f() == epyc_f()
+
+def test_append_range(language):
+    def f():
+        a = [1, 2, 3]
+        for i in range(0, 1000):
+            a.append(i)
+        a.append(1000)
+        return a
+
+    epyc_f = epyccel(f, language=language)
+    assert f() == epyc_f()
+
+def test_append_range_list(language):
+    def f():
+        a = [[1, 2, 3]]
+        for i in range(0, 1000):
+            a.append([i, i + 1])
+        return a
+
+    epyc_f = epyccel(f, language=language)
+    assert f() == epyc_f()
+
+def test_append_range_tuple(language):
+    def f():
+        a = [[1, 2, 3]]
+        for i in range(0, 1000):
+            a.append((i, i + 1))
+        return a
+
+    epyc_f = epyccel(f, language=language)
+    assert f() == epyc_f()
diff --git a/tests/errors/semantic/blocking/APPEND_LIST_INCOMPATIBLE_TYPES_0.py b/tests/errors/semantic/blocking/APPEND_LIST_INCOMPATIBLE_TYPES_0.py
new file mode 100644
index 00000000..3b3b48a5
--- /dev/null
+++ b/tests/errors/semantic/blocking/APPEND_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.int32(4)
+a.append(b)
+
diff --git a/tests/errors/semantic/blocking/APPEND_LIST_INCOMPATIBLE_TYPES_1.py b/tests/errors/semantic/blocking/APPEND_LIST_INCOMPATIBLE_TYPES_1.py
new file mode 100644
index 00000000..a381dd85
--- /dev/null
+++ b/tests/errors/semantic/blocking/APPEND_LIST_INCOMPATIBLE_TYPES_1.py
@@ -0,0 +1,6 @@
+# pylint: disable=missing-function-docstring, missing-module-docstring
+
+
+a = [1,2,3]
+a.append([4])
+
diff --git a/tests/errors/semantic/blocking/APPEND_LIST_INCOMPATIBLE_TYPES_2.py b/tests/errors/semantic/blocking/APPEND_LIST_INCOMPATIBLE_TYPES_2.py
new file mode 100644
index 00000000..e57efb65
--- /dev/null
+++ b/tests/errors/semantic/blocking/APPEND_LIST_INCOMPATIBLE_TYPES_2.py
@@ -0,0 +1,6 @@
+# pylint: disable=missing-function-docstring, missing-module-docstring
+
+a = [1,2,3]
+b = (4,5)
+a.append(b)
+
diff --git a/tests/errors/semantic/blocking/APPEND_LIST_INCOMPATIBLE_TYPES_4.py b/tests/errors/semantic/blocking/APPEND_LIST_INCOMPATIBLE_TYPES_4.py
new file mode 100644
index 00000000..b6822096
--- /dev/null
+++ b/tests/errors/semantic/blocking/APPEND_LIST_INCOMPATIBLE_TYPES_4.py
@@ -0,0 +1,5 @@
+# pylint: disable=missing-function-docstring, missing-module-docstring
+
+a = [[1, 2, 3]]
+a.append((4.4, 8.4))
+