authorgravatar for justus@klausecker.deJustus Klausecker <justus@klausecker.de> 2026-05-17 19:11:16+02:00
committergravatar for justus@klausecker.deJustus Klausecker <justus@klausecker.de> 2026-08-02 14:41:10+02:00
loge69d19d1ee06ebb50af191d2911355d817eb1249
treea004c24ecaa7cce788883e0a1067c4c8715e75c3
parent539bdfc46ff75a4a4febb97a7700ade393c1cb22

Sema: allow direct dereference of comptime-known slices

A comptime-known `[]T` should behave as similarly as possible to a comptime- known `*[n]T`. It is now possible to directly dereference both to an array. Also adds checks for slices with undefined length in code paths where a slice is dereferenced as an array which would previously crash.

7 files changed, 126 insertions(+), 34 deletions(-)

lib/std/zig/Zir.zig+1-1
......@@ -584,7 +584,7 @@ pub const Inst = struct {
584584 /// containing the instruction.
585585 /// Uses the `un_tok` union field.
586586 ref,
587 /// Implements the dereference operand (`.*`). Checks that operand is a pointer
587 /// Implements the dereference operator (`.*`). Checks that operand is a pointer
588588 /// that supports being directly dereferenced.
589589 /// Uses the `un_node` union field.
590590 deref,
src/Sema.zig+37-31
......@@ -2352,6 +2352,10 @@ pub fn failWithUseOfUndef(sema: *Sema, block: *Block, src: LazySrcLoc, vector_in
23522352 });
23532353}
23542354
2355pub fn failWithUndefSliceLen(sema: *Sema, block: *Block, src: LazySrcLoc) CompileError {
2356 return sema.fail(block, src, "use of slice with undefined length here causes illegal behavior", .{});
2357}
2358
23552359pub fn failWithDivideByZero(sema: *Sema, block: *Block, src: LazySrcLoc) CompileError {
23562360 return sema.fail(block, src, "division by zero here causes illegal behavior", .{});
23572361}
......@@ -3113,9 +3117,14 @@ fn zirRefDeref(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Ai
31133117 try sema.validateDeref(block, src, operand, operand_ty);
31143118
31153119 const ptr_info = operand_ty.ptrInfo(zcu);
3116 return switch (ptr_info.flags.size) {
3117 .many, .slice => unreachable, // cannot be dereferenced
3118 .c => single_ptr: {
3120 return single_ptr: switch (ptr_info.flags.size) {
3121 .many => unreachable, // cannot be dereferenced directly
3122 .slice => {
3123 const slice_val = sema.resolveValue(operand).?;
3124 const slice = zcu.intern_pool.indexToKey(slice_val.toIntern()).slice;
3125 break :single_ptr .fromValue(try pt.sliceToArrayPtr(slice));
3126 },
3127 .c => {
31193128 const single_ptr_ty = try pt.ptrType(p: {
31203129 var p = ptr_info;
31213130 p.flags.size = .one;
......@@ -3149,18 +3158,26 @@ fn validateDeref(
31493158) CompileError!void {
31503159 const pt = sema.pt;
31513160 const zcu = pt.zcu;
3161 const ip = &zcu.intern_pool;
31523162 if (ty.zigTypeTag(zcu) != .pointer) {
31533163 return sema.fail(block, src, "cannot dereference non-pointer type '{f}'", .{ty.fmt(pt)});
3154 } else switch (ty.ptrSize(zcu)) {
3155 .one, .c => {},
3164 }
3165 const size = ty.ptrSize(zcu);
3166 switch (size) {
31563167 .many => return sema.fail(block, src, "index syntax required for unknown-length pointer type '{f}'", .{ty.fmt(pt)}),
3157 .slice => return sema.fail(block, src, "index syntax required for slice type '{f}'", .{ty.fmt(pt)}),
3168 .one, .c, .slice => {},
31583169 }
31593170 if (sema.resolveValue(ref)) |val| {
31603171 // Error for deref of undef pointer, unless the pointee is OPV in which case it's legal.
31613172 if (val.isUndef(zcu) and ty.childType(zcu).classify(zcu) != .one_possible_value) {
31623173 return sema.fail(block, src, "cannot dereference undefined value", .{});
31633174 }
3175 // We need a defined slice length for the array type the slice should be dereferenced to.
3176 if (size == .slice and ip.indexToKey(val.toIntern()).slice.len == .undef_usize) {
3177 return sema.fail(block, src, "cannot dereference slice with undefined length", .{});
3178 }
3179 } else if (size == .slice) {
3180 return sema.fail(block, src, "index syntax required to access runtime-known slice", .{});
31643181 }
31653182}
31663183
......@@ -13570,14 +13587,14 @@ fn zirArrayCat(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Ai
1357013587 const lhs_sub_val = if (lhs_ty.isSinglePointer(zcu))
1357113588 try sema.pointerDeref(block, lhs_src, lhs_val, lhs_ty) orelse break :rs lhs_src
1357213589 else if (lhs_ty.isSlice(zcu))
13573 try sema.maybeDerefSliceAsArray(block, lhs_src, lhs_val) orelse break :rs lhs_src
13590 try sema.maybeDerefSliceAsArray(block, lhs_src, lhs_val, lhs_ty) orelse break :rs lhs_src
1357413591 else
1357513592 lhs_val;
1357613593
1357713594 const rhs_sub_val = if (rhs_ty.isSinglePointer(zcu))
1357813595 try sema.pointerDeref(block, rhs_src, rhs_val, rhs_ty) orelse break :rs rhs_src
1357913596 else if (rhs_ty.isSlice(zcu))
13580 try sema.maybeDerefSliceAsArray(block, rhs_src, rhs_val) orelse break :rs rhs_src
13597 try sema.maybeDerefSliceAsArray(block, rhs_src, rhs_val, rhs_ty) orelse break :rs rhs_src
1358113598 else
1358213599 rhs_val;
1358313600
......@@ -30940,8 +30957,11 @@ fn analyzeLoad(
3094030957 };
3094130958
3094230959 if (try sema.resolveDefinedValue(block, ptr_src, ptr)) |ptr_val| {
30943 if (try sema.pointerDeref(block, src, ptr_val, ptr_ty)) |elem_val| {
30944 return Air.internedToRef(elem_val.toIntern());
30960 if (switch (ptr_ty.ptrSize(zcu)) {
30961 .slice => try sema.maybeDerefSliceAsArray(block, src, ptr_val, ptr_ty),
30962 else => try sema.pointerDeref(block, src, ptr_val, ptr_ty),
30963 }) |elem_val| {
30964 return .fromValue(elem_val);
3094530965 }
3094630966 }
3094730967
......@@ -34558,7 +34578,7 @@ fn anyUndef(sema: *Sema, block: *Block, src: LazySrcLoc, val: Value) !bool {
3455834578 .slice => {
3455934579 // If the slice contents are runtime-known, reification will fail later on with a
3456034580 // specific error message.
34561 const arr = try sema.maybeDerefSliceAsArray(block, src, val) orelse return false;
34581 const arr = try sema.maybeDerefSliceAsArray(block, src, val, val.typeOf(zcu)) orelse return false;
3456234582 return sema.anyUndef(block, src, arr);
3456334583 },
3456434584 .aggregate => |aggregate| for (0..aggregate.storage.values().len) |i| {
......@@ -34599,7 +34619,7 @@ fn derefSliceAsArray(
3459934619 /// being comptime-resolved is that the block is being comptime-evaluated.
3460034620 reason: ?ComptimeReason,
3460134621) CompileError!Value {
34602 return try sema.maybeDerefSliceAsArray(block, src, slice_val) orelse {
34622 return try sema.maybeDerefSliceAsArray(block, src, slice_val, slice_val.typeOf(sema.pt.zcu)) orelse {
3460334623 return sema.failWithNeededComptime(block, src, reason);
3460434624 };
3460534625}
......@@ -34612,37 +34632,23 @@ fn maybeDerefSliceAsArray(
3461234632 block: *Block,
3461334633 src: LazySrcLoc,
3461434634 slice_val: Value,
34635 slice_ty: Type,
3461534636) CompileError!?Value {
3461634637 const pt = sema.pt;
3461734638 const zcu = pt.zcu;
34618 const ip = &zcu.intern_pool;
34619 const slice_ty = slice_val.typeOf(zcu);
34620 assert(slice_ty.zigTypeTag(zcu) == .pointer);
3462134639 switch (slice_ty.ptrInfo(zcu).flags.size) {
3462234640 .slice => {},
3462334641 .one => return sema.pointerDeref(block, src, slice_val, slice_ty),
3462434642 .many, .c => unreachable,
3462534643 }
34626 const slice = switch (ip.indexToKey(slice_val.toIntern())) {
34644 const slice = switch (zcu.intern_pool.indexToKey(slice_val.toIntern())) {
3462734645 .undef => return sema.failWithUseOfUndef(block, src, null),
3462834646 .slice => |slice| slice,
3462934647 else => unreachable,
3463034648 };
34631 const elem_ty = Type.fromInterned(slice.ty).childType(zcu);
34632 const len = Value.fromInterned(slice.len).toUnsignedInt(zcu);
34633 const array_ty = try pt.arrayType(.{
34634 .child = elem_ty.toIntern(),
34635 .len = len,
34636 });
34637 const ptr_ty = try pt.ptrType(p: {
34638 var p = Type.fromInterned(slice.ty).ptrInfo(zcu);
34639 p.flags.size = .one;
34640 p.child = array_ty.toIntern();
34641 p.sentinel = .none;
34642 break :p p;
34643 });
34644 const casted_ptr = try pt.getCoerced(Value.fromInterned(slice.ptr), ptr_ty);
34645 return sema.pointerDeref(block, src, casted_ptr, ptr_ty);
34649 if (slice.len == .undef_usize) return sema.failWithUndefSliceLen(block, src);
34650 const casted_ptr = try pt.sliceToArrayPtr(slice);
34651 return sema.pointerDeref(block, src, casted_ptr, casted_ptr.typeOf(zcu));
3464634652}
3464734653
3464834654fn analyzeUnreachable(sema: *Sema, block: *Block, src: LazySrcLoc, safety_check: bool) !void {
src/Zcu/PerThread.zig+19
......@@ -3498,6 +3498,25 @@ pub fn getErrorValueFromSlice(pt: Zcu.PerThread, name: []const u8) Allocator.Err
34983498 return pt.getErrorValue(try pt.zcu.intern_pool.getOrPutString(gpa, io, name));
34993499}
35003500
3501/// Asserts that `slice.len` is *not* undef.
3502pub fn sliceToArrayPtr(pt: Zcu.PerThread, slice: InternPool.Key.Slice) Allocator.Error!Value {
3503 const zcu = pt.zcu;
3504 const slice_info = Type.fromInterned(slice.ty).ptrInfo(zcu);
3505 const array_ty = try pt.arrayType(.{
3506 .len = Value.fromInterned(slice.len).toUnsignedInt(zcu),
3507 .child = slice_info.child,
3508 .sentinel = slice_info.sentinel,
3509 });
3510 const ptr_ty = try pt.ptrType(ptr_info: {
3511 var ptr_info = slice_info;
3512 ptr_info.flags.size = .one;
3513 ptr_info.child = array_ty.toIntern();
3514 ptr_info.sentinel = .none;
3515 break :ptr_info ptr_info;
3516 });
3517 return pt.getCoerced(.fromInterned(slice.ptr), ptr_ty);
3518}
3519
35013520/// Removes any entry from `Zcu.failed_files` associated with `file`. Acquires `Compilation.mutex` as needed.
35023521/// `file.zir` must be unchanged from the last update, as it is used to determine if there is such an entry.
35033522fn lockAndClearFileCompileError(pt: Zcu.PerThread, file_index: Zcu.File.Index, file: *Zcu.File) void {
test/behavior/slice.zig+44
......@@ -1089,3 +1089,47 @@ test "slice field alignment" {
10891089 var arr: [10]u8 = @splat(0);
10901090 try S.doTheTest(&&arr);
10911091}
1092
1093test "directly deref slice with comptime-known length" {
1094 {
1095 const slice: []const u16 = &.{ 1, 2, 3 };
1096 const array = slice.*;
1097
1098 comptime assert(@TypeOf(array) == [3]u16);
1099 comptime assert(array[0] == 1);
1100 comptime assert(array[1] == 2);
1101 comptime assert(array[2] == 3);
1102 }
1103 {
1104 const slice: [:0]const u16 = &.{ 1, 2, 3 };
1105 const array = slice.*;
1106
1107 comptime assert(@TypeOf(array) == [3:0]u16);
1108 comptime assert(array[0] == 1);
1109 comptime assert(array[1] == 2);
1110 comptime assert(array[2] == 3);
1111 comptime assert(array[3] == 0);
1112 }
1113}
1114
1115test "address of dereferenced slice is array pointer" {
1116 {
1117 const slice: []const u16 = &.{ 1, 2, 3 };
1118 const array_ptr = &slice.*;
1119
1120 comptime assert(@TypeOf(array_ptr) == *const [3]u16);
1121 comptime assert(array_ptr[0] == 1);
1122 comptime assert(array_ptr[1] == 2);
1123 comptime assert(array_ptr[2] == 3);
1124 }
1125 {
1126 const slice: [:0]const u16 = &.{ 1, 2, 3 };
1127 const array_ptr = &slice.*;
1128
1129 comptime assert(@TypeOf(array_ptr) == *const [3:0]u16);
1130 comptime assert(array_ptr[0] == 1);
1131 comptime assert(array_ptr[1] == 2);
1132 comptime assert(array_ptr[2] == 3);
1133 comptime assert(array_ptr[3] == 0);
1134 }
1135}
test/cases/compile_errors/deref_slice_and_get_len_field.zig+1-1
......@@ -6,4 +6,4 @@ export fn entry() void {
66
77// error
88//
9// :3:10: error: index syntax required for slice type '[]u8'
9// :3:10: error: index syntax required to access runtime-known slice
test/cases/compile_errors/deref_slice_with_undef_len.zig created+23
......@@ -0,0 +1,23 @@
1export fn entry2() void {
2 comptime var slice: []const u16 = &.{ 1, 2, 3 };
3 slice.len = undefined;
4 _ = slice.*;
5}
6
7export fn entry3() void {
8 comptime var slice: []const u16 = &.{ 1, 2, 3 };
9 slice.len = undefined;
10 _ = &slice.*;
11}
12
13export fn entry4() void {
14 comptime var slice: []const u8 = "hello";
15 slice.len = undefined;
16 @compileError(slice);
17}
18
19// error
20//
21// :4:14: error: cannot dereference slice with undefined length
22// :10:15: error: cannot dereference slice with undefined length
23// :16:19: error: use of slice with undefined length here causes illegal behavior
test/cases/compile_errors/dereference_slice.zig+1-1
......@@ -7,4 +7,4 @@ comptime {
77
88// error
99//
10// :2:13: error: index syntax required for slice type '[]i32'
10// :2:13: error: index syntax required to access runtime-known slice