authorgravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2026-03-12 00:41:05+01:00
committergravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2026-03-12 00:41:05+01:00
log58890066d9caec3d421afe7465a2586668442982
treee454d6ebeca73f33486729da0efbd3e27cc3322b
parent109d01371e56f03183afcae1d2929b347cb4e701
parentfcf64761d03ec339ab687f9dfcba2a6506804360

Merge pull request 'Sema: Support peer type resolution for floats and small integers' (#30921) from jayschwa/zig:ptr-small-int-and-floats into master

Reviewed-on: https://codeberg.org/ziglang/zig/pulls/30921 Reviewed-by: Andrew Kelley <andrew@ziglang.org>

7 files changed, 219 insertions(+), 70 deletions(-)

doc/langref/test_peer_type_resolution.zig+11
...@@ -10,6 +10,17 @@ test "peer resolve int widening" {...@@ -10,6 +10,17 @@ test "peer resolve int widening" {
10 try expectEqual(i16, @TypeOf(c));10 try expectEqual(i16, @TypeOf(c));
11}11}
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
13test "peer resolve arrays of different size to const slice" {24test "peer resolve arrays of different size to const slice" {
14 try expectEqualStrings("true", boolToStr(true));25 try expectEqualStrings("true", boolToStr(true));
15 try expectEqualStrings("false", boolToStr(false));26 try expectEqualStrings("false", boolToStr(false));
src/Sema.zig+25-18
...@@ -27799,15 +27799,7 @@ fn coerceExtra(...@@ -27799,15 +27799,7 @@ fn coerceExtra(
27799 }27799 }
27800 const int_info = inst_ty.intInfo(zcu);27800 const int_info = inst_ty.intInfo(zcu);
27801 const int_precision = int_info.bits - @intFromBool(int_info.signedness == .signed);27801 const int_precision = int_info.bits - @intFromBool(int_info.signedness == .signed);
27802 const float_precision: u8 = switch (dest_ty.toIntern()) {27802 if (int_precision <= dest_ty.floatSignificandBits(target)) {
27803 .f16_type => 11,
27804 .f32_type => 24,
27805 .f64_type => 53,
27806 .f80_type => 64,
27807 .f128_type => 113,
27808 else => unreachable,
27809 };
27810 if (int_precision <= float_precision) {
27811 try sema.requireRuntimeBlock(block, inst_src, null);27803 try sema.requireRuntimeBlock(block, inst_src, null);
27812 return block.addTyOp(.float_from_int, dest_ty, inst);27804 return block.addTyOp(.float_from_int, dest_ty, inst);
27813 }27805 }
...@@ -32762,16 +32754,10 @@ fn resolvePeerTypesInner(...@@ -32762,16 +32754,10 @@ fn resolvePeerTypesInner(
32762 .fixed_float => {32754 .fixed_float => {
32763 var opt_cur_ty: ?Type = null;32755 var opt_cur_ty: ?Type = null;
3276432756
32765 for (peer_tys, peer_vals, 0..) |opt_ty, opt_val, i| {32757 for (peer_tys, 0..) |opt_ty, i| {
32766 const ty = opt_ty orelse continue;32758 const ty = opt_ty orelse continue;
32767 switch (ty.zigTypeTag(zcu)) {32759 switch (ty.zigTypeTag(zcu)) {
32768 .comptime_float, .comptime_int => {},32760 .comptime_float, .comptime_int, .int => {},
32769 .int => {
32770 if (opt_val == null) return .{ .conflict = .{
32771 .peer_idx_a = strat_reason,
32772 .peer_idx_b = i,
32773 } };
32774 },
32775 .float => {32761 .float => {
32776 if (opt_cur_ty) |cur_ty| {32762 if (opt_cur_ty) |cur_ty| {
32777 if (cur_ty.eql(ty, zcu)) continue;32763 if (cur_ty.eql(ty, zcu)) continue;
...@@ -32798,7 +32784,28 @@ fn resolvePeerTypesInner(...@@ -32798,7 +32784,28 @@ fn resolvePeerTypesInner(
3279832784
32799 // Note that fixed_float is only chosen if there is at least one fixed-width float peer,32785 // Note that fixed_float is only chosen if there is at least one fixed-width float peer,
32800 // so opt_cur_ty must be non-null.32786 // so opt_cur_ty must be non-null.
32801 return .{ .success = opt_cur_ty.? };32787 const cur_ty = opt_cur_ty.?;
32788
32789 // Ensure that any integer peers can coerce safely to the resulting float.
32790 for (peer_tys, peer_vals, 0..) |opt_ty, opt_val, i| {
32791 const ty = opt_ty orelse continue;
32792 switch (ty.zigTypeTag(zcu)) {
32793 .comptime_float, .comptime_int, .float => {},
32794 .int => {
32795 if (opt_val != null) continue;
32796 const int_info = ty.intInfo(zcu);
32797 const int_precision = int_info.bits - @intFromBool(int_info.signedness == .signed);
32798 if (int_precision > cur_ty.floatSignificandBits(target))
32799 return .{ .conflict = .{
32800 .peer_idx_a = strat_reason,
32801 .peer_idx_b = i,
32802 } };
32803 },
32804 else => unreachable, // Previous pass returned on this branch.
32805 }
32806 }
32807
32808 return .{ .success = cur_ty };
32802 },32809 },
3280332810
32804 .tuple => {32811 .tuple => {
src/Type.zig+12
...@@ -1935,6 +1935,18 @@ pub fn floatBits(ty: Type, target: *const Target) u16 {...@@ -1935,6 +1935,18 @@ pub fn floatBits(ty: Type, target: *const Target) u16 {
1935 };1935 };
1936}1936}
19371937
1938/// Asserts the type is a fixed-size float or comptime_float.
1939pub fn floatSignificandBits(ty: Type, target: *const Target) u16 {
1940 return switch (ty.floatBits(target)) {
1941 16 => 11,
1942 32 => 24,
1943 64 => 53,
1944 80 => 64,
1945 128 => 113,
1946 else => unreachable,
1947 };
1948}
1949
1938/// Asserts the type is a function or a function pointer.1950/// Asserts the type is a function or a function pointer.
1939pub fn fnReturnType(ty: Type, zcu: *const Zcu) Type {1951pub fn fnReturnType(ty: Type, zcu: *const Zcu) Type {
1940 return Type.fromInterned(zcu.intern_pool.funcTypeReturnType(ty.toIntern()));1952 return Type.fromInterned(zcu.intern_pool.funcTypeReturnType(ty.toIntern()));
test/behavior/cast.zig+44
...@@ -175,6 +175,7 @@ test "type coercion from int to float" {...@@ -175,6 +175,7 @@ test "type coercion from int to float" {
175 var int: Int = std.math.minInt(Int);175 var int: Int = std.math.minInt(Int);
176 while (int < std.math.maxInt(Int)) : (int += 1)176 while (int < std.math.maxInt(Int)) : (int += 1)
177 try value(Float, int);177 try value(Float, int);
178 try value(Float, int); // max
178 }179 }
179180
180 // Check that the min and max values of the integer type can safely be181 // Check that the min and max values of the integer type can safely be
...@@ -202,6 +203,8 @@ test "type coercion from int to float" {...@@ -202,6 +203,8 @@ test "type coercion from int to float" {
202 try check.edgeValues(f128, u113);203 try check.edgeValues(f128, u113);
203 try check.edgeValues(f128, i114);204 try check.edgeValues(f128, i114);
204205
206 try check.value(c_longdouble, @as(u1, 0)); // Smoke test - size varies by target.
207
205 if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest;208 if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest;
206 if (builtin.zig_backend == .stage2_wasm) return error.SkipZigTest;209 if (builtin.zig_backend == .stage2_wasm) return error.SkipZigTest;
207210
...@@ -1924,6 +1927,47 @@ test "peer type resolution: float and comptime-known fixed-width integer" {...@@ -1924,6 +1927,47 @@ test "peer type resolution: float and comptime-known fixed-width integer" {
1924 try expectEqual(@as(T, 1.234), r2);1927 try expectEqual(@as(T, 1.234), r2);
1925}1928}
19261929
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
1927test "peer type resolution: same array type with sentinel" {1971test "peer type resolution: same array type with sentinel" {
1928 if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest;1972 if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest;
1929 if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO1973 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
test/cases/compile_errors/coerce_int_to_float.zig deleted-52
...@@ -1,52 +0,0 @@
1// Test that integer types above a certain size will not coerce to a float.
2
3fn testCoerce(Float: type, Int: type) void {
4 var i: Int = 0;
5 _ = &i;
6 _ = @as(Float, i);
7}
8
9export fn entry() void {
10 testCoerce(f16, u11); // Okay
11 testCoerce(f16, u12); // Too big
12
13 testCoerce(f16, i12);
14 testCoerce(f16, i13);
15
16 testCoerce(f32, u24);
17 testCoerce(f32, u25);
18
19 testCoerce(f32, i25);
20 testCoerce(f32, i26);
21
22 testCoerce(f64, u53);
23 testCoerce(f64, u54);
24
25 testCoerce(f64, i54);
26 testCoerce(f64, i55);
27
28 testCoerce(f80, u64);
29 testCoerce(f80, u65);
30
31 testCoerce(f80, i65);
32 testCoerce(f80, i66);
33
34 testCoerce(f128, u113);
35 testCoerce(f128, u114);
36
37 testCoerce(f128, i114);
38 testCoerce(f128, i115);
39}
40
41// error
42//
43// :6:20: error: expected type 'f128', found 'i115'
44// :6:20: error: expected type 'f128', found 'u114'
45// :6:20: error: expected type 'f16', found 'i13'
46// :6:20: error: expected type 'f16', found 'u12'
47// :6:20: error: expected type 'f32', found 'i26'
48// :6:20: error: expected type 'f32', found 'u25'
49// :6:20: error: expected type 'f64', found 'i55'
50// :6:20: error: expected type 'f64', found 'u54'
51// :6:20: error: expected type 'f80', found 'i66'
52// :6:20: error: expected type 'f80', found 'u65'
test/cases/compile_errors/coerce_large_int_to_float.zig created+52
...@@ -0,0 +1,52 @@
1// Test that integer types above a certain size will not coerce to a float.
2
3fn testCoerce(Float: type, Int: type) void {
4 var i: Int = 0;
5 _ = &i;
6 _ = @as(Float, i);
7}
8
9export fn entry() void {
10 testCoerce(f16, u11); // Okay
11 testCoerce(f16, u12); // Too big
12
13 testCoerce(f16, i12);
14 testCoerce(f16, i13);
15
16 testCoerce(f32, u24);
17 testCoerce(f32, u25);
18
19 testCoerce(f32, i25);
20 testCoerce(f32, i26);
21
22 testCoerce(f64, u53);
23 testCoerce(f64, u54);
24
25 testCoerce(f64, i54);
26 testCoerce(f64, i55);
27
28 testCoerce(f80, u64);
29 testCoerce(f80, u65);
30
31 testCoerce(f80, i65);
32 testCoerce(f80, i66);
33
34 testCoerce(f128, u113);
35 testCoerce(f128, u114);
36
37 testCoerce(f128, i114);
38 testCoerce(f128, i115);
39}
40
41// error
42//
43// :6:20: error: expected type 'f128', found 'i115'
44// :6:20: error: expected type 'f128', found 'u114'
45// :6:20: error: expected type 'f16', found 'i13'
46// :6:20: error: expected type 'f16', found 'u12'
47// :6:20: error: expected type 'f32', found 'i26'
48// :6:20: error: expected type 'f32', found 'u25'
49// :6:20: error: expected type 'f64', found 'i55'
50// :6:20: error: expected type 'f64', found 'u54'
51// :6:20: error: expected type 'f80', found 'i66'
52// :6:20: error: expected type 'f80', found 'u65'