authorgravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2026-08-08 09:43:59+02:00
committergravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2026-08-08 09:43:59+02:00
log276f2e1b810aef048c3ce324e0977ba2d9ff8200
treead66f0e1a007298b564f084e1ffc89d99a191d8c
parent5152987072ef2d030a704d2b4ca9cebd757d4cec
parentc0924842744fea67df49c9942b99d855b83578e5

Merge pull request 'fix: Allocator contract should allow *[len]T in more places' (#36113) from robbielyman/zig:push-oxmuzrvppsyx into master

Reviewed-on: https://codeberg.org/ziglang/zig/pulls/36113 Reviewed-by: Andrew Kelley <andrew@ziglang.org>

3 files changed, 130 insertions(+), 27 deletions(-)

lib/std/mem.zig+26-12
......@@ -9,6 +9,7 @@ const assert = debug.assert;
99const math = std.math;
1010const testing = std.testing;
1111const Endian = std.lang.Endian;
12const AbsorbSentinel = std.meta.AbsorbSentinel;
1213
1314/// The standard library currently thoroughly depends on byte size
1415/// being 8 bits. (see the use of u8 throughout allocation code as
......@@ -4738,22 +4739,28 @@ test "sliceAsBytes preserves pointer attributes" {
47384739 try testing.expectEqual(in_attrs.@"align", out_attrs.@"align");
47394740}
47404741
4741fn AbsorbSentinelReturnType(comptime Slice: type) type {
4742 const info = @typeInfo(Slice).pointer;
4743 assert(info.size == .slice);
4744 return @Pointer(.slice, info.attrs, info.child, null);
4745}
4746
47474742/// If the provided slice is not sentinel terminated, do nothing and return that slice.
47484743/// If it is sentinel-terminated, return a non-sentinel-terminated slice with the
47494744/// length increased by one to include the absorbed sentinel element.
4750pub fn absorbSentinel(slice: anytype) AbsorbSentinelReturnType(@TypeOf(slice)) {
4745pub fn absorbSentinel(slice: anytype) AbsorbSentinel(@TypeOf(slice)) {
47514746 const info = @typeInfo(@TypeOf(slice)).pointer;
4752 comptime assert(info.size == .slice);
4753 if (info.sentinel_ptr == null) {
4754 return slice;
4755 } else {
4756 return slice.ptr[0 .. slice.len + 1];
4747 switch (info.size) {
4748 .slice => {
4749 if (info.sentinel_ptr == null) {
4750 return slice;
4751 } else {
4752 return slice.ptr[0 .. slice.len + 1];
4753 }
4754 },
4755 .one => {
4756 const child_info = @typeInfo(info.child).array;
4757 if (child_info.sentinel_ptr == null) {
4758 return slice;
4759 } else {
4760 return slice[0 .. child_info.len + 1];
4761 }
4762 },
4763 else => unreachable,
47574764 }
47584765}
47594766
......@@ -4762,21 +4769,28 @@ test absorbSentinel {
47624769 var buffer: [3:0]u8 = .{ 1, 2, 3 };
47634770 const foo: [:0]const u8 = &buffer;
47644771 const bar: []const u8 = &buffer;
4772 const baz: *const [3:0]u8 = &buffer;
47654773 try testing.expectEqual([]const u8, @TypeOf(absorbSentinel(foo)));
47664774 try testing.expectEqual([]const u8, @TypeOf(absorbSentinel(bar)));
4775 try testing.expectEqual(*const [4]u8, @TypeOf(absorbSentinel(baz)));
47674776 try testing.expectEqualSlices(u8, &.{ 1, 2, 3, 0 }, absorbSentinel(foo));
47684777 try testing.expectEqualSlices(u8, &.{ 1, 2, 3 }, absorbSentinel(bar));
4778 try testing.expectEqualSlices(u8, &.{ 1, 2, 3, 0 }, absorbSentinel(baz));
47694779 }
47704780 {
47714781 var buffer: [3:0]u8 = .{ 1, 2, 3 };
47724782 const foo: [:0]u8 = &buffer;
47734783 const bar: []u8 = &buffer;
4784 const baz: *[3:0]u8 = &buffer;
47744785 try testing.expectEqual([]u8, @TypeOf(absorbSentinel(foo)));
47754786 try testing.expectEqual([]u8, @TypeOf(absorbSentinel(bar)));
4787 try testing.expectEqual(*[4]u8, @TypeOf(absorbSentinel(baz)));
47764788 var expected_foo = [_]u8{ 1, 2, 3, 0 };
47774789 try testing.expectEqualSlices(u8, &expected_foo, absorbSentinel(foo));
47784790 var expected_bar = [_]u8{ 1, 2, 3 };
47794791 try testing.expectEqualSlices(u8, &expected_bar, absorbSentinel(bar));
4792 var expected_baz = [_]u8{ 1, 2, 3, 0 };
4793 try testing.expectEqualSlices(u8, &expected_baz, absorbSentinel(baz));
47804794 }
47814795}
47824796
lib/std/mem/Allocator.zig+53-10
......@@ -8,6 +8,8 @@ const assert = std.debug.assert;
88const math = std.math;
99const mem = std.mem;
1010const Alignment = std.mem.Alignment;
11const Slice = std.meta.Slice;
12const AbsorbSentinel = std.meta.AbsorbSentinel;
1113
1214pub const Error = error{OutOfMemory};
1315pub const Log2Align = math.Log2Int(usize);
......@@ -316,8 +318,10 @@ pub fn allocBytesAligned(
316318/// `new_len` may be zero, in which case the allocation is freed.
317319pub fn resize(self: Allocator, allocation: anytype, new_len: usize) bool {
318320 const slice_info = @typeInfo(@TypeOf(allocation)).pointer;
319 comptime assert(slice_info.size == .slice);
320 const T = slice_info.child;
321 const T = if (slice_info.size != .slice) comptime T: {
322 assert(slice_info.size == .one);
323 break :T @typeInfo(slice_info.child).array.child;
324 } else slice_info.child;
321325 if (new_len == 0) {
322326 self.free(allocation);
323327 return true;
......@@ -351,10 +355,12 @@ pub fn resize(self: Allocator, allocation: anytype, new_len: usize) bool {
351355/// `new_len` may be zero, in which case the allocation is freed.
352356///
353357/// If the allocation's elements' type is zero bytes sized, `allocation.len` is set to `new_len`.
354pub fn remap(self: Allocator, allocation: anytype, new_len: usize) ?@TypeOf(allocation) {
358pub fn remap(self: Allocator, allocation: anytype, new_len: usize) ?Slice(AbsorbSentinel(@TypeOf(allocation))) {
355359 const slice_info = @typeInfo(@TypeOf(allocation)).pointer;
356 comptime assert(slice_info.size == .slice);
357 const T = slice_info.child;
360 const T = if (slice_info.size != .slice) comptime T: {
361 assert(slice_info.size == .one);
362 break :T @typeInfo(slice_info.child).array.child;
363 } else slice_info.child;
358364
359365 if (new_len == 0) {
360366 self.free(allocation);
......@@ -393,7 +399,7 @@ pub fn remap(self: Allocator, allocation: anytype, new_len: usize) ?@TypeOf(allo
393399/// do the realloc more efficiently than the caller
394400/// * `resize` which returns `false` when the `Allocator` implementation cannot
395401/// change the size without relocating the allocation.
396pub fn realloc(self: Allocator, old_mem: anytype, new_n: usize) Error!@TypeOf(old_mem) {
402pub fn realloc(self: Allocator, old_mem: anytype, new_n: usize) Error!Slice(AbsorbSentinel(@TypeOf(old_mem))) {
397403 return self.reallocAdvanced(old_mem, new_n, @returnAddress());
398404}
399405
......@@ -402,10 +408,12 @@ pub fn reallocAdvanced(
402408 old_mem: anytype,
403409 new_n: usize,
404410 return_address: usize,
405) Error!@TypeOf(old_mem) {
411) Error!Slice(AbsorbSentinel(@TypeOf(old_mem))) {
406412 const slice_info = @typeInfo(@TypeOf(old_mem)).pointer;
407 comptime assert(slice_info.size == .slice);
408 const T = slice_info.child;
413 const T = if (slice_info.size != .slice) comptime T: {
414 assert(slice_info.size == .one);
415 break :T @typeInfo(slice_info.child).array.child;
416 } else slice_info.child;
409417 if (old_mem.len == 0) {
410418 return self.allocAdvancedWithRetAddr(T, .fromByteUnitsOptional(slice_info.attrs.@"align"), new_n, return_address);
411419 }
......@@ -440,7 +448,6 @@ pub fn reallocAdvanced(
440448pub fn free(self: Allocator, memory: anytype) void {
441449 const slice_info = @typeInfo(@TypeOf(memory)).pointer;
442450 if (slice_info.size != .slice) {
443 // slicing with comptime-known start and end results in *[len]T, which may be free'd
444451 comptime assert(slice_info.size == .one and @typeInfo(slice_info.child) == .array);
445452 }
446453 const bytes: []u8 = @ptrCast(@constCast(mem.absorbSentinel(memory)));
......@@ -585,3 +592,39 @@ test failing {
585592 try std.testing.expectError(error.OutOfMemory, f.alloc(u8, std.math.maxInt(usize)));
586593 try std.testing.expectError(error.OutOfMemory, f.allocSentinel(u8, std.math.maxInt(usize) - 1, 0));
587594}
595
596test "free single-pointer to array" {
597 const allocator = std.testing.allocator;
598 {
599 const allocation = try allocator.alloc(u32, 128);
600 allocation[127] = 0;
601 const ptr: *[127:0]u32 = allocation[0..127 :0];
602 allocator.free(ptr);
603 }
604 {
605 const allocation = try allocator.alloc(u32, 128);
606 allocation[127] = 0;
607 const ptr: *[127:0]u32 = allocation[0..127 :0];
608 if (allocator.resize(ptr, 16)) {
609 allocator.free(ptr[0..16]);
610 } else allocator.free(ptr);
611 }
612 {
613 const allocation = try allocator.alloc(u32, 128);
614 allocation[127] = 0;
615 const ptr: *[127:0]u32 = allocation[0..127 :0];
616 if (allocator.remap(ptr, 16)) |new| {
617 allocator.free(new);
618 } else allocator.free(ptr);
619 }
620 {
621 const allocation = try allocator.alloc(u32, 128);
622 allocation[127] = 0;
623 const ptr: *[127:0]u32 = allocation[0..127 :0];
624 if (allocator.realloc(ptr, 16)) |new| {
625 allocator.free(new);
626 } else |_| {
627 allocator.free(allocation);
628 }
629 }
630}
lib/std/meta.zig+51-5
......@@ -1,10 +1,9 @@
11const builtin = @import("builtin");
2
23const std = @import("std.zig");
3const debug = std.debug;
4const assert = std.debug.assert;
45const mem = std.mem;
5const math = std.math;
66const testing = std.testing;
7const root = @import("root");
87
98pub const TrailerFlags = @import("meta/trailer_flags.zig").TrailerFlags;
109
......@@ -821,8 +820,8 @@ pub fn isError(error_union: anytype) bool {
821820}
822821
823822test isError {
824 try std.testing.expect(isError(math.divTrunc(u8, 5, 0)));
825 try std.testing.expect(!isError(math.divTrunc(u8, 5, 5)));
823 try std.testing.expect(isError(std.math.divTrunc(u8, 5, 0)));
824 try std.testing.expect(!isError(std.math.divTrunc(u8, 5, 5)));
826825}
827826
828827/// Returns true if a type has a namespace and the namespace contains `name`;
......@@ -1070,3 +1069,50 @@ test hasUniqueRepresentation {
10701069
10711070 try testing.expect(hasUniqueRepresentation(StructWithComptimeFields));
10721071}
1072
1073/// Given a pointer type, type-erases the array length if present, returning an
1074/// equivalent pointer type that is always a slice.
1075pub fn Slice(comptime Pointer: type) type {
1076 const info = @typeInfo(Pointer).pointer;
1077 switch (info.size) {
1078 .slice => return Pointer,
1079 .one => {
1080 const child_info = @typeInfo(info.child);
1081 comptime assert(child_info == .array);
1082 const sentinel_ptr: ?*const child_info.array.child = @ptrCast(@alignCast(child_info.array.sentinel_ptr));
1083 return @Pointer(
1084 .slice,
1085 info.attrs,
1086 child_info.array.child,
1087 if (sentinel_ptr) |ptr| ptr.* else null,
1088 );
1089 },
1090 else => unreachable,
1091 }
1092}
1093
1094/// Given a pointer type, removes the sentinel if present, returning an
1095/// equivalent pointer type with no sentinel
1096pub fn AbsorbSentinel(comptime Pointer: type) type {
1097 const info = @typeInfo(Pointer).pointer;
1098 switch (info.size) {
1099 .slice => return @Pointer(.slice, info.attrs, info.child, null),
1100 .one => {
1101 const child_info = @typeInfo(info.child).array;
1102 if (child_info.sentinel_ptr == null) {
1103 return Pointer;
1104 } else {
1105 return @Pointer(.one, info.attrs, [child_info.len + 1]child_info.child, null);
1106 }
1107 },
1108 else => unreachable,
1109 }
1110}
1111
1112test Slice {
1113 try testing.expectEqual([]i32, Slice(*[10]i32));
1114}
1115
1116test AbsorbSentinel {
1117 try testing.expectEqual(*[5]u32, AbsorbSentinel(*[4:0]u32));
1118}