Repository: slint-ui/slint (language: rust)

Issue:
Some Math operations on percent type seem incorrect
### Bug Description

Max, Min and Clamp (other not tested) behave differently on the percent type.

Only max seems to be correct, or at least what is expected imo.

See test code

### Reproducible Code (if applicable)

```slint
export component Demo {

    property <percent> p_min: min(50%, 100%);
    property <percent> p_max: max(50%, 0%);
    property <percent> p_clamp: clamp(50%, 0%, 100%);


    VerticalLayout {
        alignment: start;
        Text {
            text: "Min: \{p_min / 1%}";
            font-size: 24px;
            horizontal-alignment: center;
        }  
        Text {
            text: "Max: \{p_max / 1%}";
            font-size: 24px;
            horizontal-alignment: center;
        }    
        Text {
            text: "Clamp: \{p_clamp / 1%}";
            font-size: 24px;
            horizontal-alignment: center;
        }   
    }
}
```


### Environment Details

- Slint Version: 1.8.0
- Platform/OS:
- Programming Language:
- Backend/Renderer:


### Product Impact

_No response_


Discussion / hints:
I can reproduce the bug. Thanks for reporting.

Looks like the conversion from `%` to number is not done properly in the min/max function.

Fix patch:
diff --git a/internal/compiler/builtin_macros.rs b/internal/compiler/builtin_macros.rs
index 0b0f5bf5314..6ab25be1deb 100644
--- a/internal/compiler/builtin_macros.rs
+++ b/internal/compiler/builtin_macros.rs
@@ -79,23 +79,14 @@ fn min_max_macro(
         diag.push_error("Needs at least one argument".into(), &node);
         return Expression::Invalid;
     }
+    let ty = Expression::common_target_type_for_type_list(args.iter().map(|expr| expr.0.ty()));
+    if ty.as_unit_product().is_none() {
+        diag.push_error("Invalid argument type".into(), &node);
+        return Expression::Invalid;
+    }
     let mut args = args.into_iter();
-    let (mut base, arg_node) = args.next().unwrap();
-    let ty = match base.ty() {
-        Type::Float32 => Type::Float32,
-        // In case there are other floats, we don't want to convert the result to int
-        Type::Int32 => Type::Float32,
-        Type::PhysicalLength => Type::PhysicalLength,
-        Type::LogicalLength => Type::LogicalLength,
-        Type::Duration => Type::Duration,
-        Type::Angle => Type::Angle,
-        Type::Percent => Type::Float32,
-        Type::Rem => Type::Rem,
-        _ => {
-            diag.push_error("Invalid argument type".into(), &arg_node);
-            return Expression::Invalid;
-        }
-    };
+    let (base, arg_node) = args.next().unwrap();
+    let mut base = base.maybe_convert_to(ty.clone(), &arg_node, diag);
     for (next, arg_node) in args {
         let rhs = next.maybe_convert_to(ty.clone(), &arg_node, diag);
         base = min_max_expression(base, rhs, op);
@@ -117,20 +108,11 @@ fn clamp_macro(
         return Expression::Invalid;
     }
     let (value, value_node) = args.first().unwrap().clone();
-    let ty = match value.ty() {
-        Type::Float32 => Type::Float32,
-        // In case there are other floats, we don't want to convert the result to int
-        Type::Int32 => Type::Float32,
-        Type::PhysicalLength => Type::PhysicalLength,
-        Type::LogicalLength => Type::LogicalLength,
-        Type::Duration => Type::Duration,
-        Type::Angle => Type::Angle,
-        Type::Percent => Type::Float32,
-        _ => {
-            diag.push_error("Invalid argument type".into(), &value_node);
-            return Expression::Invalid;
-        }
-    };
+    let ty = value.ty();
+    if ty.as_unit_product().is_none() {
+        diag.push_error("Invalid argument type".into(), &value_node);
+        return Expression::Invalid;
+    }
 
     let (min, min_node) = args.get(1).unwrap().clone();
     let min = min.maybe_convert_to(ty.clone(), &min_node, diag);


Test patch:
diff --git a/internal/compiler/tests/syntax/expressions/minmax.slint b/internal/compiler/tests/syntax/expressions/minmax.slint
index 5de03db1073..aa423d56e60 100644
--- a/internal/compiler/tests/syntax/expressions/minmax.slint
+++ b/internal/compiler/tests/syntax/expressions/minmax.slint
@@ -10,21 +10,22 @@ export SuperSimple := Rectangle {
     property <int> a: max + max() + max(45, "hello");
 //                    ^error{Builtin function must be called}
 //                          ^^error{Needs at least one argument}
-//                                          ^^^error{Cannot convert string to float}
+//                                  ^^^error{Invalid argument type}
     property <length> b: max(41px, 41phx);
     property <string> c: max("a", "b");
-//                           ^error{Invalid argument type}
+//                       ^error{Invalid argument type}
 
     property <int> d: min + min() + min(45, "hello");
 //                    ^error{Builtin function must be called}
 //                          ^^error{Needs at least one argument}
-//                                          ^^^error{Cannot convert string to float}
+//                                  ^^^error{Invalid argument type}
     property <length> e: min(41px, 41phx);
     property <string> f: min("a", "b");
-//                           ^error{Invalid argument type}
+//                       ^error{Invalid argument type}
 
     property <int> g: min(42, 42, 42cm);
-//                                ^error{Cannot convert length to float}
+//                        ^error{Cannot convert float to length}
+//                            ^^error{Cannot convert float to length}
     property <int> h: min(42, 42 + 1) + max(1px , 2phx);
 //                    ^error{Cannot convert float to length}
 
diff --git a/tests/cases/types/percent.slint b/tests/cases/types/percent.slint
index 83cfd3630ba..190117ec625 100644
--- a/tests/cases/types/percent.slint
+++ b/tests/cases/types/percent.slint
@@ -6,6 +6,12 @@ TestCase := Rectangle {
     property<float> p2: 1%;
     property<float> p3: 5.5%;
     property<float> p4: 10 + 50%;
+
+    property <percent> p_min: min(50%, 100%);
+    property <percent> p_max: max(50%, 0%);
+    property <percent> p_clamp: clamp(50%, 0%, 100%);
+
+    property <bool> test: p_min == 50% && p_max == 50% && p_clamp == 50%;
 }
 
 
@@ -21,6 +27,8 @@ assert(fuzzy_compare(instance.get_p3(), 0.055));
 assert(fuzzy_compare(instance.get_p4(), 10.5));
 // self_test
 assert(!fuzzy_compare(instance.get_p1(), 1.001));
+
+assert(instance.get_test());
 ```
 
 ```rust
@@ -31,6 +39,8 @@ assert_eq!(instance.get_p3(), 0.055);
 assert_eq!(instance.get_p4(), 10.5);
 
 assert_ne!(instance.get_p3(), 0.0549);
+
+assert!(instance.get_test());
 ```
 
 ```js
@@ -43,6 +53,8 @@ assert.equal(n(instance.p4), n(10.5));
 // self_test
 assert.notEqual(n(instance.p1), n(1.001));
 
+assert(instance.test);
+
 ```
 
 */