Unify the types of constant arguments to min, max and clamp

When every argument was constant, the result took the value and type of the
selected argument without checking the arguments against each other, so
mismatched constants like `min(42, "string_literal")`, `min(i32(1), u8(2))`
and `clamp(1.5, 42, "string_test")` were accepted, and `min(1, 2.5)` was an
`int`. Unify the argument types before the constant and non-constant paths, as
the non-constant path already did.

Fixes #7975
Fixes #7976
This commit is contained in:
Matthew Winter committed 2026-10-18 21:38:58 +11:00
1 parent afa720bc4d
commit aaba3a3346
6 files changed
+143 -116

No files matched your search

+99 -116
View File
@@ -4572,61 +4572,56 @@ gb_internal bool check_builtin_procedure(CheckerContext *c, Operand *operand, As
}
}
for_array(i, operands) {
Operand *a = &operands[i];
for_array(j, operands) {
if (i == j) {
continue;
}
Operand *b = &operands[j];
convert_to_typed(c, a, b->type);
if (a->mode == Addressing_Invalid) {
return false;
}
convert_to_typed(c, b, a->type);
if (b->mode == Addressing_Invalid) {
return false;
}
}
}
for (isize i = 0; i < operands.count-1; i++) {
Operand *a = &operands[i];
Operand *b = &operands[i+1];
if (!are_types_identical(a->type, b->type)) {
gbString type_a = type_to_string(a->type);
gbString type_b = type_to_string(b->type);
error(a->expr,
"Mismatched types to 'min', '%s' vs '%s'",
type_a, type_b);
gb_string_free(type_b);
gb_string_free(type_a);
return false;
}
}
if (all_constant) {
ExactValue value = operands[0].value;
Type *type = operands[0].type;
for (isize i = 1; i < operands.count; i++) {
Operand y = operands[i];
if (compare_exact_values(Token_Lt, value, y.value)) {
// okay
} else {
value = y.value;
type = y.type;
}
}
operand->value = value;
operand->type = type;
} else {
operand->mode = Addressing_Value;
operand->type = original_type;
for_array(i, operands) {
Operand *a = &operands[i];
for_array(j, operands) {
if (i == j) {
continue;
}
Operand *b = &operands[j];
convert_to_typed(c, a, b->type);
if (a->mode == Addressing_Invalid) {
return false;
}
convert_to_typed(c, b, a->type);
if (b->mode == Addressing_Invalid) {
return false;
}
}
}
for (isize i = 0; i < operands.count-1; i++) {
Operand *a = &operands[i];
Operand *b = &operands[i+1];
if (!are_types_identical(a->type, b->type)) {
gbString type_a = type_to_string(a->type);
gbString type_b = type_to_string(b->type);
error(a->expr,
"Mismatched types to 'min', '%s' vs '%s'",
type_a, type_b);
gb_string_free(type_b);
gb_string_free(type_a);
return false;
}
}
operand->type = operands[0].type;
}
operand->type = operands[0].type;
break;
}
@@ -4769,61 +4764,56 @@ gb_internal bool check_builtin_procedure(CheckerContext *c, Operand *operand, As
}
}
for_array(i, operands) {
Operand *a = &operands[i];
for_array(j, operands) {
if (i == j) {
continue;
}
Operand *b = &operands[j];
convert_to_typed(c, a, b->type);
if (a->mode == Addressing_Invalid) {
return false;
}
convert_to_typed(c, b, a->type);
if (b->mode == Addressing_Invalid) {
return false;
}
}
}
for (isize i = 0; i < operands.count-1; i++) {
Operand *a = &operands[i];
Operand *b = &operands[i+1];
if (!are_types_identical(a->type, b->type)) {
gbString type_a = type_to_string(a->type);
gbString type_b = type_to_string(b->type);
error(a->expr,
"Mismatched types to 'max', '%s' vs '%s'",
type_a, type_b);
gb_string_free(type_b);
gb_string_free(type_a);
return false;
}
}
if (all_constant) {
ExactValue value = operands[0].value;
Type *type = operands[0].type;
for (isize i = 1; i < operands.count; i++) {
Operand y = operands[i];
if (compare_exact_values(Token_Gt, value, y.value)) {
// okay
} else {
type = y.type;
value = y.value;
}
}
operand->value = value;
operand->type = type;
} else {
operand->mode = Addressing_Value;
operand->type = original_type;
for_array(i, operands) {
Operand *a = &operands[i];
for_array(j, operands) {
if (i == j) {
continue;
}
Operand *b = &operands[j];
convert_to_typed(c, a, b->type);
if (a->mode == Addressing_Invalid) {
return false;
}
convert_to_typed(c, b, a->type);
if (b->mode == Addressing_Invalid) {
return false;
}
}
}
for (isize i = 0; i < operands.count-1; i++) {
Operand *a = &operands[i];
Operand *b = &operands[i+1];
if (!are_types_identical(a->type, b->type)) {
gbString type_a = type_to_string(a->type);
gbString type_b = type_to_string(b->type);
error(a->expr,
"Mismatched types to 'max', '%s' vs '%s'",
type_a, type_b);
gb_string_free(type_b);
gb_string_free(type_a);
return false;
}
}
operand->type = operands[0].type;
}
operand->type = operands[0].type;
break;
}
@@ -4932,7 +4922,32 @@ gb_internal bool check_builtin_procedure(CheckerContext *c, Operand *operand, As
}
}
Type *type = x.type;
for (isize i = 0; i < 3; i++) {
Operand *a = ops[i];
for (isize j = 0; j < 3; j++) {
if (i == j) {
continue;
}
Operand *b = ops[j];
convert_to_typed(c, a, b->type);
if (a->mode == Addressing_Invalid) {
return false;
}
}
}
if (!are_types_identical(x.type, y.type) || !are_types_identical(x.type, z.type)) {
gbString type_x = type_to_string(x.type);
gbString type_y = type_to_string(y.type);
gbString type_z = type_to_string(z.type);
error(call,
"Mismatched types to 'clamp', '%s', '%s', '%s'",
type_x, type_y, type_z);
gb_string_free(type_z);
gb_string_free(type_y);
gb_string_free(type_x);
return false;
}
if (x.mode == Addressing_Constant &&
y.mode == Addressing_Constant &&
@@ -4944,47 +4959,15 @@ gb_internal bool check_builtin_procedure(CheckerContext *c, Operand *operand, As
operand->mode = Addressing_Constant;
if (compare_exact_values(Token_Lt, a, b)) {
operand->value = b;
operand->type = y.type;
} else if (compare_exact_values(Token_Gt, a, c)) {
operand->value = c;
operand->type = z.type;
} else {
operand->value = a;
operand->type = x.type;
}
} else {
operand->mode = Addressing_Value;
operand->type = type;
for (isize i = 0; i < 3; i++) {
Operand *a = ops[i];
for (isize j = 0; j < 3; j++) {
if (i == j) {
continue;
}
Operand *b = ops[j];
convert_to_typed(c, a, b->type);
if (a->mode == Addressing_Invalid) {
return false;
}
}
}
if (!are_types_identical(x.type, y.type) || !are_types_identical(x.type, z.type)) {
gbString type_x = type_to_string(x.type);
gbString type_y = type_to_string(y.type);
gbString type_z = type_to_string(z.type);
error(call,
"Mismatched types to 'clamp', '%s', '%s', '%s'",
type_x, type_y, type_z);
gb_string_free(type_z);
gb_string_free(type_y);
gb_string_free(type_x);
return false;
}
operand->type = ops[0]->type;
}
operand->type = x.type;
break;
}
+1
View File
@@ -45,6 +45,7 @@ set ODIN=..\..\..\odin
%ODIN% check "..\test_issue_7429" %COMMON% || exit /b
%ODIN% check "..\test_issue_7708_mismatch" %COMMON% 2>&1 | find /c "Error:" | findstr /x "2" || exit /b
%ODIN% check "..\test_issue_7938" -no-entry-point %COMMON% 2>&1 | find /c "Error: Missing return statement" | findstr /x "3" || exit /b
%ODIN% check "..\test_issue_7975_7976_mismatch" %COMMON% 2>&1 | find /c "Error:" | findstr /x "8" || exit /b
%ODIN% check "..\test_issue_ambiguous_union_literal" %COMMON% 2>&1 | find /c "Error:" | findstr /x "1" || exit /b
%ODIN% check "..\test_issue_global_when_cycle" -no-entry-point %COMMON% 2>&1 | find /c "Contradictory global" | findstr /x "4" || exit /b
%ODIN% check "..\test_issue_global_when_cycle_ambiguous" -no-entry-point %COMMON% 2>&1 | find /c "Ambiguous global" | findstr /x "1" || exit /b
+7
View File
@@ -144,6 +144,13 @@ else
exit 1
fi
if [[ $($ODIN check ../test_issue_7975_7976_mismatch $COMMON_CHECK 2>&1 >/dev/null | grep -c "Error:") -eq 8 ]]; then
echo "SUCCESSFUL 1/1"
else
echo "SUCCESSFUL 0/1"
exit 1
fi
if [[ $($ODIN check ../test_issue_poly_proc_value $COMMON_CHECK 2>&1 >/dev/null | grep -c "Error:") -eq 4 ]]; then
echo "SUCCESSFUL 1/1"
else
@@ -0,0 +1,21 @@
// Tests issues #7975 https://github.com/odin-lang/Odin/issues/7975 and #7976 https://github.com/odin-lang/Odin/issues/7976
// Constant arguments to `min`, `max` and `clamp` are unified into a single type, as non-constant ones are
package test_issue_7975_7976
import "core:testing"
@(test)
test_constant_min_max_clamp :: proc(t: ^testing.T) {
a := min(1, 2.5)
b := max(3, 1.5)
c := clamp(3, 0.5, 2)
d := min("b", "string_literal")
testing.expect_value(t, typeid_of(type_of(a)), f64)
testing.expect_value(t, typeid_of(type_of(b)), f64)
testing.expect_value(t, typeid_of(type_of(c)), f64)
testing.expect_value(t, a, 1.0)
testing.expect_value(t, b, 3.0)
testing.expect_value(t, c, 2.0)
testing.expect_value(t, d, "b")
}
@@ -0,0 +1,14 @@
// Tests issues #7975 https://github.com/odin-lang/Odin/issues/7975 and #7976 https://github.com/odin-lang/Odin/issues/7976
// Constant arguments to `min`, `max` and `clamp` which cannot be unified into a single type must be rejected
package test_issue_7975_7976_mismatch
main :: proc() {
_ = min(3.14, "string_literal")
_ = max(42, "string_literal")
_ = min('a', "b")
_ = clamp(1.5, "string_test", 5.5)
_ = clamp(1.5, 42, "string_test")
_ = min(i32(1), u8(2))
_ = max(3.5, i32(2))
_ = clamp(i32(1), 2, u8(3))
}
+1
View File
@@ -52,6 +52,7 @@ package tests_issues
@(require) import "test_issue_7853"
@(require) import "test_issue_7875_7471"
@(require) import "test_issue_7889"
@(require) import "test_issue_7975_7976"
@(require) import "test_issue_fast_isel_lowering"
@(require) import "test_issue_omitted_field_union"
@(require) import "test_issue_equal_proc_dependencies"