Repository: pyccel/pyccel (python)

Issue:
[DICT] Add Fortran support for pop
### Describe the feature
Add Fortran 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 a, b, c
```

### Proposed Solution
This can be implemented using the undocumented `erase` method from gFTL:
https://github.com/Goddard-Fortran-Ecosystem/gFTL/blob/main/include/v2/map/specification.inc


Pull request:
Add Fortran support for dict.pop
Add Fortran support for dict.pop. Fixes #2051 

Patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 8b18b450..6acd482d 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -56,7 +56,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()`.
--   #1886 : Add Python and C support for dict method `pop()`.
+-   #1886 : Add support for dict method `pop()`.
 -   #1936 : Add missing C output for inline decorator example in documentation
 -   #1937 : Optimise `pyccel.ast.basic.PyccelAstNode.substitute` method.
 -   #1544 : Add support for `typing.TypeAlias`.
diff --git a/docs/builtin-functions.md b/docs/builtin-functions.md
index 7fd3f46c..6c9a60f2 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 and C |
 | **`items`** | **Yes** |
 | **`keys`** | **Yes** |
-| `pop` | Python and C |
+| **`pop`** | **Yes** |
 | `popitem` | Python-only |
 | `reversed` | No |
 | `setdefault` | Python-only |
diff --git a/pyccel/codegen/printing/fcode.py b/pyccel/codegen/printing/fcode.py
index f45c27fd..45b3ccc0 100644
--- a/pyccel/codegen/printing/fcode.py
+++ b/pyccel/codegen/printing/fcode.py
@@ -731,6 +731,12 @@ class FCodePrinter(CodePrinter):
                                         if getattr(i, 'source', None) == vector_filename else i \
                                         for i in type_module.imports]
                 self.add_import(Import('gFTL_functions/Vector_extensions', Module('_', (), ()), ignore_at_print = True))
+            elif isinstance(expr_type, DictType):
+                map_filename = LiteralString('map/template.inc')
+                imports_and_macros += [Import(LiteralString('Map_extensions.inc'), Module('_', (), ())) \
+                                        if getattr(i, 'source', None) == map_filename else i \
+                                        for i in type_module.imports]
+                self.add_import(Import('gFTL_functions/Map_extensions', Module('_', (), ()), ignore_at_print = True))
             else:
                 raise NotImplementedError(f"Unknown gFTL import for type {expr_type}")
 
@@ -1524,6 +1530,22 @@ class FCodePrinter(CodePrinter):
         key = self._print(expr.key)
         return f'{dict_obj} % of( {key} )'
 
+    def _print_DictPop(self, expr):
+        dict_obj = expr.dict_obj
+        class_type = dict_obj.class_type
+
+        dict_obj_code = self._print(dict_obj)
+        key = self._print(expr.key)
+
+        type_name = self._print(class_type)
+        self.add_import(self._build_gFTL_extension_module(class_type))
+
+        if expr.default_value is not None:
+            default = self._print(expr.default_value)
+            return f'{type_name}_pop_with_default({dict_obj_code}, {key}, {default})'
+        else:
+            return f'{type_name}_pop({dict_obj_code}, {key})'
+
     #========================== String Methods ===============================#
 
     def _print_PythonStr(self, expr):
diff --git a/pyccel/codegen/utilities.py b/pyccel/codegen/utilities.py
index 750f03dc..a647aa73 100644
--- a/pyccel/codegen/utilities.py
+++ b/pyccel/codegen/utilities.py
@@ -72,6 +72,10 @@ internal_libs["gFTL_functions/Vector_extensions"] = (stdlib_path / "gFTL_functio
                                                                      folder="gFTL_functions",
                                                                      has_target_file = False,
                                                                      dependencies = (internal_libs['gFTL'][2],)))
+internal_libs["gFTL_functions/Map_extensions"] = (stdlib_path / "gFTL_functions", "gFTL_functions", CompileObj("Map_Extensions.inc",
+                                                                     folder="gFTL_functions",
+                                                                     has_target_file = False,
+                                                                     dependencies = (internal_libs['gFTL'][2],)))
 internal_libs["stc/cstr"] = (ext_path / "STC/src", "STC/src", CompileObj("cstr_core.c", folder="STC/include", dependencies = (internal_libs['stc'][2],)))
 internal_libs["stc/cspan"] = (ext_path / "STC/src", "STC/src", CompileObj("cspan.c", folder="STC/include", dependencies = (internal_libs['stc'][2],)))
 internal_libs["stc/algorithm"] = (ext_path / "STC/include/stc/", "STC/include/stc/", CompileObj("algorithm.h",
diff --git a/pyccel/stdlib/gFTL_functions/Map_extensions.inc b/pyccel/stdlib/gFTL_functions/Map_extensions.inc
new file mode 100644
index 00000000..0d0e0654
--- /dev/null
+++ b/pyccel/stdlib/gFTL_functions/Map_extensions.inc
@@ -0,0 +1,46 @@
+#include <map/header.inc>
+
+contains
+
+#define __IDENTITY(x) x
+#define __guard __map_guard
+#include "parameters/Key/copy_map_Key_to_internal_Key.inc"
+#include "parameters/T/copy_map_T_to_internal_T.inc"
+#include "parameters/Key/define_derived_macros.inc"
+#include "parameters/T/define_derived_macros.inc"
+
+  function __IDENTITY(Map)_pop(my_map, key) result(value)
+    class(Map), intent(inout) :: my_map
+    __Key_declare_dummy__, intent(in) :: key
+
+    __T_declare_dummy__ :: value
+
+    type (__map_iterator) :: iter
+
+    iter = my_map % find( key )
+    value = iter % second()
+    iter = my_map % erase( iter )
+
+  end function __IDENTITY(Map)_pop
+
+  function __IDENTITY(Map)_pop_with_default(my_map, key, default) result(value)
+    class(Map), intent(inout) :: my_map
+    __Key_declare_dummy__, intent(in) :: key
+    __T_declare_dummy__, intent(in) :: default
+
+    __T_declare_dummy__ :: value
+    type (__map_iterator) :: iter
+
+    iter = my_map % find( key )
+
+    if (iter /= my_map % end()) then
+      value = iter % second()
+      iter = my_map % erase( iter )
+    else
+      value = default
+    endif
+
+  end function __IDENTITY(Map)_pop_with_default
+
+#include <map/tail.inc>
+


Test patch:
diff --git a/tests/epyccel/test_epyccel_dicts.py b/tests/epyccel/test_epyccel_dicts.py
index 41a4717b..fc10ef05 100644
--- a/tests/epyccel/test_epyccel_dicts.py
+++ b/tests/epyccel/test_epyccel_dicts.py
@@ -70,51 +70,51 @@ def test_dict_kwarg_init(python_only_language):
     assert isinstance(python_result, type(pyccel_result))
     assert python_result == pyccel_result
 
-def test_pop_element(stc_language):
+def test_pop_element(language):
     def pop_element():
         a = {1:1.0, 2:2.0}
         return a.pop(1)
-    epyc_element = epyccel(pop_element, language = stc_language)
+    epyc_element = epyccel(pop_element, language = 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(stc_language):
+def test_pop_default_element(language):
     def pop_default_element():
         a = {1:True, 2:False}
         return a.pop(3, True)
-    epyc_default_element = epyccel(pop_default_element, language = stc_language)
+    epyc_default_element = epyccel(pop_default_element, language = 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 test_pop_bool_keys(language):
     def pop_default_element():
         a = {True:1, False:2}
         return a.pop(False)
-    epyc_default_element = epyccel(pop_default_element, language = stc_language)
+    epyc_default_element = epyccel(pop_default_element, language = 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_falsy_int_default_element(stc_language):
+def test_pop_falsy_int_default_element(language):
     def pop_falsy_int_default_element():
         a = {1:2, 2:3}
         return a.pop(3, 0)
-    epyc_func = epyccel(pop_falsy_int_default_element, language = stc_language)
+    epyc_func = epyccel(pop_falsy_int_default_element, language = language)
     pyccel_result = epyc_func()
     python_result = pop_falsy_int_default_element()
     assert isinstance(python_result, type(pyccel_result))
     assert python_result == pyccel_result
 
-def test_pop_falsy_bool_default_element(stc_language):
+def test_pop_falsy_bool_default_element(language):
     def pop_falsy_bool_default_element():
         a = {1:True, 2:False}
         return a.pop(3, False)
-    epyc_default_element = epyccel(pop_falsy_bool_default_element, language = stc_language)
+    epyc_default_element = epyccel(pop_falsy_bool_default_element, language = language)
     pyccel_result = epyc_default_element()
     python_result = pop_falsy_bool_default_element()
     assert isinstance(python_result, type(pyccel_result))