Repository: pyccel/pyccel (python)

Issue:
[DICT] Add C support for pop
### Describe the feature
Add C support for the dict method `pop`.

### 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.pop(1)
    c = a.pop(3, 4.0)
    return len(a), b, c
```

### Proposed Solution
This should be implemented using the hmap_X_erase method documented here:
https://github.com/stclib/STC/blob/master/docs/hmap_api.md


Pull request description:
Add C support for `dict.pop`
Add C support for dict method `pop()`. Fixes #2052

**Commit Summary**
- Implement C support for the `dict.pop` method, including support for default value.
- Add `Dict_extensions.h` to `STC_Extensions` for handling additional dictionary methods in C.
- Add `hmap_X_pop` method in `Dict_extensions.h` for retrieving and removing a key-value pair.
- Add `hmap_X_pop_with_default` method in `Dict_extensions.h` for retrieving and removing a key-value pair with default value.
- Activate `dict.pop` tests for C (ignoring Fortran for now).

Patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index cf500029..f7450d84 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -16,7 +16,7 @@ All notable changes to this project will be documented in this file.
 -   #1893 : Add Python support for set initialisation with `set()`.
 -   #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()`.
+-   #1886 : Add Python and C support for dict method `pop()`.
 -   #1887 : Add Python support for dict method `popitem()`.
 -   #1888 : Add Python support for dict method `setdefault()`.
 -   #1885 : Add Python support for dict method `get()`.
diff --git a/docs/builtin-functions.md b/docs/builtin-functions.md
index 189dd3b0..4b219695 100644
--- a/docs/builtin-functions.md
+++ b/docs/builtin-functions.md
@@ -125,7 +125,7 @@ Python contains a limited number of builtin functions defined [here](https://doc
 | `get` | Python-only |
 | `items` | **Yes** |
 | `keys` | No |
-| `pop` | Python-only |
+| `pop` | Python and C |
 | `popitem` | Python-only |
 | `reversed` | No |
 | `setdefault` | Python-only |
diff --git a/pyccel/codegen/printing/ccode.py b/pyccel/codegen/printing/ccode.py
index 5d0854a9..308d6f6b 100644
--- a/pyccel/codegen/printing/ccode.py
+++ b/pyccel/codegen/printing/ccode.py
@@ -250,13 +250,19 @@ c_imports = {n : Import(n, Module(n, (), ())) for n in
                  'assert',
                  'stc/cstr']}
 
-import_header_guard_prefix = {'stc/hset'    : '_TOOLS_SET',
-                              'stc/vec'   : '_TOOLS_LIST',
-                              'stc/common' : '_TOOLS_COMMON'}
+import_header_guard_prefix = {
+    'stc/common': '_TOOLS_COMMON',
+    'stc/hmap': '_TOOLS_DICT',
+    'stc/hset': '_TOOLS_SET',
+    'stc/vec': '_TOOLS_LIST'
+}
 
-stc_extension_mapping = {'stc/vec': 'List_extensions',
-                      'stc/hset' : 'Set_extensions',
-                      'stc/common' : 'Common_extensions'}
+stc_extension_mapping = {
+    'stc/common': 'Common_extensions',
+    'stc/hmap': 'Dict_extensions',
+    'stc/hset': 'Set_extensions',
+    'stc/vec': 'List_extensions',
+}
 
 class CCodePrinter(CodePrinter):
     """
@@ -1350,6 +1356,9 @@ class CCodePrinter(CodePrinter):
             val_type = self.get_c_type(dtype.value_type).replace(' ', '_')
             i_type = f'{container_type}_{key_type}_{val_type}'
             self.add_import(Import(f'stc/{container_type}', AsName(VariableTypeAnnotation(dtype), i_type)))
+            self.add_import(Import(f'{stc_extension_mapping["stc/" + container_type]}',
+                                   AsName(VariableTypeAnnotation(dtype), i_type),
+                                   ignore_at_print=True))
             return i_type
         elif isinstance(dtype, StringType):
             self.add_import(c_imports['stc/cstr'])
@@ -2810,6 +2819,24 @@ class CCodePrinter(CodePrinter):
         arg_val = self._print(expr.args[0])
         return f'{var_type}_erase({set_var}, {arg_val});\n'
 
+    #================== Dict methods ==================
+
+    def _print_DictPop(self, expr):
+        target = expr.dict_obj
+        class_type = target.class_type
+        c_type = self.get_c_type(class_type)
+
+        dict_var = self._print(ObjectAddress(target))
+        key = self._print(expr.key)
+
+        if expr.default_value:
+            default = self._print(expr.default_value)
+            pop_expr = f"{c_type}_pop_with_default({dict_var}, {key}, {default})"
+        else:
+            pop_expr = f"{c_type}_pop({dict_var}, {key})"
+
+        return pop_expr
+
     #=================== MACROS ==================
 
     def _print_MacroShape(self, expr):
diff --git a/pyccel/codegen/utilities.py b/pyccel/codegen/utilities.py
index 7197a0d2..3944a731 100644
--- a/pyccel/codegen/utilities.py
+++ b/pyccel/codegen/utilities.py
@@ -55,6 +55,10 @@ internal_libs = {
                                                       folder="STC_Extensions",
                                                       has_target_file = False,
                                                       dependencies = (external_libs['stc'][1],))),
+    "Dict_extensions" : ("STC_Extensions", CompileObj("Dict_Extensions.h",
+                                                     folder="STC_Extensions",
+                                                     has_target_file=False,
+                                                     dependencies=(external_libs["stc"][1],))),
     "List_extensions" : ("STC_Extensions", CompileObj("List_Extensions.h",
                                                       folder="STC_Extensions",
                                                       has_target_file = False,
diff --git a/pyccel/stdlib/STC_Extensions/Dict_extensions.h b/pyccel/stdlib/STC_Extensions/Dict_extensions.h
new file mode 100644
index 00000000..da840097
--- /dev/null
+++ b/pyccel/stdlib/STC_Extensions/Dict_extensions.h
@@ -0,0 +1,33 @@
+
+/**
+ * Removes and returns the value associated with the key in the dictionary.
+ *
+ * @param self : A pointer to the dictionary instance.
+ * @param rkey : The raw key to be removed from the dictionary.
+ * @return The value associated with the key.
+ */
+static inline i_val _c_MEMB(_pop)(i_type* self, i_keyraw rkey) {
+    i_val value = *_c_MEMB(_at)(self, rkey);
+    _c_MEMB(_erase)(self, rkey);
+    return value;
+}
+
+/**
+ * Removes and returns the value associated with the key in the dictionary.
+ * If the key does not exist, it returns the provided default value.
+ *
+ * @param self        : A pointer to the dictionary instance.
+ * @param rkey        : The raw key to be removed from the dictionary.
+ * @param default_val : The default value to return if the key is not found.
+ * @return The value associated with the key, or the default value if the key is not found.
+ */
+static inline i_val _c_MEMB(_pop_with_default)(i_type* self, i_keyraw rkey, i_valraw default_val) {
+    if (!_c_MEMB(_contains)(self, rkey)) {
+        return default_val;
+    }
+    return _c_MEMB(_pop)(self, rkey);
+}
+
+#undef i_type
+#undef i_key
+#include <stc/priv/template2.h>


Test patch:
diff --git a/tests/epyccel/test_epyccel_dicts.py b/tests/epyccel/test_epyccel_dicts.py
index 36095a5b..10f2f3f8 100644
--- a/tests/epyccel/test_epyccel_dicts.py
+++ b/tests/epyccel/test_epyccel_dicts.py
@@ -70,21 +70,31 @@ def test_dict_kwarg_init(python_only_language):
     assert isinstance(python_result, type(pyccel_result))
     assert python_result == pyccel_result
 
-def test_pop_element(python_only_language):
+def test_pop_element(stc_language):
     def pop_element():
         a = {1:1.0, 2:2.0}
         return a.pop(1)
-    epyc_element = epyccel(pop_element, language = python_only_language)
+    epyc_element = epyccel(pop_element, language = stc_language)
     pyccel_result = epyc_element()
     python_result = pop_element()
     assert isinstance(python_result, type(pyccel_result))
     assert python_result == pyccel_result
 
-def test_pop_default_element(python_only_language):
+def test_pop_default_element(stc_language):
     def pop_default_element():
         a = {1:True, 2:False}
         return a.pop(3, True)
-    epyc_default_element = epyccel(pop_default_element, language = python_only_language)
+    epyc_default_element = epyccel(pop_default_element, language = stc_language)
+    pyccel_result = epyc_default_element()
+    python_result = pop_default_element()
+    assert isinstance(python_result, type(pyccel_result))
+    assert python_result == pyccel_result
+
+def test_pop_bool_keys(stc_language):
+    def pop_default_element():
+        a = {True:1, False:2}
+        return a.pop(False)
+    epyc_default_element = epyccel(pop_default_element, language = stc_language)
     pyccel_result = epyc_default_element()
     python_result = pop_default_element()
     assert isinstance(python_result, type(pyccel_result))