ziglang / zig

General-purpose programming language and toolchain for maintaining robust, optimal, and reusable software.
https://ziglang.org
MIT License
33.68k stars 2.47k forks source link

Peer type resolution drops array sentinel #20901

Open roig opened 1 month ago

roig commented 1 month ago

Zig Version

0.12.0

Steps to Reproduce and Observed Behavior

I want to use an std.ArrayList with zero terminated arrays.

test "test" {
    var gpa = std.heap.GeneralPurposeAllocator(.{}){};
    defer _ = gpa.deinit();

    var list = std.ArrayList([16:0]u8).init(gpa.allocator());
    defer list.deinit();

    const new_buffer = try list.addOne(); // <<<<<- ERROR
    _ = new_buffer; // autofix
}

Error:

C:\dev\Zig\lib\std\mem\Allocator.zig:193:41: error: expected type 'error{OutOfMemory}![][16:0]u8', found 'error{OutOfMemory}![][16]u8'
    return self.allocAdvancedWithRetAddr(T, alignment, n, @returnAddress());
           ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
C:\dev\Zig\lib\std\mem\Allocator.zig:193:41: note: error union payload '[][16]u8' cannot cast into error union payload '[][16:0]u8'
C:\dev\Zig\lib\std\mem\Allocator.zig:193:41: note: pointer type child '[16]u8' cannot cast into pointer type child '[16:0]u8'
C:\dev\Zig\lib\std\mem\Allocator.zig:193:41: note: destination array requires '0' sentinel
C:\dev\Zig\lib\std\mem\Allocator.zig:192:8: note: function return type declared here
) Error![]align(alignment orelse @alignOf(T)) T {

Expected Behavior

I think we should be able to allocate these in an std.ArrayList It works correctly if the array is a member of an struct:

const Foo= struct {
    buff: [16:0]u8,
};
test "test with Struct" {
    var gpa = std.heap.GeneralPurposeAllocator(.{}){};
    defer _ = gpa.deinit();
    const ptr = try gpa.allocator().alloc(Foo, 1);
    defer gpa.allocator().free(ptr);
}
SnootierMoon commented 1 month ago

Seems to be an error in evaluating the return type for allocAdvancedWithRetAddr.

const std = @import("std");

pub fn main() !void {
    std.debug.print("{s}\n", .{@typeName(@TypeOf(std.mem.Allocator.allocAdvancedWithRetAddr(undefined, [34:0]u8, null, 1, @returnAddress())))});
    std.debug.print("{s}\n", .{@typeName(std.mem.Allocator.Error![]align(null orelse @alignOf([34:0]u8)) [34:0]u8)});
}

Output:

error{OutOfMemory}![][34]u8
error{OutOfMemory}![][34:0]u8
SnootierMoon commented 1 month ago

Here's a minimal repro of the underlying issue:

inline fn f(comptime T: type) error{E}![]T {
    try g();
    return undefined;
}

fn g() error{E}!void {}

comptime {
    @compileLog(@typeName(@TypeOf(f([34:0]u8))));
    // Compile Log Output:
    // @as(*const [17:0]u8, "error{E}![][34]u8")
    //
    // Should be:
    // @as(*const [19:0]u8, "error{E}![][34:0]u8")
}

In the context of std.mem.Allocator, f is allocAdvancedWithRetAddr and g is allocWithSizeAndAlignment.

roig commented 1 month ago

@Vexu This seems a compiler bug instead of an std one.

rohlem commented 1 month ago

Can also be replicated with another try instead of return, and a third breaks compilation:

fn g() error{E}!void {}
inline fn f() error{E}![][1:0]u8 {
    try g(); //with 1 `try` : error{E}![][1:0]u8
    try g(); //with 2 `try`s: error{E}![][1]u8
    try g(); //with 3 `try`s: `error: incompatible types: 'error{E}![][1:0]u8' and 'error{E}![][1:0]u8'`
    if (true) unreachable; //without any `try`: noreturn
}

comptime { //exact same behavior from a `test` block, so same when called in a runtime scope
    @compileLog(@TypeOf(f()));
}

Normal fn don't seem affected. I also suggest renaming the issue to reflect the reduced examples, f.e. "return type resolution error with try in inline fn". EDIT: Forgot to mention, I didn't manage to trigger any of this using @TypeOf - not sure whether that's possible.