Repository: pyccel/pyccel (python)

Issue:
Modifications to a list via an assigned variable do not reflect in the original list
### Describe the bug
When assigning a list to another variable and then modifying the new variable, the changes are not reflected in the original list as they should be in Python.

### To Reproduce
```python
a = [1, 2, 3]
b = a
b.append(5)
print(a)
```

### Error details
Both `Fortran` and `C` interpret the assignment as a copy. Additionally, the `C` code introduces a bug as it loops over the size of `b` instead of looping over the size of `a`.
```C

int main()
{
    vec_int64_t a;
    vec_int64_t b;
    int64_t i;
    a = c_init(vec_int64_t, {INT64_C(1), INT64_C(2), INT64_C(3), INT64_C(4)});
    for (i = INT64_C(0); i < vec_int64_t_size(&b); i += INT64_C(1))
    {
        (*vec_int64_t_at_mut(&b,i)) = (*vec_int64_t_at(&a,i));
    }
    vec_int64_t_drop(&b);
    vec_int64_t_drop(&a);
    return 0;
}
```

```Fortran
  use boo
  use Vector_integer8_mod

  use, intrinsic :: ISO_C_Binding, only : i64 => C_INT64_T
  use, intrinsic :: ISO_FORTRAN_ENV, only : stdout => output_unit

  implicit none

  type(Vector_integer8) :: a
  type(Vector_integer8) :: b
  integer(i64) :: i

  a = Vector_integer8([1_i64, 2_i64, 3_i64, 4_i64])
  b = a
  call b % push_back(5_i64)
  write(stdout, '(A)', advance="no") '['
  do i = 0_i64, a % size() - 1_i64 - 1_i64
    write(stdout, '(I0, A)', advance="no") a%of(i + 1_i64) , ' '
  end do
  write(stdout, '(I0, A)', advance="no") a%of(a % size() - 1_i64 + 1_i64 &
        ) , ']'
  write(stdout, '()', advance="yes") 
```

### Expected behavior
The output should be:

```bash
[1, 2, 3, 5]
```
Because in Python, lists are mutable objects, and assigning one list to another variable should create a reference to the same object. Modifying b should also modify a.

### Language
Fortran, C

### Additional context
This is issue is likely to arise with sets and dictionaries as well once their indexing is implemented.

Pull request:
Fix pointer assignment for list/set/dict
Fix the recognition of an alias assign for an expression such as `a = b` where `a` and `b` are lists/sets/dicts. Fixes #2008. Fix the printing of a list/set/dict pointer

Patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 43a856d4..4d6551c2 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -71,6 +71,7 @@ All notable changes to this project will be documented in this file.
 -   #1979 : Fix memory leaks in C due to homogeneous container redefinition.
 -   #1972 : Simplified `printf` statement for Literal String.
 -   #2026 : Fix missing loop in slice assignment.
+-   #2008 : Ensure list/set/dict assignment is recognised as a reference.
 
 ### Changed
 
diff --git a/pyccel/ast/builtins.py b/pyccel/ast/builtins.py
index d80ee70d..6c234b8c 100644
--- a/pyccel/ast/builtins.py
+++ b/pyccel/ast/builtins.py
@@ -732,10 +732,8 @@ class PythonListFunction(PyccelFunction):
     def __new__(cls, arg = None):
         if arg is None:
             return PythonList()
-        elif isinstance(arg, PythonList):
-            return arg
         elif isinstance(arg.shape[0], LiteralInteger):
-            return PythonList(*[arg[i] for i in range(arg.shape[0])])
+            return PythonList(*arg)
         else:
             return super().__new__(cls)
 
@@ -838,8 +836,6 @@ class PythonSetFunction(PyccelFunction):
     def __new__(cls, arg = None):
         if arg is None:
             return PythonSet()
-        elif isinstance(arg.class_type, HomogeneousSetType):
-            return arg
         elif isinstance(arg, (PythonList, PythonSet, PythonTuple)):
             return PythonSet(*arg)
         else:
diff --git a/pyccel/codegen/printing/ccode.py b/pyccel/codegen/printing/ccode.py
index d8386437..81cee5aa 100644
--- a/pyccel/codegen/printing/ccode.py
+++ b/pyccel/codegen/printing/ccode.py
@@ -359,7 +359,7 @@ class CCodePrinter(CodePrinter):
 
         if not isinstance(a, Variable):
             return False
-        return (a.is_alias and not isinstance(a.class_type, HomogeneousContainerType)) \
+        return (a.is_alias and not isinstance(a.class_type, (HomogeneousTupleType, NumpyNDArrayType))) \
                 or a.is_optional or \
                 any(a is bi for b in self._additional_args for bi in b)
 
@@ -1363,10 +1363,9 @@ class CCodePrinter(CodePrinter):
         if rank > 0:
             if isinstance(expr.class_type, (HomogeneousSetType, HomogeneousListType, DictType)):
                 dtype = self.get_c_type(expr.class_type)
-                return dtype
-            if isinstance(expr.class_type, CStackArray):
+            elif isinstance(expr.class_type, CStackArray):
                 return self.get_c_type(expr.class_type.element_type)
-            if isinstance(expr.class_type,(HomogeneousTupleType, NumpyNDArrayType)):
+            elif isinstance(expr.class_type, (HomogeneousTupleType, NumpyNDArrayType)):
                 if expr.rank > 15:
                     errors.report(UNSUPPORTED_ARRAY_RANK, symbol=expr, severity='fatal')
                 self.add_import(c_imports['ndarrays'])
@@ -1741,6 +1740,8 @@ class CCodePrinter(CodePrinter):
 
     def _print_Deallocate(self, expr):
         if isinstance(expr.variable.class_type, (HomogeneousListType, HomogeneousSetType, DictType)):
+            if expr.variable.is_alias:
+                return ''
             variable_address = self._print(ObjectAddress(expr.variable))
             container_type = self.get_c_type(expr.variable.class_type)
             return f'{container_type}_drop({variable_address});\n'
diff --git a/pyccel/codegen/printing/fcode.py b/pyccel/codegen/printing/fcode.py
index 26568e89..31812f15 100644
--- a/pyccel/codegen/printing/fcode.py
+++ b/pyccel/codegen/printing/fcode.py
@@ -1981,7 +1981,7 @@ class FCodePrinter(CodePrinter):
         # TODO improve
         op = '=>'
         shape_code = ''
-        if lhs.rank > 0:
+        if isinstance(lhs.class_type, (NumpyNDArrayType, HomogeneousTupleType)):
             shape_code = ', '.join('0:' for i in range(lhs.rank))
             shape_code = '({s_c})'.format(s_c = shape_code)
 
diff --git a/pyccel/parser/semantic.py b/pyccel/parser/semantic.py
index 7abafde7..86b45a24 100644
--- a/pyccel/parser/semantic.py
+++ b/pyccel/parser/semantic.py
@@ -1401,7 +1401,8 @@ class SemanticParser(BasicParser):
             d_lhs['memory_handling'] = 'alias'
             rhs.internal_var.is_target = True
 
-        if isinstance(rhs, Variable) and (rhs.is_ndarray or isinstance(rhs.class_type, CustomDataType)):
+        if isinstance(rhs, Variable) and (rhs.rank > 0 or isinstance(rhs.class_type, CustomDataType)) \
+                and not isinstance(rhs.class_type, (TupleType, StringType)):
             d_lhs['memory_handling'] = 'alias'
             rhs.is_target = not rhs.is_alias
 


Test patch:
diff --git a/tests/epyccel/test_epyccel_dicts.py b/tests/epyccel/test_epyccel_dicts.py
index 458996c8..d389392a 100644
--- a/tests/epyccel/test_epyccel_dicts.py
+++ b/tests/epyccel/test_epyccel_dicts.py
@@ -191,3 +191,16 @@ def test_dict_contains(language):
     python_result = dict_contains()
     assert isinstance(python_result, type(pyccel_result))
     assert python_result == pyccel_result
+
+def test_dict_ptr(python_only_language):
+    def dict_ptr():
+        a = {1:1.0, 2:2.0, 3:3.0}
+        b = a
+        c = b.pop(2)
+        return len(a), len(b), c
+
+    epyc_func = epyccel(dict_ptr, language = python_only_language)
+    pyccel_result = epyc_func()
+    python_result = dict_ptr()
+    assert isinstance(python_result, type(pyccel_result))
+    assert python_result == pyccel_result
diff --git a/tests/epyccel/test_epyccel_lists.py b/tests/epyccel/test_epyccel_lists.py
index 0c853346..23e56379 100644
--- a/tests/epyccel/test_epyccel_lists.py
+++ b/tests/epyccel/test_epyccel_lists.py
@@ -756,3 +756,16 @@ def test_list_contains(language):
     python_result = list_contains()
     assert isinstance(python_result, type(pyccel_result))
     assert python_result == pyccel_result
+
+def test_dict_ptr(language):
+    def list_ptr():
+        a = [1, 3, 4, 7, 10, 3]
+        b = a
+        b.append(22)
+        return len(a), len(b)
+
+    epyc_list_ptr = epyccel(list_ptr, language = language)
+    pyccel_result = epyc_list_ptr()
+    python_result = list_ptr()
+    assert isinstance(python_result, type(pyccel_result))
+    assert python_result == pyccel_result
diff --git a/tests/epyccel/test_epyccel_sets.py b/tests/epyccel/test_epyccel_sets.py
index b85c43c0..a647e549 100644
--- a/tests/epyccel/test_epyccel_sets.py
+++ b/tests/epyccel/test_epyccel_sets.py
@@ -520,3 +520,15 @@ def test_set_contains(language):
     pyccel_result = epyccel_func()
     python_result = union_int()
     assert python_result == pyccel_result
+
+def test_set_ptr(language):
+    def set_ptr():
+        a = {1,2,3,4,5,6,7,8}
+        b = a
+        b.pop()
+        return len(a), len(b)
+
+    epyccel_func = epyccel(set_ptr, language = language)
+    pyccel_result = epyccel_func()
+    python_result = set_ptr()
+    assert python_result == pyccel_result