authorgravatar for justusk@noreply.codeberg.orgJustus Klausecker <justusk@noreply.codeberg.org> 2026-06-16 11:51:54+02:00
committergravatar for justusk@noreply.codeberg.orgJustus Klausecker <justusk@noreply.codeberg.org> 2026-06-16 11:51:54+02:00
log0bcaad394ddbc629ab0b38aebbe90545ea3e9bab
tree41377afcda2a66cf9b7b0cdf5c0e984085c1547e
parenta3ae499dc29747630f2801aab9bbc9661a4a0169
parentdb0d5b4c126388ab9d48435d52cbed35da811c83

Merge pull request 'Sema: make struct fields referencing comptime vars not comptime' (#31462) from rmehri01/zig:struct_comptime_var_ref into master

Reviewed-on: https://codeberg.org/ziglang/zig/pulls/31462 Reviewed-by: Justus Klausecker <justusk@noreply.codeberg.org>

3 files changed, 43 insertions(+), 6 deletions(-)

src/Sema.zig+25-6
...@@ -18777,6 +18777,18 @@ fn structInitAnon(...@@ -18777,6 +18777,18 @@ fn structInitAnon(
18777 break :rs runtime_index;18777 break :rs runtime_index;
18778 };18778 };
1877918779
18780 // A field can't be `comptime` if it references a `comptime var` but the aggregate can still be comptime-known.
18781 // Replace these fields with `.none` only for generating the type.
18782 const values_no_comptime = if (!any_values) values else blk: {
18783 const new_values = try sema.arena.alloc(InternPool.Index, types.len);
18784 for (values, new_values) |val, *new_val| {
18785 if (val != .none and Value.fromInterned(val).canMutateComptimeVarState(zcu)) {
18786 new_val.* = .none;
18787 } else new_val.* = val;
18788 }
18789 break :blk new_values;
18790 };
18791
18780 // We treat anonymous struct types as reified types, because there are similarities: they have18792 // We treat anonymous struct types as reified types, because there are similarities: they have
18781 // no captures, and instead use a form of structural equivalence which we can easy represent by18793 // no captures, and instead use a form of structural equivalence which we can easy represent by
18782 // hashing the field names/types/values. They also perform layout resolution immediately. These18794 // hashing the field names/types/values. They also perform layout resolution immediately. These
...@@ -18785,7 +18797,7 @@ fn structInitAnon(...@@ -18785,7 +18797,7 @@ fn structInitAnon(
18785 const type_hash: u64 = hash: {18797 const type_hash: u64 = hash: {
18786 var hasher = std.hash.Wyhash.init(0);18798 var hasher = std.hash.Wyhash.init(0);
18787 hasher.update(std.mem.sliceAsBytes(types));18799 hasher.update(std.mem.sliceAsBytes(types));
18788 hasher.update(std.mem.sliceAsBytes(values));18800 hasher.update(std.mem.sliceAsBytes(values_no_comptime));
18789 hasher.update(std.mem.sliceAsBytes(names));18801 hasher.update(std.mem.sliceAsBytes(names));
18790 break :hash hasher.final();18802 break :hash hasher.final();
18791 };18803 };
...@@ -18809,9 +18821,9 @@ fn structInitAnon(...@@ -18809,9 +18821,9 @@ fn structInitAnon(
18809 @memcpy(wip.field_names.get(ip), names);18821 @memcpy(wip.field_names.get(ip), names);
18810 @memcpy(wip.field_types.get(ip), types);18822 @memcpy(wip.field_types.get(ip), types);
18811 if (any_values) {18823 if (any_values) {
18812 @memcpy(wip.field_values.get(ip), values);18824 @memcpy(wip.field_values.get(ip), values_no_comptime);
18813 @memset(wip.field_is_comptime_bits.getAll(ip), 0);18825 @memset(wip.field_is_comptime_bits.getAll(ip), 0);
18814 for (values, 0..) |val, field_index| {18826 for (values_no_comptime, 0..) |val, field_index| {
18815 if (val == .none) continue;18827 if (val == .none) continue;
18816 const bit_bag_index = field_index / 32;18828 const bit_bag_index = field_index / 32;
18817 const mask = @as(u32, 1) << @intCast(field_index % 32);18829 const mask = @as(u32, 1) << @intCast(field_index % 32);
...@@ -18839,6 +18851,15 @@ fn structInitAnon(...@@ -18839,6 +18851,15 @@ fn structInitAnon(
18839 return sema.addConstantMaybeRef(struct_val, is_ref);18851 return sema.addConstantMaybeRef(struct_val, is_ref);
18840 };18852 };
1884118853
18854 for (values, 0..) |field_val, i| {
18855 if (field_val == .none) continue; // runtime-known
18856 const field_src = block.src(.{ .init_elem = .{
18857 .init_node_offset = src.offset.node_offset.x,
18858 .elem_index = @intCast(i),
18859 } });
18860 try sema.validateRuntimeValue(block, field_src, .fromIntern(field_val));
18861 }
18862
18842 if (is_ref) {18863 if (is_ref) {
18843 const target = zcu.getTarget();18864 const target = zcu.getTarget();
18844 const alloc_ty = try pt.ptrType(.{18865 const alloc_ty = try pt.ptrType(.{
...@@ -19095,13 +19116,11 @@ fn arrayInitAnon(...@@ -19095,13 +19116,11 @@ fn arrayInitAnon(
19095 .values = values_no_comptime,19116 .values = values_no_comptime,
19096 }));19117 }));
1909719118
19098 const runtime_src = opt_runtime_src orelse {19119 _ = opt_runtime_src orelse {
19099 const tuple_val = try pt.aggregateValue(tuple_ty, values);19120 const tuple_val = try pt.aggregateValue(tuple_ty, values);
19100 return sema.addConstantMaybeRef(tuple_val, is_ref);19121 return sema.addConstantMaybeRef(tuple_val, is_ref);
19101 };19122 };
1910219123
19103 try sema.requireRuntimeBlock(block, src, runtime_src);
19104
19105 for (operands, 0..) |operand, i| {19124 for (operands, 0..) |operand, i| {
19106 const operand_src = block.src(.{ .init_elem = .{19125 const operand_src = block.src(.{ .init_elem = .{
19107 .init_node_offset = src.offset.node_offset.x,19126 .init_node_offset = src.offset.node_offset.x,
test/behavior/struct.zig+9
...@@ -2326,3 +2326,12 @@ test "pointer to runtime field of struct containing struct containing comptime-o...@@ -2326,3 +2326,12 @@ test "pointer to runtime field of struct containing struct containing comptime-o
2326 ptr = &foo.number;2326 ptr = &foo.number;
2327 try expect(ptr.* == 123);2327 try expect(ptr.* == 123);
2328}2328}
2329
2330test "struct field referencing comptime var isn't comptime" {
2331 comptime var v: u8 = 0;
2332 const s = .{ .v = &v };
2333 // field isn't comptime but struct is still comptime-known
2334 comptime assert(!@typeInfo(@TypeOf(s)).@"struct".field_attrs[0].@"comptime");
2335 v = 1;
2336 comptime assert(s.v.* == 1);
2337}
test/cases/compile_errors/comptime_var_referenced_at_runtime.zig+9
...@@ -71,6 +71,12 @@ export fn bax() void {...@@ -71,6 +71,12 @@ export fn bax() void {
71 @memmove(&rt, &x);71 @memmove(&rt, &x);
72}72}
7373
74export fn qoo(i: u8) void {
75 comptime var x: u32 = 123;
76 const y = .{ .p = &x, .i = i };
77 _ = y;
78}
79
74// error80// error
75//81//
76// :5:19: error: runtime value contains reference to comptime var82// :5:19: error: runtime value contains reference to comptime var
...@@ -103,3 +109,6 @@ export fn bax() void {...@@ -103,3 +109,6 @@ export fn bax() void {
103// :71:19: error: runtime value contains reference to comptime var109// :71:19: error: runtime value contains reference to comptime var
104// :71:19: note: comptime var pointers are not available at runtime110// :71:19: note: comptime var pointers are not available at runtime
105// :67:14: note: 'runtime_value' points to comptime var declared here111// :67:14: note: 'runtime_value' points to comptime var declared here
112// :76:19: error: runtime value contains reference to comptime var
113// :76:19: note: comptime var pointers are not available at runtime
114// :75:14: note: 'runtime_value' points to comptime var declared here