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 {...@@ -207,6 +207,10 @@ pub fn FullPanic(comptime panicFn: fn ([]const u8, ?usize) noreturn) type {
207 @branchHint(.cold);207 @branchHint(.cold);
208 call("'noreturn' function returned", @returnAddress());208 call("'noreturn' function returned", @returnAddress());
209 }209 }
210 pub fn loadUninstantiableType() noreturn {
211 @branchHint(.cold);
212 call("attempt to load uninstantiable type", @returnAddress());
213 }
210 };214 };
211}215}
212216
lib/std/debug/no_panic.zig+5
...@@ -134,3 +134,8 @@ pub fn noreturnReturned() noreturn {...@@ -134,3 +134,8 @@ pub fn noreturnReturned() noreturn {
134 @branchHint(.cold);134 @branchHint(.cold);
135 @trap();135 @trap();
136}136}
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 {...@@ -126,3 +126,7 @@ pub fn memcpyAlias() noreturn {
126pub fn noreturnReturned() noreturn {126pub fn noreturnReturned() noreturn {
127 call("'noreturn' function returned", null);127 call("'noreturn' function returned", null);
128}128}
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(...@@ -31040,7 +31040,18 @@ fn analyzeLoad(
31040 const comptime_only = switch (elem_ty.classify(zcu)) {31040 const comptime_only = switch (elem_ty.classify(zcu)) {
31041 .no_possible_value => switch (elem_ty.zigTypeTag(zcu)) {31041 .no_possible_value => switch (elem_ty.zigTypeTag(zcu)) {
31042 .@"opaque" => return sema.fail(block, src, "cannot load opaque type '{f}'", .{elem_ty.fmt(pt)}),31042 .@"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 },
31044 },31055 },
31045 .one_possible_value => return .fromValue((try elem_ty.onePossibleValue(pt)).?),31056 .one_possible_value => return .fromValue((try elem_ty.onePossibleValue(pt)).?),
31046 .runtime => false,31057 .runtime => false,
...@@ -35059,12 +35070,60 @@ fn getExpectedBuiltinFnType(sema: *Sema, decl: Zcu.StdLangDecl) CompileError!Typ...@@ -35059,12 +35070,60 @@ fn getExpectedBuiltinFnType(sema: *Sema, decl: Zcu.StdLangDecl) CompileError!Typ
35059 .@"panic.copyLenMismatch",35070 .@"panic.copyLenMismatch",
35060 .@"panic.memcpyAlias",35071 .@"panic.memcpyAlias",
35061 .@"panic.noreturnReturned",35072 .@"panic.noreturnReturned",
35073 .@"panic.loadUninstantiableType",
35062 => try pt.funcType(.{35074 => try pt.funcType(.{
35063 .param_types = &.{},35075 .param_types = &.{},
35064 .return_type = .noreturn_type,35076 .return_type = .noreturn_type,
35065 }),35077 }),
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`)
35068 };35127 };
35069}35128}
3507035129
src/Zcu.zig+4
...@@ -517,6 +517,7 @@ pub const StdLangDecl = enum {...@@ -517,6 +517,7 @@ pub const StdLangDecl = enum {
517 @"panic.copyLenMismatch",517 @"panic.copyLenMismatch",
518 @"panic.memcpyAlias",518 @"panic.memcpyAlias",
519 @"panic.noreturnReturned",519 @"panic.noreturnReturned",
520 @"panic.loadUninstantiableType",
520521
521 VaList,522 VaList,
522523
...@@ -606,6 +607,7 @@ pub const StdLangDecl = enum {...@@ -606,6 +607,7 @@ pub const StdLangDecl = enum {
606 .@"panic.copyLenMismatch",607 .@"panic.copyLenMismatch",
607 .@"panic.memcpyAlias",608 .@"panic.memcpyAlias",
608 .@"panic.noreturnReturned",609 .@"panic.noreturnReturned",
610 .@"panic.loadUninstantiableType",
609 => .func,611 => .func,
610 };612 };
611 }613 }
...@@ -679,6 +681,7 @@ pub const SimplePanicId = enum {...@@ -679,6 +681,7 @@ pub const SimplePanicId = enum {
679 copy_len_mismatch,681 copy_len_mismatch,
680 memcpy_alias,682 memcpy_alias,
681 noreturn_returned,683 noreturn_returned,
684 load_uninstantiable_type,
682685
683 pub fn toStdLangDecl(id: SimplePanicId) StdLangDecl {686 pub fn toStdLangDecl(id: SimplePanicId) StdLangDecl {
684 return switch (id) {687 return switch (id) {
...@@ -702,6 +705,7 @@ pub const SimplePanicId = enum {...@@ -702,6 +705,7 @@ pub const SimplePanicId = enum {
702 .copy_len_mismatch => .@"panic.copyLenMismatch",705 .copy_len_mismatch => .@"panic.copyLenMismatch",
703 .memcpy_alias => .@"panic.memcpyAlias",706 .memcpy_alias => .@"panic.memcpyAlias",
704 .noreturn_returned => .@"panic.noreturnReturned",707 .noreturn_returned => .@"panic.noreturnReturned",
708 .load_uninstantiable_type => .@"panic.loadUninstantiableType",
705 // zig fmt: on709 // zig fmt: on
706 };710 };
707 }711 }
test/cases/compile_errors/initialize_empty_union.zig-31
...@@ -28,25 +28,6 @@ export fn init5() void {...@@ -28,25 +28,6 @@ export fn init5() void {
28 _ = @as(U5, undefined);28 _ = @as(U5, undefined);
29}29}
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
50// error31// error
51//32//
52// :13:17: error: expected type 'tmp.U0', found '@TypeOf(undefined)'33// :13:17: error: expected type 'tmp.U0', found '@TypeOf(undefined)'
...@@ -67,15 +48,3 @@ export fn deref5(ptr: *const U5) void {...@@ -67,15 +48,3 @@ export fn deref5(ptr: *const U5) void {
67// :28:17: error: expected type 'tmp.U5', found '@TypeOf(undefined)'48// :28:17: error: expected type 'tmp.U5', found '@TypeOf(undefined)'
68// :28:17: note: cannot coerce to uninstantiable type 'tmp.U5'49// :28:17: note: cannot coerce to uninstantiable type 'tmp.U5'
69// :10:12: note: union declared here50// :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 {...@@ -36,6 +36,7 @@ pub const panic = struct {
36 pub const copyLenMismatch = no_panic.copyLenMismatch;36 pub const copyLenMismatch = no_panic.copyLenMismatch;
37 pub const memcpyAlias = no_panic.memcpyAlias;37 pub const memcpyAlias = no_panic.memcpyAlias;
38 pub const noreturnReturned = no_panic.noreturnReturned;38 pub const noreturnReturned = no_panic.noreturnReturned;
39 pub const loadUninstantiableType = no_panic.loadUninstantiableType;
39};40};
40fn myPanic(msg: []const u8, _: ?usize) noreturn {41fn myPanic(msg: []const u8, _: ?usize) noreturn {
41 var stdout_writer = std.Io.File.stdout().writerStreaming(io, &.{});42 var stdout_writer = std.Io.File.stdout().writerStreaming(io, &.{});
...@@ -84,6 +85,7 @@ pub const panic = struct {...@@ -84,6 +85,7 @@ pub const panic = struct {
84 pub const copyLenMismatch = no_panic.copyLenMismatch;85 pub const copyLenMismatch = no_panic.copyLenMismatch;
85 pub const memcpyAlias = no_panic.memcpyAlias;86 pub const memcpyAlias = no_panic.memcpyAlias;
86 pub const noreturnReturned = no_panic.noreturnReturned;87 pub const noreturnReturned = no_panic.noreturnReturned;
88 pub const loadUninstantiableType = no_panic.loadUninstantiableType;
87};89};
88fn myPanic(msg: []const u8, _: ?usize) noreturn {90fn myPanic(msg: []const u8, _: ?usize) noreturn {
89 var stdout_writer = std.Io.File.stdout().writerStreaming(io, &.{});91 var stdout_writer = std.Io.File.stdout().writerStreaming(io, &.{});
...@@ -132,6 +134,7 @@ pub const panic = struct {...@@ -132,6 +134,7 @@ pub const panic = struct {
132 pub const copyLenMismatch = no_panic.copyLenMismatch;134 pub const copyLenMismatch = no_panic.copyLenMismatch;
133 pub const memcpyAlias = no_panic.memcpyAlias;135 pub const memcpyAlias = no_panic.memcpyAlias;
134 pub const noreturnReturned = no_panic.noreturnReturned;136 pub const noreturnReturned = no_panic.noreturnReturned;
137 pub const loadUninstantiableType = no_panic.loadUninstantiableType;
135};138};
136fn myPanicNew(msg: []const u8, _: ?usize) noreturn {139fn myPanicNew(msg: []const u8, _: ?usize) noreturn {
137 var stdout_writer = std.Io.File.stdout().writerStreaming(io, &.{});140 var stdout_writer = std.Io.File.stdout().writerStreaming(io, &.{});