Merge branch 'master' into args-tuple

This commit is contained in:
Alexandros Naskos 2020-09-29 16:19:44 +03:00 committed by GitHub
commit d27a34f05c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
18 changed files with 1247 additions and 528 deletions

View File

@ -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 {

View File

@ -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");
const Waiter = struct {
// forced Waiter alignment to ensure it doesn't clash with LOCKED
next: ?*Waiter align(2),
tail: *Waiter,
node: Loop.NextTickNode,
};
pub fn initLocked() Lock {
return Lock{ .head = LOCKED };
}
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 };
}
var waiter: Waiter = undefined;
waiter.next = null;
waiter.tail = &waiter;
const head = switch (self.head) {
UNLOCKED => unreachable,
LOCKED => null,
else => @intToPtr(*Waiter, self.head),
};
if (head) |h| {
h.tail.next = &waiter;
h.tail = &waiter;
} else {
self.head = @ptrToInt(&waiter);
}
suspend {
waiter.node = Loop.NextTickNode{
.prev = undefined,
.next = undefined,
.data = @frame(),
};
held.release();
}
return Held{ .lock = self };
}
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;
}
const waiter = blk: {
const held = self.lock.mutex.acquire();
defer held.release();
// We need to release the lock.
@atomicStore(bool, &self.lock.queue_empty, true, .SeqCst);
@atomicStore(bool, &self.lock.shared, false, .SeqCst);
// 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
// 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;
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;
},
}
};
// Resume the next item from the queue.
if (self.lock.queue.get()) |node| {
global_event_loop.onNextTick(node);
return;
}
// Release the lock again.
@atomicStore(bool, &self.lock.queue_empty, true, .SeqCst);
@atomicStore(bool, &self.lock.shared, false, .SeqCst);
// Find out if we can be done.
if (@atomicLoad(bool, &self.lock.queue_empty, .SeqCst)) {
return;
}
if (waiter) |w| {
global_event_loop.onNextTick(&w.node);
}
}
};
pub fn init() Lock {
return Lock{
.shared = false,
.queue = Queue.init(),
.queue_empty = true,
};
}
pub fn initLocked() Lock {
return Lock{
.shared = true,
.queue = Queue.init(),
.queue_empty = true,
};
}
/// 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());
errdefer _ = self.queue.remove(&my_tick_node); // TODO test canceling an acquire
suspend {
self.queue.put(&my_tick_node);
// At this point, we are in the queue, so we might have already been resumed.
// 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);
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;
}
}
}
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;

View File

@ -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,

View File

@ -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();

View File

@ -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);
}
}

View File

@ -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");
}

View File

@ -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 {

View File

@ -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;
},
};
}
}

View File

@ -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));
}
}
};
}
};
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 "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));
}
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 }));
}

View File

@ -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,
}

View File

@ -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),

View File

@ -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;

View File

@ -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 };

View File

@ -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;

View File

@ -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;

View File

@ -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);
}
return renderToken(tree, ais, rtoken, space);
}
if (exprs.len == 1 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),
}
if (exprs.len == 0) {
try renderToken(tree, ais, lbrace, Space.None);
return renderToken(tree, ais, rtoken, space);
}
if (exprs.len == 1 and exprs[0].tag != .MultilineStringLiteral and tree.token_ids[exprs[0].*.lastToken() + 1] == .RBrace) {
const expr = exprs[0];
try renderToken(tree, ais, lbrace, Space.None);
try renderExpression(allocator, ais, tree, expr, Space.None);
return renderToken(tree, ais, rtoken, space);
}
// 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);
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);
const comma = tree.nextToken(expr.*.lastToken());
var expr_newlines = try allocator.alloc(bool, row_exprs.len);
defer allocator.free(expr_newlines);
mem.set(bool, expr_newlines, false);
if (col != row_size) {
try renderToken(tree, ais, comma, Space.Space); // ,
var expr_widths = widths[0 .. widths.len - row_size];
var column_widths = widths[widths.len - row_size ..];
const padding = column_widths[i % row_size] - expr_widths[i];
try ais.writer().writeByteNTimes(' ', padding);
// 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);
}
col += 1;
continue;
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;
if (tree.token_ids[tree.nextToken(comma)] != .MultilineStringLiteralLine) {
try renderToken(tree, ais, comma, Space.Newline); // ,
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 {
try renderToken(tree, ais, comma, Space.None); // ,
}
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;
try renderExtraNewline(tree, ais, next_expr);
} else {
try renderExpression(allocator, ais, tree, expr, Space.Comma); // ,
if (!line_find_stream.byte_found) {
const column = column_counter % row_size;
column_widths[column] = std.math.max(column_widths[column], width);
}
break;
}
}
// 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 {
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); // ,
}
}
}
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;
}

View File

@ -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});

View File

@ -1225,9 +1225,6 @@ void tokenize(Buf *buf, Tokenization *out) {
invalid_char_error(&t, c);
break;
}
if (t.radix != 16 && t.radix != 10) {
invalid_char_error(&t, c);
}
t.state = TokenizeStateNumberDot;
break;
}
@ -1281,6 +1278,9 @@ void tokenize(Buf *buf, Tokenization *out) {
t.state = TokenizeStateStart;
continue;
}
if (t.radix != 16 && t.radix != 10) {
invalid_char_error(&t, c);
}
t.pos -= 1;
t.state = TokenizeStateFloatFractionNoUnderscore;
assert(t.cur_tok->id == TokenIdIntLiteral);