Repository: pyccel/pyccel (python)

Issue:
[DICT] Add Python support for values()
### Describe the feature
Syntactic and semantic support should be added for `values`. 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 : list[float] = []
    for bi in a.values():
        b.append(bi)
    return b
```

Pull request:
Add support for dict.values
Add support for `dict.values`. Fixes #1883 

Patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 450b03ab..a70082de 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -55,6 +55,7 @@ All notable changes to this project will be documented in this file.
 -   #1880 : Add support for dict method `clear()`.
 -   #1884 : Add support for dict method `items()`.
 -   #1884 : Add support for dict method `keys()`.
+-   #1884 : Add support for dict method `values()`.
 -   #1886 : Add support for dict method `pop()`.
 -   #1887 : Add support for dict method `popitem()`.
 -   #1936 : Add missing C output for inline decorator example in documentation
diff --git a/docs/builtin-functions.md b/docs/builtin-functions.md
index 9c06935a..165b1c4c 100644
--- a/docs/builtin-functions.md
+++ b/docs/builtin-functions.md
@@ -130,7 +130,7 @@ Python contains a limited number of builtin functions defined [here](https://doc
 | `reversed` | No |
 | `setdefault` | Python-only |
 | `update` | No |
-| `values` | No |
+| **`values`** | **Yes** |
 
 ## String methods
 
diff --git a/pyccel/ast/builtin_methods/dict_methods.py b/pyccel/ast/builtin_methods/dict_methods.py
index db72d6d6..69f65db1 100644
--- a/pyccel/ast/builtin_methods/dict_methods.py
+++ b/pyccel/ast/builtin_methods/dict_methods.py
@@ -13,7 +13,7 @@ This module contains objects which describe these methods within Pyccel's AST.
 from pyccel.ast.datatypes import InhomogeneousTupleType, VoidType, SymbolicType
 from pyccel.ast.internals import PyccelFunction, Iterable, PyccelArrayShapeElement
 from pyccel.ast.literals  import LiteralInteger
-from pyccel.ast.variable  import IndexedElement
+from pyccel.ast.variable  import IndexedElement, Variable
 
 
 __all__ = ('DictClear',
@@ -26,6 +26,7 @@ __all__ = ('DictClear',
            'DictPop',
            'DictPopitem',
            'DictSetDefault',
+           'DictValues',
            )
 
 #==============================================================================
@@ -356,7 +357,8 @@ class DictItems(Iterable):
         """
         Get the object representing the dict.
 
-        Get the object representing the dict.
+        Get the object representing the dict. The name of this method is
+        chosen to match the name of the equivalent method in VariableIterator.
         """
         return self._dict_obj
 
@@ -403,7 +405,8 @@ class DictKeys(Iterable):
         """
         Get the object representing the dict.
 
-        Get the object representing the dict.
+        Get the object representing the dict. The name of this method is
+        chosen to match the name of the equivalent method in VariableIterator.
         """
         return self._dict_obj
 
@@ -411,16 +414,16 @@ class DictKeys(Iterable):
         """
         Get the item of the iterable that will be saved to the loop targets.
 
-        Returns two objects that could be a key and a value of the dictionary.
-        These elements are used to determine the types of the loop targets.
+        Returns an object that could be a key of the dictionary.
+        This element is used to determine the type of the loop target.
 
         Returns
         -------
         list[TypedAstNode]
-            A list of objects that should be assigned to variables.
+            A list containing the object that should be assigned to the target variable.
         """
-        item = DictPopitem(self._dict_obj)
-        return [IndexedElement(item, 0)]
+        class_type = self._dict_obj.class_type.key_type
+        return [Variable(class_type, '_', memory_handling = 'heap' if class_type.rank > 0 else 'stack')]
 
 #==============================================================================
 class DictGetItem(DictMethod):
@@ -461,3 +464,51 @@ class DictGetItem(DictMethod):
         The key that is used to select the element from the dict.
         """
         return self._args[0]
+
+#==============================================================================
+class DictValues(Iterable):
+    """
+    Represents a call to the .values() method.
+
+    Represents a call to the .values() method which iterates over the keys of a
+    dictionary.
+
+    Parameters
+    ----------
+    dict_obj : TypedAstNode
+        The object from which the method is called.
+    """
+    __slots__ = ('_dict_obj',)
+    _attribute_nodes = Iterable._attribute_nodes + ("_dict_obj",)
+    _shape = None
+    _class_type = SymbolicType()
+    name = 'keys'
+
+    def __init__(self, dict_obj):
+        self._dict_obj = dict_obj
+        super().__init__(1)
+
+    @property
+    def variable(self):
+        """
+        Get the object representing the dict.
+
+        Get the object representing the dict. The name of this method is
+        chosen to match the name of the equivalent method in VariableIterator.
+        """
+        return self._dict_obj
+
+    def get_python_iterable_item(self):
+        """
+        Get the item of the iterable that will be saved to the loop targets.
+
+        Returns an object that could be a value of the dictionary.
+        This element is used to determine the types of the loop targets.
+
+        Returns
+        -------
+        list[TypedAstNode]
+            A list containing the object that should be assigned to the target variable.
+        """
+        class_type = self._dict_obj.class_type.value_type
+        return [Variable(class_type, '_', memory_handling = 'heap' if class_type.rank > 0 else 'stack')]
diff --git a/pyccel/ast/class_defs.py b/pyccel/ast/class_defs.py
index e697ce7e..96a41cc8 100644
--- a/pyccel/ast/class_defs.py
+++ b/pyccel/ast/class_defs.py
@@ -14,7 +14,8 @@ from pyccel.ast.builtin_methods.list_methods import (ListAppend, ListInsert, Lis
                                                      ListClear, ListExtend, ListRemove,
                                                      ListCopy, ListSort, ListReverse)
 from pyccel.ast.builtin_methods.dict_methods import (DictPop, DictPopitem, DictGet, DictClear,DictCopy,
-                                                     DictSetDefault, DictItems, DictGetItem, DictKeys)
+                                                     DictSetDefault, DictItems, DictGetItem, DictKeys,
+                                                     DictValues)
 
 from .builtins   import PythonImag, PythonReal, PythonConjugate
 from .core       import ClassDef, PyccelFunctionDef
@@ -191,6 +192,7 @@ DictClass = ClassDef('dict',
             PyccelFunctionDef('pop', func_class = DictPop),
             PyccelFunctionDef('popitem', func_class = DictPopitem),
             PyccelFunctionDef('setdefault', func_class = DictSetDefault),
+            PyccelFunctionDef('values', func_class = DictValues),
             PyccelFunctionDef('__getitem__', func_class = DictGetItem),
         ])
 
diff --git a/pyccel/codegen/printing/ccode.py b/pyccel/codegen/printing/ccode.py
index a3170429..afc321ce 100644
--- a/pyccel/codegen/printing/ccode.py
+++ b/pyccel/codegen/printing/ccode.py
@@ -21,7 +21,7 @@ from pyccel.ast.builtins  import PythonPrint, PythonType, VariableIterator
 
 from pyccel.ast.builtins  import PythonList, PythonTuple, PythonSet, PythonDict, PythonLen
 
-from pyccel.ast.builtin_methods.dict_methods  import DictItems, DictKeys, DictPopitem
+from pyccel.ast.builtin_methods.dict_methods  import DictItems, DictKeys, DictValues, DictPopitem
 
 from pyccel.ast.core      import Declare, For, CodeBlock, ClassDef
 from pyccel.ast.core      import FunctionCall, FunctionCallArgument
@@ -2526,7 +2526,7 @@ class CCodePrinter(CodePrinter):
         iterable = expr.iterable
         indices = iterable.loop_counters
 
-        if isinstance(iterable, (VariableIterator, DictItems, DictKeys)) and \
+        if isinstance(iterable, (VariableIterator, DictItems, DictKeys, DictValues)) and \
                 isinstance(iterable.variable.class_type, (DictType, HomogeneousSetType, HomogeneousListType)):
             var = iterable.variable
             iterable_type = var.class_type
@@ -2540,6 +2540,8 @@ class CCodePrinter(CodePrinter):
                            Assign(expr.target[1], DottedVariable(VoidType(), 'second', lhs = tmp_ref))]
             elif isinstance(iterable, DictKeys):
                 assigns = [Assign(expr.target[0], DottedVariable(VoidType(), 'first', lhs = tmp_ref))]
+            elif isinstance(iterable, DictValues):
+                assigns = [Assign(expr.target[0], DottedVariable(VoidType(), 'second', lhs = tmp_ref))]
             else:
                 assigns = [Assign(expr.target[0], tmp_ref)]
             additional_assign = CodeBlock(assigns)
diff --git a/pyccel/codegen/printing/fcode.py b/pyccel/codegen/printing/fcode.py
index 67ae0701..4c5c51c1 100644
--- a/pyccel/codegen/printing/fcode.py
+++ b/pyccel/codegen/printing/fcode.py
@@ -26,7 +26,7 @@ from pyccel.ast.builtins import PythonInt, PythonType, PythonPrint, PythonRange
 from pyccel.ast.builtins import PythonTuple, DtypePrecisionToCastFunction
 from pyccel.ast.builtins import PythonBool, PythonList, PythonSet, VariableIterator
 
-from pyccel.ast.builtin_methods.dict_methods import DictItems, DictKeys, DictPopitem
+from pyccel.ast.builtin_methods.dict_methods import DictItems, DictKeys, DictValues, DictPopitem
 
 from pyccel.ast.builtin_methods.list_methods import ListPop
 
@@ -2815,7 +2815,7 @@ class FCodePrinter(CodePrinter):
         iterable = expr.iterable
         indices = iterable.loop_counters
 
-        if isinstance(iterable, (VariableIterator, DictItems, DictKeys)) and \
+        if isinstance(iterable, (VariableIterator, DictItems, DictKeys, DictValues)) and \
                 isinstance(iterable.variable.class_type, (DictType, HomogeneousSetType)):
             var = iterable.variable
             iterable_type = var.class_type
@@ -2838,6 +2838,9 @@ class FCodePrinter(CodePrinter):
             elif isinstance(iterable, DictKeys):
                 key = self._print(expr.target[0])
                 target_assign = (f'{key} = {iterator} % first()\n')
+            elif isinstance(iterable, DictValues):
+                val = self._print(expr.target[0])
+                target_assign = (f'{val} = {iterator} % second()\n')
             else:
                 target = self._print(expr.target[0])
                 target_assign = f'{target} = {iterator} % of()\n'
diff --git a/pyccel/codegen/printing/pycode.py b/pyccel/codegen/printing/pycode.py
index 7ee320e3..e59459e8 100644
--- a/pyccel/codegen/printing/pycode.py
+++ b/pyccel/codegen/printing/pycode.py
@@ -969,6 +969,11 @@ class PythonCodePrinter(CodePrinter):
 
         return f"{dict_obj}.keys()"
 
+    def _print_DictValues(self, expr):
+        dict_obj = self._print(expr.variable)
+
+        return f"{dict_obj}.values()"
+
     def _print_DictGetItem(self, expr):
         dict_obj = self._print(expr.dict_obj)
         key = self._print(expr.key)


Test patch:
diff --git a/tests/epyccel/test_epyccel_dicts.py b/tests/epyccel/test_epyccel_dicts.py
index ee0d09cb..de8e0d70 100644
--- a/tests/epyccel/test_epyccel_dicts.py
+++ b/tests/epyccel/test_epyccel_dicts.py
@@ -424,3 +424,17 @@ def test_dict_keys_iter(language):
     pyccel_result = epyc_dict_keys()
     python_result = dict_keys()
     assert python_result == pyccel_result
+
+def test_dict_values(language):
+    def dict_values():
+        a = {1:1.0, 2:2.0, 3:3.0, 5:4.7}
+        value_sum = 0.0
+        for value in a.values(): #pylint:disable=consider-iterating-dictionary
+            value_sum += value
+
+        return value_sum
+
+    epyc_dict_values = epyccel(dict_values, language = language)
+    pyccel_result = epyc_dict_values()
+    python_result = dict_values()
+    assert python_result == pyccel_result