authorgravatar for alex_naskos@hotmail.comAlexandros Naskos <alex_naskos@hotmail.com> 2020-09-29 16:19:44+03:00
committergravatar for noreply@github.comGitHub <noreply@github.com> 2020-09-29 16:19:44+03:00
logd27a34f05c8c78aa9a9a6f2a30d4db1468b50999
tree4aea123e0325d8ce3569305d886f2a3fb1355b08
parent55dfe729b480c2ba121c6f650d160921560d9535
parent3342e28784b9ef7bf8356004a7b2698edcb70b40
signaturebadge-question-mark Signed by PGP key 4AEE18F83AFDEB23

Merge branch 'master' into args-tuple


18 files changed, 1240 insertions(+), 521 deletions(-)

lib/std/event/future.zig+1-1
......@@ -95,7 +95,7 @@ test "std.event.Future" {
9595 // TODO provide a way to run tests in evented I/O mode
9696 if (!std.io.is_async) return error.SkipZigTest;
9797
98 const handle = async testFuture();
98 testFuture();
9999}
100100
101101fn testFuture() void {
lib/std/event/lock.zig+92-113
......@@ -16,107 +16,111 @@ const Loop = std.event.Loop;
1616/// Allows only one actor to hold the lock.
1717/// TODO: make this API also work in blocking I/O mode.
1818pub const Lock = struct {
19 shared: bool,
20 queue: Queue,
21 queue_empty: bool,
19 mutex: std.Mutex = std.Mutex{},
20 head: usize = UNLOCKED,
2221
23 const Queue = std.atomic.Queue(anyframe);
22 const UNLOCKED = 0;
23 const LOCKED = 1;
2424
2525 const global_event_loop = Loop.instance orelse
2626 @compileError("std.event.Lock currently only works with event-based I/O");
2727
28 pub const Held = struct {
29 lock: *Lock,
30
31 pub fn release(self: Held) void {
32 // Resume the next item from the queue.
33 if (self.lock.queue.get()) |node| {
34 global_event_loop.onNextTick(node);
35 return;
36 }
37
38 // We need to release the lock.
39 @atomicStore(bool, &self.lock.queue_empty, true, .SeqCst);
40 @atomicStore(bool, &self.lock.shared, false, .SeqCst);
41
42 // There might be a queue item. If we know the queue is empty, we can be done,
43 // because the other actor will try to obtain the lock.
44 // But if there's a queue item, we are the actor which must loop and attempt
45 // to grab the lock again.
46 if (@atomicLoad(bool, &self.lock.queue_empty, .SeqCst)) {
47 return;
48 }
49
50 while (true) {
51 if (@atomicRmw(bool, &self.lock.shared, .Xchg, true, .SeqCst)) {
52 // We did not obtain the lock. Great, the queue is someone else's problem.
53 return;
54 }
55
56 // Resume the next item from the queue.
57 if (self.lock.queue.get()) |node| {
58 global_event_loop.onNextTick(node);
59 return;
60 }
28 const Waiter = struct {
29 // forced Waiter alignment to ensure it doesn't clash with LOCKED
30 next: ?*Waiter align(2),
31 tail: *Waiter,
32 node: Loop.NextTickNode,
33 };
6134
62 // Release the lock again.
63 @atomicStore(bool, &self.lock.queue_empty, true, .SeqCst);
64 @atomicStore(bool, &self.lock.shared, false, .SeqCst);
35 pub fn initLocked() Lock {
36 return Lock{ .head = LOCKED };
37 }
6538
66 // Find out if we can be done.
67 if (@atomicLoad(bool, &self.lock.queue_empty, .SeqCst)) {
68 return;
69 }
70 }
39 pub fn acquire(self: *Lock) Held {
40 const held = self.mutex.acquire();
41
42 // self.head transitions from multiple stages depending on the value:
43 // UNLOCKED -> LOCKED:
44 // acquire Lock ownership when theres no waiters
45 // LOCKED -> <Waiter head ptr>:
46 // Lock is already owned, enqueue first Waiter
47 // <head ptr> -> <head ptr>:
48 // Lock is owned with pending waiters. Push our waiter to the queue.
49
50 if (self.head == UNLOCKED) {
51 self.head = LOCKED;
52 held.release();
53 return Held{ .lock = self };
7154 }
72 };
7355
74 pub fn init() Lock {
75 return Lock{
76 .shared = false,
77 .queue = Queue.init(),
78 .queue_empty = true,
79 };
80 }
56 var waiter: Waiter = undefined;
57 waiter.next = null;
58 waiter.tail = &waiter;
8159
82 pub fn initLocked() Lock {
83 return Lock{
84 .shared = true,
85 .queue = Queue.init(),
86 .queue_empty = true,
60 const head = switch (self.head) {
61 UNLOCKED => unreachable,
62 LOCKED => null,
63 else => @intToPtr(*Waiter, self.head),
8764 };
88 }
8965
90 /// Must be called when not locked. Not thread safe.
91 /// All calls to acquire() and release() must complete before calling deinit().
92 pub fn deinit(self: *Lock) void {
93 assert(!self.shared);
94 while (self.queue.get()) |node| resume node.data;
95 }
96
97 pub fn acquire(self: *Lock) callconv(.Async) Held {
98 var my_tick_node = Loop.NextTickNode.init(@frame());
66 if (head) |h| {
67 h.tail.next = &waiter;
68 h.tail = &waiter;
69 } else {
70 self.head = @ptrToInt(&waiter);
71 }
9972
100 errdefer _ = self.queue.remove(&my_tick_node); // TODO test canceling an acquire
10173 suspend {
102 self.queue.put(&my_tick_node);
74 waiter.node = Loop.NextTickNode{
75 .prev = undefined,
76 .next = undefined,
77 .data = @frame(),
78 };
79 held.release();
80 }
10381
104 // At this point, we are in the queue, so we might have already been resumed.
82 return Held{ .lock = self };
83 }
10584
106 // We set this bit so that later we can rely on the fact, that if queue_empty == true, some actor
107 // will attempt to grab the lock.
108 @atomicStore(bool, &self.queue_empty, false, .SeqCst);
85 pub const Held = struct {
86 lock: *Lock,
10987
110 if (!@atomicRmw(bool, &self.shared, .Xchg, true, .SeqCst)) {
111 if (self.queue.get()) |node| {
112 // Whether this node is us or someone else, we tail resume it.
113 resume node.data;
88 pub fn release(self: Held) void {
89 const waiter = blk: {
90 const held = self.lock.mutex.acquire();
91 defer held.release();
92
93 // self.head goes through the reverse transition from acquire():
94 // <head ptr> -> <new head ptr>:
95 // pop a waiter from the queue to give Lock ownership when theres still others pending
96 // <head ptr> -> LOCKED:
97 // pop the laster waiter from the queue, while also giving it lock ownership when awaken
98 // LOCKED -> UNLOCKED:
99 // last lock owner releases lock while no one else is waiting for it
100
101 switch (self.lock.head) {
102 UNLOCKED => {
103 unreachable; // Lock unlocked while unlocking
104 },
105 LOCKED => {
106 self.lock.head = UNLOCKED;
107 break :blk null;
108 },
109 else => {
110 const waiter = @intToPtr(*Waiter, self.lock.head);
111 self.lock.head = if (waiter.next == null) LOCKED else @ptrToInt(waiter.next);
112 if (waiter.next) |next|
113 next.tail = waiter.tail;
114 break :blk waiter;
115 },
114116 }
117 };
118
119 if (waiter) |w| {
120 global_event_loop.onNextTick(&w.node);
115121 }
116122 }
117
118 return Held{ .lock = self };
119 }
123 };
120124};
121125
122126test "std.event.Lock" {
......@@ -128,41 +132,16 @@ test "std.event.Lock" {
128132 // TODO https://github.com/ziglang/zig/issues/3251
129133 if (builtin.os.tag == .freebsd) return error.SkipZigTest;
130134
131 // TODO this file has bit-rotted. repair it
132 if (true) return error.SkipZigTest;
133
134 var lock = Lock.init();
135 defer lock.deinit();
136
137 _ = async testLock(&lock);
135 var lock = Lock{};
136 testLock(&lock);
138137
139138 const expected_result = [1]i32{3 * @intCast(i32, shared_test_data.len)} ** shared_test_data.len;
140139 testing.expectEqualSlices(i32, &expected_result, &shared_test_data);
141140}
142fn testLock(lock: *Lock) callconv(.Async) void {
141fn testLock(lock: *Lock) void {
143142 var handle1 = async lockRunner(lock);
144 var tick_node1 = Loop.NextTickNode{
145 .prev = undefined,
146 .next = undefined,
147 .data = &handle1,
148 };
149 Loop.instance.?.onNextTick(&tick_node1);
150
151143 var handle2 = async lockRunner(lock);
152 var tick_node2 = Loop.NextTickNode{
153 .prev = undefined,
154 .next = undefined,
155 .data = &handle2,
156 };
157 Loop.instance.?.onNextTick(&tick_node2);
158
159144 var handle3 = async lockRunner(lock);
160 var tick_node3 = Loop.NextTickNode{
161 .prev = undefined,
162 .next = undefined,
163 .data = &handle3,
164 };
165 Loop.instance.?.onNextTick(&tick_node3);
166145
167146 await handle1;
168147 await handle2;
......@@ -171,13 +150,13 @@ fn testLock(lock: *Lock) callconv(.Async) void {
171150
172151var shared_test_data = [1]i32{0} ** 10;
173152var shared_test_index: usize = 0;
174fn lockRunner(lock: *Lock) callconv(.Async) void {
175 suspend; // resumed by onNextTick
153
154fn lockRunner(lock: *Lock) void {
155 Lock.global_event_loop.yield();
176156
177157 var i: usize = 0;
178158 while (i < shared_test_data.len) : (i += 1) {
179 var lock_frame = async lock.acquire();
180 const handle = await lock_frame;
159 const handle = lock.acquire();
181160 defer handle.release();
182161
183162 shared_test_index = 0;
lib/std/event/loop.zig+315-101
......@@ -721,6 +721,50 @@ pub const Loop = struct {
721721 }
722722 }
723723
724 /// ------- I/0 APIs -------
725 pub fn accept(
726 self: *Loop,
727 /// This argument is a socket that has been created with `socket`, bound to a local address
728 /// with `bind`, and is listening for connections after a `listen`.
729 sockfd: os.fd_t,
730 /// This argument is a pointer to a sockaddr structure. This structure is filled in with the
731 /// address of the peer socket, as known to the communications layer. The exact format of the
732 /// address returned addr is determined by the socket's address family (see `socket` and the
733 /// respective protocol man pages).
734 addr: *os.sockaddr,
735 /// This argument is a value-result argument: the caller must initialize it to contain the
736 /// size (in bytes) of the structure pointed to by addr; on return it will contain the actual size
737 /// of the peer address.
738 ///
739 /// The returned address is truncated if the buffer provided is too small; in this case, `addr_size`
740 /// will return a value greater than was supplied to the call.
741 addr_size: *os.socklen_t,
742 /// The following values can be bitwise ORed in flags to obtain different behavior:
743 /// * `SOCK_CLOEXEC` - Set the close-on-exec (`FD_CLOEXEC`) flag on the new file descriptor. See the
744 /// description of the `O_CLOEXEC` flag in `open` for reasons why this may be useful.
745 flags: u32,
746 ) os.AcceptError!os.fd_t {
747 while (true) {
748 return os.accept(sockfd, addr, addr_size, flags | os.SOCK_NONBLOCK) catch |err| switch (err) {
749 error.WouldBlock => {
750 self.waitUntilFdReadable(sockfd);
751 continue;
752 },
753 else => return err,
754 };
755 }
756 }
757
758 pub fn connect(self: *Loop, sockfd: os.socket_t, sock_addr: *const os.sockaddr, len: os.socklen_t) os.ConnectError!void {
759 os.connect(sockfd, sock_addr, len) catch |err| switch (err) {
760 error.WouldBlock => {
761 self.waitUntilFdWritable(sockfd);
762 return os.getsockoptError(sockfd);
763 },
764 else => return err,
765 };
766 }
767
724768 /// Performs an async `os.open` using a separate thread.
725769 pub fn openZ(self: *Loop, file_path: [*:0]const u8, flags: u32, mode: os.mode_t) os.OpenError!os.fd_t {
726770 var req_node = Request.Node{
......@@ -779,152 +823,309 @@ pub const Loop = struct {
779823
780824 /// Performs an async `os.read` using a separate thread.
781825 /// `fd` must block and not return EAGAIN.
782 pub fn read(self: *Loop, fd: os.fd_t, buf: []u8) os.ReadError!usize {
783 var req_node = Request.Node{
784 .data = .{
785 .msg = .{
786 .read = .{
787 .fd = fd,
788 .buf = buf,
789 .result = undefined,
826 pub fn read(self: *Loop, fd: os.fd_t, buf: []u8, simulate_evented: bool) os.ReadError!usize {
827 if (simulate_evented) {
828 var req_node = Request.Node{
829 .data = .{
830 .msg = .{
831 .read = .{
832 .fd = fd,
833 .buf = buf,
834 .result = undefined,
835 },
790836 },
837 .finish = .{ .TickNode = .{ .data = @frame() } },
791838 },
792 .finish = .{ .TickNode = .{ .data = @frame() } },
793 },
794 };
795 suspend {
796 self.posixFsRequest(&req_node);
839 };
840 suspend {
841 self.posixFsRequest(&req_node);
842 }
843 return req_node.data.msg.read.result;
844 } else {
845 while (true) {
846 return os.read(fd, buf) catch |err| switch (err) {
847 error.WouldBlock => {
848 self.waitUntilFdReadable(fd);
849 continue;
850 },
851 else => return err,
852 };
853 }
797854 }
798 return req_node.data.msg.read.result;
799855 }
800856
801857 /// Performs an async `os.readv` using a separate thread.
802858 /// `fd` must block and not return EAGAIN.
803 pub fn readv(self: *Loop, fd: os.fd_t, iov: []const os.iovec) os.ReadError!usize {
804 var req_node = Request.Node{
805 .data = .{
806 .msg = .{
807 .readv = .{
808 .fd = fd,
809 .iov = iov,
810 .result = undefined,
859 pub fn readv(self: *Loop, fd: os.fd_t, iov: []const os.iovec, simulate_evented: bool) os.ReadError!usize {
860 if (simulate_evented) {
861 var req_node = Request.Node{
862 .data = .{
863 .msg = .{
864 .readv = .{
865 .fd = fd,
866 .iov = iov,
867 .result = undefined,
868 },
811869 },
870 .finish = .{ .TickNode = .{ .data = @frame() } },
812871 },
813 .finish = .{ .TickNode = .{ .data = @frame() } },
814 },
815 };
816 suspend {
817 self.posixFsRequest(&req_node);
872 };
873 suspend {
874 self.posixFsRequest(&req_node);
875 }
876 return req_node.data.msg.readv.result;
877 } else {
878 while (true) {
879 return os.readv(fd, iov) catch |err| switch (err) {
880 error.WouldBlock => {
881 self.waitUntilFdReadable(fd);
882 continue;
883 },
884 else => return err,
885 };
886 }
818887 }
819 return req_node.data.msg.readv.result;
820888 }
821889
822890 /// Performs an async `os.pread` using a separate thread.
823891 /// `fd` must block and not return EAGAIN.
824 pub fn pread(self: *Loop, fd: os.fd_t, buf: []u8, offset: u64) os.PReadError!usize {
825 var req_node = Request.Node{
826 .data = .{
827 .msg = .{
828 .pread = .{
829 .fd = fd,
830 .buf = buf,
831 .offset = offset,
832 .result = undefined,
892 pub fn pread(self: *Loop, fd: os.fd_t, buf: []u8, offset: u64, simulate_evented: bool) os.PReadError!usize {
893 if (simulate_evented) {
894 var req_node = Request.Node{
895 .data = .{
896 .msg = .{
897 .pread = .{
898 .fd = fd,
899 .buf = buf,
900 .offset = offset,
901 .result = undefined,
902 },
833903 },
904 .finish = .{ .TickNode = .{ .data = @frame() } },
834905 },
835 .finish = .{ .TickNode = .{ .data = @frame() } },
836 },
837 };
838 suspend {
839 self.posixFsRequest(&req_node);
906 };
907 suspend {
908 self.posixFsRequest(&req_node);
909 }
910 return req_node.data.msg.pread.result;
911 } else {
912 while (true) {
913 return os.pread(fd, buf, offset) catch |err| switch (err) {
914 error.WouldBlock => {
915 self.waitUntilFdReadable(fd);
916 continue;
917 },
918 else => return err,
919 };
920 }
840921 }
841 return req_node.data.msg.pread.result;
842922 }
843923
844924 /// Performs an async `os.preadv` using a separate thread.
845925 /// `fd` must block and not return EAGAIN.
846 pub fn preadv(self: *Loop, fd: os.fd_t, iov: []const os.iovec, offset: u64) os.ReadError!usize {
847 var req_node = Request.Node{
848 .data = .{
849 .msg = .{
850 .preadv = .{
851 .fd = fd,
852 .iov = iov,
853 .offset = offset,
854 .result = undefined,
926 pub fn preadv(self: *Loop, fd: os.fd_t, iov: []const os.iovec, offset: u64, simulate_evented: bool) os.ReadError!usize {
927 if (simulate_evented) {
928 var req_node = Request.Node{
929 .data = .{
930 .msg = .{
931 .preadv = .{
932 .fd = fd,
933 .iov = iov,
934 .offset = offset,
935 .result = undefined,
936 },
855937 },
938 .finish = .{ .TickNode = .{ .data = @frame() } },
856939 },
857 .finish = .{ .TickNode = .{ .data = @frame() } },
858 },
859 };
860 suspend {
861 self.posixFsRequest(&req_node);
940 };
941 suspend {
942 self.posixFsRequest(&req_node);
943 }
944 return req_node.data.msg.preadv.result;
945 } else {
946 while (true) {
947 return os.preadv(fd, iov, offset) catch |err| switch (err) {
948 error.WouldBlock => {
949 self.waitUntilFdReadable(fd);
950 continue;
951 },
952 else => return err,
953 };
954 }
862955 }
863 return req_node.data.msg.preadv.result;
864956 }
865957
866958 /// Performs an async `os.write` using a separate thread.
867959 /// `fd` must block and not return EAGAIN.
868 pub fn write(self: *Loop, fd: os.fd_t, bytes: []const u8) os.WriteError!usize {
869 var req_node = Request.Node{
870 .data = .{
871 .msg = .{
872 .write = .{
873 .fd = fd,
874 .bytes = bytes,
875 .result = undefined,
960 pub fn write(self: *Loop, fd: os.fd_t, bytes: []const u8, simulate_evented: bool) os.WriteError!usize {
961 if (simulate_evented) {
962 var req_node = Request.Node{
963 .data = .{
964 .msg = .{
965 .write = .{
966 .fd = fd,
967 .bytes = bytes,
968 .result = undefined,
969 },
876970 },
971 .finish = .{ .TickNode = .{ .data = @frame() } },
877972 },
878 .finish = .{ .TickNode = .{ .data = @frame() } },
879 },
880 };
881 suspend {
882 self.posixFsRequest(&req_node);
973 };
974 suspend {
975 self.posixFsRequest(&req_node);
976 }
977 return req_node.data.msg.write.result;
978 } else {
979 while (true) {
980 return os.write(fd, bytes) catch |err| switch (err) {
981 error.WouldBlock => {
982 self.waitUntilFdWritable(fd);
983 continue;
984 },
985 else => return err,
986 };
987 }
883988 }
884 return req_node.data.msg.write.result;
885989 }
886990
887991 /// Performs an async `os.writev` using a separate thread.
888992 /// `fd` must block and not return EAGAIN.
889 pub fn writev(self: *Loop, fd: os.fd_t, iov: []const os.iovec_const) os.WriteError!usize {
890 var req_node = Request.Node{
891 .data = .{
892 .msg = .{
893 .writev = .{
894 .fd = fd,
895 .iov = iov,
896 .result = undefined,
993 pub fn writev(self: *Loop, fd: os.fd_t, iov: []const os.iovec_const, simulate_evented: bool) os.WriteError!usize {
994 if (simulate_evented) {
995 var req_node = Request.Node{
996 .data = .{
997 .msg = .{
998 .writev = .{
999 .fd = fd,
1000 .iov = iov,
1001 .result = undefined,
1002 },
8971003 },
1004 .finish = .{ .TickNode = .{ .data = @frame() } },
8981005 },
899 .finish = .{ .TickNode = .{ .data = @frame() } },
900 },
901 };
902 suspend {
903 self.posixFsRequest(&req_node);
1006 };
1007 suspend {
1008 self.posixFsRequest(&req_node);
1009 }
1010 return req_node.data.msg.writev.result;
1011 } else {
1012 while (true) {
1013 return os.writev(fd, iov) catch |err| switch (err) {
1014 error.WouldBlock => {
1015 self.waitUntilFdWritable(fd);
1016 continue;
1017 },
1018 else => return err,
1019 };
1020 }
1021 }
1022 }
1023
1024 /// Performs an async `os.pwrite` using a separate thread.
1025 /// `fd` must block and not return EAGAIN.
1026 pub fn pwrite(self: *Loop, fd: os.fd_t, bytes: []const u8, offset: u64, simulate_evented: bool) os.PerformsWriteError!usize {
1027 if (simulate_evented) {
1028 var req_node = Request.Node{
1029 .data = .{
1030 .msg = .{
1031 .pwrite = .{
1032 .fd = fd,
1033 .bytes = bytes,
1034 .offset = offset,
1035 .result = undefined,
1036 },
1037 },
1038 .finish = .{ .TickNode = .{ .data = @frame() } },
1039 },
1040 };
1041 suspend {
1042 self.posixFsRequest(&req_node);
1043 }
1044 return req_node.data.msg.pwrite.result;
1045 } else {
1046 while (true) {
1047 return os.pwrite(fd, bytes, offset) catch |err| switch (err) {
1048 error.WouldBlock => {
1049 self.waitUntilFdWritable(fd);
1050 continue;
1051 },
1052 else => return err,
1053 };
1054 }
9041055 }
905 return req_node.data.msg.writev.result;
9061056 }
9071057
9081058 /// Performs an async `os.pwritev` using a separate thread.
9091059 /// `fd` must block and not return EAGAIN.
910 pub fn pwritev(self: *Loop, fd: os.fd_t, iov: []const os.iovec_const, offset: u64) os.WriteError!usize {
911 var req_node = Request.Node{
912 .data = .{
913 .msg = .{
914 .pwritev = .{
915 .fd = fd,
916 .iov = iov,
917 .offset = offset,
918 .result = undefined,
1060 pub fn pwritev(self: *Loop, fd: os.fd_t, iov: []const os.iovec_const, offset: u64, simulate_evented: bool) os.PWriteError!usize {
1061 if (simulate_evented) {
1062 var req_node = Request.Node{
1063 .data = .{
1064 .msg = .{
1065 .pwritev = .{
1066 .fd = fd,
1067 .iov = iov,
1068 .offset = offset,
1069 .result = undefined,
1070 },
9191071 },
1072 .finish = .{ .TickNode = .{ .data = @frame() } },
9201073 },
921 .finish = .{ .TickNode = .{ .data = @frame() } },
922 },
923 };
924 suspend {
925 self.posixFsRequest(&req_node);
1074 };
1075 suspend {
1076 self.posixFsRequest(&req_node);
1077 }
1078 return req_node.data.msg.pwritev.result;
1079 } else {
1080 while (true) {
1081 return os.pwritev(fd, iov, offset) catch |err| switch (err) {
1082 error.WouldBlock => {
1083 self.waitUntilFdWritable(fd);
1084 continue;
1085 },
1086 else => return err,
1087 };
1088 }
1089 }
1090 }
1091
1092 pub fn sendto(
1093 self: *Loop,
1094 /// The file descriptor of the sending socket.
1095 sockfd: os.fd_t,
1096 /// Message to send.
1097 buf: []const u8,
1098 flags: u32,
1099 dest_addr: ?*const os.sockaddr,
1100 addrlen: os.socklen_t,
1101 ) os.SendError!usize {
1102 while (true) {
1103 return os.sendto(sockfd, buf, flags, dest_addr, addrlen) catch |err| switch (err) {
1104 error.WouldBlock => {
1105 self.waitUntilFdWritable(sockfd);
1106 continue;
1107 },
1108 else => return err,
1109 };
1110 }
1111 }
1112
1113 pub fn recvfrom(
1114 sockfd: os.fd_t,
1115 buf: []u8,
1116 flags: u32,
1117 src_addr: ?*os.sockaddr,
1118 addrlen: ?*os.socklen_t,
1119 ) os.RecvFromError!usize {
1120 while (true) {
1121 return os.recvfrom(sockfd, buf, flags, src_addr, addrlen) catch |err| switch (err) {
1122 error.WouldBlock => {
1123 self.waitUntilFdReadable(sockfd);
1124 continue;
1125 },
1126 else => return err,
1127 };
9261128 }
927 return req_node.data.msg.pwritev.result;
9281129 }
9291130
9301131 /// Performs an async `os.faccessatZ` using a separate thread.
......@@ -1079,6 +1280,9 @@ pub const Loop = struct {
10791280 .writev => |*msg| {
10801281 msg.result = os.writev(msg.fd, msg.iov);
10811282 },
1283 .pwrite => |*msg| {
1284 msg.result = os.pwrite(msg.fd, msg.bytes, msg.offset);
1285 },
10821286 .pwritev => |*msg| {
10831287 msg.result = os.pwritev(msg.fd, msg.iov, msg.offset);
10841288 },
......@@ -1148,6 +1352,7 @@ pub const Loop = struct {
11481352 readv: ReadV,
11491353 write: Write,
11501354 writev: WriteV,
1355 pwrite: PWrite,
11511356 pwritev: PWriteV,
11521357 pread: PRead,
11531358 preadv: PReadV,
......@@ -1191,6 +1396,15 @@ pub const Loop = struct {
11911396 pub const Error = os.WriteError;
11921397 };
11931398
1399 pub const PWrite = struct {
1400 fd: os.fd_t,
1401 bytes: []const u8,
1402 offset: usize,
1403 result: Error!usize,
1404
1405 pub const Error = os.PWriteError;
1406 };
1407
11941408 pub const PWriteV = struct {
11951409 fd: os.fd_t,
11961410 iov: []const os.iovec_const,
lib/std/fifo.zig+11-1
......@@ -186,7 +186,9 @@ pub fn LinearFifo(
186186 } else {
187187 var head = self.head + count;
188188 if (powers_of_two) {
189 head &= self.buf.len - 1;
189 // Note it is safe to do a wrapping subtract as
190 // bitwise & with all 1s is a noop
191 head &= self.buf.len -% 1;
190192 } else {
191193 head %= self.buf.len;
192194 }
......@@ -376,6 +378,14 @@ pub fn LinearFifo(
376378 };
377379}
378380
381test "LinearFifo(u8, .Dynamic) discard(0) from empty buffer should not error on overflow" {
382 var fifo = LinearFifo(u8, .Dynamic).init(testing.allocator);
383 defer fifo.deinit();
384
385 // If overflow is not explicitly allowed this will crash in debug / safe mode
386 fifo.discard(0);
387}
388
379389test "LinearFifo(u8, .Dynamic)" {
380390 var fifo = LinearFifo(u8, .Dynamic).init(testing.allocator);
381391 defer fifo.deinit();
lib/std/fs/file.zig+40-24
......@@ -414,10 +414,12 @@ pub const File = struct {
414414 pub fn read(self: File, buffer: []u8) ReadError!usize {
415415 if (is_windows) {
416416 return windows.ReadFile(self.handle, buffer, null, self.intended_io_mode);
417 } else if (self.capable_io_mode != self.intended_io_mode) {
418 return std.event.Loop.instance.?.read(self.handle, buffer);
419 } else {
417 }
418
419 if (self.intended_io_mode == .blocking) {
420420 return os.read(self.handle, buffer);
421 } else {
422 return std.event.Loop.instance.?.read(self.handle, buffer, self.capable_io_mode != self.intended_io_mode);
421423 }
422424 }
423425
......@@ -436,10 +438,12 @@ pub const File = struct {
436438 pub fn pread(self: File, buffer: []u8, offset: u64) PReadError!usize {
437439 if (is_windows) {
438440 return windows.ReadFile(self.handle, buffer, offset, self.intended_io_mode);
439 } else if (self.capable_io_mode != self.intended_io_mode) {
440 return std.event.Loop.instance.?.pread(self.handle, buffer, offset);
441 } else {
441 }
442
443 if (self.intended_io_mode == .blocking) {
442444 return os.pread(self.handle, buffer, offset);
445 } else {
446 return std.event.Loop.instance.?.pread(self.handle, buffer, offset, self.capable_io_mode != self.intended_io_mode);
443447 }
444448 }
445449
......@@ -461,10 +465,12 @@ pub const File = struct {
461465 if (iovecs.len == 0) return @as(usize, 0);
462466 const first = iovecs[0];
463467 return windows.ReadFile(self.handle, first.iov_base[0..first.iov_len], null, self.intended_io_mode);
464 } else if (self.capable_io_mode != self.intended_io_mode) {
465 return std.event.Loop.instance.?.readv(self.handle, iovecs);
466 } else {
468 }
469
470 if (self.intended_io_mode == .blocking) {
467471 return os.readv(self.handle, iovecs);
472 } else {
473 return std.event.Loop.instance.?.readv(self.handle, iovecs, self.capable_io_mode != self.intended_io_mode);
468474 }
469475 }
470476
......@@ -500,10 +506,12 @@ pub const File = struct {
500506 if (iovecs.len == 0) return @as(usize, 0);
501507 const first = iovecs[0];
502508 return windows.ReadFile(self.handle, first.iov_base[0..first.iov_len], offset, self.intended_io_mode);
503 } else if (self.capable_io_mode != self.intended_io_mode) {
504 return std.event.Loop.instance.?.preadv(self.handle, iovecs, offset);
505 } else {
509 }
510
511 if (self.intended_io_mode == .blocking) {
506512 return os.preadv(self.handle, iovecs, offset);
513 } else {
514 return std.event.Loop.instance.?.preadv(self.handle, iovecs, offset, self.capable_io_mode != self.intended_io_mode);
507515 }
508516 }
509517
......@@ -539,10 +547,12 @@ pub const File = struct {
539547 pub fn write(self: File, bytes: []const u8) WriteError!usize {
540548 if (is_windows) {
541549 return windows.WriteFile(self.handle, bytes, null, self.intended_io_mode);
542 } else if (self.capable_io_mode != self.intended_io_mode) {
543 return std.event.Loop.instance.?.write(self.handle, bytes);
544 } else {
550 }
551
552 if (self.intended_io_mode == .blocking) {
545553 return os.write(self.handle, bytes);
554 } else {
555 return std.event.Loop.instance.?.write(self.handle, bytes, self.capable_io_mode != self.intended_io_mode);
546556 }
547557 }
548558
......@@ -556,10 +566,12 @@ pub const File = struct {
556566 pub fn pwrite(self: File, bytes: []const u8, offset: u64) PWriteError!usize {
557567 if (is_windows) {
558568 return windows.WriteFile(self.handle, bytes, offset, self.intended_io_mode);
559 } else if (self.capable_io_mode != self.intended_io_mode) {
560 return std.event.Loop.instance.?.pwrite(self.handle, bytes, offset);
561 } else {
569 }
570
571 if (self.intended_io_mode == .blocking) {
562572 return os.pwrite(self.handle, bytes, offset);
573 } else {
574 return std.event.Loop.instance.?.pwrite(self.handle, bytes, offset, self.capable_io_mode != self.intended_io_mode);
563575 }
564576 }
565577
......@@ -576,10 +588,12 @@ pub const File = struct {
576588 if (iovecs.len == 0) return @as(usize, 0);
577589 const first = iovecs[0];
578590 return windows.WriteFile(self.handle, first.iov_base[0..first.iov_len], null, self.intended_io_mode);
579 } else if (self.capable_io_mode != self.intended_io_mode) {
580 return std.event.Loop.instance.?.writev(self.handle, iovecs);
581 } else {
591 }
592
593 if (self.intended_io_mode == .blocking) {
582594 return os.writev(self.handle, iovecs);
595 } else {
596 return std.event.Loop.instance.?.writev(self.handle, iovecs, self.capable_io_mode != self.intended_io_mode);
583597 }
584598 }
585599
......@@ -607,10 +621,12 @@ pub const File = struct {
607621 if (iovecs.len == 0) return @as(usize, 0);
608622 const first = iovecs[0];
609623 return windows.WriteFile(self.handle, first.iov_base[0..first.iov_len], offset, self.intended_io_mode);
610 } else if (self.capable_io_mode != self.intended_io_mode) {
611 return std.event.Loop.instance.?.pwritev(self.handle, iovecs, offset);
612 } else {
624 }
625
626 if (self.intended_io_mode == .blocking) {
613627 return os.pwritev(self.handle, iovecs, offset);
628 } else {
629 return std.event.Loop.instance.?.pwritev(self.handle, iovecs, offset, self.capable_io_mode != self.intended_io_mode);
614630 }
615631 }
616632
lib/std/fs/test.zig+23
......@@ -813,3 +813,26 @@ fn run_lock_file_test(contexts: []FileLockTestContext) !void {
813813 try threads.append(try std.Thread.spawn(ctx, FileLockTestContext.run));
814814 }
815815}
816
817test "deleteDir" {
818 var tmp_dir = tmpDir(.{});
819 defer tmp_dir.cleanup();
820
821 // deleting a non-existent directory
822 testing.expectError(error.FileNotFound, tmp_dir.dir.deleteDir("test_dir"));
823
824 var dir = try tmp_dir.dir.makeOpenPath("test_dir", .{});
825 var file = try dir.createFile("test_file", .{});
826 file.close();
827 dir.close();
828
829 // deleting a non-empty directory
830 testing.expectError(error.DirNotEmpty, tmp_dir.dir.deleteDir("test_dir"));
831
832 dir = try tmp_dir.dir.openDir("test_dir", .{});
833 try dir.deleteFile("test_file");
834 dir.close();
835
836 // deleting an empty directory
837 try tmp_dir.dir.deleteDir("test_dir");
838}
lib/std/heap.zig+7
......@@ -919,6 +919,13 @@ pub fn testAllocator(base_allocator: *mem.Allocator) !void {
919919 const zero_bit_ptr = try allocator.create(u0);
920920 zero_bit_ptr.* = 0;
921921 allocator.destroy(zero_bit_ptr);
922
923 const oversize = try allocator.allocAdvanced(u32, null, 5, .at_least);
924 testing.expect(oversize.len >= 5);
925 for (oversize) |*item| {
926 item.* = 0xDEADBEEF;
927 }
928 allocator.free(oversize);
922929}
923930
924931pub fn testAllocatorAligned(base_allocator: *mem.Allocator, comptime alignment: u29) !void {
lib/std/heap/arena_allocator.zig+15-6
......@@ -75,13 +75,22 @@ pub const ArenaAllocator = struct {
7575 const adjusted_addr = mem.alignForward(addr, ptr_align);
7676 const adjusted_index = self.state.end_index + (adjusted_addr - addr);
7777 const new_end_index = adjusted_index + n;
78 if (new_end_index > cur_buf.len) {
79 cur_node = try self.createNode(cur_buf.len, n + ptr_align);
80 continue;
78
79 if (new_end_index <= cur_buf.len) {
80 const result = cur_buf[adjusted_index..new_end_index];
81 self.state.end_index = new_end_index;
82 return result;
8183 }
82 const result = cur_buf[adjusted_index..new_end_index];
83 self.state.end_index = new_end_index;
84 return result;
84
85 const bigger_buf_size = @sizeOf(BufNode) + new_end_index;
86 // Try to grow the buffer in-place
87 cur_node.data = self.child_allocator.resize(cur_node.data, bigger_buf_size) catch |err| switch (err) {
88 error.OutOfMemory => {
89 // Allocate a new node if that's not possible
90 cur_node = try self.createNode(cur_buf.len, n + ptr_align);
91 continue;
92 },
93 };
8594 }
8695 }
8796
lib/std/meta.zig+61-24
......@@ -867,34 +867,71 @@ pub fn ArgsTuple(comptime Function: type) type {
867867 });
868868}
869869
870test "ArgsTuple" {
871 const T = struct {
872 fn assertTypeEqual(comptime Expected: type, comptime Actual: type) void {
873 if (Expected != Actual)
874 @compileError("Expected type " ++ @typeName(Expected) ++ ", but got type " ++ @typeName(Actual));
875 }
870/// For a given anonymous list of types, returns a new tuple type
871/// with those types as fields.
872///
873/// Examples:
874/// - `Tuple(&[_]type {})` ⇒ `tuple { }`
875/// - `Tuple(&[_]type {f32})` ⇒ `tuple { f32 }`
876/// - `Tuple(&[_]type {f32,u32})` ⇒ `tuple { f32, u32 }`
877pub fn Tuple(comptime types: []const type) type {
878 var tuple_fields: [types.len]std.builtin.TypeInfo.StructField = undefined;
879 inline for (types) |T, i| {
880 @setEvalBranchQuota(10_000);
881 var num_buf: [128]u8 = undefined;
882 tuple_fields[i] = std.builtin.TypeInfo.StructField{
883 .name = std.fmt.bufPrint(&num_buf, "{d}", .{i}) catch unreachable,
884 .field_type = T,
885 .default_value = @as(?T, null),
886 .is_comptime = false,
887 };
888 }
876889
877 fn assertTuple(comptime expected: anytype, comptime Actual: type) void {
878 const info = @typeInfo(Actual);
879 if (info != .Struct)
880 @compileError("Expected struct type");
881 if (!info.Struct.is_tuple)
882 @compileError("Struct type must be a tuple type");
890 return @Type(std.builtin.TypeInfo{
891 .Struct = std.builtin.TypeInfo.Struct{
892 .is_tuple = true,
893 .layout = .Auto,
894 .decls = &[_]std.builtin.TypeInfo.Declaration{},
895 .fields = &tuple_fields,
896 },
897 });
898}
883899
884 const fields_list = std.meta.fields(Actual);
885 if (expected.len != fields_list.len)
886 @compileError("Argument count mismatch");
900const TupleTester = struct {
901 fn assertTypeEqual(comptime Expected: type, comptime Actual: type) void {
902 if (Expected != Actual)
903 @compileError("Expected type " ++ @typeName(Expected) ++ ", but got type " ++ @typeName(Actual));
904 }
887905
888 inline for (fields_list) |fld, i| {
889 if (expected[i] != fld.field_type) {
890 @compileError("Field " ++ fld.name ++ " expected to be type " ++ @typeName(expected[i]) ++ ", but was type " ++ @typeName(fld.field_type));
891 }
906 fn assertTuple(comptime expected: anytype, comptime Actual: type) void {
907 const info = @typeInfo(Actual);
908 if (info != .Struct)
909 @compileError("Expected struct type");
910 if (!info.Struct.is_tuple)
911 @compileError("Struct type must be a tuple type");
912
913 const fields_list = std.meta.fields(Actual);
914 if (expected.len != fields_list.len)
915 @compileError("Argument count mismatch");
916
917 inline for (fields_list) |fld, i| {
918 if (expected[i] != fld.field_type) {
919 @compileError("Field " ++ fld.name ++ " expected to be type " ++ @typeName(expected[i]) ++ ", but was type " ++ @typeName(fld.field_type));
892920 }
893921 }
894 };
922 }
923};
924
925test "ArgsTuple" {
926 TupleTester.assertTuple(.{}, ArgsTuple(fn () void));
927 TupleTester.assertTuple(.{u32}, ArgsTuple(fn (a: u32) []const u8));
928 TupleTester.assertTuple(.{ u32, f16 }, ArgsTuple(fn (a: u32, b: f16) noreturn));
929 TupleTester.assertTuple(.{ u32, f16, []const u8 }, ArgsTuple(fn (a: u32, b: f16, c: []const u8) noreturn));
930}
895931
896 T.assertTuple(.{}, ArgsTuple(fn () void));
897 T.assertTuple(.{u32}, ArgsTuple(fn (a: u32) []const u8));
898 T.assertTuple(.{ u32, f16 }, ArgsTuple(fn (a: u32, b: f16) noreturn));
899 T.assertTuple(.{ u32, f16, []const u8 }, ArgsTuple(fn (a: u32, b: f16, c: []const u8) noreturn));
932test "Tuple" {
933 TupleTester.assertTuple(.{}, Tuple(&[_]type{}));
934 TupleTester.assertTuple(.{u32}, Tuple(&[_]type{u32}));
935 TupleTester.assertTuple(.{ u32, f16 }, Tuple(&[_]type{ u32, f16 }));
936 TupleTester.assertTuple(.{ u32, f16, []const u8 }, Tuple(&[_]type{ u32, f16, []const u8 }));
900937}
lib/std/net.zig+36-14
......@@ -614,11 +614,11 @@ pub fn connectUnixSocket(path: []const u8) !fs.File {
614614
615615 var addr = try std.net.Address.initUnix(path);
616616
617 try os.connect(
618 sockfd,
619 &addr.any,
620 addr.getOsSockLen(),
621 );
617 if (std.io.is_async) {
618 try loop.connect(sockfd, &addr.any, addr.getOsSockLen());
619 } else {
620 try os.connect(sockfd, &addr.any, addr.getOsSockLen());
621 }
622622
623623 return fs.File{
624624 .handle = sockfd,
......@@ -677,7 +677,13 @@ pub fn tcpConnectToAddress(address: Address) !fs.File {
677677 (if (builtin.os.tag == .windows) 0 else os.SOCK_CLOEXEC);
678678 const sockfd = try os.socket(address.any.family, sock_flags, os.IPPROTO_TCP);
679679 errdefer os.close(sockfd);
680 try os.connect(sockfd, &address.any, address.getOsSockLen());
680
681 if (std.io.is_async) {
682 const loop = std.event.Loop.instance orelse return error.WouldBlock;
683 try loop.connect(sockfd, &address.any, address.getOsSockLen());
684 } else {
685 try os.connect(sockfd, &address.any, address.getOsSockLen());
686 }
681687
682688 return fs.File{ .handle = sockfd };
683689}
......@@ -1429,7 +1435,11 @@ fn resMSendRc(
14291435 if (answers[i].len == 0) {
14301436 var j: usize = 0;
14311437 while (j < ns.len) : (j += 1) {
1432 _ = os.sendto(fd, queries[i], os.MSG_NOSIGNAL, &ns[j].any, sl) catch undefined;
1438 if (std.io.is_async) {
1439 _ = std.event.Loop.instance.?.sendto(fd, queries[i], os.MSG_NOSIGNAL, &ns[j].any, sl) catch undefined;
1440 } else {
1441 _ = os.sendto(fd, queries[i], os.MSG_NOSIGNAL, &ns[j].any, sl) catch undefined;
1442 }
14331443 }
14341444 }
14351445 }
......@@ -1444,7 +1454,10 @@ fn resMSendRc(
14441454
14451455 while (true) {
14461456 var sl_copy = sl;
1447 const rlen = os.recvfrom(fd, answer_bufs[next], 0, &sa.any, &sl_copy) catch break;
1457 const rlen = if (std.io.is_async)
1458 std.event.Loop.instance.?.recvfrom(fd, answer_bufs[next], 0, &sa.any, &sl_copy) catch break
1459 else
1460 os.recvfrom(fd, answer_bufs[next], 0, &sa.any, &sl_copy) catch break;
14481461
14491462 // Ignore non-identifiable packets
14501463 if (rlen < 4) continue;
......@@ -1470,7 +1483,11 @@ fn resMSendRc(
14701483 0, 3 => {},
14711484 2 => if (servfail_retry != 0) {
14721485 servfail_retry -= 1;
1473 _ = os.sendto(fd, queries[i], os.MSG_NOSIGNAL, &ns[j].any, sl) catch undefined;
1486 if (std.io.is_async) {
1487 _ = std.event.Loop.instance.?.sendto(fd, queries[i], os.MSG_NOSIGNAL, &ns[j].any, sl) catch undefined;
1488 } else {
1489 _ = os.sendto(fd, queries[i], os.MSG_NOSIGNAL, &ns[j].any, sl) catch undefined;
1490 }
14741491 },
14751492 else => continue,
14761493 }
......@@ -1661,18 +1678,23 @@ pub const StreamServer = struct {
16611678
16621679 /// If this function succeeds, the returned `Connection` is a caller-managed resource.
16631680 pub fn accept(self: *StreamServer) AcceptError!Connection {
1664 const nonblock = if (std.io.is_async) os.SOCK_NONBLOCK else 0;
1665 const accept_flags = nonblock | os.SOCK_CLOEXEC;
16661681 var accepted_addr: Address = undefined;
16671682 var adr_len: os.socklen_t = @sizeOf(Address);
1668 if (os.accept(self.sockfd.?, &accepted_addr.any, &adr_len, accept_flags)) |fd| {
1683 const accept_result = blk: {
1684 if (std.io.is_async) {
1685 const loop = std.event.Loop.instance orelse return error.UnexpectedError;
1686 break :blk loop.accept(self.sockfd.?, &accepted_addr.any, &adr_len, os.SOCK_CLOEXEC);
1687 } else {
1688 break :blk os.accept(self.sockfd.?, &accepted_addr.any, &adr_len, os.SOCK_CLOEXEC);
1689 }
1690 };
1691
1692 if (accept_result) |fd| {
16691693 return Connection{
16701694 .file = fs.File{ .handle = fd },
16711695 .address = accepted_addr,
16721696 };
16731697 } else |err| switch (err) {
1674 // We only give SOCK_NONBLOCK when I/O mode is async, in which case this error
1675 // is handled by os.accept4.
16761698 error.WouldBlock => unreachable,
16771699 else => |e| return e,
16781700 }
lib/std/os.zig+34-90
......@@ -314,8 +314,8 @@ pub const ReadError = error{
314314
315315/// Returns the number of bytes that were read, which can be less than
316316/// buf.len. If 0 bytes were read, that means EOF.
317/// If the application has a global event loop enabled, EAGAIN is handled
318/// via the event loop. Otherwise EAGAIN results in error.WouldBlock.
317/// If `fd` is opened in non blocking mode, the function will return error.WouldBlock
318/// when EAGAIN is received.
319319///
320320/// Linux has a limit on how many bytes may be transferred in one `read` call, which is `0x7ffff000`
321321/// 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 {
366366 EINTR => continue,
367367 EINVAL => unreachable,
368368 EFAULT => unreachable,
369 EAGAIN => if (std.event.Loop.instance) |loop| {
370 loop.waitUntilFdReadable(fd);
371 continue;
372 } else {
373 return error.WouldBlock;
374 },
369 EAGAIN => return error.WouldBlock,
375370 EBADF => return error.NotOpenForReading, // Can be a race condition.
376371 EIO => return error.InputOutput,
377372 EISDIR => return error.IsDir,
......@@ -387,8 +382,8 @@ pub fn read(fd: fd_t, buf: []u8) ReadError!usize {
387382
388383/// Number of bytes read is returned. Upon reading end-of-file, zero is returned.
389384///
390/// For POSIX systems, if the application has a global event loop enabled, EAGAIN is handled
391/// via the event loop. Otherwise EAGAIN results in `error.WouldBlock`.
385/// For POSIX systems, if `fd` is opened in non blocking mode, the function will
386/// return error.WouldBlock when EAGAIN is received.
392387/// On Windows, if the application has a global event loop enabled, I/O Completion Ports are
393388/// used to perform the I/O. `error.WouldBlock` is not possible on Windows.
394389///
......@@ -428,12 +423,7 @@ pub fn readv(fd: fd_t, iov: []const iovec) ReadError!usize {
428423 EINTR => continue,
429424 EINVAL => unreachable,
430425 EFAULT => unreachable,
431 EAGAIN => if (std.event.Loop.instance) |loop| {
432 loop.waitUntilFdReadable(fd);
433 continue;
434 } else {
435 return error.WouldBlock;
436 },
426 EAGAIN => return error.WouldBlock,
437427 EBADF => return error.NotOpenForReading, // can be a race condition
438428 EIO => return error.InputOutput,
439429 EISDIR => return error.IsDir,
......@@ -450,8 +440,8 @@ pub const PReadError = ReadError || error{Unseekable};
450440///
451441/// Retries when interrupted by a signal.
452442///
453/// For POSIX systems, if the application has a global event loop enabled, EAGAIN is handled
454/// via the event loop. Otherwise EAGAIN results in `error.WouldBlock`.
443/// For POSIX systems, if `fd` is opened in non blocking mode, the function will
444/// return error.WouldBlock when EAGAIN is received.
455445/// On Windows, if the application has a global event loop enabled, I/O Completion Ports are
456446/// used to perform the I/O. `error.WouldBlock` is not possible on Windows.
457447pub 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 {
492482 EINTR => continue,
493483 EINVAL => unreachable,
494484 EFAULT => unreachable,
495 EAGAIN => if (std.event.Loop.instance) |loop| {
496 loop.waitUntilFdReadable(fd);
497 continue;
498 } else {
499 return error.WouldBlock;
500 },
485 EAGAIN => return error.WouldBlock,
501486 EBADF => return error.NotOpenForReading, // Can be a race condition.
502487 EIO => return error.InputOutput,
503488 EISDIR => return error.IsDir,
......@@ -586,8 +571,8 @@ pub fn ftruncate(fd: fd_t, length: u64) TruncateError!void {
586571///
587572/// Retries when interrupted by a signal.
588573///
589/// For POSIX systems, if the application has a global event loop enabled, EAGAIN is handled
590/// via the event loop. Otherwise EAGAIN results in `error.WouldBlock`.
574/// For POSIX systems, if `fd` is opened in non blocking mode, the function will
575/// return error.WouldBlock when EAGAIN is received.
591576/// On Windows, if the application has a global event loop enabled, I/O Completion Ports are
592577/// used to perform the I/O. `error.WouldBlock` is not possible on Windows.
593578///
......@@ -637,12 +622,7 @@ pub fn preadv(fd: fd_t, iov: []const iovec, offset: u64) PReadError!usize {
637622 EINTR => continue,
638623 EINVAL => unreachable,
639624 EFAULT => unreachable,
640 EAGAIN => if (std.event.Loop.instance) |loop| {
641 loop.waitUntilFdReadable(fd);
642 continue;
643 } else {
644 return error.WouldBlock;
645 },
625 EAGAIN => return error.WouldBlock,
646626 EBADF => return error.NotOpenForReading, // can be a race condition
647627 EIO => return error.InputOutput,
648628 EISDIR => return error.IsDir,
......@@ -687,8 +667,8 @@ pub const WriteError = error{
687667/// another write() call to transfer the remaining bytes. The subsequent call will either
688668/// transfer further bytes or may result in an error (e.g., if the disk is now full).
689669///
690/// For POSIX systems, if the application has a global event loop enabled, EAGAIN is handled
691/// via the event loop. Otherwise EAGAIN results in `error.WouldBlock`.
670/// For POSIX systems, if `fd` is opened in non blocking mode, the function will
671/// return error.WouldBlock when EAGAIN is received.
692672/// On Windows, if the application has a global event loop enabled, I/O Completion Ports are
693673/// used to perform the I/O. `error.WouldBlock` is not possible on Windows.
694674///
......@@ -741,12 +721,7 @@ pub fn write(fd: fd_t, bytes: []const u8) WriteError!usize {
741721 EINTR => continue,
742722 EINVAL => unreachable,
743723 EFAULT => unreachable,
744 EAGAIN => if (std.event.Loop.instance) |loop| {
745 loop.waitUntilFdWritable(fd);
746 continue;
747 } else {
748 return error.WouldBlock;
749 },
724 EAGAIN => return error.WouldBlock,
750725 EBADF => return error.NotOpenForWriting, // can be a race condition.
751726 EDESTADDRREQ => unreachable, // `connect` was never called.
752727 EDQUOT => return error.DiskQuota,
......@@ -772,8 +747,8 @@ pub fn write(fd: fd_t, bytes: []const u8) WriteError!usize {
772747/// another write() call to transfer the remaining bytes. The subsequent call will either
773748/// transfer further bytes or may result in an error (e.g., if the disk is now full).
774749///
775/// For POSIX systems, if the application has a global event loop enabled, EAGAIN is handled
776/// via the event loop. Otherwise EAGAIN results in `error.WouldBlock`.
750/// For POSIX systems, if `fd` is opened in non blocking mode, the function will
751/// return error.WouldBlock when EAGAIN is received.k`.
777752/// On Windows, if the application has a global event loop enabled, I/O Completion Ports are
778753/// used to perform the I/O. `error.WouldBlock` is not possible on Windows.
779754///
......@@ -814,12 +789,7 @@ pub fn writev(fd: fd_t, iov: []const iovec_const) WriteError!usize {
814789 EINTR => continue,
815790 EINVAL => unreachable,
816791 EFAULT => unreachable,
817 EAGAIN => if (std.event.Loop.instance) |loop| {
818 loop.waitUntilFdWritable(fd);
819 continue;
820 } else {
821 return error.WouldBlock;
822 },
792 EAGAIN => return error.WouldBlock,
823793 EBADF => return error.NotOpenForWriting, // Can be a race condition.
824794 EDESTADDRREQ => unreachable, // `connect` was never called.
825795 EDQUOT => return error.DiskQuota,
......@@ -847,8 +817,8 @@ pub const PWriteError = WriteError || error{Unseekable};
847817/// another write() call to transfer the remaining bytes. The subsequent call will either
848818/// transfer further bytes or may result in an error (e.g., if the disk is now full).
849819///
850/// For POSIX systems, if the application has a global event loop enabled, EAGAIN is handled
851/// via the event loop. Otherwise EAGAIN results in `error.WouldBlock`.
820/// For POSIX systems, if `fd` is opened in non blocking mode, the function will
821/// return error.WouldBlock when EAGAIN is received.
852822/// On Windows, if the application has a global event loop enabled, I/O Completion Ports are
853823/// used to perform the I/O. `error.WouldBlock` is not possible on Windows.
854824///
......@@ -905,12 +875,7 @@ pub fn pwrite(fd: fd_t, bytes: []const u8, offset: u64) PWriteError!usize {
905875 EINTR => continue,
906876 EINVAL => unreachable,
907877 EFAULT => unreachable,
908 EAGAIN => if (std.event.Loop.instance) |loop| {
909 loop.waitUntilFdWritable(fd);
910 continue;
911 } else {
912 return error.WouldBlock;
913 },
878 EAGAIN => return error.WouldBlock,
914879 EBADF => return error.NotOpenForWriting, // Can be a race condition.
915880 EDESTADDRREQ => unreachable, // `connect` was never called.
916881 EDQUOT => return error.DiskQuota,
......@@ -939,8 +904,8 @@ pub fn pwrite(fd: fd_t, bytes: []const u8, offset: u64) PWriteError!usize {
939904/// another write() call to transfer the remaining bytes. The subsequent call will either
940905/// transfer further bytes or may result in an error (e.g., if the disk is now full).
941906///
942/// If the application has a global event loop enabled, EAGAIN is handled
943/// via the event loop. Otherwise EAGAIN results in `error.WouldBlock`.
907/// If `fd` is opened in non blocking mode, the function will
908/// return error.WouldBlock when EAGAIN is received.
944909///
945910/// The following systems do not have this syscall, and will return partial writes if more than one
946911/// vector is provided:
......@@ -993,12 +958,7 @@ pub fn pwritev(fd: fd_t, iov: []const iovec_const, offset: u64) PWriteError!usiz
993958 EINTR => continue,
994959 EINVAL => unreachable,
995960 EFAULT => unreachable,
996 EAGAIN => if (std.event.Loop.instance) |loop| {
997 loop.waitUntilFdWritable(fd);
998 continue;
999 } else {
1000 return error.WouldBlock;
1001 },
961 EAGAIN => return error.WouldBlock,
1002962 EBADF => return error.NotOpenForWriting, // Can be a race condition.
1003963 EDESTADDRREQ => unreachable, // `connect` was never called.
1004964 EDQUOT => return error.DiskQuota,
......@@ -2846,8 +2806,8 @@ pub const AcceptError = error{
28462806} || UnexpectedError;
28472807
28482808/// Accept a connection on a socket.
2849/// If the application has a global event loop enabled, EAGAIN is handled
2850/// via the event loop. Otherwise EAGAIN results in error.WouldBlock.
2809/// If `sockfd` is opened in non blocking mode, the function will
2810/// return error.WouldBlock when EAGAIN is received.
28512811pub fn accept(
28522812 /// This argument is a socket that has been created with `socket`, bound to a local address
28532813 /// with `bind`, and is listening for connections after a `listen`.
......@@ -2890,12 +2850,7 @@ pub fn accept(
28902850 return fd;
28912851 },
28922852 EINTR => continue,
2893 EAGAIN => if (std.event.Loop.instance) |loop| {
2894 loop.waitUntilFdReadable(sockfd);
2895 continue;
2896 } else {
2897 return error.WouldBlock;
2898 },
2853 EAGAIN => return error.WouldBlock,
28992854 EBADF => unreachable, // always a race condition
29002855 ECONNABORTED => return error.ConnectionAborted,
29012856 EFAULT => unreachable,
......@@ -3081,6 +3036,8 @@ pub const ConnectError = error{
30813036} || UnexpectedError;
30823037
30833038/// Initiate a connection on a socket.
3039/// If `sockfd` is opened in non blocking mode, the function will
3040/// return error.WouldBlock when EAGAIN or EINPROGRESS is received.
30843041pub fn connect(sockfd: socket_t, sock_addr: *const sockaddr, len: socklen_t) ConnectError!void {
30853042 if (builtin.os.tag == .windows) {
30863043 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
31133070 EADDRINUSE => return error.AddressInUse,
31143071 EADDRNOTAVAIL => return error.AddressNotAvailable,
31153072 EAFNOSUPPORT => return error.AddressFamilyNotSupported,
3116 EAGAIN, EINPROGRESS => {
3117 const loop = std.event.Loop.instance orelse return error.WouldBlock;
3118 loop.waitUntilFdWritable(sockfd);
3119 return getsockoptError(sockfd);
3120 },
3073 EAGAIN, EINPROGRESS => return error.WouldBlock,
31213074 EALREADY => unreachable, // The socket is nonblocking and a previous connection attempt has not yet been completed.
31223075 EBADF => unreachable, // sockfd is not a valid open file descriptor.
31233076 ECONNREFUSED => return error.ConnectionRefused,
......@@ -4620,14 +4573,8 @@ pub fn sendto(
46204573 const rc = system.sendto(sockfd, buf.ptr, buf.len, flags, dest_addr, addrlen);
46214574 switch (errno(rc)) {
46224575 0 => return @intCast(usize, rc),
4623
46244576 EACCES => return error.AccessDenied,
4625 EAGAIN => if (std.event.Loop.instance) |loop| {
4626 loop.waitUntilFdWritable(sockfd);
4627 continue;
4628 } else {
4629 return error.WouldBlock;
4630 },
4577 EAGAIN => return error.WouldBlock,
46314578 EALREADY => return error.FastOpenAlreadyInProgress,
46324579 EBADF => unreachable, // always a race condition
46334580 ECONNRESET => return error.ConnectionResetByPeer,
......@@ -5106,6 +5053,8 @@ pub const RecvFromError = error{
51065053 SystemResources,
51075054} || UnexpectedError;
51085055
5056/// If `sockfd` is opened in non blocking mode, the function will
5057/// return error.WouldBlock when EAGAIN is received.
51095058pub fn recvfrom(
51105059 sockfd: fd_t,
51115060 buf: []u8,
......@@ -5123,12 +5072,7 @@ pub fn recvfrom(
51235072 ENOTCONN => unreachable,
51245073 ENOTSOCK => unreachable,
51255074 EINTR => continue,
5126 EAGAIN => if (std.event.Loop.instance) |loop| {
5127 loop.waitUntilFdReadable(sockfd);
5128 continue;
5129 } else {
5130 return error.WouldBlock;
5131 },
5075 EAGAIN => return error.WouldBlock,
51325076 ENOMEM => return error.SystemResources,
51335077 ECONNREFUSED => return error.ConnectionRefused,
51345078 else => |err| return unexpectedErrno(err),
lib/std/os/uefi/tables/system_table.zig+1-1
......@@ -35,7 +35,7 @@ pub const SystemTable = extern struct {
3535 runtime_services: *RuntimeServices,
3636 boot_services: ?*BootServices,
3737 number_of_table_entries: usize,
38 configuration_table: *ConfigurationTable,
38 configuration_table: [*]ConfigurationTable,
3939
4040 pub const signature: u64 = 0x5453595320494249;
4141 pub const revision_1_02: u32 = (1 << 16) | 2;
lib/std/os/windows.zig+17-1
......@@ -764,6 +764,7 @@ pub const DeleteFileError = error{
764764 Unexpected,
765765 NotDir,
766766 IsDir,
767 DirNotEmpty,
767768};
768769
769770pub const DeleteFileOptions = struct {
......@@ -818,7 +819,7 @@ pub fn DeleteFile(sub_path_w: []const u16, options: DeleteFileOptions) DeleteFil
818819 0,
819820 );
820821 switch (rc) {
821 .SUCCESS => return CloseHandle(tmp_handle),
822 .SUCCESS => CloseHandle(tmp_handle),
822823 .OBJECT_NAME_INVALID => unreachable,
823824 .OBJECT_NAME_NOT_FOUND => return error.FileNotFound,
824825 .INVALID_PARAMETER => unreachable,
......@@ -826,6 +827,21 @@ pub fn DeleteFile(sub_path_w: []const u16, options: DeleteFileOptions) DeleteFil
826827 .NOT_A_DIRECTORY => return error.NotDir,
827828 else => return unexpectedStatus(rc),
828829 }
830
831 // If a directory fails to be deleted, CloseHandle will still report success
832 // Check if the directory still exists and return error.DirNotEmpty if true
833 if (options.remove_dir) {
834 var basic_info: FILE_BASIC_INFORMATION = undefined;
835 switch (ntdll.NtQueryAttributesFile(&attr, &basic_info)) {
836 .SUCCESS => return error.DirNotEmpty,
837 .OBJECT_NAME_NOT_FOUND => return,
838 .OBJECT_PATH_NOT_FOUND => return,
839 .INVALID_PARAMETER => unreachable,
840 .ACCESS_DENIED => return error.AccessDenied,
841 .OBJECT_PATH_SYNTAX_BAD => unreachable,
842 else => |urc| return unexpectedStatus(urc),
843 }
844 }
829845}
830846
831847pub const MoveFileError = error{ FileNotFound, Unexpected };
lib/std/zig/ast.zig+9
......@@ -823,6 +823,15 @@ pub const Node = struct {
823823 }
824824 }
825825
826 pub fn findFirstWithId(self: *Node, id: Id) ?*Node {
827 if (self.id == id) return self;
828 var child_i: usize = 0;
829 while (self.iterate(child_i)) |child| : (child_i += 1) {
830 if (child.findFirstWithId(id)) |result| return result;
831 }
832 return null;
833 }
834
826835 pub fn dump(self: *Node, indent: usize) void {
827836 {
828837 var i: usize = 0;
lib/std/zig/parser_test.zig+325-3
......@@ -1301,8 +1301,10 @@ test "zig fmt: array literal with hint" {
13011301 \\const a = []u8{
13021302 \\ 1, 2,
13031303 \\ 3, 4,
1304 \\ 5, 6, // blah
1305 \\ 7, 8,
1304 \\ 5,
1305 \\ 6, // blah
1306 \\ 7,
1307 \\ 8,
13061308 \\};
13071309 \\const a = []u8{
13081310 \\ 1, 2,
......@@ -1372,7 +1374,7 @@ test "zig fmt: multiline string parameter in fn call with trailing comma" {
13721374 \\ \\ZIG_C_HEADER_FILES {}
13731375 \\ \\ZIG_DIA_GUIDS_LIB {}
13741376 \\ \\
1375 \\ ,
1377 \\ ,
13761378 \\ std.cstr.toSliceConst(c.ZIG_CMAKE_BINARY_DIR),
13771379 \\ std.cstr.toSliceConst(c.ZIG_CXX_COMPILER),
13781380 \\ std.cstr.toSliceConst(c.ZIG_DIA_GUIDS_LIB),
......@@ -3321,6 +3323,326 @@ test "zig fmt: Don't add extra newline after if" {
33213323 );
33223324}
33233325
3326test "zig fmt: comments in ternary ifs" {
3327 try testCanonical(
3328 \\const x = if (true) {
3329 \\ 1;
3330 \\} else if (false)
3331 \\ // Comment
3332 \\ 0;
3333 \\const y = if (true)
3334 \\ // Comment
3335 \\ 1
3336 \\else
3337 \\ 0;
3338 \\
3339 \\pub extern "c" fn printf(format: [*:0]const u8, ...) c_int;
3340 \\
3341 );
3342}
3343
3344test "zig fmt: test comments in field access chain" {
3345 try testCanonical(
3346 \\pub const str = struct {
3347 \\ pub const Thing = more.more //
3348 \\ .more() //
3349 \\ .more().more() //
3350 \\ .more() //
3351 \\ // .more() //
3352 \\ .more() //
3353 \\ .more();
3354 \\ data: Data,
3355 \\};
3356 \\
3357 \\pub const str = struct {
3358 \\ pub const Thing = more.more //
3359 \\ .more() //
3360 \\ // .more() //
3361 \\ // .more() //
3362 \\ // .more() //
3363 \\ .more() //
3364 \\ .more();
3365 \\ data: Data,
3366 \\};
3367 \\
3368 \\pub const str = struct {
3369 \\ pub const Thing = more //
3370 \\ .more //
3371 \\ .more() //
3372 \\ .more();
3373 \\ data: Data,
3374 \\};
3375 \\
3376 );
3377}
3378
3379test "zig fmt: Indent comma correctly after multiline string literals in arg list (trailing comma)" {
3380 try testCanonical(
3381 \\fn foo() void {
3382 \\ z.display_message_dialog(
3383 \\ *const [323:0]u8,
3384 \\ \\Message Text
3385 \\ \\------------
3386 \\ \\xxxxxxxxxxxx
3387 \\ \\xxxxxxxxxxxx
3388 \\ ,
3389 \\ g.GtkMessageType.GTK_MESSAGE_WARNING,
3390 \\ null,
3391 \\ );
3392 \\
3393 \\ z.display_message_dialog(*const [323:0]u8,
3394 \\ \\Message Text
3395 \\ \\------------
3396 \\ \\xxxxxxxxxxxx
3397 \\ \\xxxxxxxxxxxx
3398 \\ , g.GtkMessageType.GTK_MESSAGE_WARNING, null);
3399 \\}
3400 \\
3401 );
3402}
3403
3404test "zig fmt: Control flow statement as body of blockless if" {
3405 try testCanonical(
3406 \\pub fn main() void {
3407 \\ const zoom_node = if (focused_node == layout_first)
3408 \\ if (it.next()) {
3409 \\ if (!node.view.pending.float and !node.view.pending.fullscreen) break node;
3410 \\ } else null
3411 \\ else
3412 \\ focused_node;
3413 \\
3414 \\ const zoom_node = if (focused_node == layout_first) while (it.next()) |node| {
3415 \\ if (!node.view.pending.float and !node.view.pending.fullscreen) break node;
3416 \\ } else null else
3417 \\ focused_node;
3418 \\
3419 \\ const zoom_node = if (focused_node == layout_first)
3420 \\ if (it.next()) {
3421 \\ if (!node.view.pending.float and !node.view.pending.fullscreen) break node;
3422 \\ } else null;
3423 \\
3424 \\ const zoom_node = if (focused_node == layout_first) while (it.next()) |node| {
3425 \\ if (!node.view.pending.float and !node.view.pending.fullscreen) break node;
3426 \\ };
3427 \\
3428 \\ const zoom_node = if (focused_node == layout_first) for (nodes) |node| {
3429 \\ break node;
3430 \\ };
3431 \\
3432 \\ const zoom_node = if (focused_node == layout_first) switch (nodes) {
3433 \\ 0 => 0,
3434 \\ } else
3435 \\ focused_node;
3436 \\}
3437 \\
3438 );
3439}
3440
3441test "zig fmt: " {
3442 try testCanonical(
3443 \\pub fn sendViewTags(self: Self) void {
3444 \\ var it = ViewStack(View).iterator(self.output.views.first, std.math.maxInt(u32));
3445 \\ while (it.next()) |node|
3446 \\ view_tags.append(node.view.current_tags) catch {
3447 \\ c.wl_resource_post_no_memory(self.wl_resource);
3448 \\ log.crit(.river_status, "out of memory", .{});
3449 \\ return;
3450 \\ };
3451 \\}
3452 \\
3453 );
3454}
3455
3456test "zig fmt: allow trailing line comments to do manual array formatting" {
3457 try testCanonical(
3458 \\fn foo() void {
3459 \\ self.code.appendSliceAssumeCapacity(&[_]u8{
3460 \\ 0x55, // push rbp
3461 \\ 0x48, 0x89, 0xe5, // mov rbp, rsp
3462 \\ 0x48, 0x81, 0xec, // sub rsp, imm32 (with reloc)
3463 \\ });
3464 \\
3465 \\ di_buf.appendAssumeCapacity(&[_]u8{
3466 \\ 1, DW.TAG_compile_unit, DW.CHILDREN_no, // header
3467 \\ DW.AT_stmt_list, DW_FORM_data4, // form value pairs
3468 \\ DW.AT_low_pc, DW_FORM_addr,
3469 \\ DW.AT_high_pc, DW_FORM_addr,
3470 \\ DW.AT_name, DW_FORM_strp,
3471 \\ DW.AT_comp_dir, DW_FORM_strp,
3472 \\ DW.AT_producer, DW_FORM_strp,
3473 \\ DW.AT_language, DW_FORM_data2,
3474 \\ 0, 0, // sentinel
3475 \\ });
3476 \\
3477 \\ self.code.appendSliceAssumeCapacity(&[_]u8{
3478 \\ 0x55, // push rbp
3479 \\ 0x48, 0x89, 0xe5, // mov rbp, rsp
3480 \\ // How do we handle this?
3481 \\ //0x48, 0x81, 0xec, // sub rsp, imm32 (with reloc)
3482 \\ // Here's a blank line, should that be allowed?
3483 \\
3484 \\ 0x48, 0x89, 0xe5,
3485 \\ 0x33, 0x45,
3486 \\ // Now the comment breaks a single line -- how do we handle this?
3487 \\ 0x88,
3488 \\ });
3489 \\}
3490 \\
3491 );
3492}
3493
3494test "zig fmt: multiline string literals should play nice with array initializers" {
3495 try testCanonical(
3496 \\fn main() void {
3497 \\ var a = .{.{.{.{.{.{.{.{
3498 \\ 0,
3499 \\ }}}}}}}};
3500 \\ myFunc(.{
3501 \\ "aaaaaaa", "bbbbbb", "ccccc",
3502 \\ "dddd", ("eee"), ("fff"),
3503 \\ ("gggg"),
3504 \\ // Line comment
3505 \\ \\Multiline String Literals can be quite long
3506 \\ ,
3507 \\ \\Multiline String Literals can be quite long
3508 \\ \\Multiline String Literals can be quite long
3509 \\ ,
3510 \\ \\Multiline String Literals can be quite long
3511 \\ \\Multiline String Literals can be quite long
3512 \\ \\Multiline String Literals can be quite long
3513 \\ \\Multiline String Literals can be quite long
3514 \\ ,
3515 \\ (
3516 \\ \\Multiline String Literals can be quite long
3517 \\ ),
3518 \\ .{
3519 \\ \\xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
3520 \\ \\xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
3521 \\ \\xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
3522 \\ },
3523 \\ .{(
3524 \\ \\xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
3525 \\ )},
3526 \\ .{
3527 \\ "xxxxxxx", "xxx",
3528 \\ (
3529 \\ \\ xxx
3530 \\ ),
3531 \\ "xxx", "xxx",
3532 \\ },
3533 \\ .{ "xxxxxxx", "xxx", "xxx", "xxx" }, .{ "xxxxxxx", "xxx", "xxx", "xxx" },
3534 \\ "aaaaaaa", "bbbbbb", "ccccc", // -
3535 \\ "dddd", ("eee"), ("fff"),
3536 \\ .{
3537 \\ "xxx", "xxx",
3538 \\ (
3539 \\ \\ xxx
3540 \\ ),
3541 \\ "xxxxxxxxxxxxxx", "xxx",
3542 \\ },
3543 \\ .{
3544 \\ (
3545 \\ \\xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
3546 \\ ),
3547 \\ \\xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
3548 \\ },
3549 \\ \\xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
3550 \\ \\xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
3551 \\ });
3552 \\}
3553 \\
3554 );
3555}
3556
3557test "zig fmt: use of comments and Multiline string literals may force the parameters over multiple lines" {
3558 try testCanonical(
3559 \\pub fn makeMemUndefined(qzz: []u8) i1 {
3560 \\ cases.add( // fixed bug #2032
3561 \\ "compile diagnostic string for top level decl type",
3562 \\ \\export fn entry() void {
3563 \\ \\ var foo: u32 = @This(){};
3564 \\ \\}
3565 \\ , &[_][]const u8{
3566 \\ "tmp.zig:2:27: error: type 'u32' does not support array initialization",
3567 \\ });
3568 \\ @compileError(
3569 \\ \\ unknown-length pointers and C pointers cannot be hashed deeply.
3570 \\ \\ Consider providing your own hash function.
3571 \\ \\ unknown-length pointers and C pointers cannot be hashed deeply.
3572 \\ \\ Consider providing your own hash function.
3573 \\ );
3574 \\ return @intCast(i1, doMemCheckClientRequestExpr(0, // default return
3575 \\ .MakeMemUndefined, @ptrToInt(qzz.ptr), qzz.len, 0, 0, 0));
3576 \\}
3577 \\
3578 \\// This looks like garbage don't do this
3579 \\const rparen = tree.prevToken(
3580 \\// the first token for the annotation expressions is the left
3581 \\// parenthesis, hence the need for two prevToken
3582 \\ if (fn_proto.getAlignExpr()) |align_expr|
3583 \\ tree.prevToken(tree.prevToken(align_expr.firstToken()))
3584 \\else if (fn_proto.getSectionExpr()) |section_expr|
3585 \\ tree.prevToken(tree.prevToken(section_expr.firstToken()))
3586 \\else if (fn_proto.getCallconvExpr()) |callconv_expr|
3587 \\ tree.prevToken(tree.prevToken(callconv_expr.firstToken()))
3588 \\else switch (fn_proto.return_type) {
3589 \\ .Explicit => |node| node.firstToken(),
3590 \\ .InferErrorSet => |node| tree.prevToken(node.firstToken()),
3591 \\ .Invalid => unreachable,
3592 \\});
3593 \\
3594 );
3595}
3596
3597test "zig fmt: single argument trailing commas in @builtins()" {
3598 try testCanonical(
3599 \\pub fn foo(qzz: []u8) i1 {
3600 \\ @panic(
3601 \\ foo,
3602 \\ );
3603 \\ panic(
3604 \\ foo,
3605 \\ );
3606 \\ @panic(
3607 \\ foo,
3608 \\ bar,
3609 \\ );
3610 \\}
3611 \\
3612 );
3613}
3614
3615test "zig fmt: trailing comma should force multiline 1 column" {
3616 try testTransform(
3617 \\pub const UUID_NULL: uuid_t = [16]u8{0,0,0,0,};
3618 \\
3619 ,
3620 \\pub const UUID_NULL: uuid_t = [16]u8{
3621 \\ 0,
3622 \\ 0,
3623 \\ 0,
3624 \\ 0,
3625 \\};
3626 \\
3627 );
3628}
3629
3630test "zig fmt: function params should align nicely" {
3631 try testCanonical(
3632 \\pub fn foo() void {
3633 \\ cases.addRuntimeSafety("slicing operator with sentinel",
3634 \\ \\const std = @import("std");
3635 \\ ++ check_panic_msg ++
3636 \\ \\pub fn main() void {
3637 \\ \\ var buf = [4]u8{'a','b','c',0};
3638 \\ \\ const slice = buf[0..:0];
3639 \\ \\}
3640 \\ );
3641 \\}
3642 \\
3643 );
3644}
3645
33243646const std = @import("std");
33253647const mem = std.mem;
33263648const warn = std.debug.warn;
lib/std/zig/render.zig+241-139
......@@ -522,7 +522,11 @@ fn renderExpression(
522522 break :blk if (loc.line == 0) op_space else Space.Newline;
523523 };
524524
525 try renderToken(tree, ais, infix_op_node.op_token, after_op_space);
525 {
526 ais.pushIndent();
527 defer ais.popIndent();
528 try renderToken(tree, ais, infix_op_node.op_token, after_op_space);
529 }
526530 ais.pushIndentOneShot();
527531 return renderExpression(allocator, ais, tree, infix_op_node.rhs, space);
528532 },
......@@ -710,141 +714,194 @@ fn renderExpression(
710714 .node => |node| tree.nextToken(node.lastToken()),
711715 };
712716
713 if (exprs.len == 0) {
714 switch (lhs) {
715 .dot => |dot| try renderToken(tree, ais, dot, Space.None),
716 .node => |node| try renderExpression(allocator, ais, tree, node, Space.None),
717 }
718
719 {
720 ais.pushIndent();
721 defer ais.popIndent();
722 try renderToken(tree, ais, lbrace, Space.None);
723 }
717 switch (lhs) {
718 .dot => |dot| try renderToken(tree, ais, dot, Space.None),
719 .node => |node| try renderExpression(allocator, ais, tree, node, Space.None),
720 }
724721
722 if (exprs.len == 0) {
723 try renderToken(tree, ais, lbrace, Space.None);
725724 return renderToken(tree, ais, rtoken, space);
726725 }
727 if (exprs.len == 1 and tree.token_ids[exprs[0].*.lastToken() + 1] == .RBrace) {
726
727 if (exprs.len == 1 and exprs[0].tag != .MultilineStringLiteral and tree.token_ids[exprs[0].*.lastToken() + 1] == .RBrace) {
728728 const expr = exprs[0];
729729
730 switch (lhs) {
731 .dot => |dot| try renderToken(tree, ais, dot, Space.None),
732 .node => |node| try renderExpression(allocator, ais, tree, node, Space.None),
733 }
734730 try renderToken(tree, ais, lbrace, Space.None);
735731 try renderExpression(allocator, ais, tree, expr, Space.None);
736732 return renderToken(tree, ais, rtoken, space);
737733 }
738734
739 switch (lhs) {
740 .dot => |dot| try renderToken(tree, ais, dot, Space.None),
741 .node => |node| try renderExpression(allocator, ais, tree, node, Space.None),
742 }
743
744735 // scan to find row size
745 const maybe_row_size: ?usize = blk: {
746 var count: usize = 1;
747 for (exprs) |expr, i| {
748 if (i + 1 < exprs.len) {
749 const expr_last_token = expr.lastToken() + 1;
750 const loc = tree.tokenLocation(tree.token_locs[expr_last_token].end, exprs[i + 1].firstToken());
751 if (loc.line != 0) break :blk count;
752 count += 1;
753 } else {
754 const expr_last_token = expr.lastToken();
755 const loc = tree.tokenLocation(tree.token_locs[expr_last_token].end, rtoken);
756 if (loc.line == 0) {
757 // all on one line
758 const src_has_trailing_comma = trailblk: {
759 const maybe_comma = tree.prevToken(rtoken);
760 break :trailblk tree.token_ids[maybe_comma] == .Comma;
761 };
762 if (src_has_trailing_comma) {
763 break :blk 1; // force row size 1
764 } else {
765 break :blk null; // no newlines
766 }
767 }
768 break :blk count;
769 }
770 }
771 unreachable;
772 };
773
774 if (maybe_row_size) |row_size| {
775 // A place to store the width of each expression and its column's maximum
776 var widths = try allocator.alloc(usize, exprs.len + row_size);
777 defer allocator.free(widths);
778 mem.set(usize, widths, 0);
779
780 var expr_widths = widths[0 .. widths.len - row_size];
781 var column_widths = widths[widths.len - row_size ..];
782
783 // Null ais for counting the printed length of each expression
784 var counting_stream = std.io.countingOutStream(std.io.null_out_stream);
785 var auto_indenting_stream = std.io.autoIndentingStream(indent_delta, counting_stream.writer());
786
787 for (exprs) |expr, i| {
788 counting_stream.bytes_written = 0;
789 try renderExpression(allocator, &auto_indenting_stream, tree, expr, Space.None);
790 const width = @intCast(usize, counting_stream.bytes_written);
791 const col = i % row_size;
792 column_widths[col] = std.math.max(column_widths[col], width);
793 expr_widths[i] = width;
794 }
795
736 if (rowSize(tree, exprs, rtoken) != null) {
796737 {
797738 ais.pushIndentNextLine();
798739 defer ais.popIndent();
799740 try renderToken(tree, ais, lbrace, Space.Newline);
800741
801 var col: usize = 1;
802 for (exprs) |expr, i| {
803 if (i + 1 < exprs.len) {
804 const next_expr = exprs[i + 1];
805 try renderExpression(allocator, ais, tree, expr, Space.None);
806
807 const comma = tree.nextToken(expr.*.lastToken());
808
809 if (col != row_size) {
810 try renderToken(tree, ais, comma, Space.Space); // ,
811
812 const padding = column_widths[i % row_size] - expr_widths[i];
813 try ais.writer().writeByteNTimes(' ', padding);
814
815 col += 1;
816 continue;
742 var expr_index: usize = 0;
743 while (rowSize(tree, exprs[expr_index..], rtoken)) |row_size| {
744 const row_exprs = exprs[expr_index..];
745 // A place to store the width of each expression and its column's maximum
746 var widths = try allocator.alloc(usize, row_exprs.len + row_size);
747 defer allocator.free(widths);
748 mem.set(usize, widths, 0);
749
750 var expr_newlines = try allocator.alloc(bool, row_exprs.len);
751 defer allocator.free(expr_newlines);
752 mem.set(bool, expr_newlines, false);
753
754 var expr_widths = widths[0 .. widths.len - row_size];
755 var column_widths = widths[widths.len - row_size ..];
756
757 // Find next row with trailing comment (if any) to end the current section
758 var section_end = sec_end: {
759 var this_line_first_expr: usize = 0;
760 var this_line_size = rowSize(tree, row_exprs, rtoken);
761 for (row_exprs) |expr, i| {
762 // Ignore comment on first line of this section
763 if (i == 0 or tree.tokensOnSameLine(row_exprs[0].firstToken(), expr.lastToken())) continue;
764 // Track start of line containing comment
765 if (!tree.tokensOnSameLine(row_exprs[this_line_first_expr].firstToken(), expr.lastToken())) {
766 this_line_first_expr = i;
767 this_line_size = rowSize(tree, row_exprs[this_line_first_expr..], rtoken);
768 }
769
770 const maybe_comma = expr.lastToken() + 1;
771 const maybe_comment = expr.lastToken() + 2;
772 if (maybe_comment < tree.token_ids.len) {
773 if (tree.token_ids[maybe_comma] == .Comma and
774 tree.token_ids[maybe_comment] == .LineComment and
775 tree.tokensOnSameLine(expr.lastToken(), maybe_comment))
776 {
777 var comment_token_loc = tree.token_locs[maybe_comment];
778 const comment_is_empty = mem.trimRight(u8, tree.tokenSliceLoc(comment_token_loc), " ").len == 2;
779 if (!comment_is_empty) {
780 // Found row ending in comment
781 break :sec_end i - this_line_size.? + 1;
782 }
783 }
784 }
817785 }
818 col = 1;
786 break :sec_end row_exprs.len;
787 };
788 expr_index += section_end;
789
790 const section_exprs = row_exprs[0..section_end];
791
792 // Null stream for counting the printed length of each expression
793 var line_find_stream = std.io.findByteOutStream('\n', std.io.null_out_stream);
794 var counting_stream = std.io.countingOutStream(line_find_stream.writer());
795 var auto_indenting_stream = std.io.autoIndentingStream(indent_delta, counting_stream.writer());
796
797 // Calculate size of columns in current section
798 var column_counter: usize = 0;
799 var single_line = true;
800 for (section_exprs) |expr, i| {
801 if (i + 1 < section_exprs.len) {
802 counting_stream.bytes_written = 0;
803 line_find_stream.byte_found = false;
804 try renderExpression(allocator, &auto_indenting_stream, tree, expr, Space.None);
805 const width = @intCast(usize, counting_stream.bytes_written);
806 expr_widths[i] = width;
807 expr_newlines[i] = line_find_stream.byte_found;
808
809 if (!line_find_stream.byte_found) {
810 const column = column_counter % row_size;
811 column_widths[column] = std.math.max(column_widths[column], width);
812
813 const expr_last_token = expr.*.lastToken() + 1;
814 const next_expr = section_exprs[i + 1];
815 const loc = tree.tokenLocation(tree.token_locs[expr_last_token].start, next_expr.*.firstToken());
816 if (loc.line == 0) {
817 column_counter += 1;
818 } else {
819 single_line = false;
820 column_counter = 0;
821 }
822 } else {
823 single_line = false;
824 column_counter = 0;
825 }
826 } else {
827 counting_stream.bytes_written = 0;
828 try renderExpression(allocator, &auto_indenting_stream, tree, expr, Space.None);
829 const width = @intCast(usize, counting_stream.bytes_written);
830 expr_widths[i] = width;
831 expr_newlines[i] = line_find_stream.byte_found;
832
833 if (!line_find_stream.byte_found) {
834 const column = column_counter % row_size;
835 column_widths[column] = std.math.max(column_widths[column], width);
836 }
837 break;
838 }
839 }
819840
820 if (tree.token_ids[tree.nextToken(comma)] != .MultilineStringLiteralLine) {
841 // Render exprs in current section
842 column_counter = 0;
843 var last_col_index: usize = row_size - 1;
844 for (section_exprs) |expr, i| {
845 if (i + 1 < section_exprs.len) {
846 const next_expr = section_exprs[i + 1];
847 try renderExpression(allocator, ais, tree, expr, Space.None);
848
849 const comma = tree.nextToken(expr.*.lastToken());
850
851 if (column_counter != last_col_index) {
852 if (!expr_newlines[i] and !expr_newlines[i + 1]) {
853 // Neither the current or next expression is multiline
854 try renderToken(tree, ais, comma, Space.Space); // ,
855 assert(column_widths[column_counter % row_size] >= expr_widths[i]);
856 const padding = column_widths[column_counter % row_size] - expr_widths[i];
857 try ais.writer().writeByteNTimes(' ', padding);
858
859 column_counter += 1;
860 continue;
861 }
862 }
863 if (single_line and row_size != 1) {
864 try renderToken(tree, ais, comma, Space.Space); // ,
865 continue;
866 }
867
868 column_counter = 0;
821869 try renderToken(tree, ais, comma, Space.Newline); // ,
870 try renderExtraNewline(tree, ais, next_expr);
822871 } else {
823 try renderToken(tree, ais, comma, Space.None); // ,
872 const maybe_comma = tree.nextToken(expr.*.lastToken());
873 if (tree.token_ids[maybe_comma] == .Comma) {
874 try renderExpression(allocator, ais, tree, expr, Space.None); // ,
875 try renderToken(tree, ais, maybe_comma, Space.Newline); // ,
876 } else {
877 try renderExpression(allocator, ais, tree, expr, Space.Comma); // ,
878 }
824879 }
880 }
825881
826 try renderExtraNewline(tree, ais, next_expr);
827 } else {
828 try renderExpression(allocator, ais, tree, expr, Space.Comma); // ,
882 if (expr_index == exprs.len) {
883 break;
829884 }
830885 }
831886 }
832 return renderToken(tree, ais, rtoken, space);
833 } else {
834 try renderToken(tree, ais, lbrace, Space.Space);
835 for (exprs) |expr, i| {
836 if (i + 1 < exprs.len) {
837 const next_expr = exprs[i + 1];
838 try renderExpression(allocator, ais, tree, expr, Space.None);
839 const comma = tree.nextToken(expr.*.lastToken());
840 try renderToken(tree, ais, comma, Space.Space); // ,
841 } else {
842 try renderExpression(allocator, ais, tree, expr, Space.Space);
843 }
844 }
845887
846888 return renderToken(tree, ais, rtoken, space);
847889 }
890
891 // Single line
892 try renderToken(tree, ais, lbrace, Space.Space);
893 for (exprs) |expr, i| {
894 if (i + 1 < exprs.len) {
895 const next_expr = exprs[i + 1];
896 try renderExpression(allocator, ais, tree, expr, Space.None);
897 const comma = tree.nextToken(expr.*.lastToken());
898 try renderToken(tree, ais, comma, Space.Space); // ,
899 } else {
900 try renderExpression(allocator, ais, tree, expr, Space.Space);
901 }
902 }
903
904 return renderToken(tree, ais, rtoken, space);
848905 },
849906
850907 .StructInitializer, .StructInitializerDot => {
......@@ -1004,21 +1061,29 @@ fn renderExpression(
10041061 };
10051062
10061063 if (src_has_trailing_comma) {
1007 try renderToken(tree, ais, lparen, Space.Newline);
1008
1009 const params = call.params();
1010 for (params) |param_node, i| {
1064 {
10111065 ais.pushIndent();
10121066 defer ais.popIndent();
10131067
1014 if (i + 1 < params.len) {
1015 const next_node = params[i + 1];
1016 try renderExpression(allocator, ais, tree, param_node, Space.None);
1017 const comma = tree.nextToken(param_node.lastToken());
1018 try renderToken(tree, ais, comma, Space.Newline); // ,
1019 try renderExtraNewline(tree, ais, next_node);
1020 } else {
1021 try renderExpression(allocator, ais, tree, param_node, Space.Comma);
1068 try renderToken(tree, ais, lparen, Space.Newline); // (
1069 const params = call.params();
1070 for (params) |param_node, i| {
1071 if (i + 1 < params.len) {
1072 const next_node = params[i + 1];
1073 try renderExpression(allocator, ais, tree, param_node, Space.None);
1074
1075 // Unindent the comma for multiline string literals
1076 const maybe_multiline_string = param_node.firstToken();
1077 const is_multiline_string = tree.token_ids[maybe_multiline_string] == .MultilineStringLiteralLine;
1078 if (is_multiline_string) ais.popIndent();
1079 defer if (is_multiline_string) ais.pushIndent();
1080
1081 const comma = tree.nextToken(param_node.lastToken());
1082 try renderToken(tree, ais, comma, Space.Newline); // ,
1083 try renderExtraNewline(tree, ais, next_node);
1084 } else {
1085 try renderExpression(allocator, ais, tree, param_node, Space.Comma);
1086 }
10221087 }
10231088 }
10241089 return renderToken(tree, ais, call.rtoken, space);
......@@ -1028,17 +1093,20 @@ fn renderExpression(
10281093
10291094 const params = call.params();
10301095 for (params) |param_node, i| {
1031 if (param_node.*.tag == .MultilineStringLiteral) ais.pushIndentOneShot();
1096 const maybe_comment = param_node.firstToken() - 1;
1097 const maybe_multiline_string = param_node.firstToken();
1098 if (tree.token_ids[maybe_multiline_string] == .MultilineStringLiteralLine or tree.token_ids[maybe_comment] == .LineComment) {
1099 ais.pushIndentOneShot();
1100 }
10321101
10331102 try renderExpression(allocator, ais, tree, param_node, Space.None);
10341103
10351104 if (i + 1 < params.len) {
1036 const next_param = params[i + 1];
10371105 const comma = tree.nextToken(param_node.lastToken());
10381106 try renderToken(tree, ais, comma, Space.Space);
10391107 }
10401108 }
1041 return renderToken(tree, ais, call.rtoken, space);
1109 return renderToken(tree, ais, call.rtoken, space); // )
10421110 },
10431111
10441112 .ArrayAccess => {
......@@ -1429,7 +1497,7 @@ fn renderExpression(
14291497 try renderToken(tree, ais, builtin_call.builtin_token, Space.None); // @name
14301498
14311499 const src_params_trailing_comma = blk: {
1432 if (builtin_call.params_len < 2) break :blk false;
1500 if (builtin_call.params_len == 0) break :blk false;
14331501 const last_node = builtin_call.params()[builtin_call.params_len - 1];
14341502 const maybe_comma = tree.nextToken(last_node.lastToken());
14351503 break :blk tree.token_ids[maybe_comma] == .Comma;
......@@ -1443,6 +1511,10 @@ fn renderExpression(
14431511 // render all on one line, no trailing comma
14441512 const params = builtin_call.params();
14451513 for (params) |param_node, i| {
1514 const maybe_comment = param_node.firstToken() - 1;
1515 if (param_node.*.tag == .MultilineStringLiteral or tree.token_ids[maybe_comment] == .LineComment) {
1516 ais.pushIndentOneShot();
1517 }
14461518 try renderExpression(allocator, ais, tree, param_node, Space.None);
14471519
14481520 if (i + 1 < params.len) {
......@@ -1494,19 +1566,20 @@ fn renderExpression(
14941566 assert(tree.token_ids[lparen] == .LParen);
14951567
14961568 const rparen = tree.prevToken(
1497 // the first token for the annotation expressions is the left
1498 // parenthesis, hence the need for two prevToken
1499 if (fn_proto.getAlignExpr()) |align_expr|
1500 tree.prevToken(tree.prevToken(align_expr.firstToken()))
1501 else if (fn_proto.getSectionExpr()) |section_expr|
1502 tree.prevToken(tree.prevToken(section_expr.firstToken()))
1503 else if (fn_proto.getCallconvExpr()) |callconv_expr|
1504 tree.prevToken(tree.prevToken(callconv_expr.firstToken()))
1505 else switch (fn_proto.return_type) {
1506 .Explicit => |node| node.firstToken(),
1507 .InferErrorSet => |node| tree.prevToken(node.firstToken()),
1508 .Invalid => unreachable,
1509 });
1569 // the first token for the annotation expressions is the left
1570 // parenthesis, hence the need for two prevToken
1571 if (fn_proto.getAlignExpr()) |align_expr|
1572 tree.prevToken(tree.prevToken(align_expr.firstToken()))
1573 else if (fn_proto.getSectionExpr()) |section_expr|
1574 tree.prevToken(tree.prevToken(section_expr.firstToken()))
1575 else if (fn_proto.getCallconvExpr()) |callconv_expr|
1576 tree.prevToken(tree.prevToken(callconv_expr.firstToken()))
1577 else switch (fn_proto.return_type) {
1578 .Explicit => |node| node.firstToken(),
1579 .InferErrorSet => |node| tree.prevToken(node.firstToken()),
1580 .Invalid => unreachable,
1581 },
1582 );
15101583 assert(tree.token_ids[rparen] == .RParen);
15111584
15121585 const src_params_trailing_comma = blk: {
......@@ -1758,7 +1831,7 @@ fn renderExpression(
17581831 }
17591832
17601833 if (while_node.payload) |payload| {
1761 const payload_space = Space.Space; //if (while_node.continue_expr != null) Space.Space else block_start_space;
1834 const payload_space = if (while_node.continue_expr != null) Space.Space else block_start_space;
17621835 try renderExpression(allocator, ais, tree, payload, payload_space);
17631836 }
17641837
......@@ -1873,7 +1946,12 @@ fn renderExpression(
18731946
18741947 if (src_has_newline) {
18751948 const after_rparen_space = if (if_node.payload == null) Space.Newline else Space.Space;
1876 try renderToken(tree, ais, rparen, after_rparen_space); // )
1949
1950 {
1951 ais.pushIndent();
1952 defer ais.popIndent();
1953 try renderToken(tree, ais, rparen, after_rparen_space); // )
1954 }
18771955
18781956 if (if_node.payload) |payload| {
18791957 try renderExpression(allocator, ais, tree, payload, Space.Newline);
......@@ -2558,3 +2636,27 @@ fn copyFixingWhitespace(ais: anytype, slice: []const u8) @TypeOf(ais.*).Error!vo
25582636 else => try ais.writer().writeByte(byte),
25592637 };
25602638}
2639
2640fn rowSize(tree: *ast.Tree, exprs: []*ast.Node, rtoken: ast.TokenIndex) ?usize {
2641 const first_token = exprs[0].firstToken();
2642 const first_loc = tree.tokenLocation(tree.token_locs[first_token].start, rtoken);
2643 if (first_loc.line == 0) {
2644 const maybe_comma = tree.prevToken(rtoken);
2645 if (tree.token_ids[maybe_comma] == .Comma)
2646 return 1;
2647 return null; // no newlines
2648 }
2649
2650 var count: usize = 1;
2651 for (exprs) |expr, i| {
2652 if (i + 1 < exprs.len) {
2653 const expr_last_token = expr.lastToken() + 1;
2654 const loc = tree.tokenLocation(tree.token_locs[expr_last_token].start, exprs[i + 1].firstToken());
2655 if (loc.line != 0) return count;
2656 count += 1;
2657 } else {
2658 return count;
2659 }
2660 }
2661 unreachable;
2662}
lib/std/zig/tokenizer.zig+9
......@@ -1195,6 +1195,7 @@ pub const Tokenizer = struct {
11951195 },
11961196 .num_dot_hex => switch (c) {
11971197 '.' => {
1198 result.id = .IntegerLiteral;
11981199 self.index -= 1;
11991200 state = .start;
12001201 break;
......@@ -1758,6 +1759,14 @@ test "correctly parse pointer assignment" {
17581759 });
17591760}
17601761
1762test "tokenizer - range literals" {
1763 testTokenize("0...9", &[_]Token.Id{ .IntegerLiteral, .Ellipsis3, .IntegerLiteral });
1764 testTokenize("'0'...'9'", &[_]Token.Id{ .CharLiteral, .Ellipsis3, .CharLiteral });
1765 testTokenize("0x00...0x09", &[_]Token.Id{ .IntegerLiteral, .Ellipsis3, .IntegerLiteral });
1766 testTokenize("0b00...0b11", &[_]Token.Id{ .IntegerLiteral, .Ellipsis3, .IntegerLiteral });
1767 testTokenize("0o00...0o11", &[_]Token.Id{ .IntegerLiteral, .Ellipsis3, .IntegerLiteral });
1768}
1769
17611770test "tokenizer - number literals decimal" {
17621771 testTokenize("0", &[_]Token.Id{.IntegerLiteral});
17631772 testTokenize("1", &[_]Token.Id{.IntegerLiteral});
src/tokenizer.cpp+3-3
......@@ -1225,9 +1225,6 @@ void tokenize(Buf *buf, Tokenization *out) {
12251225 invalid_char_error(&t, c);
12261226 break;
12271227 }
1228 if (t.radix != 16 && t.radix != 10) {
1229 invalid_char_error(&t, c);
1230 }
12311228 t.state = TokenizeStateNumberDot;
12321229 break;
12331230 }
......@@ -1281,6 +1278,9 @@ void tokenize(Buf *buf, Tokenization *out) {
12811278 t.state = TokenizeStateStart;
12821279 continue;
12831280 }
1281 if (t.radix != 16 && t.radix != 10) {
1282 invalid_char_error(&t, c);
1283 }
12841284 t.pos -= 1;
12851285 t.state = TokenizeStateFloatFractionNoUnderscore;
12861286 assert(t.cur_tok->id == TokenIdIntLiteral);