Repository: pyccel/pyccel
Pull request #2171 (2025-01-21 12:38:59)

Add `min/max` support for `Lists` and `Sets`
This PR aims to add support for functions `min` and `max` with `lists` and `sets`.
Necessary extension functions were added for `STC` and `gFTL`

pyccel-bot[bot]: Hello again! Thank you for this new pull request :star_struck:.

Please begin by requesting your checklist using the command `/bot checklist`

jalalium: /bot run linux
jalalium: /bot run linux
jalalium: /bot run linux
jalalium: /bot run windows
pyccel-bot[bot]: I can't seem to find your checklist to confirm that you have completed all necessary tasks. Please request one using `/bot checklist`.

jalalium: /bot run linux
jalalium: /bot run linux
pyccel-bot[bot]: I can't seem to find your checklist to confirm that you have completed all necessary tasks. Please request one using `/bot checklist`.

pyccel-bot[bot]: I can't seem to find your checklist to confirm that you have completed all necessary tasks. Please request one using `/bot checklist`.

jalalium: /bot checklist

pyccel-bot[bot]: Unfortunately your PR is not passing the tests so it is not quite ready for review yet. Let me know when it is fixed with `/bot mark as ready`.

jalalium: /bot run linux
jalalium: /bot run pylint
pyccel-bot[bot]: Unfortunately your PR is not passing the tests so it is not quite ready for review yet. Let me know when it is fixed with `/bot mark as ready`.

pyccel-bot[bot]: Unfortunately your PR is not passing the tests so it is not quite ready for review yet. Let me know when it is fixed with `/bot mark as ready`.

pyccel-bot[bot]: Hey @pyccel/pyccel-dev ! @jalalium has just created this great new pull request! Check it out and let me know what you think!

pyccel-bot[bot]: @jalalium, @EmilyBourne has a few questions/comments about your code. Can you go through and see if you agree with them. If not go ahead and explain why. Once you've adressed all the comments let me know with `/bot mark as ready` and we will see if we can get approval.

jalalium: /bot run linux
EmilyBourne: /bot run pr_tests

Patch:
diff --git a/pyccel/ast/builtins.py b/pyccel/ast/builtins.py
index fd300297..f57d27ba 100644
--- a/pyccel/ast/builtins.py
+++ b/pyccel/ast/builtins.py
@@ -1485,10 +1485,11 @@ class PythonMax(PyccelFunction):
 
         if isinstance(x, (list, tuple)):
             x = PythonTuple(*x)
-        elif not isinstance(x, (PythonTuple, PythonList)):
+        elif not (isinstance(x, (PythonTuple, PythonList))
+                  or (isinstance(x, Variable) and isinstance(x.class_type, HomogeneousContainerType))):
             raise TypeError(f'Unknown type of {type(x)}.' )
 
-        if not x.is_homogeneous:
+        if isinstance(x, (PythonTuple, PythonList)) and not x.is_homogeneous:
             types = ', '.join(str(xi.dtype) for xi in x)
             raise TypeError("Cannot determine final dtype of 'max' call with arguments of different "
                              f"types ({types}). Please cast arguments to the desired dtype")
@@ -1521,10 +1522,11 @@ class PythonMin(PyccelFunction):
 
         if isinstance(x, (list, tuple)):
             x = PythonTuple(*x)
-        elif not isinstance(x, (PythonTuple, PythonList)):
+        elif not (isinstance(x, (PythonTuple, PythonList))
+                  or (isinstance(x, Variable) and isinstance(x.class_type, HomogeneousContainerType))):
             raise TypeError(f'Unknown type of {type(x)}.' )
 
-        if not x.is_homogeneous:
+        if isinstance(x, (PythonTuple, PythonList)) and not x.is_homogeneous:
             types = ', '.join(str(xi.dtype) for xi in x)
             raise TypeError("Cannot determine final dtype of 'min' call with arguments of different "
                               f"types ({types}). Please cast arguments to the desired dtype")
diff --git a/pyccel/codegen/printing/ccode.py b/pyccel/codegen/printing/ccode.py
index d577c7f6..cfd47de9 100644
--- a/pyccel/codegen/printing/ccode.py
+++ b/pyccel/codegen/printing/ccode.py
@@ -822,13 +822,40 @@ class CCodePrinter(CodePrinter):
     def _print_PythonMinMax(self, expr):
         arg = expr.args[0]
         primitive_type = arg.dtype.primitive_type
+        variadic_args = isinstance(primitive_type, (PrimitiveFloatingPointType, PrimitiveIntegerType))
+        can_compare = primitive_type is not PrimitiveComplexType()
 
-        if primitive_type is PrimitiveFloatingPointType() and len(arg) == 2:
+        if isinstance(arg, Variable) and isinstance(arg.class_type , HomogeneousTupleType):
+            if isinstance(arg.shape[0], LiteralInteger):
+                arg = PythonTuple(*arg)
+            else:
+                return errors.report(f"{expr.name} in C does not support tuples of unknown length\n"
+                                     + PYCCEL_RESTRICTION_TODO, symbol=expr, severity='fatal')
+        if isinstance(arg, (PythonTuple, PythonList)) and variadic_args:
+            key = self.get_c_type(arg.class_type.element_type)
+            self.add_import(Import('stc/common', AsName(VariableTypeAnnotation(arg.dtype), key)))
+            args_code = ", ".join(self._print(a) for a in arg.args)
+            return  f'{key}_{expr.name}({len(arg.args)}, {args_code})'
+        elif isinstance(arg, Variable):
+            if isinstance(arg.class_type, (HomogeneousListType, HomogeneousSetType)) and can_compare:
+                class_type = arg.class_type
+                c_type = self.get_c_type(class_type)
+                arg_obj = self._print(ObjectAddress(arg))
+                import_loc = 'stc/vec' if isinstance(arg.class_type, HomogeneousListType) else 'stc/hset'
+                self.add_import(Import(import_loc, AsName(VariableTypeAnnotation(class_type), c_type)))
+                return f'{c_type}_{expr.name}({arg_obj})'
+            else:
+                return errors.report(f"{expr.name} in C does not support arguments of type {arg.class_type}", symbol=expr,
+                    severity='fatal')
+        if len(arg) != 2:
+            return errors.report(f"{expr.name} in C does not support {len(arg)} arguments of type {arg.dtype}\n"
+                                 + PYCCEL_RESTRICTION_TODO, symbol=expr, severity='fatal')
+        if primitive_type is PrimitiveFloatingPointType():
             self.add_import(c_imports['math'])
             arg1 = self._print(arg[0])
             arg2 = self._print(arg[1])
             return f"f{expr.name}({arg1}, {arg2})"
-        elif isinstance(primitive_type, (PrimitiveBooleanType, PrimitiveIntegerType)) and len(arg) == 2:
+        elif isinstance(primitive_type, (PrimitiveBooleanType, PrimitiveIntegerType)):
             if isinstance(arg[0], (Variable, Literal)):
                 arg1 = self._print(arg[0])
             else:
@@ -837,7 +864,6 @@ class CCodePrinter(CodePrinter):
                 code = self._print(assign1)
                 self._additional_code += code
                 arg1 = self._print(arg1_temp)
-
             if isinstance(arg[1], (Variable, Literal)):
                 arg2 = self._print(arg[1])
             else:
@@ -846,21 +872,16 @@ class CCodePrinter(CodePrinter):
                 code = self._print(assign2)
                 self._additional_code += code
                 arg2 = self._print(arg2_temp)
-
             op = '<' if isinstance(expr, PythonMin) else '>'
             return f"({arg1} {op} {arg2} ? {arg1} : {arg2})"
-        elif len(arg) > 2 and isinstance(arg.dtype.primitive_type, (PrimitiveFloatingPointType, PrimitiveIntegerType)):
-            key = self.get_c_type(arg[0].class_type)
-            self.add_import(Import('stc/common', AsName(VariableTypeAnnotation(arg.dtype), key)))
-            return  f'{key}_{expr.name}({len(arg)}, {", ".join(self._print(a) for a in arg)})'
         elif isinstance(primitive_type, PrimitiveComplexType):
             self.add_import(c_imports['pyc_math_c'])
             arg1 = self._print(arg[0])
             arg2 = self._print(arg[1])
             return f"complex_{expr.name}({arg1}, {arg2})"
         else:
-            return errors.report(f"{expr.name} in C does not support arguments of type {arg.dtype}", symbol=expr,
-                    severity='fatal')
+            return errors.report(f"{expr.name} in C does not support {len(arg)} arguments of type {arg.dtype}\n"
+                                 + PYCCEL_RESTRICTION_TODO, symbol=expr, severity='fatal')
 
     def _print_PythonMin(self, expr):
         return self._print_PythonMinMax(expr)
@@ -1226,7 +1247,7 @@ class CCodePrinter(CodePrinter):
                         decl_line = ''
                         errors.report(f"The declaration of type {class_type} is not yet implemented.",
                                 symbol=expr, severity='error')
-                if isinstance(class_type, HomogeneousListType) and isinstance(class_type.element_type, FixedSizeNumericType) \
+                if isinstance(class_type, (HomogeneousListType, HomogeneousSetType)) and isinstance(class_type.element_type, FixedSizeNumericType) \
                         and not isinstance(class_type.element_type.primitive_type, PrimitiveComplexType):
                     decl_line += '#define i_use_cmp\n'
                 header_guard_prefix = import_header_guard_prefix.get(source, '')
diff --git a/pyccel/codegen/printing/fcode.py b/pyccel/codegen/printing/fcode.py
index fbaeccfe..1647b69b 100644
--- a/pyccel/codegen/printing/fcode.py
+++ b/pyccel/codegen/printing/fcode.py
@@ -724,6 +724,12 @@ class FCodePrinter(CodePrinter):
                                         if getattr(i, 'source', None) == set_filename else i \
                                         for i in type_module.imports]
                 self.add_import(Import('gFTL_functions/Set_extensions', Module('_', (), ()), ignore_at_print = True))
+            elif isinstance(expr_type, HomogeneousListType):
+                vector_filename = LiteralString('vector/template.inc')
+                imports_and_macros += [Import(LiteralString('Vector_extensions.inc'), Module('_', (), ())) \
+                                        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))
             else:
                 raise NotImplementedError(f"Unkown gFTL import for type {expr_type}")
 
@@ -1875,27 +1881,28 @@ class FCodePrinter(CodePrinter):
         else:
             return f'minval({arg_code})'
         
-    def _print_PythonMin(self, expr):
-        args = expr.args
-        if len(args) == 1:
-            arg = args[0]
-            arg_code = self._get_node_without_gFTL(arg)
-            code = f'minval({arg_code})'
+    def _print_PythonMinMax(self, expr):
+        arg, = expr.args
+        if isinstance(arg, Variable):
+            if isinstance(arg.class_type, (HomogeneousListType, HomogeneousSetType)):
+                self.add_import(self._build_gFTL_extension_module(arg.class_type))
+                target = self._print(arg)
+                type_name = self._print(arg.class_type)
+                code = f'{type_name}_{expr.name}({target})'
+            elif isinstance(arg.class_type, HomogeneousTupleType):
+                code = f'{expr.name}val({self._print(arg)})'
+            else:
+                raise TypeError(f'Expecting a variable of type list or set, given {arg.class_type}')
         else:
-            code = ','.join(self._print(arg) for arg in args)
-            code = 'min('+code+')'
+            arg_code = self._get_node_without_gFTL(arg)
+            code = f'{expr.name}val({arg_code})'
         return code
 
+    def _print_PythonMin(self, expr):
+        return self._print_PythonMinMax(expr)
+
     def _print_PythonMax(self, expr):
-        args = expr.args
-        if len(args) == 1:
-            arg = args[0]
-            arg_code = self._get_node_without_gFTL(arg)
-            code = f'maxval({arg_code})'
-        else:
-            code = ','.join(self._print(arg) for arg in args)
-            code = 'max('+code+')'
-        return code
+        return self._print_PythonMinMax(expr)
 
     # ... MACROS
     def _print_MacroShape(self, expr):
diff --git a/pyccel/codegen/utilities.py b/pyccel/codegen/utilities.py
index 851f8d3b..26ef06b6 100644
--- a/pyccel/codegen/utilities.py
+++ b/pyccel/codegen/utilities.py
@@ -68,9 +68,14 @@ internal_libs["gFTL_functions/Set_extensions"] = (stdlib_path / "gFTL_functions"
                                                                      folder="gFTL_functions",
                                                                      has_target_file = False,
                                                                      dependencies = (internal_libs['gFTL'][2],)))
+internal_libs["gFTL_functions/Vector_extensions"] = (stdlib_path / "gFTL_functions", "gFTL_functions", CompileObj("Vector_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],)))
 
+
 #==============================================================================
 
 def not_a_copy(src_folder, dst_folder, filename):
diff --git a/pyccel/stdlib/STC_Extensions/List_extensions.h b/pyccel/stdlib/STC_Extensions/List_extensions.h
index d4534e61..5a918f52 100644
--- a/pyccel/stdlib/STC_Extensions/List_extensions.h
+++ b/pyccel/stdlib/STC_Extensions/List_extensions.h
@@ -1,6 +1,7 @@
 #include <stc/priv/template.h>
 
 
+
 // This function represents a call to the .pop() method.
 // i_type: Class type (e.g., hset_int64_t).
 // i_key: Data type of the elements in the set (e.g., int64_t).
@@ -19,4 +20,32 @@ static inline i_key _c_MEMB(_pull_elem)(Self* self, intptr_t pop_idx) {
     return *(itr.ref); // Return the element that is being popped.
 }
 
+
+#if defined (i_use_cmp) 
+// Function to get the minimum element from the vector
+static inline i_key _c_MEMB(_min)(const Self* self) {
+    i_key min_val = *_c_MEMB(_front)(self);
+    c_foreach(it, Self, *self) {
+        if (i_less(it.ref, &min_val)) {
+            min_val = *(it.ref);
+        }
+    }
+    return min_val;
+}
+
+// Function to get the maximum element from the vector
+static inline i_key _c_MEMB(_max)(const Self* self) {
+    i_key max_val = *_c_MEMB(_front)(self);
+    c_foreach(it, Self, *self) {
+        if (i_less(&max_val, it.ref)) {
+            max_val = *(it.ref);
+        }
+    }
+    return max_val;
+}
+#endif
+#undef i_type
+#undef i_key
+#undef i_use_cmp
+
 #include <stc/priv/template2.h>
diff --git a/pyccel/stdlib/STC_Extensions/Set_extensions.h b/pyccel/stdlib/STC_Extensions/Set_extensions.h
index 6f7faf4c..9077b343 100644
--- a/pyccel/stdlib/STC_Extensions/Set_extensions.h
+++ b/pyccel/stdlib/STC_Extensions/Set_extensions.h
@@ -56,5 +56,35 @@ static inline void _c_MEMB(_intersection_update)(Self* self, Self* other) {
         }
     }
 }
+#if defined (i_use_cmp) 
+// Function to get the minimum element from the set
+static inline i_key _c_MEMB(_min)(const Self* self) {
+    _c_MEMB(_iter) itr = _c_MEMB(_begin)(self);
+    i_key min_val = *(itr.ref);
+    while (itr.ref)
+    {
+        if (i_less(itr.ref, &min_val))
+            min_val = *(itr.ref);
+        _c_MEMB(_next)(&itr);
+    }
+    return min_val;
+}
+
+// Function to get the maximum element from the set
+static inline i_key _c_MEMB(_max)(const Self* self) {
+    _c_MEMB(_iter) itr = _c_MEMB(_begin)(self);
+    i_key max_val = *(itr.ref);
+    while (itr.ref)
+    {
+        if (i_less(&max_val, itr.ref))
+            max_val = *(itr.ref);
+        _c_MEMB(_next)(&itr);
+    }
+    return max_val;
+}
+#endif
+#undef i_type
+#undef i_key
+#undef i_use_cmp
 
 #include <stc/priv/template2.h>
diff --git a/pyccel/stdlib/gFTL_functions/Set_extensions.inc b/pyccel/stdlib/gFTL_functions/Set_extensions.inc
index bc08e769..361180e1 100644
--- a/pyccel/stdlib/gFTL_functions/Set_extensions.inc
+++ b/pyccel/stdlib/gFTL_functions/Set_extensions.inc
@@ -41,4 +41,30 @@ contains
 
   end subroutine __IDENTITY(Set)_intersection_update
 
+  ! Sets in gFTL are BSTs, therefore the min is the first element
+   function __IDENTITY(Set)_min(my_set) result(result)
+    class(Set), intent(in) :: my_set
+    __T_declare_dummy__ :: result
+
+    type(SetIterator) :: iter
+
+    iter = my_set%begin()
+
+    result = iter%of()
+  end function __IDENTITY(Set)_min
+
+  ! Sets in gFTL are BSTs, therefore the max is the last element
+  function __IDENTITY(Set)_max(my_set) result(result)
+    class(Set), intent(in) :: my_set
+    __T_declare_dummy__ :: result
+
+    type(SetIterator) :: last
+
+    last = my_set%end()
+
+    call last%prev()
+
+    result = last%of()
+  end function __IDENTITY(Set)_max
+
 #include <set/tail.inc>
diff --git a/pyccel/stdlib/gFTL_functions/Vector_extensions.inc b/pyccel/stdlib/gFTL_functions/Vector_extensions.inc
new file mode 100644
index 00000000..1cfcecc3
--- /dev/null
+++ b/pyccel/stdlib/gFTL_functions/Vector_extensions.inc
@@ -0,0 +1,59 @@
+#include <vector/header.inc>
+
+contains
+
+#define __IDENTITY(x) x
+#define __guard __vector_guard
+#include "parameters/T/copy_vector_T_to_internal_T.inc"
+#include "parameters/T/define_derived_macros.inc"
+
+  function __IDENTITY(Vector)_min(my_vector) result(result)
+    class(Vector), intent(in) :: my_vector
+    __T_declare_dummy__ :: result
+
+    type(VectorIterator) :: iter
+    type(VectorIterator) :: last
+
+    iter = my_vector%begin()
+    last = my_vector%end()
+
+    if (iter == last) then
+       print *, "Error: Empty vector, cannot determine min."
+       stop
+    end if
+
+    result = iter%of()
+    do while (iter /= last)
+      if (T_LT(iter%of(), result)) then
+        result = iter%of()
+      end if
+      call iter%next()
+    end do
+
+  end function __IDENTITY(Vector)_min
+
+  function __IDENTITY(Vector)_max(my_vector) result(result)
+    class(Vector), intent(in) :: my_vector
+    __T_declare_dummy__ :: result
+
+    type(VectorIterator) :: iter
+    type(VectorIterator) :: last
+
+    iter = my_vector%begin()
+    last = my_vector%end()
+
+    if (iter == last) then
+       print *, "Error: Empty vector, cannot determine max."
+       stop
+    end if
+
+    result = iter%of()
+    do while (iter /= last)
+      if (T_LT(result, iter%of())) then
+        result = iter%of()
+      end if
+      call iter%next()
+    end do
+
+  end function __IDENTITY(Vector)_max
+#include <vector/tail.inc>


Test patch:
diff --git a/tests/epyccel/test_builtins.py b/tests/epyccel/test_builtins.py
index cfa2f58b..842b2e0a 100644
--- a/tests/epyccel/test_builtins.py
+++ b/tests/epyccel/test_builtins.py
@@ -149,15 +149,6 @@ def test_min_in_min(language):
 
     assert epyc_f(*int_args) == f(*int_args)
 
-@pytest.mark.parametrize( 'language', (
-        pytest.param("fortran", marks = pytest.mark.fortran),
-        pytest.param("c", marks = [
-            pytest.mark.skip(reason="min not implemented in C for more than 2 args"),
-            pytest.mark.c]
-        ),
-        pytest.param("python", marks = pytest.mark.python)
-    )
-)
 def test_min_list(language):
     @template('T', [int, float])
     def f(x : 'T', y : 'T', z : 'T'):
@@ -171,15 +162,6 @@ def test_min_list(language):
     assert epyc_f(*int_args) == f(*int_args)
     assert np.isclose(epyc_f(*float_args), f(*float_args), rtol=RTOL, atol=ATOL)
 
-@pytest.mark.parametrize( 'language', (
-        pytest.param("fortran", marks = pytest.mark.fortran),
-        pytest.param("c", marks = [
-            pytest.mark.skip(reason="min not implemented in C for more than 2 args"),
-            pytest.mark.c]
-        ),
-        pytest.param("python", marks = pytest.mark.python)
-    )
-)
 def test_min_tuple(language):
     @template('T', [int, float])
     def f(x : 'T', y : 'T', z : 'T'):
@@ -193,6 +175,34 @@ def test_min_tuple(language):
     assert epyc_f(*int_args) == f(*int_args)
     assert np.isclose(epyc_f(*float_args), f(*float_args), rtol=RTOL, atol=ATOL)
 
+def test_min_list_var(language):
+    @template('T', [int, float])
+    def f(x : 'T', y : 'T', z : 'T'):
+        w = [x, y, z]
+        return min(w)
+
+    epyc_f = epyccel(f, language=language)
+
+    int_args = [randint(min_int, max_int) for _ in range(3)]
+    float_args = [uniform(min_float/2, max_float/2) for _ in range(3)]
+
+    assert epyc_f(*int_args) == f(*int_args)
+    assert np.isclose(epyc_f(*float_args), f(*float_args), rtol=RTOL, atol=ATOL)
+
+def test_min_tuple_var(language):
+    @template('T', [int, float])
+    def f(x : 'T', y : 'T', z : 'T'):
+        w = (x, y, z)
+        return min(w)
+
+    epyc_f = epyccel(f, language=language)
+
+    int_args = [randint(min_int, max_int) for _ in range(3)]
+    float_args = [uniform(min_float/2, max_float/2) for _ in range(3)]
+
+    assert epyc_f(*int_args) == f(*int_args)
+    assert np.isclose(epyc_f(*float_args), f(*float_args), rtol=RTOL, atol=ATOL)
+
 def test_min_expr(language):
     @template('T', [int, float])
     def f(x : 'T', y : 'T'):
@@ -259,15 +269,6 @@ def test_max_3_args(language):
     assert epyc_f(*int_args) == f(*int_args)
     assert np.isclose(epyc_f(*float_args), f(*float_args), rtol=RTOL, atol=ATOL)
 
-@pytest.mark.parametrize( 'language', (
-        pytest.param("fortran", marks = pytest.mark.fortran),
-        pytest.param("c", marks = [
-            pytest.mark.skip(reason="max not implemented in C for more than 2 args"),
-            pytest.mark.c]
-        ),
-        pytest.param("python", marks = pytest.mark.python)
-    )
-)
 def test_max_list(language):
     @template('T', [int, float])
     def f(x : 'T', y : 'T', z : 'T'):
@@ -281,15 +282,6 @@ def test_max_list(language):
     assert epyc_f(*int_args) == f(*int_args)
     assert np.isclose(epyc_f(*float_args), f(*float_args), rtol=RTOL, atol=ATOL)
 
-@pytest.mark.parametrize( 'language', (
-        pytest.param("fortran", marks = pytest.mark.fortran),
-        pytest.param("c", marks = [
-            pytest.mark.skip(reason="max not implemented in C for more than 2 args"),
-            pytest.mark.c]
-        ),
-        pytest.param("python", marks = pytest.mark.python)
-    )
-)
 def test_max_tuple(language):
     @template('T', [int, float])
     def f(x : 'T', y : 'T', z : 'T'):
@@ -303,6 +295,34 @@ def test_max_tuple(language):
     assert epyc_f(*int_args) == f(*int_args)
     assert np.isclose(epyc_f(*float_args), f(*float_args), rtol=RTOL, atol=ATOL)
 
+def test_max_list_var(language):
+    @template('T', [int, float])
+    def f(x : 'T', y : 'T', z : 'T'):
+        w = [x, y, z]
+        return max(w)
+
+    epyc_f = epyccel(f, language=language)
+
+    int_args = [randint(min_int, max_int) for _ in range(3)]
+    float_args = [uniform(min_float/2, max_float/2) for _ in range(3)]
+
+    assert epyc_f(*int_args) == f(*int_args)
+    assert np.isclose(epyc_f(*float_args), f(*float_args), rtol=RTOL, atol=ATOL)
+
+def test_max_tuple_var(language):
+    @template('T', [int, float])
+    def f(x : 'T', y : 'T', z : 'T'):
+        w = (x, y, z)
+        return max(w)
+
+    epyc_f = epyccel(f, language=language)
+
+    int_args = [randint(min_int, max_int) for _ in range(3)]
+    float_args = [uniform(min_float/2, max_float/2) for _ in range(3)]
+
+    assert epyc_f(*int_args) == f(*int_args)
+    assert np.isclose(epyc_f(*float_args), f(*float_args), rtol=RTOL, atol=ATOL)
+
 def test_max_expr(language):
     @template('T', [int, float])
     def f(x : 'T', y : 'T'):
diff --git a/tests/epyccel/test_epyccel_lists.py b/tests/epyccel/test_epyccel_lists.py
index bdc24b16..2da5a3e7 100644
--- a/tests/epyccel/test_epyccel_lists.py
+++ b/tests/epyccel/test_epyccel_lists.py
@@ -803,6 +803,18 @@ def test_list_return(language):
     assert isinstance(python_result, type(pyccel_result))
     assert isinstance(python_result.pop(), type(pyccel_result.pop()))
 
+
+def test_list_min_max(language):
+    def list_min_max():
+        a_int = [1, 2, 3, 4]
+        a_float = [1.1, 2.2, 3.3, 4.4]
+        return min(a_int), max(a_int), min(a_float), max(a_float)
+    epyccel_func = epyccel(list_min_max, language = language)
+    pyccel_result = epyccel_func()
+    python_result = list_min_max()
+    assert python_result == pyccel_result
+    assert isinstance(python_result, type(pyccel_result))
+
 def test_list_str(stc_language):
     def list_str():
         a = ['hello', 'world', '!']
@@ -812,3 +824,4 @@ def test_list_str(stc_language):
     pyccel_result = epyccel_func()
     python_result = list_str()
     assert python_result == pyccel_result
+
diff --git a/tests/epyccel/test_epyccel_sets.py b/tests/epyccel/test_epyccel_sets.py
index 361483ae..bd2f9bfd 100644
--- a/tests/epyccel/test_epyccel_sets.py
+++ b/tests/epyccel/test_epyccel_sets.py
@@ -744,3 +744,15 @@ def test_set_return(language):
     assert python_result == pyccel_result
     assert isinstance(python_result, type(pyccel_result))
     assert isinstance(python_result.pop(), type(pyccel_result.pop()))
+
+def test_set_min_max(language):
+    def set_min_max():
+        a_int = {1,2,3,4,5}
+        a_float = {1.1,2.2,3.3,4.4,5.5}
+        return min(a_int), min(a_float), max(a_int), max(a_float)
+
+    epyccel_func = epyccel(set_min_max, language = language)
+    pyccel_result = epyccel_func()
+    python_result = set_min_max()
+    assert python_result == pyccel_result
+    assert isinstance(python_result, type(pyccel_result))