authorgravatar for mlugg@mlugg.co.ukMatthew Lugg <mlugg@mlugg.co.uk> 2026-07-21 11:02:29+01:00
committergravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2026-08-04 22:29:50+02:00
log5f74e4f3f8b909835ef794253a98a77868b3880e
tree302677d0bc78a562cff04f6a8fa8f7418f57b6cd
parent8cb4459f36704a3660dcbce30e80f6e01a66b39e

Sema: make loading uninstantiable types a runtime safety panic

For generic code, it is far more useful to consider this equivalent to `unreachable`. Meanwhile, it is difficult to accidentally write concrete code which performs this operation, so very little is actually lost from not having a compile error. If we accept that this operation does not trigger a compile error, then it already has the potential to invoke Illegal Behavior today, because uninstantiable types have an unspecified size in memory, so the dereference always potentially exceeds the pointer's provenance. (That said, this only means it is *possible* for the operation to invoke IB, so the langspec should nonetheless explicitly specify that dereferencing a pointer to an uninstantiable type is itself Illegal Behavior.) Storing uninstantiable types into memory does not require any specific language rules, because that operation can never be reached anyway due to it requiring an operand (the RHS of `a = b`) whose type is the store type, which is (by definition) impossible for uninstantiable types. Resolves: https://codeberg.org/ziglang/zig/issues/36247

11 files changed, 169 insertions(+), 33 deletions(-)

lib/std/debug.zig+4
......@@ -207,6 +207,10 @@ pub fn FullPanic(comptime panicFn: fn ([]const u8, ?usize) noreturn) type {
207207 @branchHint(.cold);
208208 call("'noreturn' function returned", @returnAddress());
209209 }
210 pub fn loadUninstantiableType() noreturn {
211 @branchHint(.cold);
212 call("attempt to load uninstantiable type", @returnAddress());
213 }
210214 };
211215}
212216
lib/std/debug/no_panic.zig+5
......@@ -134,3 +134,8 @@ pub fn noreturnReturned() noreturn {
134134 @branchHint(.cold);
135135 @trap();
136136}
137
138pub fn loadUninstantiableType() noreturn {
139 @branchHint(.cold);
140 @trap();
141}
lib/std/debug/simple_panic.zig+4
......@@ -126,3 +126,7 @@ pub fn memcpyAlias() noreturn {
126126pub fn noreturnReturned() noreturn {
127127 call("'noreturn' function returned", null);
128128}
129
130pub fn loadUninstantiableType() noreturn {
131 call("attempt to load uninstantiable type", null);
132}
src/Sema.zig+61-2
......@@ -31040,7 +31040,18 @@ fn analyzeLoad(
3104031040 const comptime_only = switch (elem_ty.classify(zcu)) {
3104131041 .no_possible_value => switch (elem_ty.zigTypeTag(zcu)) {
3104231042 .@"opaque" => return sema.fail(block, src, "cannot load opaque type '{f}'", .{elem_ty.fmt(pt)}),
31043 else => return sema.fail(block, src, "cannot load uninstantiable type '{f}'", .{elem_ty.fmt(pt)}),
31043 else => {
31044 // Loading an uninstantiable type always invokes Illegal Behavior.
31045 if (block.isComptime()) {
31046 return sema.fail(block, src, "cannot load uninstantiable type '{f}'", .{elem_ty.fmt(pt)});
31047 } else if (block.wantSafety()) {
31048 try sema.safetyPanic(block, src, .load_uninstantiable_type);
31049 return .unreachable_value;
31050 } else {
31051 _ = try block.addNoOp(.unreach);
31052 return .unreachable_value;
31053 }
31054 },
3104431055 },
3104531056 .one_possible_value => return .fromValue((try elem_ty.onePossibleValue(pt)).?),
3104631057 .runtime => false,
......@@ -35059,12 +35070,60 @@ fn getExpectedBuiltinFnType(sema: *Sema, decl: Zcu.StdLangDecl) CompileError!Typ
3505935070 .@"panic.copyLenMismatch",
3506035071 .@"panic.memcpyAlias",
3506135072 .@"panic.noreturnReturned",
35073 .@"panic.loadUninstantiableType",
3506235074 => try pt.funcType(.{
3506335075 .param_types = &.{},
3506435076 .return_type = .noreturn_type,
3506535077 }),
3506635078
35067 else => unreachable,
35079 .StackTrace,
35080 .CallingConvention,
35081 .SourceLocation,
35082 .Signedness,
35083 .AddressSpace,
35084 .VaList,
35085 .CallModifier,
35086 .AtomicOrder,
35087 .AtomicRmwOp,
35088 .ReduceOp,
35089 .FloatMode,
35090 .PrefetchOptions,
35091 .ExportOptions,
35092 .ExternOptions,
35093 .BranchHint,
35094 .assembly,
35095 .@"assembly.Clobbers",
35096 .Type,
35097 .@"Type.Fn",
35098 .@"Type.Fn.ParamAttributes",
35099 .@"Type.Fn.Attributes",
35100 .@"Type.Int",
35101 .@"Type.Float",
35102 .@"Type.Pointer",
35103 .@"Type.Pointer.Size",
35104 .@"Type.Pointer.Attributes",
35105 .@"Type.Array",
35106 .@"Type.Vector",
35107 .@"Type.Optional",
35108 .@"Type.ErrorUnion",
35109 .@"Type.ErrorSet",
35110 .@"Type.Enum",
35111 .@"Type.Enum.Mode",
35112 .@"Type.Union",
35113 .@"Type.Union.FieldAttributes",
35114 .@"Type.Struct",
35115 .@"Type.Struct.FieldAttributes",
35116 .@"Type.ContainerLayout",
35117 .@"Type.Opaque",
35118 .@"Type.Spirv",
35119 .@"Type.Spirv.Image",
35120 .@"Type.Spirv.Image.Usage",
35121 .@"Type.Spirv.Image.Format",
35122 .@"Type.Spirv.Image.Dimensionality",
35123 .@"Type.Spirv.Image.Depth",
35124 .@"Type.Spirv.Image.Access",
35125 .panic,
35126 => unreachable, // not a function (`decl.kind() != .func`)
3506835127 };
3506935128}
3507035129
src/Zcu.zig+4
......@@ -517,6 +517,7 @@ pub const StdLangDecl = enum {
517517 @"panic.copyLenMismatch",
518518 @"panic.memcpyAlias",
519519 @"panic.noreturnReturned",
520 @"panic.loadUninstantiableType",
520521
521522 VaList,
522523
......@@ -606,6 +607,7 @@ pub const StdLangDecl = enum {
606607 .@"panic.copyLenMismatch",
607608 .@"panic.memcpyAlias",
608609 .@"panic.noreturnReturned",
610 .@"panic.loadUninstantiableType",
609611 => .func,
610612 };
611613 }
......@@ -679,6 +681,7 @@ pub const SimplePanicId = enum {
679681 copy_len_mismatch,
680682 memcpy_alias,
681683 noreturn_returned,
684 load_uninstantiable_type,
682685
683686 pub fn toStdLangDecl(id: SimplePanicId) StdLangDecl {
684687 return switch (id) {
......@@ -702,6 +705,7 @@ pub const SimplePanicId = enum {
702705 .copy_len_mismatch => .@"panic.copyLenMismatch",
703706 .memcpy_alias => .@"panic.memcpyAlias",
704707 .noreturn_returned => .@"panic.noreturnReturned",
708 .load_uninstantiable_type => .@"panic.loadUninstantiableType",
705709 // zig fmt: on
706710 };
707711 }
test/cases/compile_errors/initialize_empty_union.zig-31
......@@ -28,25 +28,6 @@ export fn init5() void {
2828 _ = @as(U5, undefined);
2929}
3030
31export fn deref0(ptr: *const U0) void {
32 _ = ptr.*;
33}
34export fn deref1(ptr: *const U1) void {
35 _ = ptr.*;
36}
37export fn deref2(ptr: *const U2) void {
38 _ = ptr.*;
39}
40export fn deref3(ptr: *const U3) void {
41 _ = ptr.*;
42}
43export fn deref4(ptr: *const U4) void {
44 _ = ptr.*;
45}
46export fn deref5(ptr: *const U5) void {
47 _ = ptr.*;
48}
49
5031// error
5132//
5233// :13:17: error: expected type 'tmp.U0', found '@TypeOf(undefined)'
......@@ -67,15 +48,3 @@ export fn deref5(ptr: *const U5) void {
6748// :28:17: error: expected type 'tmp.U5', found '@TypeOf(undefined)'
6849// :28:17: note: cannot coerce to uninstantiable type 'tmp.U5'
6950// :10:12: note: union declared here
70// :32:12: error: cannot load uninstantiable type 'tmp.U0'
71// :5:12: note: union declared here
72// :35:12: error: cannot load uninstantiable type 'tmp.U1'
73// :6:12: note: union declared here
74// :38:12: error: cannot load uninstantiable type 'tmp.U2'
75// :7:12: note: union declared here
76// :41:12: error: cannot load uninstantiable type 'tmp.U3'
77// :8:12: note: union declared here
78// :44:12: error: cannot load uninstantiable type 'tmp.U4'
79// :9:12: note: union declared here
80// :47:12: error: cannot load uninstantiable type 'tmp.U5'
81// :10:12: note: union declared here
test/cases/safety/load_uninstantiable_enum.zig created+20
......@@ -0,0 +1,20 @@
1const std = @import("std");
2
3pub fn panic(message: []const u8, stack_trace: ?*std.builtin.StackTrace, _: ?usize) noreturn {
4 _ = stack_trace;
5 if (std.mem.eql(u8, message, "attempt to load uninstantiable type")) {
6 std.process.exit(0);
7 }
8 std.process.exit(1);
9}
10
11const E = enum {};
12pub fn main() error{TestFailed}!void {
13 const bytes: [32]u8 = @splat(0);
14 const ptr: *const E = @ptrCast(&bytes);
15 _ = ptr.*;
16 return error.TestFailed;
17}
18// run
19// backend=selfhosted,llvm
20// target=x86_64-linux,aarch64-linux,wasm32-wasi
test/cases/safety/load_uninstantiable_enum_from_slice.zig created+21
......@@ -0,0 +1,21 @@
1const std = @import("std");
2
3pub fn panic(message: []const u8, stack_trace: ?*std.builtin.StackTrace, _: ?usize) noreturn {
4 _ = stack_trace;
5 if (std.mem.eql(u8, message, "attempt to load uninstantiable type")) {
6 std.process.exit(0);
7 }
8 std.process.exit(1);
9}
10
11const E = enum {};
12pub fn main() error{TestFailed}!void {
13 const bytes: [32]u8 = @splat(0);
14 const ptr: *const [1]E = @ptrCast(&bytes);
15 const slice: []const E = ptr;
16 _ = slice[0];
17 return error.TestFailed;
18}
19// run
20// backend=selfhosted,llvm
21// target=x86_64-linux,aarch64-linux,wasm32-wasi
test/cases/safety/load_uninstantiable_union.zig created+23
......@@ -0,0 +1,23 @@
1const std = @import("std");
2
3pub fn panic(message: []const u8, stack_trace: ?*std.builtin.StackTrace, _: ?usize) noreturn {
4 _ = stack_trace;
5 if (std.mem.eql(u8, message, "attempt to load uninstantiable type")) {
6 std.process.exit(0);
7 }
8 std.process.exit(1);
9}
10
11const U = union {
12 foo: struct { a: u8, b: noreturn, },
13 bar: enum {},
14};
15pub fn main() error{TestFailed}!void {
16 const bytes: [32]u8 = @splat(0);
17 const ptr: *const U = @ptrCast(&bytes);
18 _ = ptr.*;
19 return error.TestFailed;
20}
21// run
22// backend=selfhosted,llvm
23// target=x86_64-linux,aarch64-linux,wasm32-wasi
test/cases/safety/load_uninstantiable_union_from_slice.zig created+24
......@@ -0,0 +1,24 @@
1const std = @import("std");
2
3pub fn panic(message: []const u8, stack_trace: ?*std.builtin.StackTrace, _: ?usize) noreturn {
4 _ = stack_trace;
5 if (std.mem.eql(u8, message, "attempt to load uninstantiable type")) {
6 std.process.exit(0);
7 }
8 std.process.exit(1);
9}
10
11const U = union {
12 foo: struct { a: u8, b: noreturn, },
13 bar: enum {},
14};
15pub fn main() error{TestFailed}!void {
16 const bytes: [32]u8 = @splat(0);
17 const ptr: *const [1]U = @ptrCast(&bytes);
18 const slice: []const U = ptr;
19 _ = slice[0];
20 return error.TestFailed;
21}
22// run
23// backend=selfhosted,llvm
24// target=x86_64-linux,aarch64-linux,wasm32-wasi
test/incremental/change_panic_handler_explicit+3
......@@ -36,6 +36,7 @@ pub const panic = struct {
3636 pub const copyLenMismatch = no_panic.copyLenMismatch;
3737 pub const memcpyAlias = no_panic.memcpyAlias;
3838 pub const noreturnReturned = no_panic.noreturnReturned;
39 pub const loadUninstantiableType = no_panic.loadUninstantiableType;
3940};
4041fn myPanic(msg: []const u8, _: ?usize) noreturn {
4142 var stdout_writer = std.Io.File.stdout().writerStreaming(io, &.{});
......@@ -84,6 +85,7 @@ pub const panic = struct {
8485 pub const copyLenMismatch = no_panic.copyLenMismatch;
8586 pub const memcpyAlias = no_panic.memcpyAlias;
8687 pub const noreturnReturned = no_panic.noreturnReturned;
88 pub const loadUninstantiableType = no_panic.loadUninstantiableType;
8789};
8890fn myPanic(msg: []const u8, _: ?usize) noreturn {
8991 var stdout_writer = std.Io.File.stdout().writerStreaming(io, &.{});
......@@ -132,6 +134,7 @@ pub const panic = struct {
132134 pub const copyLenMismatch = no_panic.copyLenMismatch;
133135 pub const memcpyAlias = no_panic.memcpyAlias;
134136 pub const noreturnReturned = no_panic.noreturnReturned;
137 pub const loadUninstantiableType = no_panic.loadUninstantiableType;
135138};
136139fn myPanicNew(msg: []const u8, _: ?usize) noreturn {
137140 var stdout_writer = std.Io.File.stdout().writerStreaming(io, &.{});