Repository: pyccel/pyccel (python)

Issue:
copy: It returns a shallow copy of a list.
### Feature Description
Add support for copy() method for lists in the Python translation.

### Test Code
This code should work when this issue is fixed:
```python
lst = [2, 3, 5]

new_lst = lst.copy()

print(new_lst)
>> [2, 3, 5]
```

### Proposed Solution
See description in https://github.com/pyccel/pyccel/issues/1529

Pull request description:
List support: `copy` method
Add support for the `copy()` method of Python lists to the semantic stage and Python codegen. This fixes #1696.

**Commit Summary**

- Add a class representation for the `copy()` method, inheriting from `ListMethod`. The constructor copies the attributes of the list object, to the current object that represents the resulting new copy of the list.
- Add check for `HomogeneousListType` in `_print_IndexedElement` to support indexing a multidimensional list.
- Add success and error tests for the `copy()` method involving many scenarios where the function could be used.

Patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index ca20a6ae..d0962522 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -6,6 +6,7 @@ All notable changes to this project will be documented in this file.
 ### Added
 -   #1720 : Add support for `Ellipsis` as the only index for an array.
 -   #1694 : Add Python support for list method `extend()`.
+-   #1696 : Add Python support for list method `copy()`.
 -   #1693 : Add Python support for list method `remove()`.
 -   #1739 : Add abstract class `SetMethod` to handle calls to various set methods.
 -   #1739 : Add Python support for set method `clear()`.
@@ -22,6 +23,7 @@ All notable changes to this project will be documented in this file.
 -   Allow printing the result of a function returning multiple objects of different types.
 -   #1792 : Fix array unpacking.
 -   #1795 : Fix bug when returning slices in C.
+-   #1732 : Fix multidimensional list indexing in Python.
 
 ### Changed
 -   #1720 : functions with the `@inline` decorator are no longer exposed to Python in the shared library.
diff --git a/pyccel/ast/builtin_methods/list_methods.py b/pyccel/ast/builtin_methods/list_methods.py
index 26f99ffa..46ac641f 100644
--- a/pyccel/ast/builtin_methods/list_methods.py
+++ b/pyccel/ast/builtin_methods/list_methods.py
@@ -15,6 +15,7 @@ from pyccel.ast.internals import PyccelInternalFunction
 
 __all__ = ('ListAppend',
            'ListClear',
+           'ListCopy',
            'ListExtend',
            'ListInsert',
            'ListMethod',
@@ -270,3 +271,38 @@ class ListRemove(ListMethod) :
             raise TypeError(f"Can't remove an element of type {removed_obj.class_type} from {list_obj.class_type}")
         super().__init__(list_obj, removed_obj)
 
+#==============================================================================
+class ListCopy(ListMethod) :
+    """
+    Represents a call to the .copy() method.
+    
+    Represents a call to the .copy() method which is used to create a shallow
+    copy of a list, meaning that any modification in the new list will be
+    reflected in the original list.
+    The method returns a list.
+
+    >>> a = [1, 2, 3, 4]
+    >>> b = a.copy()
+    >>> print(a, b)
+    [1, 2, 3, 4]
+    [1, 2, 3, 4]
+    >>> a[0] = 0
+    >>> a[1] = 0
+    >>> print(a, b)
+    [0, 0, 3, 4]
+    [0, 0, 3, 4]
+
+    Parameters
+    ----------
+    list_obj : TypedAstNode
+        The list object which the method is called from.
+    """
+    __slots__ = ('_class_type', '_rank', '_shape', '_order')
+    name = 'copy'
+
+    def __init__(self, list_obj) -> None:
+        self._rank = list_obj.rank
+        self._shape = list_obj.shape
+        self._order = list_obj.order
+        self._class_type = list_obj.class_type
+        super().__init__(list_obj)
diff --git a/pyccel/ast/class_defs.py b/pyccel/ast/class_defs.py
index e44e0709..c7bf3182 100644
--- a/pyccel/ast/class_defs.py
+++ b/pyccel/ast/class_defs.py
@@ -8,7 +8,8 @@ This module contains all types which define a python class which is automaticall
 
 from pyccel.ast.builtin_methods.set_methods  import SetAdd, SetClear, SetCopy, SetPop, SetRemove
 from pyccel.ast.builtin_methods.list_methods import (ListAppend, ListInsert, ListPop,
-                                                     ListClear, ListExtend, ListRemove)
+                                                     ListClear, ListExtend, ListRemove,
+                                                     ListCopy)
 
 from .builtins   import PythonImag, PythonReal, PythonConjugate
 from .core       import ClassDef, PyccelFunctionDef
@@ -142,6 +143,7 @@ ListClass = ClassDef('list',
         methods=[
             PyccelFunctionDef('append', func_class = ListAppend),
             PyccelFunctionDef('clear', func_class = ListClear),
+            PyccelFunctionDef('copy', func_class = ListCopy),
             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 6ced1aac..643b7072 100644
--- a/pyccel/codegen/printing/pycode.py
+++ b/pyccel/codegen/printing/pycode.py
@@ -11,7 +11,7 @@ from pyccel.ast.builtins   import PythonMin, PythonMax, PythonType, PythonBool,
 from pyccel.ast.builtins   import PythonComplex, DtypePrecisionToCastFunction
 from pyccel.ast.core       import CodeBlock, Import, Assign, FunctionCall, For, AsName, FunctionAddress
 from pyccel.ast.core       import IfSection, FunctionDef, Module, PyccelFunctionDef
-from pyccel.ast.datatypes  import HomogeneousTupleType
+from pyccel.ast.datatypes  import HomogeneousTupleType, HomogeneousListType
 from pyccel.ast.functionalexpr import FunctionalFor
 from pyccel.ast.literals   import LiteralTrue, LiteralString
 from pyccel.ast.literals   import LiteralInteger, LiteralFloat, LiteralComplex
@@ -284,7 +284,7 @@ class PythonCodePrinter(CodePrinter):
                 indices = indices[0]
 
             indices = [self._print(i) for i in indices]
-            if expr.pyccel_staging != 'syntactic' and isinstance(expr.base.class_type, HomogeneousTupleType):
+            if expr.pyccel_staging != 'syntactic' and isinstance(expr.base.class_type, (HomogeneousTupleType, HomogeneousListType)):
                 indices = ']['.join(i for i in indices)
             else:
                 indices = ','.join(i for i in indices)


Test patch:
diff --git a/tests/epyccel/test_epyccel_lists.py b/tests/epyccel/test_epyccel_lists.py
index 6bc2f68b..05b48bc6 100644
--- a/tests/epyccel/test_epyccel_lists.py
+++ b/tests/epyccel/test_epyccel_lists.py
@@ -568,6 +568,35 @@ def test_extend_list_class_attribute(language):
     accelerated_list = modnew.fn()
     assert python_list == accelerated_list
 
+def test_copy_basic(language):
+    def f():
+        a = [1, 2, 3]
+        b = a.copy()
+        return b
+
+    epyc_f = epyccel(f, language=language)
+    assert f() == epyc_f()
+
+def test_copy_nested(language):
+    def f():
+        a = [[1, 2], [3, 4]]
+        b = a.copy()
+        return b
+
+    epyc_f = epyccel(f, language=language)
+    assert f() == epyc_f()
+
+def test_copy_modify_nested_values(language):
+    def f():
+        a = [[1, 2], [3, 4]]
+        b = a.copy()
+        a[0][0] = 0
+        a[0][1] = 0
+        return b
+
+    epyc_f = epyccel(f, language=language)
+    assert f() == epyc_f()
+
 def test_mixed_list_methods(language):
     def f():
         a = [(1, 4, 5), (33, 12, 5), (3, 5)]