authorgravatar for mlugg@mlugg.co.ukMatthew Lugg <mlugg@mlugg.co.uk> 2026-09-26 11:30:30+01:00
committergravatar for mlugg@mlugg.co.ukMatthew Lugg <mlugg@mlugg.co.uk> 2026-09-26 19:55:52+02:00
log9a810ff16e1969b7be4a3e0799741fda0077b2f7
treea871143f49499767c366849f9715914fb5281d02
parent1b7a78122ecf9d5f6d36b46c26bd5e3fb8211eea

Sema: coerce value loaded from pointer at comptime

And add an assertion to `Sema.pointerDerefExtra` to hopefully catch this kind of bug a little more easily next time: this bug actually snuck past our existing assertions in many cases! Resolves: https://codeberg.org/ziglang/zig/issues/36452

3 files changed, 29 insertions(+), 5 deletions(-)

src/Sema.zig+8-2
......@@ -34258,9 +34258,15 @@ const DerefResult = union(enum) {
3425834258
3425934259fn pointerDerefExtra(sema: *Sema, block: *Block, src: LazySrcLoc, ptr_val: Value) CompileError!DerefResult {
3426034260 const pt = sema.pt;
34261 const ip = &pt.zcu.intern_pool;
34261 const zcu = pt.zcu;
34262 const ip = &zcu.intern_pool;
3426234263 switch (try sema.loadComptimePtr(block, src, ptr_val)) {
34263 .success => |mv| return .{ .val = try mv.intern(pt, sema.arena) },
34264 .success => |mv| {
34265 const loaded_val = try mv.intern(pt, sema.arena);
34266 const elem_ty_ip = ptr_val.typeOf(zcu).ptrInfo(zcu).child;
34267 assert(loaded_val.typeOf(zcu).toIntern() == elem_ty_ip);
34268 return .{ .val = loaded_val };
34269 },
3426434270 .runtime_load => return .runtime_load,
3426534271 .undef => return sema.failWithUseOfUndef(block, src, null),
3426634272 .err_payload => |err_name| return sema.fail(block, src, "attempt to unwrap error: {f}", .{err_name.fmt(ip)}),
src/Sema/comptime_ptr_access.zig+9-2
......@@ -377,8 +377,15 @@ fn loadComptimePtrInner(
377377 src,
378378 null,
379379 )) {
380 // We already have a value which is IMC to the desired type.
381 return .{ .success = base_val };
380 // We already have a value which is IMC to the desired type. If the type matches
381 // exactly we can just return `base_val`, otherwise we need to do the coercion.
382 if (base_val.typeOf(zcu).toIntern() == load_ty.toIntern()) {
383 return .{ .success = base_val };
384 } else {
385 const interned_val = try base_val.intern(pt, sema.arena);
386 const coerced_val = try pt.getCoerced(interned_val, load_ty);
387 return .{ .success = .{ .interned = coerced_val.toIntern() } };
388 }
382389 }
383390 }
384391 }
test/behavior/comptime_memory.zig+12-1
......@@ -1,7 +1,8 @@
11const std = @import("std");
22const builtin = @import("builtin");
33const endian = builtin.cpu.arch.endian();
4const testing = @import("std").testing;
4const testing = std.testing;
5const assert = std.debug.assert;
56const ptr_size = @sizeOf(usize);
67
78test "type pun signed and unsigned as single pointer" {
......@@ -605,3 +606,13 @@ test "reinterpret sentinel-terminated array as packed struct" {
605606 },
606607 }
607608}
609
610test "reinterpret pointer as optional pointer via double-pointer coercion" {
611 const p0: *const *anyopaque = &@ptrFromInt(0x1000);
612 const p1: *const ?*anyopaque = p0;
613
614 const loaded = p1.*;
615
616 comptime assert(@TypeOf(loaded) == ?*anyopaque);
617 comptime assert(@intFromPtr(loaded) == 0x1000);
618}