diff --git a/core/math/cmplx/cmplx.odin b/core/math/cmplx/cmplx.odin index f5c572cc6..3a1c3f511 100644 --- a/core/math/cmplx/cmplx.odin +++ b/core/math/cmplx/cmplx.odin @@ -253,7 +253,7 @@ exp_complex64 :: proc "contextless" (x: complex64) -> complex64 { if re < 0 { return complex(0, math.copy_sign(0, im)) } else { - return complex(math.inf_f64(1.0), math.nan_f64()) + return complex(math.inf_f32(1.0), math.nan_f32()) } } case math.is_nan(re): diff --git a/src/check_builtin.cpp b/src/check_builtin.cpp index 20fa2f92c..350636b95 100644 --- a/src/check_builtin.cpp +++ b/src/check_builtin.cpp @@ -3604,7 +3604,13 @@ gb_internal bool check_builtin_procedure(CheckerContext *c, Operand *operand, As default: GB_PANIC("Invalid type"); break; } - if (type_hint != nullptr && check_is_castable_to(c, operand, type_hint)) { + // Only a complex hint of the same element type, or context-typing an untyped constant. + // Castability is the rule for a conversion the programmer wrote; used here it adopted any + // castable hint, which silently narrowed f64 to f32 and left the value with no element type + // at all when the hint was `any` or a union + if (type_hint != nullptr && is_type_complex(type_hint) && + (is_type_untyped(operand->type) || + are_types_identical(core_type(operand->type), core_type(type_hint)))) { operand->type = type_hint; } @@ -3803,7 +3809,10 @@ gb_internal bool check_builtin_procedure(CheckerContext *c, Operand *operand, As default: GB_PANIC("Invalid type"); break; } - if (type_hint != nullptr && check_is_castable_to(c, operand, type_hint)) { + // see the note in BuiltinProc_complex + if (type_hint != nullptr && is_type_quaternion(type_hint) && + (is_type_untyped(operand->type) || + are_types_identical(core_type(operand->type), core_type(type_hint)))) { operand->type = type_hint; } diff --git a/tests/internal/test_complex_type_hint.odin b/tests/internal/test_complex_type_hint.odin new file mode 100644 index 000000000..cffd7b06c --- /dev/null +++ b/tests/internal/test_complex_type_hint.odin @@ -0,0 +1,55 @@ +package test_internal + +import "core:testing" + +// `complex` and `quaternion` took their result type from any hint they were *castable* to, which is +// the rule for a conversion the programmer writes. Used as an assignment rule it silently narrowed +// f64 to f32, produced a quaternion from a two-component builtin with w and k uninitialised, and +// left the value with no element type at all when the hint was `any` or a union -- a compiler panic +@(test) +complex_takes_its_type_from_its_arguments :: proc(t: ^testing.T) { + a, b: f64 = 3, 4 + c, d: f32 = 3, 4 + + // the element type follows the arguments, not the hint + x: complex128 = complex(a, b) + y: complex64 = complex(c, d) + testing.expect_value(t, real(x), f64(3)) + testing.expect_value(t, imag(x), f64(4)) + testing.expect_value(t, real(y), f32(3)) + testing.expect_value(t, imag(y), f32(4)) + + // a distinct complex of the same width still takes the hint, which core:c/libc relies on + CD :: distinct complex128 + z: CD = complex(a, b) + testing.expect_value(t, real(complex128(z)), f64(3)) + + // an untyped constant is still context-typed + u: complex64 = complex(1, 2) + testing.expect_value(t, real(u), f32(1)) + + q: quaternion256 = quaternion(w = a, x = b, y = a, z = b) + testing.expect_value(t, real(q), f64(3)) + testing.expect_value(t, imag(q), f64(4)) + testing.expect_value(t, jmag(q), f64(3)) + testing.expect_value(t, kmag(q), f64(4)) +} + +// a hint that is not a complex type is no longer adopted, so these reach the ordinary assignment +// rule instead of the backend. `any` and a union both used to panic the compiler +@(test) +complex_into_any_and_union :: proc(t: ^testing.T) { + U :: union { complex128 } + + a, b: f64 = 3, 4 + + v: any = complex(a, b) + if c, ok := v.(complex128); testing.expect(t, ok, "any did not hold a complex128") { + testing.expect_value(t, real(c), f64(3)) + } + + u: U = complex(a, b) + if c, ok := u.(complex128); testing.expect(t, ok, "union did not hold a complex128") { + testing.expect_value(t, imag(c), f64(4)) + } +}