authorgravatar for jay@jayschwa.netJay Petacat <jay@jayschwa.net> 2026-01-22 00:16:14-07:00
committergravatar for jay@jayschwa.netJay Petacat <jay@jayschwa.net> 2026-03-11 11:31:58-06:00
logfcf64761d03ec339ab687f9dfcba2a6506804360
tree5a26af415f203860b604c9eeae2df3caac4248a9
parent89c98e200166aa3e4f5b41859f450d1307216191
signaturebadge-check Signed by SSH key SHA256:gFsluxqxLX9Aks7x0PXTsmNb7kH6i2dF38wwPoX4sZ4

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.

4 files changed, 151 insertions(+), 9 deletions(-)

doc/langref/test_peer_type_resolution.zig+11
......@@ -10,6 +10,17 @@ test "peer resolve int widening" {
1010 try expectEqual(i16, @TypeOf(c));
1111}
1212
13test "peer resolve small int and float" {
14 // This only works for integer types that can coerce to the float type.
15 // Larger integer types will cause a compiler error; no float widening occurs.
16 var i: u8 = 12;
17 var f: f32 = 34;
18 _ = .{ &i, &f };
19 const x = i + f;
20 try expectEqual(x, 46.0);
21 try expectEqual(@TypeOf(x), f32);
22}
23
1324test "peer resolve arrays of different size to const slice" {
1425 try expectEqualStrings("true", boolToStr(true));
1526 try expectEqualStrings("false", boolToStr(false));
src/Sema.zig+24-9
......@@ -32729,16 +32729,10 @@ fn resolvePeerTypesInner(
3272932729 .fixed_float => {
3273032730 var opt_cur_ty: ?Type = null;
3273132731
32732 for (peer_tys, peer_vals, 0..) |opt_ty, opt_val, i| {
32732 for (peer_tys, 0..) |opt_ty, i| {
3273332733 const ty = opt_ty orelse continue;
3273432734 switch (ty.zigTypeTag(zcu)) {
32735 .comptime_float, .comptime_int => {},
32736 .int => {
32737 if (opt_val == null) return .{ .conflict = .{
32738 .peer_idx_a = strat_reason,
32739 .peer_idx_b = i,
32740 } };
32741 },
32735 .comptime_float, .comptime_int, .int => {},
3274232736 .float => {
3274332737 if (opt_cur_ty) |cur_ty| {
3274432738 if (cur_ty.eql(ty, zcu)) continue;
......@@ -32765,7 +32759,28 @@ fn resolvePeerTypesInner(
3276532759
3276632760 // Note that fixed_float is only chosen if there is at least one fixed-width float peer,
3276732761 // so opt_cur_ty must be non-null.
32768 return .{ .success = opt_cur_ty.? };
32762 const cur_ty = opt_cur_ty.?;
32763
32764 // Ensure that any integer peers can coerce safely to the resulting float.
32765 for (peer_tys, peer_vals, 0..) |opt_ty, opt_val, i| {
32766 const ty = opt_ty orelse continue;
32767 switch (ty.zigTypeTag(zcu)) {
32768 .comptime_float, .comptime_int, .float => {},
32769 .int => {
32770 if (opt_val != null) continue;
32771 const int_info = ty.intInfo(zcu);
32772 const int_precision = int_info.bits - @intFromBool(int_info.signedness == .signed);
32773 if (int_precision > cur_ty.floatSignificandBits(target))
32774 return .{ .conflict = .{
32775 .peer_idx_a = strat_reason,
32776 .peer_idx_b = i,
32777 } };
32778 },
32779 else => unreachable, // Previous pass returned on this branch.
32780 }
32781 }
32782
32783 return .{ .success = cur_ty };
3276932784 },
3277032785
3277132786 .tuple => {
test/behavior/cast.zig+41
......@@ -1927,6 +1927,47 @@ test "peer type resolution: float and comptime-known fixed-width integer" {
19271927 try expectEqual(@as(T, 1.234), r2);
19281928}
19291929
1930test "peer type resolution: float and runtime-known fixed-width integer" {
1931 if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO
1932 if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO
1933
1934 const S = struct {
1935 fn testPeerType(Float: type, Int: type) !void {
1936 var i: Int = 100;
1937 _ = &i;
1938 var f: Float = 1.234;
1939 _ = &f;
1940 comptime assert(@TypeOf(i, f) == Float);
1941 comptime assert(@TypeOf(f, i) == Float);
1942
1943 var t = true;
1944 _ = &t;
1945 const r1 = if (t) i else f;
1946 const r2 = if (t) f else i;
1947
1948 try expectEqual(@as(Float, 100.0), r1);
1949 try expectEqual(@as(Float, 1.234), r2);
1950 }
1951 };
1952
1953 try S.testPeerType(f16, u11);
1954 try S.testPeerType(f16, i12);
1955
1956 try S.testPeerType(f32, u24);
1957 try S.testPeerType(f32, i25);
1958
1959 try S.testPeerType(f64, u53);
1960 try S.testPeerType(f64, i54);
1961
1962 try S.testPeerType(f80, u64);
1963 try S.testPeerType(f80, i65);
1964
1965 try S.testPeerType(f128, u113);
1966 try S.testPeerType(f128, i114);
1967
1968 try S.testPeerType(c_longdouble, u8); // Smoke test - size varies by target.
1969}
1970
19301971test "peer type resolution: same array type with sentinel" {
19311972 if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest;
19321973 if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO
test/cases/compile_errors/add_large_int_and_float.zig created+75
......@@ -0,0 +1,75 @@
1// Test that peer type resolution fails for integer types that cannot safely coerce to a float.
2
3fn testAdd(Float: type, Int: type) void {
4 var i: Int = 0;
5 _ = &i;
6 var f: Float = 0;
7 _ = &f;
8 _ = i + f;
9 _ = f + i;
10}
11
12export fn entry() void {
13 testAdd(f16, u11); // Okay
14 testAdd(f16, u12); // Too big
15
16 testAdd(f16, i12);
17 testAdd(f16, i13);
18
19 testAdd(f32, u24);
20 testAdd(f32, u25);
21
22 testAdd(f32, i25);
23 testAdd(f32, i26);
24
25 testAdd(f64, u53);
26 testAdd(f64, u54);
27
28 testAdd(f64, i54);
29 testAdd(f64, i55);
30
31 testAdd(f80, u64);
32 testAdd(f80, u65);
33
34 testAdd(f80, i65);
35 testAdd(f80, i66);
36
37 testAdd(f128, u113);
38 testAdd(f128, u114);
39
40 testAdd(f128, i114);
41 testAdd(f128, i115);
42}
43
44// error
45//
46// :8:11: error: incompatible types: 'i115' and 'f128'
47// :8:9: note: type 'i115' here
48// :8:13: note: type 'f128' here
49// :8:11: error: incompatible types: 'i13' and 'f16'
50// :8:9: note: type 'i13' here
51// :8:13: note: type 'f16' here
52// :8:11: error: incompatible types: 'i26' and 'f32'
53// :8:9: note: type 'i26' here
54// :8:13: note: type 'f32' here
55// :8:11: error: incompatible types: 'i55' and 'f64'
56// :8:9: note: type 'i55' here
57// :8:13: note: type 'f64' here
58// :8:11: error: incompatible types: 'i66' and 'f80'
59// :8:9: note: type 'i66' here
60// :8:13: note: type 'f80' here
61// :8:11: error: incompatible types: 'u114' and 'f128'
62// :8:9: note: type 'u114' here
63// :8:13: note: type 'f128' here
64// :8:11: error: incompatible types: 'u12' and 'f16'
65// :8:9: note: type 'u12' here
66// :8:13: note: type 'f16' here
67// :8:11: error: incompatible types: 'u25' and 'f32'
68// :8:9: note: type 'u25' here
69// :8:13: note: type 'f32' here
70// :8:11: error: incompatible types: 'u54' and 'f64'
71// :8:9: note: type 'u54' here
72// :8:13: note: type 'f64' here
73// :8:11: error: incompatible types: 'u65' and 'f80'
74// :8:9: note: type 'u65' here
75// :8:13: note: type 'f80' here