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;...@@ -9,6 +9,7 @@ const assert = debug.assert;
9const math = std.math;9const math = std.math;
10const testing = std.testing;10const testing = std.testing;
11const Endian = std.lang.Endian;11const Endian = std.lang.Endian;
12const AbsorbSentinel = std.meta.AbsorbSentinel;
1213
13/// The standard library currently thoroughly depends on byte size14/// The standard library currently thoroughly depends on byte size
14/// being 8 bits. (see the use of u8 throughout allocation code as15/// being 8 bits. (see the use of u8 throughout allocation code as
...@@ -4738,22 +4739,28 @@ test "sliceAsBytes preserves pointer attributes" {...@@ -4738,22 +4739,28 @@ test "sliceAsBytes preserves pointer attributes" {
4738 try testing.expectEqual(in_attrs.@"align", out_attrs.@"align");4739 try testing.expectEqual(in_attrs.@"align", out_attrs.@"align");
4739}4740}
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
4747/// If the provided slice is not sentinel terminated, do nothing and return that slice.4742/// If the provided slice is not sentinel terminated, do nothing and return that slice.
4748/// If it is sentinel-terminated, return a non-sentinel-terminated slice with the4743/// If it is sentinel-terminated, return a non-sentinel-terminated slice with the
4749/// length increased by one to include the absorbed sentinel element.4744/// 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)) {
4751 const info = @typeInfo(@TypeOf(slice)).pointer;4746 const info = @typeInfo(@TypeOf(slice)).pointer;
4752 comptime assert(info.size == .slice);4747 switch (info.size) {
4753 if (info.sentinel_ptr == null) {4748 .slice => {
4754 return slice;4749 if (info.sentinel_ptr == null) {
4755 } else {4750 return slice;
4756 return slice.ptr[0 .. slice.len + 1];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,
4757 }4764 }
4758}4765}
47594766
...@@ -4762,21 +4769,28 @@ test absorbSentinel {...@@ -4762,21 +4769,28 @@ test absorbSentinel {
4762 var buffer: [3:0]u8 = .{ 1, 2, 3 };4769 var buffer: [3:0]u8 = .{ 1, 2, 3 };
4763 const foo: [:0]const u8 = &buffer;4770 const foo: [:0]const u8 = &buffer;
4764 const bar: []const u8 = &buffer;4771 const bar: []const u8 = &buffer;
4772 const baz: *const [3:0]u8 = &buffer;
4765 try testing.expectEqual([]const u8, @TypeOf(absorbSentinel(foo)));4773 try testing.expectEqual([]const u8, @TypeOf(absorbSentinel(foo)));
4766 try testing.expectEqual([]const u8, @TypeOf(absorbSentinel(bar)));4774 try testing.expectEqual([]const u8, @TypeOf(absorbSentinel(bar)));
4775 try testing.expectEqual(*const [4]u8, @TypeOf(absorbSentinel(baz)));
4767 try testing.expectEqualSlices(u8, &.{ 1, 2, 3, 0 }, absorbSentinel(foo));4776 try testing.expectEqualSlices(u8, &.{ 1, 2, 3, 0 }, absorbSentinel(foo));
4768 try testing.expectEqualSlices(u8, &.{ 1, 2, 3 }, absorbSentinel(bar));4777 try testing.expectEqualSlices(u8, &.{ 1, 2, 3 }, absorbSentinel(bar));
4778 try testing.expectEqualSlices(u8, &.{ 1, 2, 3, 0 }, absorbSentinel(baz));
4769 }4779 }
4770 {4780 {
4771 var buffer: [3:0]u8 = .{ 1, 2, 3 };4781 var buffer: [3:0]u8 = .{ 1, 2, 3 };
4772 const foo: [:0]u8 = &buffer;4782 const foo: [:0]u8 = &buffer;
4773 const bar: []u8 = &buffer;4783 const bar: []u8 = &buffer;
4784 const baz: *[3:0]u8 = &buffer;
4774 try testing.expectEqual([]u8, @TypeOf(absorbSentinel(foo)));4785 try testing.expectEqual([]u8, @TypeOf(absorbSentinel(foo)));
4775 try testing.expectEqual([]u8, @TypeOf(absorbSentinel(bar)));4786 try testing.expectEqual([]u8, @TypeOf(absorbSentinel(bar)));
4787 try testing.expectEqual(*[4]u8, @TypeOf(absorbSentinel(baz)));
4776 var expected_foo = [_]u8{ 1, 2, 3, 0 };4788 var expected_foo = [_]u8{ 1, 2, 3, 0 };
4777 try testing.expectEqualSlices(u8, &expected_foo, absorbSentinel(foo));4789 try testing.expectEqualSlices(u8, &expected_foo, absorbSentinel(foo));
4778 var expected_bar = [_]u8{ 1, 2, 3 };4790 var expected_bar = [_]u8{ 1, 2, 3 };
4779 try testing.expectEqualSlices(u8, &expected_bar, absorbSentinel(bar));4791 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));
4780 }4794 }
4781}4795}
47824796
lib/std/mem/Allocator.zig+53-10
...@@ -8,6 +8,8 @@ const assert = std.debug.assert;...@@ -8,6 +8,8 @@ const assert = std.debug.assert;
8const math = std.math;8const math = std.math;
9const mem = std.mem;9const mem = std.mem;
10const Alignment = std.mem.Alignment;10const Alignment = std.mem.Alignment;
11const Slice = std.meta.Slice;
12const AbsorbSentinel = std.meta.AbsorbSentinel;
1113
12pub const Error = error{OutOfMemory};14pub const Error = error{OutOfMemory};
13pub const Log2Align = math.Log2Int(usize);15pub const Log2Align = math.Log2Int(usize);
...@@ -316,8 +318,10 @@ pub fn allocBytesAligned(...@@ -316,8 +318,10 @@ pub fn allocBytesAligned(
316/// `new_len` may be zero, in which case the allocation is freed.318/// `new_len` may be zero, in which case the allocation is freed.
317pub fn resize(self: Allocator, allocation: anytype, new_len: usize) bool {319pub fn resize(self: Allocator, allocation: anytype, new_len: usize) bool {
318 const slice_info = @typeInfo(@TypeOf(allocation)).pointer;320 const slice_info = @typeInfo(@TypeOf(allocation)).pointer;
319 comptime assert(slice_info.size == .slice);321 const T = if (slice_info.size != .slice) comptime T: {
320 const T = slice_info.child;322 assert(slice_info.size == .one);
323 break :T @typeInfo(slice_info.child).array.child;
324 } else slice_info.child;
321 if (new_len == 0) {325 if (new_len == 0) {
322 self.free(allocation);326 self.free(allocation);
323 return true;327 return true;
...@@ -351,10 +355,12 @@ pub fn resize(self: Allocator, allocation: anytype, new_len: usize) bool {...@@ -351,10 +355,12 @@ pub fn resize(self: Allocator, allocation: anytype, new_len: usize) bool {
351/// `new_len` may be zero, in which case the allocation is freed.355/// `new_len` may be zero, in which case the allocation is freed.
352///356///
353/// If the allocation's elements' type is zero bytes sized, `allocation.len` is set to `new_len`.357/// 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))) {
355 const slice_info = @typeInfo(@TypeOf(allocation)).pointer;359 const slice_info = @typeInfo(@TypeOf(allocation)).pointer;
356 comptime assert(slice_info.size == .slice);360 const T = if (slice_info.size != .slice) comptime T: {
357 const T = slice_info.child;361 assert(slice_info.size == .one);
362 break :T @typeInfo(slice_info.child).array.child;
363 } else slice_info.child;
358364
359 if (new_len == 0) {365 if (new_len == 0) {
360 self.free(allocation);366 self.free(allocation);
...@@ -393,7 +399,7 @@ pub fn remap(self: Allocator, allocation: anytype, new_len: usize) ?@TypeOf(allo...@@ -393,7 +399,7 @@ pub fn remap(self: Allocator, allocation: anytype, new_len: usize) ?@TypeOf(allo
393/// do the realloc more efficiently than the caller399/// do the realloc more efficiently than the caller
394/// * `resize` which returns `false` when the `Allocator` implementation cannot400/// * `resize` which returns `false` when the `Allocator` implementation cannot
395/// change the size without relocating the allocation.401/// 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))) {
397 return self.reallocAdvanced(old_mem, new_n, @returnAddress());403 return self.reallocAdvanced(old_mem, new_n, @returnAddress());
398}404}
399405
...@@ -402,10 +408,12 @@ pub fn reallocAdvanced(...@@ -402,10 +408,12 @@ pub fn reallocAdvanced(
402 old_mem: anytype,408 old_mem: anytype,
403 new_n: usize,409 new_n: usize,
404 return_address: usize,410 return_address: usize,
405) Error!@TypeOf(old_mem) {411) Error!Slice(AbsorbSentinel(@TypeOf(old_mem))) {
406 const slice_info = @typeInfo(@TypeOf(old_mem)).pointer;412 const slice_info = @typeInfo(@TypeOf(old_mem)).pointer;
407 comptime assert(slice_info.size == .slice);413 const T = if (slice_info.size != .slice) comptime T: {
408 const T = slice_info.child;414 assert(slice_info.size == .one);
415 break :T @typeInfo(slice_info.child).array.child;
416 } else slice_info.child;
409 if (old_mem.len == 0) {417 if (old_mem.len == 0) {
410 return self.allocAdvancedWithRetAddr(T, .fromByteUnitsOptional(slice_info.attrs.@"align"), new_n, return_address);418 return self.allocAdvancedWithRetAddr(T, .fromByteUnitsOptional(slice_info.attrs.@"align"), new_n, return_address);
411 }419 }
...@@ -440,7 +448,6 @@ pub fn reallocAdvanced(...@@ -440,7 +448,6 @@ pub fn reallocAdvanced(
440pub fn free(self: Allocator, memory: anytype) void {448pub fn free(self: Allocator, memory: anytype) void {
441 const slice_info = @typeInfo(@TypeOf(memory)).pointer;449 const slice_info = @typeInfo(@TypeOf(memory)).pointer;
442 if (slice_info.size != .slice) {450 if (slice_info.size != .slice) {
443 // slicing with comptime-known start and end results in *[len]T, which may be free'd
444 comptime assert(slice_info.size == .one and @typeInfo(slice_info.child) == .array);451 comptime assert(slice_info.size == .one and @typeInfo(slice_info.child) == .array);
445 }452 }
446 const bytes: []u8 = @ptrCast(@constCast(mem.absorbSentinel(memory)));453 const bytes: []u8 = @ptrCast(@constCast(mem.absorbSentinel(memory)));
...@@ -585,3 +592,39 @@ test failing {...@@ -585,3 +592,39 @@ test failing {
585 try std.testing.expectError(error.OutOfMemory, f.alloc(u8, std.math.maxInt(usize)));592 try std.testing.expectError(error.OutOfMemory, f.alloc(u8, std.math.maxInt(usize)));
586 try std.testing.expectError(error.OutOfMemory, f.allocSentinel(u8, std.math.maxInt(usize) - 1, 0));593 try std.testing.expectError(error.OutOfMemory, f.allocSentinel(u8, std.math.maxInt(usize) - 1, 0));
587}594}
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 @@...@@ -1,10 +1,9 @@
1const builtin = @import("builtin");1const builtin = @import("builtin");
2
2const std = @import("std.zig");3const std = @import("std.zig");
3const debug = std.debug;4const assert = std.debug.assert;
4const mem = std.mem;5const mem = std.mem;
5const math = std.math;
6const testing = std.testing;6const testing = std.testing;
7const root = @import("root");
87
9pub const TrailerFlags = @import("meta/trailer_flags.zig").TrailerFlags;8pub const TrailerFlags = @import("meta/trailer_flags.zig").TrailerFlags;
109
...@@ -821,8 +820,8 @@ pub fn isError(error_union: anytype) bool {...@@ -821,8 +820,8 @@ pub fn isError(error_union: anytype) bool {
821}820}
822821
823test isError {822test isError {
824 try std.testing.expect(isError(math.divTrunc(u8, 5, 0)));823 try std.testing.expect(isError(std.math.divTrunc(u8, 5, 0)));
825 try std.testing.expect(!isError(math.divTrunc(u8, 5, 5)));824 try std.testing.expect(!isError(std.math.divTrunc(u8, 5, 5)));
826}825}
827826
828/// Returns true if a type has a namespace and the namespace contains `name`;827/// Returns true if a type has a namespace and the namespace contains `name`;
...@@ -1070,3 +1069,50 @@ test hasUniqueRepresentation {...@@ -1070,3 +1069,50 @@ test hasUniqueRepresentation {
10701069
1071 try testing.expect(hasUniqueRepresentation(StructWithComptimeFields));1070 try testing.expect(hasUniqueRepresentation(StructWithComptimeFields));
1072}1071}
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}