authorgravatar for mlugg@mlugg.co.ukMatthew Lugg <mlugg@mlugg.co.uk> 2026-08-03 13:38:13+02:00
committergravatar for mlugg@mlugg.co.ukMatthew Lugg <mlugg@mlugg.co.uk> 2026-08-03 13:38:13+02:00
log39967484d58a33d3c9ee0883dcd782acb63f8a09
tree97570e9ba5d23656d1ca4a06e7b342a0f834b6ad
parentc41f0e5529d22c3b002689002e31761860a73528
parent1dd69217729a6b95b4ac644cc5385a8b1bc6c044

Merge pull request 'Sema: allow direct dereference of and coercion to array pointer for slices with comptime-known length' (#35389) from justusk/zig:deref-ct-slice into master

Reviewed-on: https://codeberg.org/ziglang/zig/pulls/35389

9 files changed, 341 insertions(+), 35 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+130-32
......@@ -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
......@@ -28241,9 +28258,94 @@ fn coerceExtra(
2824128258 },
2824228259 else => {},
2824328260 },
28244 .one => {},
28261 // []T to *[n]T
28262 .one => slice_to_array_ptr: {
28263 if (!inst_ty.isSlice(zcu)) break :slice_to_array_ptr;
28264 if (!sema.checkPtrAttributes(dest_ty, inst_ty, &in_memory_result)) break :slice_to_array_ptr;
28265 const array_ty: Type = .fromInterned(dest_info.child);
28266 if (array_ty.zigTypeTag(zcu) != .array) break :slice_to_array_ptr;
28267 const inst_val = maybe_inst_val orelse {
28268 if (!opts.report_err) return error.NotCoercible;
28269 return sema.fail(
28270 block,
28271 inst_src,
28272 "coercion from slice to array pointer type '{f}' requires length to be known at compile-time",
28273 .{dest_ty.fmt(pt)},
28274 );
28275 };
28276
28277 const slice: InternPool.Key.Slice = slice: {
28278 switch (ip.indexToKey(inst_val.toIntern())) {
28279 .undef => {},
28280 .slice => |slice| if (slice.len != .undef_usize) break :slice slice,
28281 else => unreachable,
28282 }
28283 if (!opts.report_err) return error.NotCoercible;
28284 return sema.failWithOwnedErrorMsg(block, msg: {
28285 const msg = try sema.errMsg(inst_src, "slice with undefined length cannot cast into array pointer type '{f}'", .{
28286 dest_ty.fmt(pt),
28287 });
28288 errdefer msg.destroy(gpa);
28289 try sema.errNote(inst_src, msg, "length of slice must be defined and match length of array type", .{});
28290 break :msg msg;
28291 });
28292 };
28293 const slice_len = Value.fromInterned(slice.len).toUnsignedInt(zcu);
28294 if (array_ty.arrayLen(zcu) != slice_len) {
28295 if (!opts.report_err) return error.NotCoercible;
28296 return sema.failWithOwnedErrorMsg(block, msg: {
28297 const msg = try sema.errMsg(inst_src, "slice of length {d} cannot cast into array pointer type '{f}'", .{
28298 slice_len, dest_ty.fmt(pt),
28299 });
28300 errdefer msg.destroy(gpa);
28301 try sema.errNote(inst_src, msg, "length of slice must match length of array type", .{});
28302 break :msg msg;
28303 });
28304 }
28305
28306 const inst_elem_ty = inst_ty.childType(zcu);
28307 const dest_elem_ty = array_ty.childType(zcu);
28308 const dest_is_mut = !dest_info.flags.is_const;
28309 switch (try sema.coerceInMemoryAllowed(block, dest_elem_ty, inst_elem_ty, dest_is_mut, target, dest_ty_src, inst_src, null)) {
28310 .ok => {},
28311 else => |elem_res| {
28312 in_memory_result = .{ .ptr_child = .{
28313 .child = try elem_res.dupe(sema.arena),
28314 .actual = inst_elem_ty,
28315 .wanted = dest_elem_ty,
28316 } };
28317 break :slice_to_array_ptr;
28318 },
28319 }
28320
28321 if (array_ty.sentinel(zcu)) |array_sentinel| {
28322 if (inst_ty.sentinel(zcu)) |slice_sentinel| {
28323 if (array_sentinel.toIntern() !=
28324 (try pt.getCoerced(slice_sentinel, dest_elem_ty)).toIntern())
28325 {
28326 in_memory_result = .{ .ptr_sentinel = .{
28327 .actual = slice_sentinel,
28328 .wanted = array_sentinel,
28329 .ty = dest_elem_ty,
28330 } };
28331 break :slice_to_array_ptr;
28332 }
28333 } else {
28334 in_memory_result = .{ .ptr_sentinel = .{
28335 .actual = .@"unreachable",
28336 .wanted = array_sentinel,
28337 .ty = dest_elem_ty,
28338 } };
28339 break :slice_to_array_ptr;
28340 }
28341 }
28342
28343 const array_ptr = try pt.sliceToArrayPtr(slice);
28344 return sema.coerceCompatiblePtrs(block, dest_ty, .fromValue(array_ptr), inst_src);
28345 },
2824528346 .slice => to_slice: {
2824628347 if (inst_ty.zigTypeTag(zcu) == .array) {
28348 if (!opts.report_err) return error.NotCoercible;
2824728349 return sema.fail(
2824828350 block,
2824928351 inst_src,
......@@ -28271,6 +28373,7 @@ fn coerceExtra(
2827128373
2827228374 // pointer to tuple to slice
2827328375 if (!dest_info.flags.is_const) {
28376 if (!opts.report_err) return error.NotCoercible;
2827428377 const err_msg = err_msg: {
2827528378 const err_msg = try sema.errMsg(inst_src, "cannot cast pointer to tuple to '{f}'", .{dest_ty.fmt(pt)});
2827628379 errdefer err_msg.destroy(sema.gpa);
......@@ -28366,6 +28469,7 @@ fn coerceExtra(
2836628469 if (maybe_inst_val) |val| {
2836728470 const result_val = try val.floatCast(dest_ty, pt);
2836828471 if (!val.eql(try result_val.floatCast(inst_ty, pt), inst_ty, zcu)) {
28472 if (!opts.report_err) return error.NotCoercible;
2836928473 return sema.fail(
2837028474 block,
2837128475 inst_src,
......@@ -28423,12 +28527,15 @@ fn coerceExtra(
2842328527 break :fits result_big_int.toConst().eql(operand_big_int);
2842428528 },
2842528529 };
28426 if (!fits) return sema.fail(
28427 block,
28428 inst_src,
28429 "type '{f}' cannot represent integer value '{f}'",
28430 .{ dest_ty.fmt(pt), val.fmtValue(pt) },
28431 );
28530 if (!fits) {
28531 if (!opts.report_err) return error.NotCoercible;
28532 return sema.fail(
28533 block,
28534 inst_src,
28535 "type '{f}' cannot represent integer value '{f}'",
28536 .{ dest_ty.fmt(pt), val.fmtValue(pt) },
28537 );
28538 }
2843228539 return .fromValue(result_val);
2843328540 },
2843428541 else => {},
......@@ -28439,6 +28546,7 @@ fn coerceExtra(
2843928546 const val = sema.resolveValue(inst).?;
2844028547 const string = zcu.intern_pool.indexToKey(val.toIntern()).enum_literal;
2844128548 const field_index = dest_ty.enumFieldIndex(string, zcu) orelse {
28549 if (!opts.report_err) return error.NotCoercible;
2844228550 return sema.fail(block, inst_src, "no field named '{f}' in enum '{f}'", .{
2844328551 string.fmt(&zcu.intern_pool), dest_ty.fmt(pt),
2844428552 });
......@@ -30940,8 +31048,11 @@ fn analyzeLoad(
3094031048 };
3094131049
3094231050 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());
31051 if (switch (ptr_ty.ptrSize(zcu)) {
31052 .slice => try sema.maybeDerefSliceAsArray(block, src, ptr_val),
31053 else => try sema.pointerDeref(block, src, ptr_val, ptr_ty),
31054 }) |elem_val| {
31055 return .fromValue(elem_val);
3094531056 }
3094631057 }
3094731058
......@@ -34615,7 +34726,6 @@ fn maybeDerefSliceAsArray(
3461534726) CompileError!?Value {
3461634727 const pt = sema.pt;
3461734728 const zcu = pt.zcu;
34618 const ip = &zcu.intern_pool;
3461934729 const slice_ty = slice_val.typeOf(zcu);
3462034730 assert(slice_ty.zigTypeTag(zcu) == .pointer);
3462134731 switch (slice_ty.ptrInfo(zcu).flags.size) {
......@@ -34623,26 +34733,14 @@ fn maybeDerefSliceAsArray(
3462334733 .one => return sema.pointerDeref(block, src, slice_val, slice_ty),
3462434734 .many, .c => unreachable,
3462534735 }
34626 const slice = switch (ip.indexToKey(slice_val.toIntern())) {
34736 const slice = switch (zcu.intern_pool.indexToKey(slice_val.toIntern())) {
3462734737 .undef => return sema.failWithUseOfUndef(block, src, null),
3462834738 .slice => |slice| slice,
3462934739 else => unreachable,
3463034740 };
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);
34741 if (slice.len == .undef_usize) return sema.failWithUndefSliceLen(block, src);
34742 const casted_ptr = try pt.sliceToArrayPtr(slice);
34743 return sema.pointerDeref(block, src, casted_ptr, casted_ptr.typeOf(zcu));
3464634744}
3464734745
3464834746fn 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+82
......@@ -1089,3 +1089,85 @@ 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}
1136
1137test "coerce slice with comptime-known length to array pointer" {
1138 {
1139 const slice: []const u16 = &.{ 1, 2, 3 };
1140 const array_ptr: *const [3]u16 = slice;
1141
1142 comptime assert(array_ptr[0] == 1);
1143 comptime assert(array_ptr[1] == 2);
1144 comptime assert(array_ptr[2] == 3);
1145 }
1146 {
1147 const slice: [:0]const u16 = &.{ 1, 2, 3 };
1148 const array_ptr: *const [3:0]u16 = slice;
1149
1150 comptime assert(array_ptr[0] == 1);
1151 comptime assert(array_ptr[1] == 2);
1152 comptime assert(array_ptr[2] == 3);
1153 comptime assert(array_ptr[3] == 0);
1154 }
1155 {
1156 const slice: [:0]const u16 = &.{ 1, 2, 3 };
1157 const array_ptr: *const [3]u16 = slice;
1158
1159 comptime assert(array_ptr[0] == 1);
1160 comptime assert(array_ptr[1] == 2);
1161 comptime assert(array_ptr[2] == 3);
1162 }
1163}
1164
1165test "modify slice through coerced array pointer" {
1166 comptime {
1167 var array: [3]u16 = .{ 1, 2, 3 };
1168 const slice: []u16 = &array;
1169 const array_ptr: *[3]u16 = slice;
1170 array_ptr[2] = 0;
1171 assert(slice[2] == 0);
1172 }
1173}
test/cases/compile_errors/coerce_pointers_with_uncoercable_child_pointers.zig+14
......@@ -28,6 +28,16 @@ export fn entry5() void {
2828 _ = q;
2929}
3030
31export fn entry6(p: **[3]u8) void {
32 const q: *[]u8 = p;
33 _ = q;
34}
35
36export fn entry7(p: *[]u8) void {
37 const q: **[3]u8 = p;
38 _ = q;
39}
40
3141// error
3242//
3343// :3:22: error: expected type '**i32', found '**u32'
......@@ -50,3 +60,7 @@ export fn entry5() void {
5060// :27:24: note: pointer type child '*[1:42]u8' cannot cast into pointer type child '*[1]u8'
5161// :27:24: note: pointer type child '[1:42]u8' cannot cast into pointer type child '[1]u8'
5262// :27:24: note: source array cannot be guaranteed to maintain '42' sentinel
63// :32:22: error: expected type '*[]u8', found '**[3]u8'
64// :32:22: note: pointer type child '*[3]u8' cannot cast into pointer type child '[]u8'
65// :37:24: error: expected type '**[3]u8', found '*[]u8'
66// :37:24: note: pointer type child '[]u8' cannot cast into pointer type child '*[3]u8'
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
test/cases/compile_errors/slice_to_array_pointer.zig created+70
......@@ -0,0 +1,70 @@
1export fn entry1() void {
2 var array: [2]u16 = .{ 1, 2 };
3 const slice: []const u16 = &array;
4 foo(slice);
5}
6
7export fn entry2() void {
8 const slice: []const u16 = undefined;
9 foo(slice);
10}
11
12export fn entry3() void {
13 comptime var slice: []const u16 = &.{ 1, 2 };
14 slice.len = undefined;
15 foo(slice);
16}
17
18export fn entry4() void {
19 const slice: []const u16 = &.{ 1, 2, 3 };
20 foo(slice);
21}
22
23export fn entry5() void {
24 const slice: []const u8 = &.{ 1, 2 };
25 foo(slice);
26}
27
28fn foo(x: *const [2]u16) void {
29 _ = x;
30}
31
32export fn entry6() void {
33 const slice: [:0]const u16 = &.{ 1, 2, 3 };
34 bar(slice);
35}
36
37export fn entry7() void {
38 const slice: [:1]const u16 = &.{ 1, 2 };
39 bar(slice);
40}
41
42export fn entry8() void {
43 const slice: []const u16 = &.{ 1, 2 };
44 bar(slice);
45}
46
47fn bar(x: *const [2:0]u16) void {
48 _ = x;
49}
50
51// error
52//
53// :4:9: error: coercion from slice to array pointer type '*const [2]u16' requires length to be known at compile-time
54// :9:9: error: slice with undefined length cannot cast into array pointer type '*const [2]u16'
55// :9:9: note: length of slice must be defined and match length of array type
56// :15:9: error: slice with undefined length cannot cast into array pointer type '*const [2]u16'
57// :15:9: note: length of slice must be defined and match length of array type
58// :20:9: error: slice of length 3 cannot cast into array pointer type '*const [2]u16'
59// :20:9: note: length of slice must match length of array type
60// :25:9: error: expected type '*const [2]u16', found '[]const u8'
61// :25:9: note: pointer type child 'u8' cannot cast into pointer type child 'u16'
62// :28:11: note: parameter type declared here
63// :34:9: error: slice of length 3 cannot cast into array pointer type '*const [2:0]u16'
64// :34:9: note: length of slice must match length of array type
65// :39:9: error: expected type '*const [2:0]u16', found '[:1]const u16'
66// :39:9: note: pointer sentinel '1' cannot cast into pointer sentinel '0'
67// :47:11: note: parameter type declared here
68// :44:9: error: expected type '*const [2:0]u16', found '[]const u16'
69// :44:9: note: destination pointer requires '0' sentinel
70// :47:11: note: parameter type declared here