authorgravatar for mlugg@mlugg.co.ukMatthew Lugg <mlugg@mlugg.co.uk> 2025-07-25 18:59:35+01:00
committergravatar for mlugg@mlugg.co.ukMatthew Lugg <mlugg@mlugg.co.uk> 2025-07-26 14:54:04+01:00
logc9ce1debe7cb59b63c00f56bb0d233eafc04dfd9
tree3ee70759f69de36659d77fc237b8bca8da40a3b0
parentfc4b7c968afa6fa0780a011f3c8cfeaea38b7b98

Sema: exclude sentinel from source array length in pointer cast to slice

Resolves: #24569

2 files changed, 24 insertions(+), 8 deletions(-)

src/Sema.zig+14-8
...@@ -22483,11 +22483,18 @@ fn ptrCastFull(...@@ -22483,11 +22483,18 @@ fn ptrCastFull(
22483 .slice => {},22483 .slice => {},
22484 .many, .c, .one => break :len null,22484 .many, .c, .one => break :len null,
22485 }22485 }
22486 // `null` means the operand is a runtime-known slice (so the length is runtime-known).22486 // A `null` length means the operand is a runtime-known slice (so the length is runtime-known).
22487 const opt_src_len: ?u64 = switch (src_info.flags.size) {22487 // `src_elem_type` is different from `src_info.child` if the latter is an array, to ensure we ignore sentinels.
22488 .one => 1,22488 const src_elem_ty: Type, const opt_src_len: ?u64 = switch (src_info.flags.size) {
22489 .slice => src_len: {22489 .one => src: {
22490 const operand_val = try sema.resolveValue(operand) orelse break :src_len null;22490 const true_child: Type = .fromInterned(src_info.child);
22491 break :src switch (true_child.zigTypeTag(zcu)) {
22492 .array => .{ true_child.childType(zcu), true_child.arrayLen(zcu) },
22493 else => .{ true_child, 1 },
22494 };
22495 },
22496 .slice => src: {
22497 const operand_val = try sema.resolveValue(operand) orelse break :src .{ .fromInterned(src_info.child), null };
22491 if (operand_val.isUndef(zcu)) break :len .undef;22498 if (operand_val.isUndef(zcu)) break :len .undef;
22492 const slice_val = switch (operand_ty.zigTypeTag(zcu)) {22499 const slice_val = switch (operand_ty.zigTypeTag(zcu)) {
22493 .optional => operand_val.optionalValue(zcu) orelse break :len .undef,22500 .optional => operand_val.optionalValue(zcu) orelse break :len .undef,
...@@ -22496,14 +22503,13 @@ fn ptrCastFull(...@@ -22496,14 +22503,13 @@ fn ptrCastFull(
22496 };22503 };
22497 const slice_len_resolved = try sema.resolveLazyValue(.fromInterned(zcu.intern_pool.sliceLen(slice_val.toIntern())));22504 const slice_len_resolved = try sema.resolveLazyValue(.fromInterned(zcu.intern_pool.sliceLen(slice_val.toIntern())));
22498 if (slice_len_resolved.isUndef(zcu)) break :len .undef;22505 if (slice_len_resolved.isUndef(zcu)) break :len .undef;
22499 break :src_len slice_len_resolved.toUnsignedInt(zcu);22506 break :src .{ .fromInterned(src_info.child), slice_len_resolved.toUnsignedInt(zcu) };
22500 },22507 },
22501 .many, .c => {22508 .many, .c => {
22502 return sema.fail(block, src, "cannot infer length of slice from {s}", .{pointerSizeString(src_info.flags.size)});22509 return sema.fail(block, src, "cannot infer length of slice from {s}", .{pointerSizeString(src_info.flags.size)});
22503 },22510 },
22504 };22511 };
22505 const dest_elem_ty: Type = .fromInterned(dest_info.child);22512 const dest_elem_ty: Type = .fromInterned(dest_info.child);
22506 const src_elem_ty: Type = .fromInterned(src_info.child);
22507 if (dest_elem_ty.toIntern() == src_elem_ty.toIntern()) {22513 if (dest_elem_ty.toIntern() == src_elem_ty.toIntern()) {
22508 break :len if (opt_src_len) |l| .{ .constant = l } else .equal_runtime_src_slice;22514 break :len if (opt_src_len) |l| .{ .constant = l } else .equal_runtime_src_slice;
22509 }22515 }
...@@ -22519,7 +22525,7 @@ fn ptrCastFull(...@@ -22519,7 +22525,7 @@ fn ptrCastFull(
22519 const bytes = src_len * src_elem_size;22525 const bytes = src_len * src_elem_size;
22520 const dest_len = std.math.divExact(u64, bytes, dest_elem_size) catch switch (src_info.flags.size) {22526 const dest_len = std.math.divExact(u64, bytes, dest_elem_size) catch switch (src_info.flags.size) {
22521 .slice => return sema.fail(block, src, "slice length '{d}' does not divide exactly into destination elements", .{src_len}),22527 .slice => return sema.fail(block, src, "slice length '{d}' does not divide exactly into destination elements", .{src_len}),
22522 .one => return sema.fail(block, src, "type '{f}' does not divide exactly into destination elements", .{src_elem_ty.fmt(pt)}),22528 .one => return sema.fail(block, src, "type '{f}' does not divide exactly into destination elements", .{Type.fromInterned(src_info.child).fmt(pt)}),
22523 else => unreachable,22529 else => unreachable,
22524 };22530 };
22525 break :len .{ .constant = dest_len };22531 break :len .{ .constant = dest_len };
test/behavior/ptrcast.zig+10
...@@ -552,3 +552,13 @@ test "@ptrCast single-item pointer to slice of bytes" {...@@ -552,3 +552,13 @@ test "@ptrCast single-item pointer to slice of bytes" {
552 try comptime S.doTheTest(void, &{});552 try comptime S.doTheTest(void, &{});
553 try comptime S.doTheTest(struct { x: u32 }, &.{ .x = 123 });553 try comptime S.doTheTest(struct { x: u32 }, &.{ .x = 123 });
554}554}
555
556test "@ptrCast array pointer removing sentinel" {
557 const in: *const [4:0]u8 = &.{ 1, 2, 3, 4 };
558 const out: []const i8 = @ptrCast(in);
559 comptime assert(out.len == 4);
560 comptime assert(out[0] == 1);
561 comptime assert(out[1] == 2);
562 comptime assert(out[2] == 3);
563 comptime assert(out[3] == 4);
564}