From fcf64761d03ec339ab687f9dfcba2a6506804360 Mon Sep 17 00:00:00 2001 From: Jay Petacat Date: Thu, 22 Jan 2026 00:16:14 -0700 Subject: [PATCH] Sema: Support peer type resolution for floats and small integers This builds on the changes in PR #30053 / commit 484cc15366. Previously, peer type resolution would always result in a conflict for fixed-width integer and float types. Now that small integer types can coerce to floats, peer type resolution can take that into account. This primarily benefits arithmetic with mixed float and integer operands. If the integer operand can coerce to the float operand's type, it will do so without requiring an explicit cast. If the integer type can't coerce, there will be a compiler error; no float widening will occur. Explicit casting will still be required to make it work. --- doc/langref/test_peer_type_resolution.zig | 11 +++ src/Sema.zig | 33 +++++--- test/behavior/cast.zig | 41 ++++++++++ .../add_large_int_and_float.zig | 75 +++++++++++++++++++ 4 files changed, 151 insertions(+), 9 deletions(-) create mode 100644 test/cases/compile_errors/add_large_int_and_float.zig diff --git a/doc/langref/test_peer_type_resolution.zig b/doc/langref/test_peer_type_resolution.zig index 9bbac881e3..de498fc5c8 100644 --- a/doc/langref/test_peer_type_resolution.zig +++ b/doc/langref/test_peer_type_resolution.zig @@ -10,6 +10,17 @@ test "peer resolve int widening" { try expectEqual(i16, @TypeOf(c)); } +test "peer resolve small int and float" { + // This only works for integer types that can coerce to the float type. + // Larger integer types will cause a compiler error; no float widening occurs. + var i: u8 = 12; + var f: f32 = 34; + _ = .{ &i, &f }; + const x = i + f; + try expectEqual(x, 46.0); + try expectEqual(@TypeOf(x), f32); +} + test "peer resolve arrays of different size to const slice" { try expectEqualStrings("true", boolToStr(true)); try expectEqualStrings("false", boolToStr(false)); diff --git a/src/Sema.zig b/src/Sema.zig index f9e343f549..ad4b99781e 100644 --- a/src/Sema.zig +++ b/src/Sema.zig @@ -32729,16 +32729,10 @@ fn resolvePeerTypesInner( .fixed_float => { var opt_cur_ty: ?Type = null; - for (peer_tys, peer_vals, 0..) |opt_ty, opt_val, i| { + for (peer_tys, 0..) |opt_ty, i| { const ty = opt_ty orelse continue; switch (ty.zigTypeTag(zcu)) { - .comptime_float, .comptime_int => {}, - .int => { - if (opt_val == null) return .{ .conflict = .{ - .peer_idx_a = strat_reason, - .peer_idx_b = i, - } }; - }, + .comptime_float, .comptime_int, .int => {}, .float => { if (opt_cur_ty) |cur_ty| { if (cur_ty.eql(ty, zcu)) continue; @@ -32765,7 +32759,28 @@ fn resolvePeerTypesInner( // Note that fixed_float is only chosen if there is at least one fixed-width float peer, // so opt_cur_ty must be non-null. - return .{ .success = opt_cur_ty.? }; + const cur_ty = opt_cur_ty.?; + + // Ensure that any integer peers can coerce safely to the resulting float. + for (peer_tys, peer_vals, 0..) |opt_ty, opt_val, i| { + const ty = opt_ty orelse continue; + switch (ty.zigTypeTag(zcu)) { + .comptime_float, .comptime_int, .float => {}, + .int => { + if (opt_val != null) continue; + const int_info = ty.intInfo(zcu); + const int_precision = int_info.bits - @intFromBool(int_info.signedness == .signed); + if (int_precision > cur_ty.floatSignificandBits(target)) + return .{ .conflict = .{ + .peer_idx_a = strat_reason, + .peer_idx_b = i, + } }; + }, + else => unreachable, // Previous pass returned on this branch. + } + } + + return .{ .success = cur_ty }; }, .tuple => { diff --git a/test/behavior/cast.zig b/test/behavior/cast.zig index eca43fea44..d3f6ea5f48 100644 --- a/test/behavior/cast.zig +++ b/test/behavior/cast.zig @@ -1927,6 +1927,47 @@ test "peer type resolution: float and comptime-known fixed-width integer" { try expectEqual(@as(T, 1.234), r2); } +test "peer type resolution: float and runtime-known fixed-width integer" { + if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO + if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO + + const S = struct { + fn testPeerType(Float: type, Int: type) !void { + var i: Int = 100; + _ = &i; + var f: Float = 1.234; + _ = &f; + comptime assert(@TypeOf(i, f) == Float); + comptime assert(@TypeOf(f, i) == Float); + + var t = true; + _ = &t; + const r1 = if (t) i else f; + const r2 = if (t) f else i; + + try expectEqual(@as(Float, 100.0), r1); + try expectEqual(@as(Float, 1.234), r2); + } + }; + + try S.testPeerType(f16, u11); + try S.testPeerType(f16, i12); + + try S.testPeerType(f32, u24); + try S.testPeerType(f32, i25); + + try S.testPeerType(f64, u53); + try S.testPeerType(f64, i54); + + try S.testPeerType(f80, u64); + try S.testPeerType(f80, i65); + + try S.testPeerType(f128, u113); + try S.testPeerType(f128, i114); + + try S.testPeerType(c_longdouble, u8); // Smoke test - size varies by target. +} + test "peer type resolution: same array type with sentinel" { if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO diff --git a/test/cases/compile_errors/add_large_int_and_float.zig b/test/cases/compile_errors/add_large_int_and_float.zig new file mode 100644 index 0000000000..1faed312b7 --- /dev/null +++ b/test/cases/compile_errors/add_large_int_and_float.zig @@ -0,0 +1,75 @@ +// Test that peer type resolution fails for integer types that cannot safely coerce to a float. + +fn testAdd(Float: type, Int: type) void { + var i: Int = 0; + _ = &i; + var f: Float = 0; + _ = &f; + _ = i + f; + _ = f + i; +} + +export fn entry() void { + testAdd(f16, u11); // Okay + testAdd(f16, u12); // Too big + + testAdd(f16, i12); + testAdd(f16, i13); + + testAdd(f32, u24); + testAdd(f32, u25); + + testAdd(f32, i25); + testAdd(f32, i26); + + testAdd(f64, u53); + testAdd(f64, u54); + + testAdd(f64, i54); + testAdd(f64, i55); + + testAdd(f80, u64); + testAdd(f80, u65); + + testAdd(f80, i65); + testAdd(f80, i66); + + testAdd(f128, u113); + testAdd(f128, u114); + + testAdd(f128, i114); + testAdd(f128, i115); +} + +// error +// +// :8:11: error: incompatible types: 'i115' and 'f128' +// :8:9: note: type 'i115' here +// :8:13: note: type 'f128' here +// :8:11: error: incompatible types: 'i13' and 'f16' +// :8:9: note: type 'i13' here +// :8:13: note: type 'f16' here +// :8:11: error: incompatible types: 'i26' and 'f32' +// :8:9: note: type 'i26' here +// :8:13: note: type 'f32' here +// :8:11: error: incompatible types: 'i55' and 'f64' +// :8:9: note: type 'i55' here +// :8:13: note: type 'f64' here +// :8:11: error: incompatible types: 'i66' and 'f80' +// :8:9: note: type 'i66' here +// :8:13: note: type 'f80' here +// :8:11: error: incompatible types: 'u114' and 'f128' +// :8:9: note: type 'u114' here +// :8:13: note: type 'f128' here +// :8:11: error: incompatible types: 'u12' and 'f16' +// :8:9: note: type 'u12' here +// :8:13: note: type 'f16' here +// :8:11: error: incompatible types: 'u25' and 'f32' +// :8:9: note: type 'u25' here +// :8:13: note: type 'f32' here +// :8:11: error: incompatible types: 'u54' and 'f64' +// :8:9: note: type 'u54' here +// :8:13: note: type 'f64' here +// :8:11: error: incompatible types: 'u65' and 'f80' +// :8:9: note: type 'u65' here +// :8:13: note: type 'f80' here