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 {...@@ -584,7 +584,7 @@ pub const Inst = struct {
584 /// containing the instruction.584 /// containing the instruction.
585 /// Uses the `un_tok` union field.585 /// Uses the `un_tok` union field.
586 ref,586 ref,
587 /// Implements the dereference operand (`.*`). Checks that operand is a pointer587 /// Implements the dereference operator (`.*`). Checks that operand is a pointer
588 /// that supports being directly dereferenced.588 /// that supports being directly dereferenced.
589 /// Uses the `un_node` union field.589 /// Uses the `un_node` union field.
590 deref,590 deref,
src/Sema.zig+37-31
...@@ -2352,6 +2352,10 @@ pub fn failWithUseOfUndef(sema: *Sema, block: *Block, src: LazySrcLoc, vector_in...@@ -2352,6 +2352,10 @@ pub fn failWithUseOfUndef(sema: *Sema, block: *Block, src: LazySrcLoc, vector_in
2352 });2352 });
2353}2353}
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
2355pub fn failWithDivideByZero(sema: *Sema, block: *Block, src: LazySrcLoc) CompileError {2359pub fn failWithDivideByZero(sema: *Sema, block: *Block, src: LazySrcLoc) CompileError {
2356 return sema.fail(block, src, "division by zero here causes illegal behavior", .{});2360 return sema.fail(block, src, "division by zero here causes illegal behavior", .{});
2357}2361}
...@@ -3113,9 +3117,14 @@ fn zirRefDeref(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Ai...@@ -3113,9 +3117,14 @@ fn zirRefDeref(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Ai
3113 try sema.validateDeref(block, src, operand, operand_ty);3117 try sema.validateDeref(block, src, operand, operand_ty);
31143118
3115 const ptr_info = operand_ty.ptrInfo(zcu);3119 const ptr_info = operand_ty.ptrInfo(zcu);
3116 return switch (ptr_info.flags.size) {3120 return single_ptr: switch (ptr_info.flags.size) {
3117 .many, .slice => unreachable, // cannot be dereferenced3121 .many => unreachable, // cannot be dereferenced directly
3118 .c => single_ptr: {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 => {
3119 const single_ptr_ty = try pt.ptrType(p: {3128 const single_ptr_ty = try pt.ptrType(p: {
3120 var p = ptr_info;3129 var p = ptr_info;
3121 p.flags.size = .one;3130 p.flags.size = .one;
...@@ -3149,18 +3158,26 @@ fn validateDeref(...@@ -3149,18 +3158,26 @@ fn validateDeref(
3149) CompileError!void {3158) CompileError!void {
3150 const pt = sema.pt;3159 const pt = sema.pt;
3151 const zcu = pt.zcu;3160 const zcu = pt.zcu;
3161 const ip = &zcu.intern_pool;
3152 if (ty.zigTypeTag(zcu) != .pointer) {3162 if (ty.zigTypeTag(zcu) != .pointer) {
3153 return sema.fail(block, src, "cannot dereference non-pointer type '{f}'", .{ty.fmt(pt)});3163 return sema.fail(block, src, "cannot dereference non-pointer type '{f}'", .{ty.fmt(pt)});
3154 } else switch (ty.ptrSize(zcu)) {3164 }
3155 .one, .c => {},3165 const size = ty.ptrSize(zcu);
3166 switch (size) {
3156 .many => return sema.fail(block, src, "index syntax required for unknown-length pointer type '{f}'", .{ty.fmt(pt)}),3167 .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 => {},
3158 }3169 }
3159 if (sema.resolveValue(ref)) |val| {3170 if (sema.resolveValue(ref)) |val| {
3160 // Error for deref of undef pointer, unless the pointee is OPV in which case it's legal.3171 // Error for deref of undef pointer, unless the pointee is OPV in which case it's legal.
3161 if (val.isUndef(zcu) and ty.childType(zcu).classify(zcu) != .one_possible_value) {3172 if (val.isUndef(zcu) and ty.childType(zcu).classify(zcu) != .one_possible_value) {
3162 return sema.fail(block, src, "cannot dereference undefined value", .{});3173 return sema.fail(block, src, "cannot dereference undefined value", .{});
3163 }3174 }
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", .{});
3164 }3181 }
3165}3182}
31663183
...@@ -13570,14 +13587,14 @@ fn zirArrayCat(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Ai...@@ -13570,14 +13587,14 @@ fn zirArrayCat(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Ai
13570 const lhs_sub_val = if (lhs_ty.isSinglePointer(zcu))13587 const lhs_sub_val = if (lhs_ty.isSinglePointer(zcu))
13571 try sema.pointerDeref(block, lhs_src, lhs_val, lhs_ty) orelse break :rs lhs_src13588 try sema.pointerDeref(block, lhs_src, lhs_val, lhs_ty) orelse break :rs lhs_src
13572 else if (lhs_ty.isSlice(zcu))13589 else if (lhs_ty.isSlice(zcu))
13573 try sema.maybeDerefSliceAsArray(block, lhs_src, lhs_val) orelse break :rs lhs_src13590 try sema.maybeDerefSliceAsArray(block, lhs_src, lhs_val, lhs_ty) orelse break :rs lhs_src
13574 else13591 else
13575 lhs_val;13592 lhs_val;
1357613593
13577 const rhs_sub_val = if (rhs_ty.isSinglePointer(zcu))13594 const rhs_sub_val = if (rhs_ty.isSinglePointer(zcu))
13578 try sema.pointerDeref(block, rhs_src, rhs_val, rhs_ty) orelse break :rs rhs_src13595 try sema.pointerDeref(block, rhs_src, rhs_val, rhs_ty) orelse break :rs rhs_src
13579 else if (rhs_ty.isSlice(zcu))13596 else if (rhs_ty.isSlice(zcu))
13580 try sema.maybeDerefSliceAsArray(block, rhs_src, rhs_val) orelse break :rs rhs_src13597 try sema.maybeDerefSliceAsArray(block, rhs_src, rhs_val, rhs_ty) orelse break :rs rhs_src
13581 else13598 else
13582 rhs_val;13599 rhs_val;
1358313600
...@@ -30940,8 +30957,11 @@ fn analyzeLoad(...@@ -30940,8 +30957,11 @@ fn analyzeLoad(
30940 };30957 };
3094130958
30942 if (try sema.resolveDefinedValue(block, ptr_src, ptr)) |ptr_val| {30959 if (try sema.resolveDefinedValue(block, ptr_src, ptr)) |ptr_val| {
30943 if (try sema.pointerDeref(block, src, ptr_val, ptr_ty)) |elem_val| {30960 if (switch (ptr_ty.ptrSize(zcu)) {
30944 return Air.internedToRef(elem_val.toIntern());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);
30945 }30965 }
30946 }30966 }
3094730967
...@@ -34558,7 +34578,7 @@ fn anyUndef(sema: *Sema, block: *Block, src: LazySrcLoc, val: Value) !bool {...@@ -34558,7 +34578,7 @@ fn anyUndef(sema: *Sema, block: *Block, src: LazySrcLoc, val: Value) !bool {
34558 .slice => {34578 .slice => {
34559 // If the slice contents are runtime-known, reification will fail later on with a34579 // If the slice contents are runtime-known, reification will fail later on with a
34560 // specific error message.34580 // 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;
34562 return sema.anyUndef(block, src, arr);34582 return sema.anyUndef(block, src, arr);
34563 },34583 },
34564 .aggregate => |aggregate| for (0..aggregate.storage.values().len) |i| {34584 .aggregate => |aggregate| for (0..aggregate.storage.values().len) |i| {
...@@ -34599,7 +34619,7 @@ fn derefSliceAsArray(...@@ -34599,7 +34619,7 @@ fn derefSliceAsArray(
34599 /// being comptime-resolved is that the block is being comptime-evaluated.34619 /// being comptime-resolved is that the block is being comptime-evaluated.
34600 reason: ?ComptimeReason,34620 reason: ?ComptimeReason,
34601) CompileError!Value {34621) 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 {
34603 return sema.failWithNeededComptime(block, src, reason);34623 return sema.failWithNeededComptime(block, src, reason);
34604 };34624 };
34605}34625}
...@@ -34612,37 +34632,23 @@ fn maybeDerefSliceAsArray(...@@ -34612,37 +34632,23 @@ fn maybeDerefSliceAsArray(
34612 block: *Block,34632 block: *Block,
34613 src: LazySrcLoc,34633 src: LazySrcLoc,
34614 slice_val: Value,34634 slice_val: Value,
34635 slice_ty: Type,
34615) CompileError!?Value {34636) CompileError!?Value {
34616 const pt = sema.pt;34637 const pt = sema.pt;
34617 const zcu = pt.zcu;34638 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);
34621 switch (slice_ty.ptrInfo(zcu).flags.size) {34639 switch (slice_ty.ptrInfo(zcu).flags.size) {
34622 .slice => {},34640 .slice => {},
34623 .one => return sema.pointerDeref(block, src, slice_val, slice_ty),34641 .one => return sema.pointerDeref(block, src, slice_val, slice_ty),
34624 .many, .c => unreachable,34642 .many, .c => unreachable,
34625 }34643 }
34626 const slice = switch (ip.indexToKey(slice_val.toIntern())) {34644 const slice = switch (zcu.intern_pool.indexToKey(slice_val.toIntern())) {
34627 .undef => return sema.failWithUseOfUndef(block, src, null),34645 .undef => return sema.failWithUseOfUndef(block, src, null),
34628 .slice => |slice| slice,34646 .slice => |slice| slice,
34629 else => unreachable,34647 else => unreachable,
34630 };34648 };
34631 const elem_ty = Type.fromInterned(slice.ty).childType(zcu);34649 if (slice.len == .undef_usize) return sema.failWithUndefSliceLen(block, src);
34632 const len = Value.fromInterned(slice.len).toUnsignedInt(zcu);34650 const casted_ptr = try pt.sliceToArrayPtr(slice);
34633 const array_ty = try pt.arrayType(.{34651 return sema.pointerDeref(block, src, casted_ptr, casted_ptr.typeOf(zcu));
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);
34646}34652}
3464734653
34648fn analyzeUnreachable(sema: *Sema, block: *Block, src: LazySrcLoc, safety_check: bool) !void {34654fn 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...@@ -3498,6 +3498,25 @@ pub fn getErrorValueFromSlice(pt: Zcu.PerThread, name: []const u8) Allocator.Err
3498 return pt.getErrorValue(try pt.zcu.intern_pool.getOrPutString(gpa, io, name));3498 return pt.getErrorValue(try pt.zcu.intern_pool.getOrPutString(gpa, io, name));
3499}3499}
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
3501/// Removes any entry from `Zcu.failed_files` associated with `file`. Acquires `Compilation.mutex` as needed.3520/// Removes any entry from `Zcu.failed_files` associated with `file`. Acquires `Compilation.mutex` as needed.
3502/// `file.zir` must be unchanged from the last update, as it is used to determine if there is such an entry.3521/// `file.zir` must be unchanged from the last update, as it is used to determine if there is such an entry.
3503fn lockAndClearFileCompileError(pt: Zcu.PerThread, file_index: Zcu.File.Index, file: *Zcu.File) void {3522fn 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" {...@@ -1089,3 +1089,47 @@ test "slice field alignment" {
1089 var arr: [10]u8 = @splat(0);1089 var arr: [10]u8 = @splat(0);
1090 try S.doTheTest(&&arr);1090 try S.doTheTest(&&arr);
1091}1091}
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 {...@@ -6,4 +6,4 @@ export fn entry() void {
66
7// error7// error
8//8//
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 {...@@ -7,4 +7,4 @@ comptime {
77
8// error8// error
9//9//
10// :2:13: error: index syntax required for slice type '[]i32'10// :2:13: error: index syntax required to access runtime-known slice