Repository: pyccel/pyccel (2024-09-03 ~ 2024-10-03)

problem_statement: [DICT] Add Python support for popitem
### Describe the feature
Syntactic and semantic support should be added for `popitem`. This should be tested by implementing Python printing

### Test Code
Provide code which does not currently work but which should do when this issue is fixed:
```python
def foo():
    a = {1,1.0, 2:2.0}
    b = a.popitem()
    c = a.popitem()
    return a, b, c
```
date: 2024-09-03
patch: diff --git a/CHANGELOG.md b/CHANGELOG.md
index a7ea8310..5a2d718a 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -27,6 +27,7 @@ All notable changes to this project will be documented in this file.
 -   #1895 : Add Python support for dict initialisation with `{}`.
 -   #1895 : Add Python support for dict initialisation with `dict()`.
 -   #1886 : Add Python support for dict method `pop()`.
+-   #1887 : Add Python support for dict method `popitem()`.
 -   #1944 : Add the appropriate C language equivalent for declaring a Python `dict` container using the STC library.
 -   #1936 : Add missing C output for inline decorator example in documentation
 -   #1937 : Optimise `pyccel.ast.basic.PyccelAstNode.substitute` method.
diff --git a/docs/builtin-functions.md b/docs/builtin-functions.md
index 57bb7f2e..6d3d82d5 100644
--- a/docs/builtin-functions.md
+++ b/docs/builtin-functions.md
@@ -105,7 +105,7 @@ Python contains a limited number of builtin functions defined [here](https://doc
 | `items` | No |
 | `keys` | No |
 | `pop` | Python-only |
-| `popitem` | No |
+| `popitem` | Python-only |
 | `reversed` | No |
 | `setdefault` | No |
 | `update` | No |
diff --git a/pyccel/ast/builtin_methods/dict_methods.py b/pyccel/ast/builtin_methods/dict_methods.py
index e83fc9bd..de542f67 100644
--- a/pyccel/ast/builtin_methods/dict_methods.py
+++ b/pyccel/ast/builtin_methods/dict_methods.py
@@ -10,10 +10,12 @@ always available.
 This module contains objects which describe these methods within Pyccel's AST.
 """
 
+from pyccel.ast.datatypes import InhomogeneousTupleType
 from pyccel.ast.internals import PyccelFunction
 
 __all__ = ('DictMethod',
            'DictPop',
+           'DictPopitem',
            )
 
 #==============================================================================
@@ -99,3 +101,24 @@ class DictPop(DictMethod):
         The value that should be returned if the key is not present in the dictionary.
         """
         return self._args[1]
+
+
+class DictPopitem(DictMethod):
+    """
+    Represents a call to the .popitem() method.
+
+    The popitem() method removes the last inserted key-value pair from the dict.
+
+    Parameters
+    ----------
+    dict_obj : TypedAstNode
+        The object from which the method is called.
+    """
+    __slots__ = ('_class_type',)
+    _shape = (2,)
+    name = 'popitem'
+
+    def __init__(self, dict_obj):
+        dict_type = dict_obj.class_type
+        self._class_type = InhomogeneousTupleType(dict_type.key_type, dict_type.value_type)
+        super().__init__(dict_obj)
diff --git a/pyccel/ast/class_defs.py b/pyccel/ast/class_defs.py
index cc0e78a7..0c4c43fc 100644
--- a/pyccel/ast/class_defs.py
+++ b/pyccel/ast/class_defs.py
@@ -10,7 +10,7 @@ from pyccel.ast.builtin_methods.set_methods  import SetAdd, SetClear, SetCopy, S
 from pyccel.ast.builtin_methods.list_methods import (ListAppend, ListInsert, ListPop,
                                                      ListClear, ListExtend, ListRemove,
                                                      ListCopy, ListSort)
-from pyccel.ast.builtin_methods.dict_methods  import DictPop
+from pyccel.ast.builtin_methods.dict_methods  import DictPop, DictPopitem
 
 from .builtins   import PythonImag, PythonReal, PythonConjugate
 from .core       import ClassDef, PyccelFunctionDef
@@ -171,6 +171,7 @@ SetClass = ClassDef('set',
 DictClass = ClassDef('dict',
         methods=[
             PyccelFunctionDef('pop', func_class = DictPop),
+            PyccelFunctionDef('popitem', func_class = DictPopitem),
         ])
 
 #=======================================================================================
diff --git a/pyccel/ast/variable.py b/pyccel/ast/variable.py
index 128f5aa7..e2f9d649 100644
--- a/pyccel/ast/variable.py
+++ b/pyccel/ast/variable.py
@@ -713,7 +713,10 @@ class IndexedElement(TypedAstNode):
             assert len(self._indices) == 1 and isinstance(self._indices[0], LiteralInteger)
             self._class_type = base.class_type[self._indices[0]]
             self._is_slice = False
-            self._shape = (None,)*self.rank
+            if self.rank:
+                self._shape = (None,)*self.rank
+            else:
+                self._shape = None
 
         else:
             # Calculate new shape
diff --git a/pyccel/parser/semantic.py b/pyccel/parser/semantic.py
index 2c3fad96..14b884f3 100644
--- a/pyccel/parser/semantic.py
+++ b/pyccel/parser/semantic.py
@@ -813,17 +813,28 @@ class SemanticParser(BasicParser):
                 self._additional_exprs[-1].append(self._visit(assign))
                 var = self._visit(tmp_var)
 
+        elif isinstance(var, Variable):
+            # Nothing to do but excludes this case from the subsequent ifs
+            pass
 
-        elif not isinstance(var, Variable):
-            if hasattr(var,'__getitem__'):
-                if len(indices)==1:
-                    return var[indices[0]]
-                else:
-                    return self._visit(var[indices[0]][indices[1:]])
+        elif hasattr(var,'__getitem__'):
+            if len(indices)==1:
+                return var[indices[0]]
             else:
-                var_type = type(var)
-                errors.report(f"Can't index {var_type}", symbol=expr,
-                    severity='fatal')
+                return self._visit(var[indices[0]][indices[1:]])
+
+        elif isinstance(var, PyccelFunction):
+            pyccel_stage.set_stage('syntactic')
+            tmp_var = PyccelSymbol(self.scope.get_new_name())
+            assign = Assign(tmp_var, var)
+            assign.set_current_ast(expr.python_ast)
+            pyccel_stage.set_stage('semantic')
+            self._additional_exprs[-1].append(self._visit(assign))
+            var = self._visit(tmp_var)
+
+        else:
+            errors.report(f"Can't index {type(var)}", symbol=expr,
+                severity='fatal')
 
         indices = tuple(indices)
 
@@ -1301,21 +1312,22 @@ class SemanticParser(BasicParser):
 
         if isinstance(class_type, InhomogeneousTupleType):
             if isinstance(rhs, FunctionCall):
-                iterable = [r.var for r in rhs.funcdef.results]
+                iterable = [self.scope.collect_tuple_element(r.var) for r in rhs.funcdef.results]
+            elif isinstance(rhs, PyccelFunction):
+                iterable = [IndexedElement(rhs, i)  for i in range(rhs.shape[0])]
             else:
-                iterable = rhs
+                iterable = [self.scope.collect_tuple_element(r) for r in rhs]
             elem_vars = []
             for i,tuple_elem in enumerate(iterable):
-                r = self.scope.collect_tuple_element(tuple_elem)
                 elem_name = self.scope.get_new_name( name + '_' + str(i) )
-                elem_d_lhs = self._infer_type( r )
+                elem_d_lhs = self._infer_type( tuple_elem )
 
                 if not arr_in_multirets:
-                    self._ensure_target( r, elem_d_lhs )
+                    self._ensure_target( tuple_elem, elem_d_lhs )
 
                 elem_type = elem_d_lhs.pop('class_type')
 
-                var = self._create_variable(elem_name, elem_type, r, elem_d_lhs,
+                var = self._create_variable(elem_name, elem_type, tuple_elem, elem_d_lhs,
                         insertion_scope = insertion_scope)
                 elem_vars.append(var)
 
@@ -3286,7 +3298,7 @@ class SemanticParser(BasicParser):
             for l,r in zip(lhs, rhs):
                 # Split assign (e.g. for a,b = 1,c)
                 if isinstance(l.class_type, InhomogeneousTupleType) \
-                        and not isinstance(r, FunctionCall):
+                        and not isinstance(r, (FunctionCall, PyccelFunction)):
                     new_lhs.extend(self.scope.collect_tuple_element(v) for v in l)
                     new_rhs.extend(self.scope.collect_tuple_element(v) for v in r)
                     # Repeat step to handle tuples of tuples of etc.
@@ -3360,6 +3372,7 @@ class SemanticParser(BasicParser):
 
             new_expressions.append(new_expr)
 
+
         if (len(new_expressions)==1):
             new_expressions = new_expressions[0]
 
@@ -3767,6 +3780,10 @@ class SemanticParser(BasicParser):
                     a.cls_variable.is_temp = False
 
         results = [self._visit(i.var) for i in return_objs]
+        if any(isinstance(i.class_type, InhomogeneousTupleType) for i in results):
+            # Extraction of underlying variables is not yet implemented here
+            errors.report("Returning tuples is not yet implemented",
+                    severity='error', symbol=expr)
 
         # add the Deallocate node before the Return node and eliminating the Deallocate nodes
         # the arrays that will be returned.

test_patch: diff --git a/tests/epyccel/test_epyccel_dicts.py b/tests/epyccel/test_epyccel_dicts.py
index b15945b4..c27e6a1c 100644
--- a/tests/epyccel/test_epyccel_dicts.py
+++ b/tests/epyccel/test_epyccel_dicts.py
@@ -98,3 +98,46 @@ def test_pop_str_keys(language) :
     python_result = pop_str_keys()
     assert isinstance(python_result, type(pyccel_result))
     assert python_result == pyccel_result
+
+@pytest.mark.skip("Returning tuples is not yet implemented. See #337")
+def test_pop_item(language):
+    def pop_item():
+        a = {1:1.0, 2:2.0}
+        return a.popitem()
+    epyc_default_element = epyccel(pop_item, language = language)
+    pyccel_result = epyc_default_element()
+    python_result = pop_item()
+    assert isinstance(python_result, type(pyccel_result))
+    assert python_result == pyccel_result
+
+def test_pop_item_elements(language):
+    def pop_item():
+        a = {1:1.0, 2:2.0}
+        b = a.popitem()
+        return b[0], b[1]
+    epyc_default_element = epyccel(pop_item, language = language)
+    pyccel_result = epyc_default_element()
+    python_result = pop_item()
+    assert isinstance(python_result, type(pyccel_result))
+    assert python_result == pyccel_result
+
+def test_pop_item_str_keys(language):
+    def pop_item_str_keys():
+        a = {'a':1, 'b':2}
+        b = a.popitem()
+        return b[0], b[1]
+    epyc_default_element = epyccel(pop_item_str_keys, language = language)
+    pyccel_result = epyc_default_element()
+    python_result = pop_item_str_keys()
+    assert isinstance(python_result, type(pyccel_result))
+    assert python_result == pyccel_result
+
+def test_pop_item_key(language):
+    def pop_item():
+        a = {1:1.0, 2:2.0}
+        return a.popitem()[0]
+    epyc_default_element = epyccel(pop_item, language = language)
+    pyccel_result = epyc_default_element()
+    python_result = pop_item()
+    assert isinstance(python_result, type(pyccel_result))
+    assert python_result == pyccel_result


----

problem_statement: [SETS] Add C support for len function
### Describe the feature
In Python the len function can be called on many functions however the implementation of this function is different in different languages. Support should be added to test the length of a list in C (and Python if it does not work out of the box).

### Test Code
Provide code which does not currently work but which should do when this issue is fixed:
```python
def f():
    a = {1,2,1}
    b = len(a)
    return b
```

### Proposed Solution
This feature should be implemented using the functionalities provided by STC
date: 2024-10-01
patch: diff --git a/ci_tools/annotation_helpers.py b/ci_tools/annotation_helpers.py
--- a/ci_tools/annotation_helpers.py
+++ b/ci_tools/annotation_helpers.py
@@ -1,4 +1,5 @@
 """ File containing functionalities which help output the CI annotations easily
+# noop comment for filter
 """
 import importlib
 import inspect


----

problem_statement: [SETS] Support variable declarations in Fortran
### Describe the feature
Minimal printing support should be implemented for lists. It should be possible to declare a list using a type annotation.

### Test Code
Provide code which does not currently work but which should do when this issue is fixed:
```python
def declare_list():
    a : set[int]
```

### Proposed Solution
This feature should be implemented with the https://github.com/Goddard-Fortran-Ecosystem/gFTL library. The Set type should be used.

### Target Language
Fortran

### Pre-requisites
- #1656
date: 2024-10-03
patch: diff --git a/ci_tools/annotation_helpers.py b/ci_tools/annotation_helpers.py
--- a/ci_tools/annotation_helpers.py
+++ b/ci_tools/annotation_helpers.py
@@ -1,4 +1,5 @@
 """ File containing functionalities which help output the CI annotations easily
+# noop comment for filter
 # noop comment for filter
 """
 import importlib