diff options
| author | Alexandros Naskos <alex_naskos@hotmail.com> | 2020-09-29 16:19:44 +0300 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2020-09-29 16:19:44 +0300 |
| commit | d27a34f05c8c78aa9a9a6f2a30d4db1468b50999 (patch) | |
| tree | 4aea123e0325d8ce3569305d886f2a3fb1355b08 /lib/std | |
| parent | 55dfe729b480c2ba121c6f650d160921560d9535 (diff) | |
| parent | 3342e28784b9ef7bf8356004a7b2698edcb70b40 (diff) | |
| download | zig-d27a34f05c8c78aa9a9a6f2a30d4db1468b50999.tar.gz zig-d27a34f05c8c78aa9a9a6f2a30d4db1468b50999.zip | |
Merge branch 'master' into args-tuple
Diffstat (limited to 'lib/std')
| -rw-r--r-- | lib/std/event/future.zig | 2 | ||||
| -rw-r--r-- | lib/std/event/lock.zig | 205 | ||||
| -rw-r--r-- | lib/std/event/loop.zig | 416 | ||||
| -rw-r--r-- | lib/std/fifo.zig | 12 | ||||
| -rw-r--r-- | lib/std/fs/file.zig | 64 | ||||
| -rw-r--r-- | lib/std/fs/test.zig | 23 | ||||
| -rw-r--r-- | lib/std/heap.zig | 7 | ||||
| -rw-r--r-- | lib/std/heap/arena_allocator.zig | 21 | ||||
| -rw-r--r-- | lib/std/meta.zig | 85 | ||||
| -rw-r--r-- | lib/std/net.zig | 50 | ||||
| -rw-r--r-- | lib/std/os.zig | 124 | ||||
| -rw-r--r-- | lib/std/os/uefi/tables/system_table.zig | 2 | ||||
| -rw-r--r-- | lib/std/os/windows.zig | 18 | ||||
| -rw-r--r-- | lib/std/zig/ast.zig | 9 | ||||
| -rw-r--r-- | lib/std/zig/parser_test.zig | 328 | ||||
| -rw-r--r-- | lib/std/zig/render.zig | 380 | ||||
| -rw-r--r-- | lib/std/zig/tokenizer.zig | 9 |
17 files changed, 1237 insertions, 518 deletions
diff --git a/lib/std/event/future.zig b/lib/std/event/future.zig index c9777288e4..40c7845d53 100644 --- a/lib/std/event/future.zig +++ b/lib/std/event/future.zig @@ -95,7 +95,7 @@ test "std.event.Future" { // TODO provide a way to run tests in evented I/O mode if (!std.io.is_async) return error.SkipZigTest; - const handle = async testFuture(); + testFuture(); } fn testFuture() void { diff --git a/lib/std/event/lock.zig b/lib/std/event/lock.zig index d27a12aef8..6819e413d2 100644 --- a/lib/std/event/lock.zig +++ b/lib/std/event/lock.zig @@ -16,107 +16,111 @@ const Loop = std.event.Loop; /// Allows only one actor to hold the lock. /// TODO: make this API also work in blocking I/O mode. pub const Lock = struct { - shared: bool, - queue: Queue, - queue_empty: bool, + mutex: std.Mutex = std.Mutex{}, + head: usize = UNLOCKED, - const Queue = std.atomic.Queue(anyframe); + const UNLOCKED = 0; + const LOCKED = 1; const global_event_loop = Loop.instance orelse @compileError("std.event.Lock currently only works with event-based I/O"); - pub const Held = struct { - lock: *Lock, - - pub fn release(self: Held) void { - // Resume the next item from the queue. - if (self.lock.queue.get()) |node| { - global_event_loop.onNextTick(node); - return; - } - - // We need to release the lock. - @atomicStore(bool, &self.lock.queue_empty, true, .SeqCst); - @atomicStore(bool, &self.lock.shared, false, .SeqCst); - - // There might be a queue item. If we know the queue is empty, we can be done, - // because the other actor will try to obtain the lock. - // But if there's a queue item, we are the actor which must loop and attempt - // to grab the lock again. - if (@atomicLoad(bool, &self.lock.queue_empty, .SeqCst)) { - return; - } - - while (true) { - if (@atomicRmw(bool, &self.lock.shared, .Xchg, true, .SeqCst)) { - // We did not obtain the lock. Great, the queue is someone else's problem. - return; - } - - // Resume the next item from the queue. - if (self.lock.queue.get()) |node| { - global_event_loop.onNextTick(node); - return; - } + const Waiter = struct { + // forced Waiter alignment to ensure it doesn't clash with LOCKED + next: ?*Waiter align(2), + tail: *Waiter, + node: Loop.NextTickNode, + }; - // Release the lock again. - @atomicStore(bool, &self.lock.queue_empty, true, .SeqCst); - @atomicStore(bool, &self.lock.shared, false, .SeqCst); + pub fn initLocked() Lock { + return Lock{ .head = LOCKED }; + } - // Find out if we can be done. - if (@atomicLoad(bool, &self.lock.queue_empty, .SeqCst)) { - return; - } - } + pub fn acquire(self: *Lock) Held { + const held = self.mutex.acquire(); + + // self.head transitions from multiple stages depending on the value: + // UNLOCKED -> LOCKED: + // acquire Lock ownership when theres no waiters + // LOCKED -> <Waiter head ptr>: + // Lock is already owned, enqueue first Waiter + // <head ptr> -> <head ptr>: + // Lock is owned with pending waiters. Push our waiter to the queue. + + if (self.head == UNLOCKED) { + self.head = LOCKED; + held.release(); + return Held{ .lock = self }; } - }; - pub fn init() Lock { - return Lock{ - .shared = false, - .queue = Queue.init(), - .queue_empty = true, - }; - } + var waiter: Waiter = undefined; + waiter.next = null; + waiter.tail = &waiter; - pub fn initLocked() Lock { - return Lock{ - .shared = true, - .queue = Queue.init(), - .queue_empty = true, + const head = switch (self.head) { + UNLOCKED => unreachable, + LOCKED => null, + else => @intToPtr(*Waiter, self.head), }; - } - /// Must be called when not locked. Not thread safe. - /// All calls to acquire() and release() must complete before calling deinit(). - pub fn deinit(self: *Lock) void { - assert(!self.shared); - while (self.queue.get()) |node| resume node.data; - } - - pub fn acquire(self: *Lock) callconv(.Async) Held { - var my_tick_node = Loop.NextTickNode.init(@frame()); + if (head) |h| { + h.tail.next = &waiter; + h.tail = &waiter; + } else { + self.head = @ptrToInt(&waiter); + } - errdefer _ = self.queue.remove(&my_tick_node); // TODO test canceling an acquire suspend { - self.queue.put(&my_tick_node); + waiter.node = Loop.NextTickNode{ + .prev = undefined, + .next = undefined, + .data = @frame(), + }; + held.release(); + } - // At this point, we are in the queue, so we might have already been resumed. + return Held{ .lock = self }; + } - // We set this bit so that later we can rely on the fact, that if queue_empty == true, some actor - // will attempt to grab the lock. - @atomicStore(bool, &self.queue_empty, false, .SeqCst); + pub const Held = struct { + lock: *Lock, - if (!@atomicRmw(bool, &self.shared, .Xchg, true, .SeqCst)) { - if (self.queue.get()) |node| { - // Whether this node is us or someone else, we tail resume it. - resume node.data; + pub fn release(self: Held) void { + const waiter = blk: { + const held = self.lock.mutex.acquire(); + defer held.release(); + + // self.head goes through the reverse transition from acquire(): + // <head ptr> -> <new head ptr>: + // pop a waiter from the queue to give Lock ownership when theres still others pending + // <head ptr> -> LOCKED: + // pop the laster waiter from the queue, while also giving it lock ownership when awaken + // LOCKED -> UNLOCKED: + // last lock owner releases lock while no one else is waiting for it + + switch (self.lock.head) { + UNLOCKED => { + unreachable; // Lock unlocked while unlocking + }, + LOCKED => { + self.lock.head = UNLOCKED; + break :blk null; + }, + else => { + const waiter = @intToPtr(*Waiter, self.lock.head); + self.lock.head = if (waiter.next == null) LOCKED else @ptrToInt(waiter.next); + if (waiter.next) |next| + next.tail = waiter.tail; + break :blk waiter; + }, } + }; + + if (waiter) |w| { + global_event_loop.onNextTick(&w.node); } } - - return Held{ .lock = self }; - } + }; }; test "std.event.Lock" { @@ -128,41 +132,16 @@ test "std.event.Lock" { // TODO https://github.com/ziglang/zig/issues/3251 if (builtin.os.tag == .freebsd) return error.SkipZigTest; - // TODO this file has bit-rotted. repair it - if (true) return error.SkipZigTest; - - var lock = Lock.init(); - defer lock.deinit(); - - _ = async testLock(&lock); + var lock = Lock{}; + testLock(&lock); const expected_result = [1]i32{3 * @intCast(i32, shared_test_data.len)} ** shared_test_data.len; testing.expectEqualSlices(i32, &expected_result, &shared_test_data); } -fn testLock(lock: *Lock) callconv(.Async) void { +fn testLock(lock: *Lock) void { var handle1 = async lockRunner(lock); - var tick_node1 = Loop.NextTickNode{ - .prev = undefined, - .next = undefined, - .data = &handle1, - }; - Loop.instance.?.onNextTick(&tick_node1); - var handle2 = async lockRunner(lock); - var tick_node2 = Loop.NextTickNode{ - .prev = undefined, - .next = undefined, - .data = &handle2, - }; - Loop.instance.?.onNextTick(&tick_node2); - var handle3 = async lockRunner(lock); - var tick_node3 = Loop.NextTickNode{ - .prev = undefined, - .next = undefined, - .data = &handle3, - }; - Loop.instance.?.onNextTick(&tick_node3); await handle1; await handle2; @@ -171,13 +150,13 @@ fn testLock(lock: *Lock) callconv(.Async) void { var shared_test_data = [1]i32{0} ** 10; var shared_test_index: usize = 0; -fn lockRunner(lock: *Lock) callconv(.Async) void { - suspend; // resumed by onNextTick + +fn lockRunner(lock: *Lock) void { + Lock.global_event_loop.yield(); var i: usize = 0; while (i < shared_test_data.len) : (i += 1) { - var lock_frame = async lock.acquire(); - const handle = await lock_frame; + const handle = lock.acquire(); defer handle.release(); shared_test_index = 0; diff --git a/lib/std/event/loop.zig b/lib/std/event/loop.zig index 13e704a8d3..2ed9f938d8 100644 --- a/lib/std/event/loop.zig +++ b/lib/std/event/loop.zig @@ -721,6 +721,50 @@ pub const Loop = struct { } } + /// ------- I/0 APIs ------- + pub fn accept( + self: *Loop, + /// This argument is a socket that has been created with `socket`, bound to a local address + /// with `bind`, and is listening for connections after a `listen`. + sockfd: os.fd_t, + /// This argument is a pointer to a sockaddr structure. This structure is filled in with the + /// address of the peer socket, as known to the communications layer. The exact format of the + /// address returned addr is determined by the socket's address family (see `socket` and the + /// respective protocol man pages). + addr: *os.sockaddr, + /// This argument is a value-result argument: the caller must initialize it to contain the + /// size (in bytes) of the structure pointed to by addr; on return it will contain the actual size + /// of the peer address. + /// + /// The returned address is truncated if the buffer provided is too small; in this case, `addr_size` + /// will return a value greater than was supplied to the call. + addr_size: *os.socklen_t, + /// The following values can be bitwise ORed in flags to obtain different behavior: + /// * `SOCK_CLOEXEC` - Set the close-on-exec (`FD_CLOEXEC`) flag on the new file descriptor. See the + /// description of the `O_CLOEXEC` flag in `open` for reasons why this may be useful. + flags: u32, + ) os.AcceptError!os.fd_t { + while (true) { + return os.accept(sockfd, addr, addr_size, flags | os.SOCK_NONBLOCK) catch |err| switch (err) { + error.WouldBlock => { + self.waitUntilFdReadable(sockfd); + continue; + }, + else => return err, + }; + } + } + + pub fn connect(self: *Loop, sockfd: os.socket_t, sock_addr: *const os.sockaddr, len: os.socklen_t) os.ConnectError!void { + os.connect(sockfd, sock_addr, len) catch |err| switch (err) { + error.WouldBlock => { + self.waitUntilFdWritable(sockfd); + return os.getsockoptError(sockfd); + }, + else => return err, + }; + } + /// Performs an async `os.open` using a separate thread. pub fn openZ(self: *Loop, file_path: [*:0]const u8, flags: u32, mode: os.mode_t) os.OpenError!os.fd_t { var req_node = Request.Node{ @@ -779,152 +823,309 @@ pub const Loop = struct { /// Performs an async `os.read` using a separate thread. /// `fd` must block and not return EAGAIN. - pub fn read(self: *Loop, fd: os.fd_t, buf: []u8) os.ReadError!usize { - var req_node = Request.Node{ - .data = .{ - .msg = .{ - .read = .{ - .fd = fd, - .buf = buf, - .result = undefined, + pub fn read(self: *Loop, fd: os.fd_t, buf: []u8, simulate_evented: bool) os.ReadError!usize { + if (simulate_evented) { + var req_node = Request.Node{ + .data = .{ + .msg = .{ + .read = .{ + .fd = fd, + .buf = buf, + .result = undefined, + }, }, + .finish = .{ .TickNode = .{ .data = @frame() } }, }, - .finish = .{ .TickNode = .{ .data = @frame() } }, - }, - }; - suspend { - self.posixFsRequest(&req_node); + }; + suspend { + self.posixFsRequest(&req_node); + } + return req_node.data.msg.read.result; + } else { + while (true) { + return os.read(fd, buf) catch |err| switch (err) { + error.WouldBlock => { + self.waitUntilFdReadable(fd); + continue; + }, + else => return err, + }; + } } - return req_node.data.msg.read.result; } /// Performs an async `os.readv` using a separate thread. /// `fd` must block and not return EAGAIN. - pub fn readv(self: *Loop, fd: os.fd_t, iov: []const os.iovec) os.ReadError!usize { - var req_node = Request.Node{ - .data = .{ - .msg = .{ - .readv = .{ - .fd = fd, - .iov = iov, - .result = undefined, + pub fn readv(self: *Loop, fd: os.fd_t, iov: []const os.iovec, simulate_evented: bool) os.ReadError!usize { + if (simulate_evented) { + var req_node = Request.Node{ + .data = .{ + .msg = .{ + .readv = .{ + .fd = fd, + .iov = iov, + .result = undefined, + }, }, + .finish = .{ .TickNode = .{ .data = @frame() } }, }, - .finish = .{ .TickNode = .{ .data = @frame() } }, - }, - }; - suspend { - self.posixFsRequest(&req_node); + }; + suspend { + self.posixFsRequest(&req_node); + } + return req_node.data.msg.readv.result; + } else { + while (true) { + return os.readv(fd, iov) catch |err| switch (err) { + error.WouldBlock => { + self.waitUntilFdReadable(fd); + continue; + }, + else => return err, + }; + } } - return req_node.data.msg.readv.result; } /// Performs an async `os.pread` using a separate thread. /// `fd` must block and not return EAGAIN. - pub fn pread(self: *Loop, fd: os.fd_t, buf: []u8, offset: u64) os.PReadError!usize { - var req_node = Request.Node{ - .data = .{ - .msg = .{ - .pread = .{ - .fd = fd, - .buf = buf, - .offset = offset, - .result = undefined, + pub fn pread(self: *Loop, fd: os.fd_t, buf: []u8, offset: u64, simulate_evented: bool) os.PReadError!usize { + if (simulate_evented) { + var req_node = Request.Node{ + .data = .{ + .msg = .{ + .pread = .{ + .fd = fd, + .buf = buf, + .offset = offset, + .result = undefined, + }, }, + .finish = .{ .TickNode = .{ .data = @frame() } }, }, - .finish = .{ .TickNode = .{ .data = @frame() } }, - }, - }; - suspend { - self.posixFsRequest(&req_node); + }; + suspend { + self.posixFsRequest(&req_node); + } + return req_node.data.msg.pread.result; + } else { + while (true) { + return os.pread(fd, buf, offset) catch |err| switch (err) { + error.WouldBlock => { + self.waitUntilFdReadable(fd); + continue; + }, + else => return err, + }; + } } - return req_node.data.msg.pread.result; } /// Performs an async `os.preadv` using a separate thread. /// `fd` must block and not return EAGAIN. - pub fn preadv(self: *Loop, fd: os.fd_t, iov: []const os.iovec, offset: u64) os.ReadError!usize { - var req_node = Request.Node{ - .data = .{ - .msg = .{ - .preadv = .{ - .fd = fd, - .iov = iov, - .offset = offset, - .result = undefined, + pub fn preadv(self: *Loop, fd: os.fd_t, iov: []const os.iovec, offset: u64, simulate_evented: bool) os.ReadError!usize { + if (simulate_evented) { + var req_node = Request.Node{ + .data = .{ + .msg = .{ + .preadv = .{ + .fd = fd, + .iov = iov, + .offset = offset, + .result = undefined, + }, }, + .finish = .{ .TickNode = .{ .data = @frame() } }, }, - .finish = .{ .TickNode = .{ .data = @frame() } }, - }, - }; - suspend { - self.posixFsRequest(&req_node); + }; + suspend { + self.posixFsRequest(&req_node); + } + return req_node.data.msg.preadv.result; + } else { + while (true) { + return os.preadv(fd, iov, offset) catch |err| switch (err) { + error.WouldBlock => { + self.waitUntilFdReadable(fd); + continue; + }, + else => return err, + }; + } } - return req_node.data.msg.preadv.result; } /// Performs an async `os.write` using a separate thread. /// `fd` must block and not return EAGAIN. - pub fn write(self: *Loop, fd: os.fd_t, bytes: []const u8) os.WriteError!usize { - var req_node = Request.Node{ - .data = .{ - .msg = .{ - .write = .{ - .fd = fd, - .bytes = bytes, - .result = undefined, + pub fn write(self: *Loop, fd: os.fd_t, bytes: []const u8, simulate_evented: bool) os.WriteError!usize { + if (simulate_evented) { + var req_node = Request.Node{ + .data = .{ + .msg = .{ + .write = .{ + .fd = fd, + .bytes = bytes, + .result = undefined, + }, }, + .finish = .{ .TickNode = .{ .data = @frame() } }, }, - .finish = .{ .TickNode = .{ .data = @frame() } }, - }, - }; - suspend { - self.posixFsRequest(&req_node); + }; + suspend { + self.posixFsRequest(&req_node); + } + return req_node.data.msg.write.result; + } else { + while (true) { + return os.write(fd, bytes) catch |err| switch (err) { + error.WouldBlock => { + self.waitUntilFdWritable(fd); + continue; + }, + else => return err, + }; + } } - return req_node.data.msg.write.result; } /// Performs an async `os.writev` using a separate thread. /// `fd` must block and not return EAGAIN. - pub fn writev(self: *Loop, fd: os.fd_t, iov: []const os.iovec_const) os.WriteError!usize { - var req_node = Request.Node{ - .data = .{ - .msg = .{ - .writev = .{ - .fd = fd, - .iov = iov, - .result = undefined, + pub fn writev(self: *Loop, fd: os.fd_t, iov: []const os.iovec_const, simulate_evented: bool) os.WriteError!usize { + if (simulate_evented) { + var req_node = Request.Node{ + .data = .{ + .msg = .{ + .writev = .{ + .fd = fd, + .iov = iov, + .result = undefined, + }, }, + .finish = .{ .TickNode = .{ .data = @frame() } }, }, - .finish = .{ .TickNode = .{ .data = @frame() } }, - }, - }; - suspend { - self.posixFsRequest(&req_node); + }; + suspend { + self.posixFsRequest(&req_node); + } + return req_node.data.msg.writev.result; + } else { + while (true) { + return os.writev(fd, iov) catch |err| switch (err) { + error.WouldBlock => { + self.waitUntilFdWritable(fd); + continue; + }, + else => return err, + }; + } + } + } + + /// Performs an async `os.pwrite` using a separate thread. + /// `fd` must block and not return EAGAIN. + pub fn pwrite(self: *Loop, fd: os.fd_t, bytes: []const u8, offset: u64, simulate_evented: bool) os.PerformsWriteError!usize { + if (simulate_evented) { + var req_node = Request.Node{ + .data = .{ + .msg = .{ + .pwrite = .{ + .fd = fd, + .bytes = bytes, + .offset = offset, + .result = undefined, + }, + }, + .finish = .{ .TickNode = .{ .data = @frame() } }, + }, + }; + suspend { + self.posixFsRequest(&req_node); + } + return req_node.data.msg.pwrite.result; + } else { + while (true) { + return os.pwrite(fd, bytes, offset) catch |err| switch (err) { + error.WouldBlock => { + self.waitUntilFdWritable(fd); + continue; + }, + else => return err, + }; + } } - return req_node.data.msg.writev.result; } /// Performs an async `os.pwritev` using a separate thread. /// `fd` must block and not return EAGAIN. - pub fn pwritev(self: *Loop, fd: os.fd_t, iov: []const os.iovec_const, offset: u64) os.WriteError!usize { - var req_node = Request.Node{ - .data = .{ - .msg = .{ - .pwritev = .{ - .fd = fd, - .iov = iov, - .offset = offset, - .result = undefined, + pub fn pwritev(self: *Loop, fd: os.fd_t, iov: []const os.iovec_const, offset: u64, simulate_evented: bool) os.PWriteError!usize { + if (simulate_evented) { + var req_node = Request.Node{ + .data = .{ + .msg = .{ + .pwritev = .{ + .fd = fd, + .iov = iov, + .offset = offset, + .result = undefined, + }, }, + .finish = .{ .TickNode = .{ .data = @frame() } }, }, - .finish = .{ .TickNode = .{ .data = @frame() } }, - }, - }; - suspend { - self.posixFsRequest(&req_node); + }; + suspend { + self.posixFsRequest(&req_node); + } + return req_node.data.msg.pwritev.result; + } else { + while (true) { + return os.pwritev(fd, iov, offset) catch |err| switch (err) { + error.WouldBlock => { + self.waitUntilFdWritable(fd); + continue; + }, + else => return err, + }; + } + } + } + + pub fn sendto( + self: *Loop, + /// The file descriptor of the sending socket. + sockfd: os.fd_t, + /// Message to send. + buf: []const u8, + flags: u32, + dest_addr: ?*const os.sockaddr, + addrlen: os.socklen_t, + ) os.SendError!usize { + while (true) { + return os.sendto(sockfd, buf, flags, dest_addr, addrlen) catch |err| switch (err) { + error.WouldBlock => { + self.waitUntilFdWritable(sockfd); + continue; + }, + else => return err, + }; + } + } + + pub fn recvfrom( + sockfd: os.fd_t, + buf: []u8, + flags: u32, + src_addr: ?*os.sockaddr, + addrlen: ?*os.socklen_t, + ) os.RecvFromError!usize { + while (true) { + return os.recvfrom(sockfd, buf, flags, src_addr, addrlen) catch |err| switch (err) { + error.WouldBlock => { + self.waitUntilFdReadable(sockfd); + continue; + }, + else => return err, + }; } - return req_node.data.msg.pwritev.result; } /// Performs an async `os.faccessatZ` using a separate thread. @@ -1079,6 +1280,9 @@ pub const Loop = struct { .writev => |*msg| { msg.result = os.writev(msg.fd, msg.iov); }, + .pwrite => |*msg| { + msg.result = os.pwrite(msg.fd, msg.bytes, msg.offset); + }, .pwritev => |*msg| { msg.result = os.pwritev(msg.fd, msg.iov, msg.offset); }, @@ -1148,6 +1352,7 @@ pub const Loop = struct { readv: ReadV, write: Write, writev: WriteV, + pwrite: PWrite, pwritev: PWriteV, pread: PRead, preadv: PReadV, @@ -1191,6 +1396,15 @@ pub const Loop = struct { pub const Error = os.WriteError; }; + pub const PWrite = struct { + fd: os.fd_t, + bytes: []const u8, + offset: usize, + result: Error!usize, + + pub const Error = os.PWriteError; + }; + pub const PWriteV = struct { fd: os.fd_t, iov: []const os.iovec_const, diff --git a/lib/std/fifo.zig b/lib/std/fifo.zig index c92da615f9..91d4c0330b 100644 --- a/lib/std/fifo.zig +++ b/lib/std/fifo.zig @@ -186,7 +186,9 @@ pub fn LinearFifo( } else { var head = self.head + count; if (powers_of_two) { - head &= self.buf.len - 1; + // Note it is safe to do a wrapping subtract as + // bitwise & with all 1s is a noop + head &= self.buf.len -% 1; } else { head %= self.buf.len; } @@ -376,6 +378,14 @@ pub fn LinearFifo( }; } +test "LinearFifo(u8, .Dynamic) discard(0) from empty buffer should not error on overflow" { + var fifo = LinearFifo(u8, .Dynamic).init(testing.allocator); + defer fifo.deinit(); + + // If overflow is not explicitly allowed this will crash in debug / safe mode + fifo.discard(0); +} + test "LinearFifo(u8, .Dynamic)" { var fifo = LinearFifo(u8, .Dynamic).init(testing.allocator); defer fifo.deinit(); diff --git a/lib/std/fs/file.zig b/lib/std/fs/file.zig index 73babf5fa2..8d4f5df2e8 100644 --- a/lib/std/fs/file.zig +++ b/lib/std/fs/file.zig @@ -414,10 +414,12 @@ pub const File = struct { pub fn read(self: File, buffer: []u8) ReadError!usize { if (is_windows) { return windows.ReadFile(self.handle, buffer, null, self.intended_io_mode); - } else if (self.capable_io_mode != self.intended_io_mode) { - return std.event.Loop.instance.?.read(self.handle, buffer); - } else { + } + + if (self.intended_io_mode == .blocking) { return os.read(self.handle, buffer); + } else { + return std.event.Loop.instance.?.read(self.handle, buffer, self.capable_io_mode != self.intended_io_mode); } } @@ -436,10 +438,12 @@ pub const File = struct { pub fn pread(self: File, buffer: []u8, offset: u64) PReadError!usize { if (is_windows) { return windows.ReadFile(self.handle, buffer, offset, self.intended_io_mode); - } else if (self.capable_io_mode != self.intended_io_mode) { - return std.event.Loop.instance.?.pread(self.handle, buffer, offset); - } else { + } + + if (self.intended_io_mode == .blocking) { return os.pread(self.handle, buffer, offset); + } else { + return std.event.Loop.instance.?.pread(self.handle, buffer, offset, self.capable_io_mode != self.intended_io_mode); } } @@ -461,10 +465,12 @@ pub const File = struct { if (iovecs.len == 0) return @as(usize, 0); const first = iovecs[0]; return windows.ReadFile(self.handle, first.iov_base[0..first.iov_len], null, self.intended_io_mode); - } else if (self.capable_io_mode != self.intended_io_mode) { - return std.event.Loop.instance.?.readv(self.handle, iovecs); - } else { + } + + if (self.intended_io_mode == .blocking) { return os.readv(self.handle, iovecs); + } else { + return std.event.Loop.instance.?.readv(self.handle, iovecs, self.capable_io_mode != self.intended_io_mode); } } @@ -500,10 +506,12 @@ pub const File = struct { if (iovecs.len == 0) return @as(usize, 0); const first = iovecs[0]; return windows.ReadFile(self.handle, first.iov_base[0..first.iov_len], offset, self.intended_io_mode); - } else if (self.capable_io_mode != self.intended_io_mode) { - return std.event.Loop.instance.?.preadv(self.handle, iovecs, offset); - } else { + } + + if (self.intended_io_mode == .blocking) { return os.preadv(self.handle, iovecs, offset); + } else { + return std.event.Loop.instance.?.preadv(self.handle, iovecs, offset, self.capable_io_mode != self.intended_io_mode); } } @@ -539,10 +547,12 @@ pub const File = struct { pub fn write(self: File, bytes: []const u8) WriteError!usize { if (is_windows) { return windows.WriteFile(self.handle, bytes, null, self.intended_io_mode); - } else if (self.capable_io_mode != self.intended_io_mode) { - return std.event.Loop.instance.?.write(self.handle, bytes); - } else { + } + + if (self.intended_io_mode == .blocking) { return os.write(self.handle, bytes); + } else { + return std.event.Loop.instance.?.write(self.handle, bytes, self.capable_io_mode != self.intended_io_mode); } } @@ -556,10 +566,12 @@ pub const File = struct { pub fn pwrite(self: File, bytes: []const u8, offset: u64) PWriteError!usize { if (is_windows) { return windows.WriteFile(self.handle, bytes, offset, self.intended_io_mode); - } else if (self.capable_io_mode != self.intended_io_mode) { - return std.event.Loop.instance.?.pwrite(self.handle, bytes, offset); - } else { + } + + if (self.intended_io_mode == .blocking) { return os.pwrite(self.handle, bytes, offset); + } else { + return std.event.Loop.instance.?.pwrite(self.handle, bytes, offset, self.capable_io_mode != self.intended_io_mode); } } @@ -576,10 +588,12 @@ pub const File = struct { if (iovecs.len == 0) return @as(usize, 0); const first = iovecs[0]; return windows.WriteFile(self.handle, first.iov_base[0..first.iov_len], null, self.intended_io_mode); - } else if (self.capable_io_mode != self.intended_io_mode) { - return std.event.Loop.instance.?.writev(self.handle, iovecs); - } else { + } + + if (self.intended_io_mode == .blocking) { return os.writev(self.handle, iovecs); + } else { + return std.event.Loop.instance.?.writev(self.handle, iovecs, self.capable_io_mode != self.intended_io_mode); } } @@ -607,10 +621,12 @@ pub const File = struct { if (iovecs.len == 0) return @as(usize, 0); const first = iovecs[0]; return windows.WriteFile(self.handle, first.iov_base[0..first.iov_len], offset, self.intended_io_mode); - } else if (self.capable_io_mode != self.intended_io_mode) { - return std.event.Loop.instance.?.pwritev(self.handle, iovecs, offset); - } else { + } + + if (self.intended_io_mode == .blocking) { return os.pwritev(self.handle, iovecs, offset); + } else { + return std.event.Loop.instance.?.pwritev(self.handle, iovecs, offset, self.capable_io_mode != self.intended_io_mode); } } diff --git a/lib/std/fs/test.zig b/lib/std/fs/test.zig index b3cc1fe569..8d7ef5172e 100644 --- a/lib/std/fs/test.zig +++ b/lib/std/fs/test.zig @@ -813,3 +813,26 @@ fn run_lock_file_test(contexts: []FileLockTestContext) !void { try threads.append(try std.Thread.spawn(ctx, FileLockTestContext.run)); } } + +test "deleteDir" { + var tmp_dir = tmpDir(.{}); + defer tmp_dir.cleanup(); + + // deleting a non-existent directory + testing.expectError(error.FileNotFound, tmp_dir.dir.deleteDir("test_dir")); + + var dir = try tmp_dir.dir.makeOpenPath("test_dir", .{}); + var file = try dir.createFile("test_file", .{}); + file.close(); + dir.close(); + + // deleting a non-empty directory + testing.expectError(error.DirNotEmpty, tmp_dir.dir.deleteDir("test_dir")); + + dir = try tmp_dir.dir.openDir("test_dir", .{}); + try dir.deleteFile("test_file"); + dir.close(); + + // deleting an empty directory + try tmp_dir.dir.deleteDir("test_dir"); +} diff --git a/lib/std/heap.zig b/lib/std/heap.zig index 16de215cc2..cf32cff645 100644 --- a/lib/std/heap.zig +++ b/lib/std/heap.zig @@ -919,6 +919,13 @@ pub fn testAllocator(base_allocator: *mem.Allocator) !void { const zero_bit_ptr = try allocator.create(u0); zero_bit_ptr.* = 0; allocator.destroy(zero_bit_ptr); + + const oversize = try allocator.allocAdvanced(u32, null, 5, .at_least); + testing.expect(oversize.len >= 5); + for (oversize) |*item| { + item.* = 0xDEADBEEF; + } + allocator.free(oversize); } pub fn testAllocatorAligned(base_allocator: *mem.Allocator, comptime alignment: u29) !void { diff --git a/lib/std/heap/arena_allocator.zig b/lib/std/heap/arena_allocator.zig index 0737cb2ef8..b7ee1d54c1 100644 --- a/lib/std/heap/arena_allocator.zig +++ b/lib/std/heap/arena_allocator.zig @@ -75,13 +75,22 @@ pub const ArenaAllocator = struct { const adjusted_addr = mem.alignForward(addr, ptr_align); const adjusted_index = self.state.end_index + (adjusted_addr - addr); const new_end_index = adjusted_index + n; - if (new_end_index > cur_buf.len) { - cur_node = try self.createNode(cur_buf.len, n + ptr_align); - continue; + + if (new_end_index <= cur_buf.len) { + const result = cur_buf[adjusted_index..new_end_index]; + self.state.end_index = new_end_index; + return result; } - const result = cur_buf[adjusted_index..new_end_index]; - self.state.end_index = new_end_index; - return result; + + const bigger_buf_size = @sizeOf(BufNode) + new_end_index; + // Try to grow the buffer in-place + cur_node.data = self.child_allocator.resize(cur_node.data, bigger_buf_size) catch |err| switch (err) { + error.OutOfMemory => { + // Allocate a new node if that's not possible + cur_node = try self.createNode(cur_buf.len, n + ptr_align); + continue; + }, + }; } } diff --git a/lib/std/meta.zig b/lib/std/meta.zig index 2744690555..492e497ff4 100644 --- a/lib/std/meta.zig +++ b/lib/std/meta.zig @@ -867,34 +867,71 @@ pub fn ArgsTuple(comptime Function: type) type { }); } -test "ArgsTuple" { - const T = struct { - fn assertTypeEqual(comptime Expected: type, comptime Actual: type) void { - if (Expected != Actual) - @compileError("Expected type " ++ @typeName(Expected) ++ ", but got type " ++ @typeName(Actual)); - } +/// For a given anonymous list of types, returns a new tuple type +/// with those types as fields. +/// +/// Examples: +/// - `Tuple(&[_]type {})` ⇒ `tuple { }` +/// - `Tuple(&[_]type {f32})` ⇒ `tuple { f32 }` +/// - `Tuple(&[_]type {f32,u32})` ⇒ `tuple { f32, u32 }` +pub fn Tuple(comptime types: []const type) type { + var tuple_fields: [types.len]std.builtin.TypeInfo.StructField = undefined; + inline for (types) |T, i| { + @setEvalBranchQuota(10_000); + var num_buf: [128]u8 = undefined; + tuple_fields[i] = std.builtin.TypeInfo.StructField{ + .name = std.fmt.bufPrint(&num_buf, "{d}", .{i}) catch unreachable, + .field_type = T, + .default_value = @as(?T, null), + .is_comptime = false, + }; + } - fn assertTuple(comptime expected: anytype, comptime Actual: type) void { - const info = @typeInfo(Actual); - if (info != .Struct) - @compileError("Expected struct type"); - if (!info.Struct.is_tuple) - @compileError("Struct type must be a tuple type"); + return @Type(std.builtin.TypeInfo{ + .Struct = std.builtin.TypeInfo.Struct{ + .is_tuple = true, + .layout = .Auto, + .decls = &[_]std.builtin.TypeInfo.Declaration{}, + .fields = &tuple_fields, + }, + }); +} - const fields_list = std.meta.fields(Actual); - if (expected.len != fields_list.len) - @compileError("Argument count mismatch"); +const TupleTester = struct { + fn assertTypeEqual(comptime Expected: type, comptime Actual: type) void { + if (Expected != Actual) + @compileError("Expected type " ++ @typeName(Expected) ++ ", but got type " ++ @typeName(Actual)); + } - inline for (fields_list) |fld, i| { - if (expected[i] != fld.field_type) { - @compileError("Field " ++ fld.name ++ " expected to be type " ++ @typeName(expected[i]) ++ ", but was type " ++ @typeName(fld.field_type)); - } + fn assertTuple(comptime expected: anytype, comptime Actual: type) void { + const info = @typeInfo(Actual); + if (info != .Struct) + @compileError("Expected struct type"); + if (!info.Struct.is_tuple) + @compileError("Struct type must be a tuple type"); + + const fields_list = std.meta.fields(Actual); + if (expected.len != fields_list.len) + @compileError("Argument count mismatch"); + + inline for (fields_list) |fld, i| { + if (expected[i] != fld.field_type) { + @compileError("Field " ++ fld.name ++ " expected to be type " ++ @typeName(expected[i]) ++ ", but was type " ++ @typeName(fld.field_type)); } } - }; + } +}; + +test "ArgsTuple" { + TupleTester.assertTuple(.{}, ArgsTuple(fn () void)); + TupleTester.assertTuple(.{u32}, ArgsTuple(fn (a: u32) []const u8)); + TupleTester.assertTuple(.{ u32, f16 }, ArgsTuple(fn (a: u32, b: f16) noreturn)); + TupleTester.assertTuple(.{ u32, f16, []const u8 }, ArgsTuple(fn (a: u32, b: f16, c: []const u8) noreturn)); +} - T.assertTuple(.{}, ArgsTuple(fn () void)); - T.assertTuple(.{u32}, ArgsTuple(fn (a: u32) []const u8)); - T.assertTuple(.{ u32, f16 }, ArgsTuple(fn (a: u32, b: f16) noreturn)); - T.assertTuple(.{ u32, f16, []const u8 }, ArgsTuple(fn (a: u32, b: f16, c: []const u8) noreturn)); +test "Tuple" { + TupleTester.assertTuple(.{}, Tuple(&[_]type{})); + TupleTester.assertTuple(.{u32}, Tuple(&[_]type{u32})); + TupleTester.assertTuple(.{ u32, f16 }, Tuple(&[_]type{ u32, f16 })); + TupleTester.assertTuple(.{ u32, f16, []const u8 }, Tuple(&[_]type{ u32, f16, []const u8 })); } diff --git a/lib/std/net.zig b/lib/std/net.zig index 45d8f07f04..fe7d0fafe6 100644 --- a/lib/std/net.zig +++ b/lib/std/net.zig @@ -614,11 +614,11 @@ pub fn connectUnixSocket(path: []const u8) !fs.File { var addr = try std.net.Address.initUnix(path); - try os.connect( - sockfd, - &addr.any, - addr.getOsSockLen(), - ); + if (std.io.is_async) { + try loop.connect(sockfd, &addr.any, addr.getOsSockLen()); + } else { + try os.connect(sockfd, &addr.any, addr.getOsSockLen()); + } return fs.File{ .handle = sockfd, @@ -677,7 +677,13 @@ pub fn tcpConnectToAddress(address: Address) !fs.File { (if (builtin.os.tag == .windows) 0 else os.SOCK_CLOEXEC); const sockfd = try os.socket(address.any.family, sock_flags, os.IPPROTO_TCP); errdefer os.close(sockfd); - try os.connect(sockfd, &address.any, address.getOsSockLen()); + + if (std.io.is_async) { + const loop = std.event.Loop.instance orelse return error.WouldBlock; + try loop.connect(sockfd, &address.any, address.getOsSockLen()); + } else { + try os.connect(sockfd, &address.any, address.getOsSockLen()); + } return fs.File{ .handle = sockfd }; } @@ -1429,7 +1435,11 @@ fn resMSendRc( if (answers[i].len == 0) { var j: usize = 0; while (j < ns.len) : (j += 1) { - _ = os.sendto(fd, queries[i], os.MSG_NOSIGNAL, &ns[j].any, sl) catch undefined; + if (std.io.is_async) { + _ = std.event.Loop.instance.?.sendto(fd, queries[i], os.MSG_NOSIGNAL, &ns[j].any, sl) catch undefined; + } else { + _ = os.sendto(fd, queries[i], os.MSG_NOSIGNAL, &ns[j].any, sl) catch undefined; + } } } } @@ -1444,7 +1454,10 @@ fn resMSendRc( while (true) { var sl_copy = sl; - const rlen = os.recvfrom(fd, answer_bufs[next], 0, &sa.any, &sl_copy) catch break; + const rlen = if (std.io.is_async) + std.event.Loop.instance.?.recvfrom(fd, answer_bufs[next], 0, &sa.any, &sl_copy) catch break + else + os.recvfrom(fd, answer_bufs[next], 0, &sa.any, &sl_copy) catch break; // Ignore non-identifiable packets if (rlen < 4) continue; @@ -1470,7 +1483,11 @@ fn resMSendRc( 0, 3 => {}, 2 => if (servfail_retry != 0) { servfail_retry -= 1; - _ = os.sendto(fd, queries[i], os.MSG_NOSIGNAL, &ns[j].any, sl) catch undefined; + if (std.io.is_async) { + _ = std.event.Loop.instance.?.sendto(fd, queries[i], os.MSG_NOSIGNAL, &ns[j].any, sl) catch undefined; + } else { + _ = os.sendto(fd, queries[i], os.MSG_NOSIGNAL, &ns[j].any, sl) catch undefined; + } }, else => continue, } @@ -1661,18 +1678,23 @@ pub const StreamServer = struct { /// If this function succeeds, the returned `Connection` is a caller-managed resource. pub fn accept(self: *StreamServer) AcceptError!Connection { - const nonblock = if (std.io.is_async) os.SOCK_NONBLOCK else 0; - const accept_flags = nonblock | os.SOCK_CLOEXEC; var accepted_addr: Address = undefined; var adr_len: os.socklen_t = @sizeOf(Address); - if (os.accept(self.sockfd.?, &accepted_addr.any, &adr_len, accept_flags)) |fd| { + const accept_result = blk: { + if (std.io.is_async) { + const loop = std.event.Loop.instance orelse return error.UnexpectedError; + break :blk loop.accept(self.sockfd.?, &accepted_addr.any, &adr_len, os.SOCK_CLOEXEC); + } else { + break :blk os.accept(self.sockfd.?, &accepted_addr.any, &adr_len, os.SOCK_CLOEXEC); + } + }; + + if (accept_result) |fd| { return Connection{ .file = fs.File{ .handle = fd }, .address = accepted_addr, }; } else |err| switch (err) { - // We only give SOCK_NONBLOCK when I/O mode is async, in which case this error - // is handled by os.accept4. error.WouldBlock => unreachable, else => |e| return e, } diff --git a/lib/std/os.zig b/lib/std/os.zig index 0b09b1f82a..c06ce4ed00 100644 --- a/lib/std/os.zig +++ b/lib/std/os.zig @@ -314,8 +314,8 @@ pub const ReadError = error{ /// Returns the number of bytes that were read, which can be less than /// buf.len. If 0 bytes were read, that means EOF. -/// If the application has a global event loop enabled, EAGAIN is handled -/// via the event loop. Otherwise EAGAIN results in error.WouldBlock. +/// If `fd` is opened in non blocking mode, the function will return error.WouldBlock +/// when EAGAIN is received. /// /// Linux has a limit on how many bytes may be transferred in one `read` call, which is `0x7ffff000` /// on both 64-bit and 32-bit systems. This is due to using a signed C int as the return value, as @@ -366,12 +366,7 @@ pub fn read(fd: fd_t, buf: []u8) ReadError!usize { EINTR => continue, EINVAL => unreachable, EFAULT => unreachable, - EAGAIN => if (std.event.Loop.instance) |loop| { - loop.waitUntilFdReadable(fd); - continue; - } else { - return error.WouldBlock; - }, + EAGAIN => return error.WouldBlock, EBADF => return error.NotOpenForReading, // Can be a race condition. EIO => return error.InputOutput, EISDIR => return error.IsDir, @@ -387,8 +382,8 @@ pub fn read(fd: fd_t, buf: []u8) ReadError!usize { /// Number of bytes read is returned. Upon reading end-of-file, zero is returned. /// -/// For POSIX systems, if the application has a global event loop enabled, EAGAIN is handled -/// via the event loop. Otherwise EAGAIN results in `error.WouldBlock`. +/// For POSIX systems, if `fd` is opened in non blocking mode, the function will +/// return error.WouldBlock when EAGAIN is received. /// On Windows, if the application has a global event loop enabled, I/O Completion Ports are /// used to perform the I/O. `error.WouldBlock` is not possible on Windows. /// @@ -428,12 +423,7 @@ pub fn readv(fd: fd_t, iov: []const iovec) ReadError!usize { EINTR => continue, EINVAL => unreachable, EFAULT => unreachable, - EAGAIN => if (std.event.Loop.instance) |loop| { - loop.waitUntilFdReadable(fd); - continue; - } else { - return error.WouldBlock; - }, + EAGAIN => return error.WouldBlock, EBADF => return error.NotOpenForReading, // can be a race condition EIO => return error.InputOutput, EISDIR => return error.IsDir, @@ -450,8 +440,8 @@ pub const PReadError = ReadError || error{Unseekable}; /// /// Retries when interrupted by a signal. /// -/// For POSIX systems, if the application has a global event loop enabled, EAGAIN is handled -/// via the event loop. Otherwise EAGAIN results in `error.WouldBlock`. +/// For POSIX systems, if `fd` is opened in non blocking mode, the function will +/// return error.WouldBlock when EAGAIN is received. /// On Windows, if the application has a global event loop enabled, I/O Completion Ports are /// used to perform the I/O. `error.WouldBlock` is not possible on Windows. pub fn pread(fd: fd_t, buf: []u8, offset: u64) PReadError!usize { @@ -492,12 +482,7 @@ pub fn pread(fd: fd_t, buf: []u8, offset: u64) PReadError!usize { EINTR => continue, EINVAL => unreachable, EFAULT => unreachable, - EAGAIN => if (std.event.Loop.instance) |loop| { - loop.waitUntilFdReadable(fd); - continue; - } else { - return error.WouldBlock; - }, + EAGAIN => return error.WouldBlock, EBADF => return error.NotOpenForReading, // Can be a race condition. EIO => return error.InputOutput, EISDIR => return error.IsDir, @@ -586,8 +571,8 @@ pub fn ftruncate(fd: fd_t, length: u64) TruncateError!void { /// /// Retries when interrupted by a signal. /// -/// For POSIX systems, if the application has a global event loop enabled, EAGAIN is handled -/// via the event loop. Otherwise EAGAIN results in `error.WouldBlock`. +/// For POSIX systems, if `fd` is opened in non blocking mode, the function will +/// return error.WouldBlock when EAGAIN is received. /// On Windows, if the application has a global event loop enabled, I/O Completion Ports are /// used to perform the I/O. `error.WouldBlock` is not possible on Windows. /// @@ -637,12 +622,7 @@ pub fn preadv(fd: fd_t, iov: []const iovec, offset: u64) PReadError!usize { EINTR => continue, EINVAL => unreachable, EFAULT => unreachable, - EAGAIN => if (std.event.Loop.instance) |loop| { - loop.waitUntilFdReadable(fd); - continue; - } else { - return error.WouldBlock; - }, + EAGAIN => return error.WouldBlock, EBADF => return error.NotOpenForReading, // can be a race condition EIO => return error.InputOutput, EISDIR => return error.IsDir, @@ -687,8 +667,8 @@ pub const WriteError = error{ /// another write() call to transfer the remaining bytes. The subsequent call will either /// transfer further bytes or may result in an error (e.g., if the disk is now full). /// -/// For POSIX systems, if the application has a global event loop enabled, EAGAIN is handled -/// via the event loop. Otherwise EAGAIN results in `error.WouldBlock`. +/// For POSIX systems, if `fd` is opened in non blocking mode, the function will +/// return error.WouldBlock when EAGAIN is received. /// On Windows, if the application has a global event loop enabled, I/O Completion Ports are /// used to perform the I/O. `error.WouldBlock` is not possible on Windows. /// @@ -741,12 +721,7 @@ pub fn write(fd: fd_t, bytes: []const u8) WriteError!usize { EINTR => continue, EINVAL => unreachable, EFAULT => unreachable, - EAGAIN => if (std.event.Loop.instance) |loop| { - loop.waitUntilFdWritable(fd); - continue; - } else { - return error.WouldBlock; - }, + EAGAIN => return error.WouldBlock, EBADF => return error.NotOpenForWriting, // can be a race condition. EDESTADDRREQ => unreachable, // `connect` was never called. EDQUOT => return error.DiskQuota, @@ -772,8 +747,8 @@ pub fn write(fd: fd_t, bytes: []const u8) WriteError!usize { /// another write() call to transfer the remaining bytes. The subsequent call will either /// transfer further bytes or may result in an error (e.g., if the disk is now full). /// -/// For POSIX systems, if the application has a global event loop enabled, EAGAIN is handled -/// via the event loop. Otherwise EAGAIN results in `error.WouldBlock`. +/// For POSIX systems, if `fd` is opened in non blocking mode, the function will +/// return error.WouldBlock when EAGAIN is received.k`. /// On Windows, if the application has a global event loop enabled, I/O Completion Ports are /// used to perform the I/O. `error.WouldBlock` is not possible on Windows. /// @@ -814,12 +789,7 @@ pub fn writev(fd: fd_t, iov: []const iovec_const) WriteError!usize { EINTR => continue, EINVAL => unreachable, EFAULT => unreachable, - EAGAIN => if (std.event.Loop.instance) |loop| { - loop.waitUntilFdWritable(fd); - continue; - } else { - return error.WouldBlock; - }, + EAGAIN => return error.WouldBlock, EBADF => return error.NotOpenForWriting, // Can be a race condition. EDESTADDRREQ => unreachable, // `connect` was never called. EDQUOT => return error.DiskQuota, @@ -847,8 +817,8 @@ pub const PWriteError = WriteError || error{Unseekable}; /// another write() call to transfer the remaining bytes. The subsequent call will either /// transfer further bytes or may result in an error (e.g., if the disk is now full). /// -/// For POSIX systems, if the application has a global event loop enabled, EAGAIN is handled -/// via the event loop. Otherwise EAGAIN results in `error.WouldBlock`. +/// For POSIX systems, if `fd` is opened in non blocking mode, the function will +/// return error.WouldBlock when EAGAIN is received. /// On Windows, if the application has a global event loop enabled, I/O Completion Ports are /// used to perform the I/O. `error.WouldBlock` is not possible on Windows. /// @@ -905,12 +875,7 @@ pub fn pwrite(fd: fd_t, bytes: []const u8, offset: u64) PWriteError!usize { EINTR => continue, EINVAL => unreachable, EFAULT => unreachable, - EAGAIN => if (std.event.Loop.instance) |loop| { - loop.waitUntilFdWritable(fd); - continue; - } else { - return error.WouldBlock; - }, + EAGAIN => return error.WouldBlock, EBADF => return error.NotOpenForWriting, // Can be a race condition. EDESTADDRREQ => unreachable, // `connect` was never called. EDQUOT => return error.DiskQuota, @@ -939,8 +904,8 @@ pub fn pwrite(fd: fd_t, bytes: []const u8, offset: u64) PWriteError!usize { /// another write() call to transfer the remaining bytes. The subsequent call will either /// transfer further bytes or may result in an error (e.g., if the disk is now full). /// -/// If the application has a global event loop enabled, EAGAIN is handled -/// via the event loop. Otherwise EAGAIN results in `error.WouldBlock`. +/// If `fd` is opened in non blocking mode, the function will +/// return error.WouldBlock when EAGAIN is received. /// /// The following systems do not have this syscall, and will return partial writes if more than one /// vector is provided: @@ -993,12 +958,7 @@ pub fn pwritev(fd: fd_t, iov: []const iovec_const, offset: u64) PWriteError!usiz EINTR => continue, EINVAL => unreachable, EFAULT => unreachable, - EAGAIN => if (std.event.Loop.instance) |loop| { - loop.waitUntilFdWritable(fd); - continue; - } else { - return error.WouldBlock; - }, + EAGAIN => return error.WouldBlock, EBADF => return error.NotOpenForWriting, // Can be a race condition. EDESTADDRREQ => unreachable, // `connect` was never called. EDQUOT => return error.DiskQuota, @@ -2846,8 +2806,8 @@ pub const AcceptError = error{ } || UnexpectedError; /// Accept a connection on a socket. -/// If the application has a global event loop enabled, EAGAIN is handled -/// via the event loop. Otherwise EAGAIN results in error.WouldBlock. +/// If `sockfd` is opened in non blocking mode, the function will +/// return error.WouldBlock when EAGAIN is received. pub fn accept( /// This argument is a socket that has been created with `socket`, bound to a local address /// with `bind`, and is listening for connections after a `listen`. @@ -2890,12 +2850,7 @@ pub fn accept( return fd; }, EINTR => continue, - EAGAIN => if (std.event.Loop.instance) |loop| { - loop.waitUntilFdReadable(sockfd); - continue; - } else { - return error.WouldBlock; - }, + EAGAIN => return error.WouldBlock, EBADF => unreachable, // always a race condition ECONNABORTED => return error.ConnectionAborted, EFAULT => unreachable, @@ -3081,6 +3036,8 @@ pub const ConnectError = error{ } || UnexpectedError; /// Initiate a connection on a socket. +/// If `sockfd` is opened in non blocking mode, the function will +/// return error.WouldBlock when EAGAIN or EINPROGRESS is received. pub fn connect(sockfd: socket_t, sock_addr: *const sockaddr, len: socklen_t) ConnectError!void { if (builtin.os.tag == .windows) { const rc = windows.ws2_32.connect(sockfd, sock_addr, len); @@ -3113,11 +3070,7 @@ pub fn connect(sockfd: socket_t, sock_addr: *const sockaddr, len: socklen_t) Con EADDRINUSE => return error.AddressInUse, EADDRNOTAVAIL => return error.AddressNotAvailable, EAFNOSUPPORT => return error.AddressFamilyNotSupported, - EAGAIN, EINPROGRESS => { - const loop = std.event.Loop.instance orelse return error.WouldBlock; - loop.waitUntilFdWritable(sockfd); - return getsockoptError(sockfd); - }, + EAGAIN, EINPROGRESS => return error.WouldBlock, EALREADY => unreachable, // The socket is nonblocking and a previous connection attempt has not yet been completed. EBADF => unreachable, // sockfd is not a valid open file descriptor. ECONNREFUSED => return error.ConnectionRefused, @@ -4620,14 +4573,8 @@ pub fn sendto( const rc = system.sendto(sockfd, buf.ptr, buf.len, flags, dest_addr, addrlen); switch (errno(rc)) { 0 => return @intCast(usize, rc), - EACCES => return error.AccessDenied, - EAGAIN => if (std.event.Loop.instance) |loop| { - loop.waitUntilFdWritable(sockfd); - continue; - } else { - return error.WouldBlock; - }, + EAGAIN => return error.WouldBlock, EALREADY => return error.FastOpenAlreadyInProgress, EBADF => unreachable, // always a race condition ECONNRESET => return error.ConnectionResetByPeer, @@ -5106,6 +5053,8 @@ pub const RecvFromError = error{ SystemResources, } || UnexpectedError; +/// If `sockfd` is opened in non blocking mode, the function will +/// return error.WouldBlock when EAGAIN is received. pub fn recvfrom( sockfd: fd_t, buf: []u8, @@ -5123,12 +5072,7 @@ pub fn recvfrom( ENOTCONN => unreachable, ENOTSOCK => unreachable, EINTR => continue, - EAGAIN => if (std.event.Loop.instance) |loop| { - loop.waitUntilFdReadable(sockfd); - continue; - } else { - return error.WouldBlock; - }, + EAGAIN => return error.WouldBlock, ENOMEM => return error.SystemResources, ECONNREFUSED => return error.ConnectionRefused, else => |err| return unexpectedErrno(err), diff --git a/lib/std/os/uefi/tables/system_table.zig b/lib/std/os/uefi/tables/system_table.zig index cbe66fbb68..3f0624d2ce 100644 --- a/lib/std/os/uefi/tables/system_table.zig +++ b/lib/std/os/uefi/tables/system_table.zig @@ -35,7 +35,7 @@ pub const SystemTable = extern struct { runtime_services: *RuntimeServices, boot_services: ?*BootServices, number_of_table_entries: usize, - configuration_table: *ConfigurationTable, + configuration_table: [*]ConfigurationTable, pub const signature: u64 = 0x5453595320494249; pub const revision_1_02: u32 = (1 << 16) | 2; diff --git a/lib/std/os/windows.zig b/lib/std/os/windows.zig index de0d0ea45f..2aa222414f 100644 --- a/lib/std/os/windows.zig +++ b/lib/std/os/windows.zig @@ -764,6 +764,7 @@ pub const DeleteFileError = error{ Unexpected, NotDir, IsDir, + DirNotEmpty, }; pub const DeleteFileOptions = struct { @@ -818,7 +819,7 @@ pub fn DeleteFile(sub_path_w: []const u16, options: DeleteFileOptions) DeleteFil 0, ); switch (rc) { - .SUCCESS => return CloseHandle(tmp_handle), + .SUCCESS => CloseHandle(tmp_handle), .OBJECT_NAME_INVALID => unreachable, .OBJECT_NAME_NOT_FOUND => return error.FileNotFound, .INVALID_PARAMETER => unreachable, @@ -826,6 +827,21 @@ pub fn DeleteFile(sub_path_w: []const u16, options: DeleteFileOptions) DeleteFil .NOT_A_DIRECTORY => return error.NotDir, else => return unexpectedStatus(rc), } + + // If a directory fails to be deleted, CloseHandle will still report success + // Check if the directory still exists and return error.DirNotEmpty if true + if (options.remove_dir) { + var basic_info: FILE_BASIC_INFORMATION = undefined; + switch (ntdll.NtQueryAttributesFile(&attr, &basic_info)) { + .SUCCESS => return error.DirNotEmpty, + .OBJECT_NAME_NOT_FOUND => return, + .OBJECT_PATH_NOT_FOUND => return, + .INVALID_PARAMETER => unreachable, + .ACCESS_DENIED => return error.AccessDenied, + .OBJECT_PATH_SYNTAX_BAD => unreachable, + else => |urc| return unexpectedStatus(urc), + } + } } pub const MoveFileError = error{ FileNotFound, Unexpected }; diff --git a/lib/std/zig/ast.zig b/lib/std/zig/ast.zig index 404e8c413a..d8943adde0 100644 --- a/lib/std/zig/ast.zig +++ b/lib/std/zig/ast.zig @@ -823,6 +823,15 @@ pub const Node = struct { } } + pub fn findFirstWithId(self: *Node, id: Id) ?*Node { + if (self.id == id) return self; + var child_i: usize = 0; + while (self.iterate(child_i)) |child| : (child_i += 1) { + if (child.findFirstWithId(id)) |result| return result; + } + return null; + } + pub fn dump(self: *Node, indent: usize) void { { var i: usize = 0; diff --git a/lib/std/zig/parser_test.zig b/lib/std/zig/parser_test.zig index 36ceb400dc..994ad6d5d1 100644 --- a/lib/std/zig/parser_test.zig +++ b/lib/std/zig/parser_test.zig @@ -1301,8 +1301,10 @@ test "zig fmt: array literal with hint" { \\const a = []u8{ \\ 1, 2, \\ 3, 4, - \\ 5, 6, // blah - \\ 7, 8, + \\ 5, + \\ 6, // blah + \\ 7, + \\ 8, \\}; \\const a = []u8{ \\ 1, 2, @@ -1372,7 +1374,7 @@ test "zig fmt: multiline string parameter in fn call with trailing comma" { \\ \\ZIG_C_HEADER_FILES {} \\ \\ZIG_DIA_GUIDS_LIB {} \\ \\ - \\ , + \\ , \\ std.cstr.toSliceConst(c.ZIG_CMAKE_BINARY_DIR), \\ std.cstr.toSliceConst(c.ZIG_CXX_COMPILER), \\ std.cstr.toSliceConst(c.ZIG_DIA_GUIDS_LIB), @@ -3321,6 +3323,326 @@ test "zig fmt: Don't add extra newline after if" { ); } +test "zig fmt: comments in ternary ifs" { + try testCanonical( + \\const x = if (true) { + \\ 1; + \\} else if (false) + \\ // Comment + \\ 0; + \\const y = if (true) + \\ // Comment + \\ 1 + \\else + \\ 0; + \\ + \\pub extern "c" fn printf(format: [*:0]const u8, ...) c_int; + \\ + ); +} + +test "zig fmt: test comments in field access chain" { + try testCanonical( + \\pub const str = struct { + \\ pub const Thing = more.more // + \\ .more() // + \\ .more().more() // + \\ .more() // + \\ // .more() // + \\ .more() // + \\ .more(); + \\ data: Data, + \\}; + \\ + \\pub const str = struct { + \\ pub const Thing = more.more // + \\ .more() // + \\ // .more() // + \\ // .more() // + \\ // .more() // + \\ .more() // + \\ .more(); + \\ data: Data, + \\}; + \\ + \\pub const str = struct { + \\ pub const Thing = more // + \\ .more // + \\ .more() // + \\ .more(); + \\ data: Data, + \\}; + \\ + ); +} + +test "zig fmt: Indent comma correctly after multiline string literals in arg list (trailing comma)" { + try testCanonical( + \\fn foo() void { + \\ z.display_message_dialog( + \\ *const [323:0]u8, + \\ \\Message Text + \\ \\------------ + \\ \\xxxxxxxxxxxx + \\ \\xxxxxxxxxxxx + \\ , + \\ g.GtkMessageType.GTK_MESSAGE_WARNING, + \\ null, + \\ ); + \\ + \\ z.display_message_dialog(*const [323:0]u8, + \\ \\Message Text + \\ \\------------ + \\ \\xxxxxxxxxxxx + \\ \\xxxxxxxxxxxx + \\ , g.GtkMessageType.GTK_MESSAGE_WARNING, null); + \\} + \\ + ); +} + +test "zig fmt: Control flow statement as body of blockless if" { + try testCanonical( + \\pub fn main() void { + \\ const zoom_node = if (focused_node == layout_first) + \\ if (it.next()) { + \\ if (!node.view.pending.float and !node.view.pending.fullscreen) break node; + \\ } else null + \\ else + \\ focused_node; + \\ + \\ const zoom_node = if (focused_node == layout_first) while (it.next()) |node| { + \\ if (!node.view.pending.float and !node.view.pending.fullscreen) break node; + \\ } else null else + \\ focused_node; + \\ + \\ const zoom_node = if (focused_node == layout_first) + \\ if (it.next()) { + \\ if (!node.view.pending.float and !node.view.pending.fullscreen) break node; + \\ } else null; + \\ + \\ const zoom_node = if (focused_node == layout_first) while (it.next()) |node| { + \\ if (!node.view.pending.float and !node.view.pending.fullscreen) break node; + \\ }; + \\ + \\ const zoom_node = if (focused_node == layout_first) for (nodes) |node| { + \\ break node; + \\ }; + \\ + \\ const zoom_node = if (focused_node == layout_first) switch (nodes) { + \\ 0 => 0, + \\ } else + \\ focused_node; + \\} + \\ + ); +} + +test "zig fmt: " { + try testCanonical( + \\pub fn sendViewTags(self: Self) void { + \\ var it = ViewStack(View).iterator(self.output.views.first, std.math.maxInt(u32)); + \\ while (it.next()) |node| + \\ view_tags.append(node.view.current_tags) catch { + \\ c.wl_resource_post_no_memory(self.wl_resource); + \\ log.crit(.river_status, "out of memory", .{}); + \\ return; + \\ }; + \\} + \\ + ); +} + +test "zig fmt: allow trailing line comments to do manual array formatting" { + try testCanonical( + \\fn foo() void { + \\ self.code.appendSliceAssumeCapacity(&[_]u8{ + \\ 0x55, // push rbp + \\ 0x48, 0x89, 0xe5, // mov rbp, rsp + \\ 0x48, 0x81, 0xec, // sub rsp, imm32 (with reloc) + \\ }); + \\ + \\ di_buf.appendAssumeCapacity(&[_]u8{ + \\ 1, DW.TAG_compile_unit, DW.CHILDREN_no, // header + \\ DW.AT_stmt_list, DW_FORM_data4, // form value pairs + \\ DW.AT_low_pc, DW_FORM_addr, + \\ DW.AT_high_pc, DW_FORM_addr, + \\ DW.AT_name, DW_FORM_strp, + \\ DW.AT_comp_dir, DW_FORM_strp, + \\ DW.AT_producer, DW_FORM_strp, + \\ DW.AT_language, DW_FORM_data2, + \\ 0, 0, // sentinel + \\ }); + \\ + \\ self.code.appendSliceAssumeCapacity(&[_]u8{ + \\ 0x55, // push rbp + \\ 0x48, 0x89, 0xe5, // mov rbp, rsp + \\ // How do we handle this? + \\ //0x48, 0x81, 0xec, // sub rsp, imm32 (with reloc) + \\ // Here's a blank line, should that be allowed? + \\ + \\ 0x48, 0x89, 0xe5, + \\ 0x33, 0x45, + \\ // Now the comment breaks a single line -- how do we handle this? + \\ 0x88, + \\ }); + \\} + \\ + ); +} + +test "zig fmt: multiline string literals should play nice with array initializers" { + try testCanonical( + \\fn main() void { + \\ var a = .{.{.{.{.{.{.{.{ + \\ 0, + \\ }}}}}}}}; + \\ myFunc(.{ + \\ "aaaaaaa", "bbbbbb", "ccccc", + \\ "dddd", ("eee"), ("fff"), + \\ ("gggg"), + \\ // Line comment + \\ \\Multiline String Literals can be quite long + \\ , + \\ \\Multiline String Literals can be quite long + \\ \\Multiline String Literals can be quite long + \\ , + \\ \\Multiline String Literals can be quite long + \\ \\Multiline String Literals can be quite long + \\ \\Multiline String Literals can be quite long + \\ \\Multiline String Literals can be quite long + \\ , + \\ ( + \\ \\Multiline String Literals can be quite long + \\ ), + \\ .{ + \\ \\xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx + \\ \\xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx + \\ \\xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx + \\ }, + \\ .{( + \\ \\xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx + \\ )}, + \\ .{ + \\ "xxxxxxx", "xxx", + \\ ( + \\ \\ xxx + \\ ), + \\ "xxx", "xxx", + \\ }, + \\ .{ "xxxxxxx", "xxx", "xxx", "xxx" }, .{ "xxxxxxx", "xxx", "xxx", "xxx" }, + \\ "aaaaaaa", "bbbbbb", "ccccc", // - + \\ "dddd", ("eee"), ("fff"), + \\ .{ + \\ "xxx", "xxx", + \\ ( + \\ \\ xxx + \\ ), + \\ "xxxxxxxxxxxxxx", "xxx", + \\ }, + \\ .{ + \\ ( + \\ \\xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx + \\ ), + \\ \\xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx + \\ }, + \\ \\xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx + \\ \\xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx + \\ }); + \\} + \\ + ); +} + +test "zig fmt: use of comments and Multiline string literals may force the parameters over multiple lines" { + try testCanonical( + \\pub fn makeMemUndefined(qzz: []u8) i1 { + \\ cases.add( // fixed bug #2032 + \\ "compile diagnostic string for top level decl type", + \\ \\export fn entry() void { + \\ \\ var foo: u32 = @This(){}; + \\ \\} + \\ , &[_][]const u8{ + \\ "tmp.zig:2:27: error: type 'u32' does not support array initialization", + \\ }); + \\ @compileError( + \\ \\ unknown-length pointers and C pointers cannot be hashed deeply. + \\ \\ Consider providing your own hash function. + \\ \\ unknown-length pointers and C pointers cannot be hashed deeply. + \\ \\ Consider providing your own hash function. + \\ ); + \\ return @intCast(i1, doMemCheckClientRequestExpr(0, // default return + \\ .MakeMemUndefined, @ptrToInt(qzz.ptr), qzz.len, 0, 0, 0)); + \\} + \\ + \\// This looks like garbage don't do this + \\const rparen = tree.prevToken( + \\// the first token for the annotation expressions is the left + \\// parenthesis, hence the need for two prevToken + \\ if (fn_proto.getAlignExpr()) |align_expr| + \\ tree.prevToken(tree.prevToken(align_expr.firstToken())) + \\else if (fn_proto.getSectionExpr()) |section_expr| + \\ tree.prevToken(tree.prevToken(section_expr.firstToken())) + \\else if (fn_proto.getCallconvExpr()) |callconv_expr| + \\ tree.prevToken(tree.prevToken(callconv_expr.firstToken())) + \\else switch (fn_proto.return_type) { + \\ .Explicit => |node| node.firstToken(), + \\ .InferErrorSet => |node| tree.prevToken(node.firstToken()), + \\ .Invalid => unreachable, + \\}); + \\ + ); +} + +test "zig fmt: single argument trailing commas in @builtins()" { + try testCanonical( + \\pub fn foo(qzz: []u8) i1 { + \\ @panic( + \\ foo, + \\ ); + \\ panic( + \\ foo, + \\ ); + \\ @panic( + \\ foo, + \\ bar, + \\ ); + \\} + \\ + ); +} + +test "zig fmt: trailing comma should force multiline 1 column" { + try testTransform( + \\pub const UUID_NULL: uuid_t = [16]u8{0,0,0,0,}; + \\ + , + \\pub const UUID_NULL: uuid_t = [16]u8{ + \\ 0, + \\ 0, + \\ 0, + \\ 0, + \\}; + \\ + ); +} + +test "zig fmt: function params should align nicely" { + try testCanonical( + \\pub fn foo() void { + \\ cases.addRuntimeSafety("slicing operator with sentinel", + \\ \\const std = @import("std"); + \\ ++ check_panic_msg ++ + \\ \\pub fn main() void { + \\ \\ var buf = [4]u8{'a','b','c',0}; + \\ \\ const slice = buf[0..:0]; + \\ \\} + \\ ); + \\} + \\ + ); +} + const std = @import("std"); const mem = std.mem; const warn = std.debug.warn; diff --git a/lib/std/zig/render.zig b/lib/std/zig/render.zig index 237ca07d2b..67afbb77d9 100644 --- a/lib/std/zig/render.zig +++ b/lib/std/zig/render.zig @@ -522,7 +522,11 @@ fn renderExpression( break :blk if (loc.line == 0) op_space else Space.Newline; }; - try renderToken(tree, ais, infix_op_node.op_token, after_op_space); + { + ais.pushIndent(); + defer ais.popIndent(); + try renderToken(tree, ais, infix_op_node.op_token, after_op_space); + } ais.pushIndentOneShot(); return renderExpression(allocator, ais, tree, infix_op_node.rhs, space); }, @@ -710,141 +714,194 @@ fn renderExpression( .node => |node| tree.nextToken(node.lastToken()), }; - if (exprs.len == 0) { - switch (lhs) { - .dot => |dot| try renderToken(tree, ais, dot, Space.None), - .node => |node| try renderExpression(allocator, ais, tree, node, Space.None), - } - - { - ais.pushIndent(); - defer ais.popIndent(); - try renderToken(tree, ais, lbrace, Space.None); - } + switch (lhs) { + .dot => |dot| try renderToken(tree, ais, dot, Space.None), + .node => |node| try renderExpression(allocator, ais, tree, node, Space.None), + } + if (exprs.len == 0) { + try renderToken(tree, ais, lbrace, Space.None); return renderToken(tree, ais, rtoken, space); } - if (exprs.len == 1 and tree.token_ids[exprs[0].*.lastToken() + 1] == .RBrace) { + + if (exprs.len == 1 and exprs[0].tag != .MultilineStringLiteral and tree.token_ids[exprs[0].*.lastToken() + 1] == .RBrace) { const expr = exprs[0]; - switch (lhs) { - .dot => |dot| try renderToken(tree, ais, dot, Space.None), - .node => |node| try renderExpression(allocator, ais, tree, node, Space.None), - } try renderToken(tree, ais, lbrace, Space.None); try renderExpression(allocator, ais, tree, expr, Space.None); return renderToken(tree, ais, rtoken, space); } - switch (lhs) { - .dot => |dot| try renderToken(tree, ais, dot, Space.None), - .node => |node| try renderExpression(allocator, ais, tree, node, Space.None), - } - // scan to find row size - const maybe_row_size: ?usize = blk: { - var count: usize = 1; - for (exprs) |expr, i| { - if (i + 1 < exprs.len) { - const expr_last_token = expr.lastToken() + 1; - const loc = tree.tokenLocation(tree.token_locs[expr_last_token].end, exprs[i + 1].firstToken()); - if (loc.line != 0) break :blk count; - count += 1; - } else { - const expr_last_token = expr.lastToken(); - const loc = tree.tokenLocation(tree.token_locs[expr_last_token].end, rtoken); - if (loc.line == 0) { - // all on one line - const src_has_trailing_comma = trailblk: { - const maybe_comma = tree.prevToken(rtoken); - break :trailblk tree.token_ids[maybe_comma] == .Comma; - }; - if (src_has_trailing_comma) { - break :blk 1; // force row size 1 - } else { - break :blk null; // no newlines - } - } - break :blk count; - } - } - unreachable; - }; - - if (maybe_row_size) |row_size| { - // A place to store the width of each expression and its column's maximum - var widths = try allocator.alloc(usize, exprs.len + row_size); - defer allocator.free(widths); - mem.set(usize, widths, 0); - - var expr_widths = widths[0 .. widths.len - row_size]; - var column_widths = widths[widths.len - row_size ..]; - - // Null ais for counting the printed length of each expression - var counting_stream = std.io.countingOutStream(std.io.null_out_stream); - var auto_indenting_stream = std.io.autoIndentingStream(indent_delta, counting_stream.writer()); - - for (exprs) |expr, i| { - counting_stream.bytes_written = 0; - try renderExpression(allocator, &auto_indenting_stream, tree, expr, Space.None); - const width = @intCast(usize, counting_stream.bytes_written); - const col = i % row_size; - column_widths[col] = std.math.max(column_widths[col], width); - expr_widths[i] = width; - } - + if (rowSize(tree, exprs, rtoken) != null) { { ais.pushIndentNextLine(); defer ais.popIndent(); try renderToken(tree, ais, lbrace, Space.Newline); - var col: usize = 1; - for (exprs) |expr, i| { - if (i + 1 < exprs.len) { - const next_expr = exprs[i + 1]; - try renderExpression(allocator, ais, tree, expr, Space.None); - - const comma = tree.nextToken(expr.*.lastToken()); - - if (col != row_size) { - try renderToken(tree, ais, comma, Space.Space); // , - - const padding = column_widths[i % row_size] - expr_widths[i]; - try ais.writer().writeByteNTimes(' ', padding); - - col += 1; - continue; + var expr_index: usize = 0; + while (rowSize(tree, exprs[expr_index..], rtoken)) |row_size| { + const row_exprs = exprs[expr_index..]; + // A place to store the width of each expression and its column's maximum + var widths = try allocator.alloc(usize, row_exprs.len + row_size); + defer allocator.free(widths); + mem.set(usize, widths, 0); + + var expr_newlines = try allocator.alloc(bool, row_exprs.len); + defer allocator.free(expr_newlines); + mem.set(bool, expr_newlines, false); + + var expr_widths = widths[0 .. widths.len - row_size]; + var column_widths = widths[widths.len - row_size ..]; + + // Find next row with trailing comment (if any) to end the current section + var section_end = sec_end: { + var this_line_first_expr: usize = 0; + var this_line_size = rowSize(tree, row_exprs, rtoken); + for (row_exprs) |expr, i| { + // Ignore comment on first line of this section + if (i == 0 or tree.tokensOnSameLine(row_exprs[0].firstToken(), expr.lastToken())) continue; + // Track start of line containing comment + if (!tree.tokensOnSameLine(row_exprs[this_line_first_expr].firstToken(), expr.lastToken())) { + this_line_first_expr = i; + this_line_size = rowSize(tree, row_exprs[this_line_first_expr..], rtoken); + } + + const maybe_comma = expr.lastToken() + 1; + const maybe_comment = expr.lastToken() + 2; + if (maybe_comment < tree.token_ids.len) { + if (tree.token_ids[maybe_comma] == .Comma and + tree.token_ids[maybe_comment] == .LineComment and + tree.tokensOnSameLine(expr.lastToken(), maybe_comment)) + { + var comment_token_loc = tree.token_locs[maybe_comment]; + const comment_is_empty = mem.trimRight(u8, tree.tokenSliceLoc(comment_token_loc), " ").len == 2; + if (!comment_is_empty) { + // Found row ending in comment + break :sec_end i - this_line_size.? + 1; + } + } + } } - col = 1; + break :sec_end row_exprs.len; + }; + expr_index += section_end; + + const section_exprs = row_exprs[0..section_end]; + + // Null stream for counting the printed length of each expression + var line_find_stream = std.io.findByteOutStream('\n', std.io.null_out_stream); + var counting_stream = std.io.countingOutStream(line_find_stream.writer()); + var auto_indenting_stream = std.io.autoIndentingStream(indent_delta, counting_stream.writer()); + + // Calculate size of columns in current section + var column_counter: usize = 0; + var single_line = true; + for (section_exprs) |expr, i| { + if (i + 1 < section_exprs.len) { + counting_stream.bytes_written = 0; + line_find_stream.byte_found = false; + try renderExpression(allocator, &auto_indenting_stream, tree, expr, Space.None); + const width = @intCast(usize, counting_stream.bytes_written); + expr_widths[i] = width; + expr_newlines[i] = line_find_stream.byte_found; + + if (!line_find_stream.byte_found) { + const column = column_counter % row_size; + column_widths[column] = std.math.max(column_widths[column], width); + + const expr_last_token = expr.*.lastToken() + 1; + const next_expr = section_exprs[i + 1]; + const loc = tree.tokenLocation(tree.token_locs[expr_last_token].start, next_expr.*.firstToken()); + if (loc.line == 0) { + column_counter += 1; + } else { + single_line = false; + column_counter = 0; + } + } else { + single_line = false; + column_counter = 0; + } + } else { + counting_stream.bytes_written = 0; + try renderExpression(allocator, &auto_indenting_stream, tree, expr, Space.None); + const width = @intCast(usize, counting_stream.bytes_written); + expr_widths[i] = width; + expr_newlines[i] = line_find_stream.byte_found; + + if (!line_find_stream.byte_found) { + const column = column_counter % row_size; + column_widths[column] = std.math.max(column_widths[column], width); + } + break; + } + } - if (tree.token_ids[tree.nextToken(comma)] != .MultilineStringLiteralLine) { + // Render exprs in current section + column_counter = 0; + var last_col_index: usize = row_size - 1; + for (section_exprs) |expr, i| { + if (i + 1 < section_exprs.len) { + const next_expr = section_exprs[i + 1]; + try renderExpression(allocator, ais, tree, expr, Space.None); + + const comma = tree.nextToken(expr.*.lastToken()); + + if (column_counter != last_col_index) { + if (!expr_newlines[i] and !expr_newlines[i + 1]) { + // Neither the current or next expression is multiline + try renderToken(tree, ais, comma, Space.Space); // , + assert(column_widths[column_counter % row_size] >= expr_widths[i]); + const padding = column_widths[column_counter % row_size] - expr_widths[i]; + try ais.writer().writeByteNTimes(' ', padding); + + column_counter += 1; + continue; + } + } + if (single_line and row_size != 1) { + try renderToken(tree, ais, comma, Space.Space); // , + continue; + } + + column_counter = 0; try renderToken(tree, ais, comma, Space.Newline); // , + try renderExtraNewline(tree, ais, next_expr); } else { - try renderToken(tree, ais, comma, Space.None); // , + const maybe_comma = tree.nextToken(expr.*.lastToken()); + if (tree.token_ids[maybe_comma] == .Comma) { + try renderExpression(allocator, ais, tree, expr, Space.None); // , + try renderToken(tree, ais, maybe_comma, Space.Newline); // , + } else { + try renderExpression(allocator, ais, tree, expr, Space.Comma); // , + } } + } - try renderExtraNewline(tree, ais, next_expr); - } else { - try renderExpression(allocator, ais, tree, expr, Space.Comma); // , + if (expr_index == exprs.len) { + break; } } } - return renderToken(tree, ais, rtoken, space); - } else { - try renderToken(tree, ais, lbrace, Space.Space); - for (exprs) |expr, i| { - if (i + 1 < exprs.len) { - const next_expr = exprs[i + 1]; - try renderExpression(allocator, ais, tree, expr, Space.None); - const comma = tree.nextToken(expr.*.lastToken()); - try renderToken(tree, ais, comma, Space.Space); // , - } else { - try renderExpression(allocator, ais, tree, expr, Space.Space); - } - } return renderToken(tree, ais, rtoken, space); } + + // Single line + try renderToken(tree, ais, lbrace, Space.Space); + for (exprs) |expr, i| { + if (i + 1 < exprs.len) { + const next_expr = exprs[i + 1]; + try renderExpression(allocator, ais, tree, expr, Space.None); + const comma = tree.nextToken(expr.*.lastToken()); + try renderToken(tree, ais, comma, Space.Space); // , + } else { + try renderExpression(allocator, ais, tree, expr, Space.Space); + } + } + + return renderToken(tree, ais, rtoken, space); }, .StructInitializer, .StructInitializerDot => { @@ -1004,21 +1061,29 @@ fn renderExpression( }; if (src_has_trailing_comma) { - try renderToken(tree, ais, lparen, Space.Newline); - - const params = call.params(); - for (params) |param_node, i| { + { ais.pushIndent(); defer ais.popIndent(); - if (i + 1 < params.len) { - const next_node = params[i + 1]; - try renderExpression(allocator, ais, tree, param_node, Space.None); - const comma = tree.nextToken(param_node.lastToken()); - try renderToken(tree, ais, comma, Space.Newline); // , - try renderExtraNewline(tree, ais, next_node); - } else { - try renderExpression(allocator, ais, tree, param_node, Space.Comma); + try renderToken(tree, ais, lparen, Space.Newline); // ( + const params = call.params(); + for (params) |param_node, i| { + if (i + 1 < params.len) { + const next_node = params[i + 1]; + try renderExpression(allocator, ais, tree, param_node, Space.None); + + // Unindent the comma for multiline string literals + const maybe_multiline_string = param_node.firstToken(); + const is_multiline_string = tree.token_ids[maybe_multiline_string] == .MultilineStringLiteralLine; + if (is_multiline_string) ais.popIndent(); + defer if (is_multiline_string) ais.pushIndent(); + + const comma = tree.nextToken(param_node.lastToken()); + try renderToken(tree, ais, comma, Space.Newline); // , + try renderExtraNewline(tree, ais, next_node); + } else { + try renderExpression(allocator, ais, tree, param_node, Space.Comma); + } } } return renderToken(tree, ais, call.rtoken, space); @@ -1028,17 +1093,20 @@ fn renderExpression( const params = call.params(); for (params) |param_node, i| { - if (param_node.*.tag == .MultilineStringLiteral) ais.pushIndentOneShot(); + const maybe_comment = param_node.firstToken() - 1; + const maybe_multiline_string = param_node.firstToken(); + if (tree.token_ids[maybe_multiline_string] == .MultilineStringLiteralLine or tree.token_ids[maybe_comment] == .LineComment) { + ais.pushIndentOneShot(); + } try renderExpression(allocator, ais, tree, param_node, Space.None); if (i + 1 < params.len) { - const next_param = params[i + 1]; const comma = tree.nextToken(param_node.lastToken()); try renderToken(tree, ais, comma, Space.Space); } } - return renderToken(tree, ais, call.rtoken, space); + return renderToken(tree, ais, call.rtoken, space); // ) }, .ArrayAccess => { @@ -1429,7 +1497,7 @@ fn renderExpression( try renderToken(tree, ais, builtin_call.builtin_token, Space.None); // @name const src_params_trailing_comma = blk: { - if (builtin_call.params_len < 2) break :blk false; + if (builtin_call.params_len == 0) break :blk false; const last_node = builtin_call.params()[builtin_call.params_len - 1]; const maybe_comma = tree.nextToken(last_node.lastToken()); break :blk tree.token_ids[maybe_comma] == .Comma; @@ -1443,6 +1511,10 @@ fn renderExpression( // render all on one line, no trailing comma const params = builtin_call.params(); for (params) |param_node, i| { + const maybe_comment = param_node.firstToken() - 1; + if (param_node.*.tag == .MultilineStringLiteral or tree.token_ids[maybe_comment] == .LineComment) { + ais.pushIndentOneShot(); + } try renderExpression(allocator, ais, tree, param_node, Space.None); if (i + 1 < params.len) { @@ -1494,19 +1566,20 @@ fn renderExpression( assert(tree.token_ids[lparen] == .LParen); const rparen = tree.prevToken( - // the first token for the annotation expressions is the left - // parenthesis, hence the need for two prevToken - if (fn_proto.getAlignExpr()) |align_expr| - tree.prevToken(tree.prevToken(align_expr.firstToken())) - else if (fn_proto.getSectionExpr()) |section_expr| - tree.prevToken(tree.prevToken(section_expr.firstToken())) - else if (fn_proto.getCallconvExpr()) |callconv_expr| - tree.prevToken(tree.prevToken(callconv_expr.firstToken())) - else switch (fn_proto.return_type) { - .Explicit => |node| node.firstToken(), - .InferErrorSet => |node| tree.prevToken(node.firstToken()), - .Invalid => unreachable, - }); + // the first token for the annotation expressions is the left + // parenthesis, hence the need for two prevToken + if (fn_proto.getAlignExpr()) |align_expr| + tree.prevToken(tree.prevToken(align_expr.firstToken())) + else if (fn_proto.getSectionExpr()) |section_expr| + tree.prevToken(tree.prevToken(section_expr.firstToken())) + else if (fn_proto.getCallconvExpr()) |callconv_expr| + tree.prevToken(tree.prevToken(callconv_expr.firstToken())) + else switch (fn_proto.return_type) { + .Explicit => |node| node.firstToken(), + .InferErrorSet => |node| tree.prevToken(node.firstToken()), + .Invalid => unreachable, + }, + ); assert(tree.token_ids[rparen] == .RParen); const src_params_trailing_comma = blk: { @@ -1758,7 +1831,7 @@ fn renderExpression( } if (while_node.payload) |payload| { - const payload_space = Space.Space; //if (while_node.continue_expr != null) Space.Space else block_start_space; + const payload_space = if (while_node.continue_expr != null) Space.Space else block_start_space; try renderExpression(allocator, ais, tree, payload, payload_space); } @@ -1873,7 +1946,12 @@ fn renderExpression( if (src_has_newline) { const after_rparen_space = if (if_node.payload == null) Space.Newline else Space.Space; - try renderToken(tree, ais, rparen, after_rparen_space); // ) + + { + ais.pushIndent(); + defer ais.popIndent(); + try renderToken(tree, ais, rparen, after_rparen_space); // ) + } if (if_node.payload) |payload| { try renderExpression(allocator, ais, tree, payload, Space.Newline); @@ -2558,3 +2636,27 @@ fn copyFixingWhitespace(ais: anytype, slice: []const u8) @TypeOf(ais.*).Error!vo else => try ais.writer().writeByte(byte), }; } + +fn rowSize(tree: *ast.Tree, exprs: []*ast.Node, rtoken: ast.TokenIndex) ?usize { + const first_token = exprs[0].firstToken(); + const first_loc = tree.tokenLocation(tree.token_locs[first_token].start, rtoken); + if (first_loc.line == 0) { + const maybe_comma = tree.prevToken(rtoken); + if (tree.token_ids[maybe_comma] == .Comma) + return 1; + return null; // no newlines + } + + var count: usize = 1; + for (exprs) |expr, i| { + if (i + 1 < exprs.len) { + const expr_last_token = expr.lastToken() + 1; + const loc = tree.tokenLocation(tree.token_locs[expr_last_token].start, exprs[i + 1].firstToken()); + if (loc.line != 0) return count; + count += 1; + } else { + return count; + } + } + unreachable; +} diff --git a/lib/std/zig/tokenizer.zig b/lib/std/zig/tokenizer.zig index 86968c73b2..e40483c022 100644 --- a/lib/std/zig/tokenizer.zig +++ b/lib/std/zig/tokenizer.zig @@ -1195,6 +1195,7 @@ pub const Tokenizer = struct { }, .num_dot_hex => switch (c) { '.' => { + result.id = .IntegerLiteral; self.index -= 1; state = .start; break; @@ -1758,6 +1759,14 @@ test "correctly parse pointer assignment" { }); } +test "tokenizer - range literals" { + testTokenize("0...9", &[_]Token.Id{ .IntegerLiteral, .Ellipsis3, .IntegerLiteral }); + testTokenize("'0'...'9'", &[_]Token.Id{ .CharLiteral, .Ellipsis3, .CharLiteral }); + testTokenize("0x00...0x09", &[_]Token.Id{ .IntegerLiteral, .Ellipsis3, .IntegerLiteral }); + testTokenize("0b00...0b11", &[_]Token.Id{ .IntegerLiteral, .Ellipsis3, .IntegerLiteral }); + testTokenize("0o00...0o11", &[_]Token.Id{ .IntegerLiteral, .Ellipsis3, .IntegerLiteral }); +} + test "tokenizer - number literals decimal" { testTokenize("0", &[_]Token.Id{.IntegerLiteral}); testTokenize("1", &[_]Token.Id{.IntegerLiteral}); |
