authorgravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2020-09-03 23:52:19-07:00
committergravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2020-09-03 23:52:19-07:00
log338f155a02b72117ff710f72c8578e7d2f8eb296
treea902526d5dc901de7458ef318f52f5ac0dad77e7
parentc354f074fa91d3d1672469ba4bbc49a1730e1d01
parent88724b2a89157ecc3a8eea03aa0f8a6b66829915

Merge remote-tracking branch 'origin/master' into llvm11


56 files changed, 4686 insertions(+), 2143 deletions(-)

cmake/Findclang.cmake+2
......@@ -25,6 +25,8 @@ if(ZIG_PREFER_CLANG_CPP_DYLIB)
2525 clang-cpp
2626 PATHS
2727 ${CLANG_LIBDIRS}
28 /usr/lib/llvm/11/lib
29 /usr/lib/llvm/11/lib64
2830 /usr/lib/llvm-11/lib
2931 /usr/local/llvm110/lib
3032 /usr/local/llvm11/lib
cmake/Findllvm.cmake+2
......@@ -26,6 +26,8 @@ if(ZIG_PREFER_CLANG_CPP_DYLIB)
2626 LLVM
2727 PATHS
2828 ${LLVM_LIBDIRS}
29 /usr/lib/llvm/11/lib
30 /usr/lib/llvm/11/lib64
2931 /usr/lib/llvm-11/lib
3032 /usr/local/llvm11/lib
3133 /usr/local/llvm110/lib
lib/std/array_hash_map.zig created+1087
......@@ -0,0 +1,1087 @@
1// SPDX-License-Identifier: MIT
2// Copyright (c) 2015-2020 Zig Contributors
3// This file is part of [zig](https://ziglang.org/), which is MIT licensed.
4// The MIT license requires this copyright notice to be included in all copies
5// and substantial portions of the software.
6const std = @import("std.zig");
7const debug = std.debug;
8const assert = debug.assert;
9const testing = std.testing;
10const math = std.math;
11const mem = std.mem;
12const meta = std.meta;
13const trait = meta.trait;
14const autoHash = std.hash.autoHash;
15const Wyhash = std.hash.Wyhash;
16const Allocator = mem.Allocator;
17const builtin = @import("builtin");
18const hash_map = @This();
19
20pub fn AutoArrayHashMap(comptime K: type, comptime V: type) type {
21 return ArrayHashMap(K, V, getAutoHashFn(K), getAutoEqlFn(K), autoEqlIsCheap(K));
22}
23
24pub fn AutoArrayHashMapUnmanaged(comptime K: type, comptime V: type) type {
25 return ArrayHashMapUnmanaged(K, V, getAutoHashFn(K), getAutoEqlFn(K), autoEqlIsCheap(K));
26}
27
28/// Builtin hashmap for strings as keys.
29pub fn StringArrayHashMap(comptime V: type) type {
30 return ArrayHashMap([]const u8, V, hashString, eqlString, true);
31}
32
33pub fn StringArrayHashMapUnmanaged(comptime V: type) type {
34 return ArrayHashMapUnmanaged([]const u8, V, hashString, eqlString, true);
35}
36
37pub fn eqlString(a: []const u8, b: []const u8) bool {
38 return mem.eql(u8, a, b);
39}
40
41pub fn hashString(s: []const u8) u32 {
42 return @truncate(u32, std.hash.Wyhash.hash(0, s));
43}
44
45/// Insertion order is preserved.
46/// Deletions perform a "swap removal" on the entries list.
47/// Modifying the hash map while iterating is allowed, however one must understand
48/// the (well defined) behavior when mixing insertions and deletions with iteration.
49/// For a hash map that can be initialized directly that does not store an Allocator
50/// field, see `ArrayHashMapUnmanaged`.
51/// When `store_hash` is `false`, this data structure is biased towards cheap `eql`
52/// functions. It does not store each item's hash in the table. Setting `store_hash`
53/// to `true` incurs slightly more memory cost by storing each key's hash in the table
54/// but only has to call `eql` for hash collisions.
55/// If typical operations (except iteration over entries) need to be faster, prefer
56/// the alternative `std.HashMap`.
57pub fn ArrayHashMap(
58 comptime K: type,
59 comptime V: type,
60 comptime hash: fn (key: K) u32,
61 comptime eql: fn (a: K, b: K) bool,
62 comptime store_hash: bool,
63) type {
64 return struct {
65 unmanaged: Unmanaged,
66 allocator: *Allocator,
67
68 pub const Unmanaged = ArrayHashMapUnmanaged(K, V, hash, eql, store_hash);
69 pub const Entry = Unmanaged.Entry;
70 pub const Hash = Unmanaged.Hash;
71 pub const GetOrPutResult = Unmanaged.GetOrPutResult;
72
73 /// Deprecated. Iterate using `items`.
74 pub const Iterator = struct {
75 hm: *const Self,
76 /// Iterator through the entry array.
77 index: usize,
78
79 pub fn next(it: *Iterator) ?*Entry {
80 if (it.index >= it.hm.unmanaged.entries.items.len) return null;
81 const result = &it.hm.unmanaged.entries.items[it.index];
82 it.index += 1;
83 return result;
84 }
85
86 /// Reset the iterator to the initial index
87 pub fn reset(it: *Iterator) void {
88 it.index = 0;
89 }
90 };
91
92 const Self = @This();
93 const Index = Unmanaged.Index;
94
95 pub fn init(allocator: *Allocator) Self {
96 return .{
97 .unmanaged = .{},
98 .allocator = allocator,
99 };
100 }
101
102 pub fn deinit(self: *Self) void {
103 self.unmanaged.deinit(self.allocator);
104 self.* = undefined;
105 }
106
107 pub fn clearRetainingCapacity(self: *Self) void {
108 return self.unmanaged.clearRetainingCapacity();
109 }
110
111 pub fn clearAndFree(self: *Self) void {
112 return self.unmanaged.clearAndFree(self.allocator);
113 }
114
115 /// Deprecated. Use `items().len`.
116 pub fn count(self: Self) usize {
117 return self.items().len;
118 }
119
120 /// Deprecated. Iterate using `items`.
121 pub fn iterator(self: *const Self) Iterator {
122 return Iterator{
123 .hm = self,
124 .index = 0,
125 };
126 }
127
128 /// If key exists this function cannot fail.
129 /// If there is an existing item with `key`, then the result
130 /// `Entry` pointer points to it, and found_existing is true.
131 /// Otherwise, puts a new item with undefined value, and
132 /// the `Entry` pointer points to it. Caller should then initialize
133 /// the value (but not the key).
134 pub fn getOrPut(self: *Self, key: K) !GetOrPutResult {
135 return self.unmanaged.getOrPut(self.allocator, key);
136 }
137
138 /// If there is an existing item with `key`, then the result
139 /// `Entry` pointer points to it, and found_existing is true.
140 /// Otherwise, puts a new item with undefined value, and
141 /// the `Entry` pointer points to it. Caller should then initialize
142 /// the value (but not the key).
143 /// If a new entry needs to be stored, this function asserts there
144 /// is enough capacity to store it.
145 pub fn getOrPutAssumeCapacity(self: *Self, key: K) GetOrPutResult {
146 return self.unmanaged.getOrPutAssumeCapacity(key);
147 }
148
149 pub fn getOrPutValue(self: *Self, key: K, value: V) !*Entry {
150 return self.unmanaged.getOrPutValue(self.allocator, key, value);
151 }
152
153 /// Increases capacity, guaranteeing that insertions up until the
154 /// `expected_count` will not cause an allocation, and therefore cannot fail.
155 pub fn ensureCapacity(self: *Self, new_capacity: usize) !void {
156 return self.unmanaged.ensureCapacity(self.allocator, new_capacity);
157 }
158
159 /// Returns the number of total elements which may be present before it is
160 /// no longer guaranteed that no allocations will be performed.
161 pub fn capacity(self: *Self) usize {
162 return self.unmanaged.capacity();
163 }
164
165 /// Clobbers any existing data. To detect if a put would clobber
166 /// existing data, see `getOrPut`.
167 pub fn put(self: *Self, key: K, value: V) !void {
168 return self.unmanaged.put(self.allocator, key, value);
169 }
170
171 /// Inserts a key-value pair into the hash map, asserting that no previous
172 /// entry with the same key is already present
173 pub fn putNoClobber(self: *Self, key: K, value: V) !void {
174 return self.unmanaged.putNoClobber(self.allocator, key, value);
175 }
176
177 /// Asserts there is enough capacity to store the new key-value pair.
178 /// Clobbers any existing data. To detect if a put would clobber
179 /// existing data, see `getOrPutAssumeCapacity`.
180 pub fn putAssumeCapacity(self: *Self, key: K, value: V) void {
181 return self.unmanaged.putAssumeCapacity(key, value);
182 }
183
184 /// Asserts there is enough capacity to store the new key-value pair.
185 /// Asserts that it does not clobber any existing data.
186 /// To detect if a put would clobber existing data, see `getOrPutAssumeCapacity`.
187 pub fn putAssumeCapacityNoClobber(self: *Self, key: K, value: V) void {
188 return self.unmanaged.putAssumeCapacityNoClobber(key, value);
189 }
190
191 /// Inserts a new `Entry` into the hash map, returning the previous one, if any.
192 pub fn fetchPut(self: *Self, key: K, value: V) !?Entry {
193 return self.unmanaged.fetchPut(self.allocator, key, value);
194 }
195
196 /// Inserts a new `Entry` into the hash map, returning the previous one, if any.
197 /// If insertion happuns, asserts there is enough capacity without allocating.
198 pub fn fetchPutAssumeCapacity(self: *Self, key: K, value: V) ?Entry {
199 return self.unmanaged.fetchPutAssumeCapacity(key, value);
200 }
201
202 pub fn getEntry(self: Self, key: K) ?*Entry {
203 return self.unmanaged.getEntry(key);
204 }
205
206 pub fn getIndex(self: Self, key: K) ?usize {
207 return self.unmanaged.getIndex(key);
208 }
209
210 pub fn get(self: Self, key: K) ?V {
211 return self.unmanaged.get(key);
212 }
213
214 pub fn contains(self: Self, key: K) bool {
215 return self.unmanaged.contains(key);
216 }
217
218 /// If there is an `Entry` with a matching key, it is deleted from
219 /// the hash map, and then returned from this function.
220 pub fn remove(self: *Self, key: K) ?Entry {
221 return self.unmanaged.remove(key);
222 }
223
224 /// Asserts there is an `Entry` with matching key, deletes it from the hash map,
225 /// and discards it.
226 pub fn removeAssertDiscard(self: *Self, key: K) void {
227 return self.unmanaged.removeAssertDiscard(key);
228 }
229
230 pub fn items(self: Self) []Entry {
231 return self.unmanaged.items();
232 }
233
234 pub fn clone(self: Self) !Self {
235 var other = try self.unmanaged.clone(self.allocator);
236 return other.promote(self.allocator);
237 }
238 };
239}
240
241/// General purpose hash table.
242/// Insertion order is preserved.
243/// Deletions perform a "swap removal" on the entries list.
244/// Modifying the hash map while iterating is allowed, however one must understand
245/// the (well defined) behavior when mixing insertions and deletions with iteration.
246/// This type does not store an Allocator field - the Allocator must be passed in
247/// with each function call that requires it. See `ArrayHashMap` for a type that stores
248/// an Allocator field for convenience.
249/// Can be initialized directly using the default field values.
250/// This type is designed to have low overhead for small numbers of entries. When
251/// `store_hash` is `false` and the number of entries in the map is less than 9,
252/// the overhead cost of using `ArrayHashMapUnmanaged` rather than `std.ArrayList` is
253/// only a single pointer-sized integer.
254/// When `store_hash` is `false`, this data structure is biased towards cheap `eql`
255/// functions. It does not store each item's hash in the table. Setting `store_hash`
256/// to `true` incurs slightly more memory cost by storing each key's hash in the table
257/// but guarantees only one call to `eql` per insertion/deletion.
258pub fn ArrayHashMapUnmanaged(
259 comptime K: type,
260 comptime V: type,
261 comptime hash: fn (key: K) u32,
262 comptime eql: fn (a: K, b: K) bool,
263 comptime store_hash: bool,
264) type {
265 return struct {
266 /// It is permitted to access this field directly.
267 entries: std.ArrayListUnmanaged(Entry) = .{},
268
269 /// When entries length is less than `linear_scan_max`, this remains `null`.
270 /// Once entries length grows big enough, this field is allocated. There is
271 /// an IndexHeader followed by an array of Index(I) structs, where I is defined
272 /// by how many total indexes there are.
273 index_header: ?*IndexHeader = null,
274
275 /// Modifying the key is illegal behavior.
276 /// Modifying the value is allowed.
277 /// Entry pointers become invalid whenever this ArrayHashMap is modified,
278 /// unless `ensureCapacity` was previously used.
279 pub const Entry = struct {
280 /// This field is `void` if `store_hash` is `false`.
281 hash: Hash,
282 key: K,
283 value: V,
284 };
285
286 pub const Hash = if (store_hash) u32 else void;
287
288 pub const GetOrPutResult = struct {
289 entry: *Entry,
290 found_existing: bool,
291 };
292
293 pub const Managed = ArrayHashMap(K, V, hash, eql, store_hash);
294
295 const Self = @This();
296
297 const linear_scan_max = 8;
298
299 pub fn promote(self: Self, allocator: *Allocator) Managed {
300 return .{
301 .unmanaged = self,
302 .allocator = allocator,
303 };
304 }
305
306 pub fn deinit(self: *Self, allocator: *Allocator) void {
307 self.entries.deinit(allocator);
308 if (self.index_header) |header| {
309 header.free(allocator);
310 }
311 self.* = undefined;
312 }
313
314 pub fn clearRetainingCapacity(self: *Self) void {
315 self.entries.items.len = 0;
316 if (self.index_header) |header| {
317 header.max_distance_from_start_index = 0;
318 switch (header.capacityIndexType()) {
319 .u8 => mem.set(Index(u8), header.indexes(u8), Index(u8).empty),
320 .u16 => mem.set(Index(u16), header.indexes(u16), Index(u16).empty),
321 .u32 => mem.set(Index(u32), header.indexes(u32), Index(u32).empty),
322 .usize => mem.set(Index(usize), header.indexes(usize), Index(usize).empty),
323 }
324 }
325 }
326
327 pub fn clearAndFree(self: *Self, allocator: *Allocator) void {
328 self.entries.shrink(allocator, 0);
329 if (self.index_header) |header| {
330 header.free(allocator);
331 self.index_header = null;
332 }
333 }
334
335 /// If key exists this function cannot fail.
336 /// If there is an existing item with `key`, then the result
337 /// `Entry` pointer points to it, and found_existing is true.
338 /// Otherwise, puts a new item with undefined value, and
339 /// the `Entry` pointer points to it. Caller should then initialize
340 /// the value (but not the key).
341 pub fn getOrPut(self: *Self, allocator: *Allocator, key: K) !GetOrPutResult {
342 self.ensureCapacity(allocator, self.entries.items.len + 1) catch |err| {
343 // "If key exists this function cannot fail."
344 return GetOrPutResult{
345 .entry = self.getEntry(key) orelse return err,
346 .found_existing = true,
347 };
348 };
349 return self.getOrPutAssumeCapacity(key);
350 }
351
352 /// If there is an existing item with `key`, then the result
353 /// `Entry` pointer points to it, and found_existing is true.
354 /// Otherwise, puts a new item with undefined value, and
355 /// the `Entry` pointer points to it. Caller should then initialize
356 /// the value (but not the key).
357 /// If a new entry needs to be stored, this function asserts there
358 /// is enough capacity to store it.
359 pub fn getOrPutAssumeCapacity(self: *Self, key: K) GetOrPutResult {
360 const header = self.index_header orelse {
361 // Linear scan.
362 const h = if (store_hash) hash(key) else {};
363 for (self.entries.items) |*item| {
364 if (item.hash == h and eql(key, item.key)) {
365 return GetOrPutResult{
366 .entry = item,
367 .found_existing = true,
368 };
369 }
370 }
371 const new_entry = self.entries.addOneAssumeCapacity();
372 new_entry.* = .{
373 .hash = if (store_hash) h else {},
374 .key = key,
375 .value = undefined,
376 };
377 return GetOrPutResult{
378 .entry = new_entry,
379 .found_existing = false,
380 };
381 };
382
383 switch (header.capacityIndexType()) {
384 .u8 => return self.getOrPutInternal(key, header, u8),
385 .u16 => return self.getOrPutInternal(key, header, u16),
386 .u32 => return self.getOrPutInternal(key, header, u32),
387 .usize => return self.getOrPutInternal(key, header, usize),
388 }
389 }
390
391 pub fn getOrPutValue(self: *Self, allocator: *Allocator, key: K, value: V) !*Entry {
392 const res = try self.getOrPut(allocator, key);
393 if (!res.found_existing)
394 res.entry.value = value;
395
396 return res.entry;
397 }
398
399 /// Increases capacity, guaranteeing that insertions up until the
400 /// `expected_count` will not cause an allocation, and therefore cannot fail.
401 pub fn ensureCapacity(self: *Self, allocator: *Allocator, new_capacity: usize) !void {
402 try self.entries.ensureCapacity(allocator, new_capacity);
403 if (new_capacity <= linear_scan_max) return;
404
405 // Ensure that the indexes will be at most 60% full if
406 // `new_capacity` items are put into it.
407 const needed_len = new_capacity * 5 / 3;
408 if (self.index_header) |header| {
409 if (needed_len > header.indexes_len) {
410 // An overflow here would mean the amount of memory required would not
411 // be representable in the address space.
412 const new_indexes_len = math.ceilPowerOfTwo(usize, needed_len) catch unreachable;
413 const new_header = try IndexHeader.alloc(allocator, new_indexes_len);
414 self.insertAllEntriesIntoNewHeader(new_header);
415 header.free(allocator);
416 self.index_header = new_header;
417 }
418 } else {
419 // An overflow here would mean the amount of memory required would not
420 // be representable in the address space.
421 const new_indexes_len = math.ceilPowerOfTwo(usize, needed_len) catch unreachable;
422 const header = try IndexHeader.alloc(allocator, new_indexes_len);
423 self.insertAllEntriesIntoNewHeader(header);
424 self.index_header = header;
425 }
426 }
427
428 /// Returns the number of total elements which may be present before it is
429 /// no longer guaranteed that no allocations will be performed.
430 pub fn capacity(self: Self) usize {
431 const entry_cap = self.entries.capacity;
432 const header = self.index_header orelse return math.min(linear_scan_max, entry_cap);
433 const indexes_cap = (header.indexes_len + 1) * 3 / 4;
434 return math.min(entry_cap, indexes_cap);
435 }
436
437 /// Clobbers any existing data. To detect if a put would clobber
438 /// existing data, see `getOrPut`.
439 pub fn put(self: *Self, allocator: *Allocator, key: K, value: V) !void {
440 const result = try self.getOrPut(allocator, key);
441 result.entry.value = value;
442 }
443
444 /// Inserts a key-value pair into the hash map, asserting that no previous
445 /// entry with the same key is already present
446 pub fn putNoClobber(self: *Self, allocator: *Allocator, key: K, value: V) !void {
447 const result = try self.getOrPut(allocator, key);
448 assert(!result.found_existing);
449 result.entry.value = value;
450 }
451
452 /// Asserts there is enough capacity to store the new key-value pair.
453 /// Clobbers any existing data. To detect if a put would clobber
454 /// existing data, see `getOrPutAssumeCapacity`.
455 pub fn putAssumeCapacity(self: *Self, key: K, value: V) void {
456 const result = self.getOrPutAssumeCapacity(key);
457 result.entry.value = value;
458 }
459
460 /// Asserts there is enough capacity to store the new key-value pair.
461 /// Asserts that it does not clobber any existing data.
462 /// To detect if a put would clobber existing data, see `getOrPutAssumeCapacity`.
463 pub fn putAssumeCapacityNoClobber(self: *Self, key: K, value: V) void {
464 const result = self.getOrPutAssumeCapacity(key);
465 assert(!result.found_existing);
466 result.entry.value = value;
467 }
468
469 /// Inserts a new `Entry` into the hash map, returning the previous one, if any.
470 pub fn fetchPut(self: *Self, allocator: *Allocator, key: K, value: V) !?Entry {
471 const gop = try self.getOrPut(allocator, key);
472 var result: ?Entry = null;
473 if (gop.found_existing) {
474 result = gop.entry.*;
475 }
476 gop.entry.value = value;
477 return result;
478 }
479
480 /// Inserts a new `Entry` into the hash map, returning the previous one, if any.
481 /// If insertion happens, asserts there is enough capacity without allocating.
482 pub fn fetchPutAssumeCapacity(self: *Self, key: K, value: V) ?Entry {
483 const gop = self.getOrPutAssumeCapacity(key);
484 var result: ?Entry = null;
485 if (gop.found_existing) {
486 result = gop.entry.*;
487 }
488 gop.entry.value = value;
489 return result;
490 }
491
492 pub fn getEntry(self: Self, key: K) ?*Entry {
493 const index = self.getIndex(key) orelse return null;
494 return &self.entries.items[index];
495 }
496
497 pub fn getIndex(self: Self, key: K) ?usize {
498 const header = self.index_header orelse {
499 // Linear scan.
500 const h = if (store_hash) hash(key) else {};
501 for (self.entries.items) |*item, i| {
502 if (item.hash == h and eql(key, item.key)) {
503 return i;
504 }
505 }
506 return null;
507 };
508 switch (header.capacityIndexType()) {
509 .u8 => return self.getInternal(key, header, u8),
510 .u16 => return self.getInternal(key, header, u16),
511 .u32 => return self.getInternal(key, header, u32),
512 .usize => return self.getInternal(key, header, usize),
513 }
514 }
515
516 pub fn get(self: Self, key: K) ?V {
517 return if (self.getEntry(key)) |entry| entry.value else null;
518 }
519
520 pub fn contains(self: Self, key: K) bool {
521 return self.getEntry(key) != null;
522 }
523
524 /// If there is an `Entry` with a matching key, it is deleted from
525 /// the hash map, and then returned from this function.
526 pub fn remove(self: *Self, key: K) ?Entry {
527 const header = self.index_header orelse {
528 // Linear scan.
529 const h = if (store_hash) hash(key) else {};
530 for (self.entries.items) |item, i| {
531 if (item.hash == h and eql(key, item.key)) {
532 return self.entries.swapRemove(i);
533 }
534 }
535 return null;
536 };
537 switch (header.capacityIndexType()) {
538 .u8 => return self.removeInternal(key, header, u8),
539 .u16 => return self.removeInternal(key, header, u16),
540 .u32 => return self.removeInternal(key, header, u32),
541 .usize => return self.removeInternal(key, header, usize),
542 }
543 }
544
545 /// Asserts there is an `Entry` with matching key, deletes it from the hash map,
546 /// and discards it.
547 pub fn removeAssertDiscard(self: *Self, key: K) void {
548 assert(self.remove(key) != null);
549 }
550
551 pub fn items(self: Self) []Entry {
552 return self.entries.items;
553 }
554
555 pub fn clone(self: Self, allocator: *Allocator) !Self {
556 var other: Self = .{};
557 try other.entries.appendSlice(allocator, self.entries.items);
558
559 if (self.index_header) |header| {
560 const new_header = try IndexHeader.alloc(allocator, header.indexes_len);
561 other.insertAllEntriesIntoNewHeader(new_header);
562 other.index_header = new_header;
563 }
564 return other;
565 }
566
567 fn removeInternal(self: *Self, key: K, header: *IndexHeader, comptime I: type) ?Entry {
568 const indexes = header.indexes(I);
569 const h = hash(key);
570 const start_index = header.constrainIndex(h);
571 var roll_over: usize = 0;
572 while (roll_over <= header.max_distance_from_start_index) : (roll_over += 1) {
573 const index_index = header.constrainIndex(start_index + roll_over);
574 var index = &indexes[index_index];
575 if (index.isEmpty())
576 return null;
577
578 const entry = &self.entries.items[index.entry_index];
579
580 const hash_match = if (store_hash) h == entry.hash else true;
581 if (!hash_match or !eql(key, entry.key))
582 continue;
583
584 const removed_entry = self.entries.swapRemove(index.entry_index);
585 if (self.entries.items.len > 0 and self.entries.items.len != index.entry_index) {
586 // Because of the swap remove, now we need to update the index that was
587 // pointing to the last entry and is now pointing to this removed item slot.
588 self.updateEntryIndex(header, self.entries.items.len, index.entry_index, I, indexes);
589 }
590
591 // Now we have to shift over the following indexes.
592 roll_over += 1;
593 while (roll_over < header.indexes_len) : (roll_over += 1) {
594 const next_index_index = header.constrainIndex(start_index + roll_over);
595 const next_index = &indexes[next_index_index];
596 if (next_index.isEmpty() or next_index.distance_from_start_index == 0) {
597 index.setEmpty();
598 return removed_entry;
599 }
600 index.* = next_index.*;
601 index.distance_from_start_index -= 1;
602 index = next_index;
603 }
604 unreachable;
605 }
606 return null;
607 }
608
609 fn updateEntryIndex(
610 self: *Self,
611 header: *IndexHeader,
612 old_entry_index: usize,
613 new_entry_index: usize,
614 comptime I: type,
615 indexes: []Index(I),
616 ) void {
617 const h = if (store_hash) self.entries.items[new_entry_index].hash else hash(self.entries.items[new_entry_index].key);
618 const start_index = header.constrainIndex(h);
619 var roll_over: usize = 0;
620 while (roll_over <= header.max_distance_from_start_index) : (roll_over += 1) {
621 const index_index = header.constrainIndex(start_index + roll_over);
622 const index = &indexes[index_index];
623 if (index.entry_index == old_entry_index) {
624 index.entry_index = @intCast(I, new_entry_index);
625 return;
626 }
627 }
628 unreachable;
629 }
630
631 /// Must ensureCapacity before calling this.
632 fn getOrPutInternal(self: *Self, key: K, header: *IndexHeader, comptime I: type) GetOrPutResult {
633 const indexes = header.indexes(I);
634 const h = hash(key);
635 const start_index = header.constrainIndex(h);
636 var roll_over: usize = 0;
637 var distance_from_start_index: usize = 0;
638 while (roll_over <= header.indexes_len) : ({
639 roll_over += 1;
640 distance_from_start_index += 1;
641 }) {
642 const index_index = header.constrainIndex(start_index + roll_over);
643 const index = indexes[index_index];
644 if (index.isEmpty()) {
645 indexes[index_index] = .{
646 .distance_from_start_index = @intCast(I, distance_from_start_index),
647 .entry_index = @intCast(I, self.entries.items.len),
648 };
649 header.maybeBumpMax(distance_from_start_index);
650 const new_entry = self.entries.addOneAssumeCapacity();
651 new_entry.* = .{
652 .hash = if (store_hash) h else {},
653 .key = key,
654 .value = undefined,
655 };
656 return .{
657 .found_existing = false,
658 .entry = new_entry,
659 };
660 }
661
662 // This pointer survives the following append because we call
663 // entries.ensureCapacity before getOrPutInternal.
664 const entry = &self.entries.items[index.entry_index];
665 const hash_match = if (store_hash) h == entry.hash else true;
666 if (hash_match and eql(key, entry.key)) {
667 return .{
668 .found_existing = true,
669 .entry = entry,
670 };
671 }
672 if (index.distance_from_start_index < distance_from_start_index) {
673 // In this case, we did not find the item. We will put a new entry.
674 // However, we will use this index for the new entry, and move
675 // the previous index down the line, to keep the max_distance_from_start_index
676 // as small as possible.
677 indexes[index_index] = .{
678 .distance_from_start_index = @intCast(I, distance_from_start_index),
679 .entry_index = @intCast(I, self.entries.items.len),
680 };
681 header.maybeBumpMax(distance_from_start_index);
682 const new_entry = self.entries.addOneAssumeCapacity();
683 new_entry.* = .{
684 .hash = if (store_hash) h else {},
685 .key = key,
686 .value = undefined,
687 };
688
689 distance_from_start_index = index.distance_from_start_index;
690 var prev_entry_index = index.entry_index;
691
692 // Find somewhere to put the index we replaced by shifting
693 // following indexes backwards.
694 roll_over += 1;
695 distance_from_start_index += 1;
696 while (roll_over < header.indexes_len) : ({
697 roll_over += 1;
698 distance_from_start_index += 1;
699 }) {
700 const next_index_index = header.constrainIndex(start_index + roll_over);
701 const next_index = indexes[next_index_index];
702 if (next_index.isEmpty()) {
703 header.maybeBumpMax(distance_from_start_index);
704 indexes[next_index_index] = .{
705 .entry_index = prev_entry_index,
706 .distance_from_start_index = @intCast(I, distance_from_start_index),
707 };
708 return .{
709 .found_existing = false,
710 .entry = new_entry,
711 };
712 }
713 if (next_index.distance_from_start_index < distance_from_start_index) {
714 header.maybeBumpMax(distance_from_start_index);
715 indexes[next_index_index] = .{
716 .entry_index = prev_entry_index,
717 .distance_from_start_index = @intCast(I, distance_from_start_index),
718 };
719 distance_from_start_index = next_index.distance_from_start_index;
720 prev_entry_index = next_index.entry_index;
721 }
722 }
723 unreachable;
724 }
725 }
726 unreachable;
727 }
728
729 fn getInternal(self: Self, key: K, header: *IndexHeader, comptime I: type) ?usize {
730 const indexes = header.indexes(I);
731 const h = hash(key);
732 const start_index = header.constrainIndex(h);
733 var roll_over: usize = 0;
734 while (roll_over <= header.max_distance_from_start_index) : (roll_over += 1) {
735 const index_index = header.constrainIndex(start_index + roll_over);
736 const index = indexes[index_index];
737 if (index.isEmpty())
738 return null;
739
740 const entry = &self.entries.items[index.entry_index];
741 const hash_match = if (store_hash) h == entry.hash else true;
742 if (hash_match and eql(key, entry.key))
743 return index.entry_index;
744 }
745 return null;
746 }
747
748 fn insertAllEntriesIntoNewHeader(self: *Self, header: *IndexHeader) void {
749 switch (header.capacityIndexType()) {
750 .u8 => return self.insertAllEntriesIntoNewHeaderGeneric(header, u8),
751 .u16 => return self.insertAllEntriesIntoNewHeaderGeneric(header, u16),
752 .u32 => return self.insertAllEntriesIntoNewHeaderGeneric(header, u32),
753 .usize => return self.insertAllEntriesIntoNewHeaderGeneric(header, usize),
754 }
755 }
756
757 fn insertAllEntriesIntoNewHeaderGeneric(self: *Self, header: *IndexHeader, comptime I: type) void {
758 const indexes = header.indexes(I);
759 entry_loop: for (self.entries.items) |entry, i| {
760 const h = if (store_hash) entry.hash else hash(entry.key);
761 const start_index = header.constrainIndex(h);
762 var entry_index = i;
763 var roll_over: usize = 0;
764 var distance_from_start_index: usize = 0;
765 while (roll_over < header.indexes_len) : ({
766 roll_over += 1;
767 distance_from_start_index += 1;
768 }) {
769 const index_index = header.constrainIndex(start_index + roll_over);
770 const next_index = indexes[index_index];
771 if (next_index.isEmpty()) {
772 header.maybeBumpMax(distance_from_start_index);
773 indexes[index_index] = .{
774 .distance_from_start_index = @intCast(I, distance_from_start_index),
775 .entry_index = @intCast(I, entry_index),
776 };
777 continue :entry_loop;
778 }
779 if (next_index.distance_from_start_index < distance_from_start_index) {
780 header.maybeBumpMax(distance_from_start_index);
781 indexes[index_index] = .{
782 .distance_from_start_index = @intCast(I, distance_from_start_index),
783 .entry_index = @intCast(I, entry_index),
784 };
785 distance_from_start_index = next_index.distance_from_start_index;
786 entry_index = next_index.entry_index;
787 }
788 }
789 unreachable;
790 }
791 }
792 };
793}
794
795const CapacityIndexType = enum { u8, u16, u32, usize };
796
797fn capacityIndexType(indexes_len: usize) CapacityIndexType {
798 if (indexes_len < math.maxInt(u8))
799 return .u8;
800 if (indexes_len < math.maxInt(u16))
801 return .u16;
802 if (indexes_len < math.maxInt(u32))
803 return .u32;
804 return .usize;
805}
806
807fn capacityIndexSize(indexes_len: usize) usize {
808 switch (capacityIndexType(indexes_len)) {
809 .u8 => return @sizeOf(Index(u8)),
810 .u16 => return @sizeOf(Index(u16)),
811 .u32 => return @sizeOf(Index(u32)),
812 .usize => return @sizeOf(Index(usize)),
813 }
814}
815
816fn Index(comptime I: type) type {
817 return extern struct {
818 entry_index: I,
819 distance_from_start_index: I,
820
821 const Self = @This();
822
823 const empty = Self{
824 .entry_index = math.maxInt(I),
825 .distance_from_start_index = undefined,
826 };
827
828 fn isEmpty(idx: Self) bool {
829 return idx.entry_index == math.maxInt(I);
830 }
831
832 fn setEmpty(idx: *Self) void {
833 idx.entry_index = math.maxInt(I);
834 }
835 };
836}
837
838/// This struct is trailed by an array of `Index(I)`, where `I`
839/// and the array length are determined by `indexes_len`.
840const IndexHeader = struct {
841 max_distance_from_start_index: usize,
842 indexes_len: usize,
843
844 fn constrainIndex(header: IndexHeader, i: usize) usize {
845 // This is an optimization for modulo of power of two integers;
846 // it requires `indexes_len` to always be a power of two.
847 return i & (header.indexes_len - 1);
848 }
849
850 fn indexes(header: *IndexHeader, comptime I: type) []Index(I) {
851 const start = @ptrCast([*]Index(I), @ptrCast([*]u8, header) + @sizeOf(IndexHeader));
852 return start[0..header.indexes_len];
853 }
854
855 fn capacityIndexType(header: IndexHeader) CapacityIndexType {
856 return hash_map.capacityIndexType(header.indexes_len);
857 }
858
859 fn maybeBumpMax(header: *IndexHeader, distance_from_start_index: usize) void {
860 if (distance_from_start_index > header.max_distance_from_start_index) {
861 header.max_distance_from_start_index = distance_from_start_index;
862 }
863 }
864
865 fn alloc(allocator: *Allocator, len: usize) !*IndexHeader {
866 const index_size = hash_map.capacityIndexSize(len);
867 const nbytes = @sizeOf(IndexHeader) + index_size * len;
868 const bytes = try allocator.allocAdvanced(u8, @alignOf(IndexHeader), nbytes, .exact);
869 @memset(bytes.ptr + @sizeOf(IndexHeader), 0xff, bytes.len - @sizeOf(IndexHeader));
870 const result = @ptrCast(*IndexHeader, bytes.ptr);
871 result.* = .{
872 .max_distance_from_start_index = 0,
873 .indexes_len = len,
874 };
875 return result;
876 }
877
878 fn free(header: *IndexHeader, allocator: *Allocator) void {
879 const index_size = hash_map.capacityIndexSize(header.indexes_len);
880 const ptr = @ptrCast([*]u8, header);
881 const slice = ptr[0 .. @sizeOf(IndexHeader) + header.indexes_len * index_size];
882 allocator.free(slice);
883 }
884};
885
886test "basic hash map usage" {
887 var map = AutoArrayHashMap(i32, i32).init(std.testing.allocator);
888 defer map.deinit();
889
890 testing.expect((try map.fetchPut(1, 11)) == null);
891 testing.expect((try map.fetchPut(2, 22)) == null);
892 testing.expect((try map.fetchPut(3, 33)) == null);
893 testing.expect((try map.fetchPut(4, 44)) == null);
894
895 try map.putNoClobber(5, 55);
896 testing.expect((try map.fetchPut(5, 66)).?.value == 55);
897 testing.expect((try map.fetchPut(5, 55)).?.value == 66);
898
899 const gop1 = try map.getOrPut(5);
900 testing.expect(gop1.found_existing == true);
901 testing.expect(gop1.entry.value == 55);
902 gop1.entry.value = 77;
903 testing.expect(map.getEntry(5).?.value == 77);
904
905 const gop2 = try map.getOrPut(99);
906 testing.expect(gop2.found_existing == false);
907 gop2.entry.value = 42;
908 testing.expect(map.getEntry(99).?.value == 42);
909
910 const gop3 = try map.getOrPutValue(5, 5);
911 testing.expect(gop3.value == 77);
912
913 const gop4 = try map.getOrPutValue(100, 41);
914 testing.expect(gop4.value == 41);
915
916 testing.expect(map.contains(2));
917 testing.expect(map.getEntry(2).?.value == 22);
918 testing.expect(map.get(2).? == 22);
919
920 const rmv1 = map.remove(2);
921 testing.expect(rmv1.?.key == 2);
922 testing.expect(rmv1.?.value == 22);
923 testing.expect(map.remove(2) == null);
924 testing.expect(map.getEntry(2) == null);
925 testing.expect(map.get(2) == null);
926
927 map.removeAssertDiscard(3);
928}
929
930test "iterator hash map" {
931 // https://github.com/ziglang/zig/issues/5127
932 if (std.Target.current.cpu.arch == .mips) return error.SkipZigTest;
933
934 var reset_map = AutoArrayHashMap(i32, i32).init(std.testing.allocator);
935 defer reset_map.deinit();
936
937 // test ensureCapacity with a 0 parameter
938 try reset_map.ensureCapacity(0);
939
940 try reset_map.putNoClobber(0, 11);
941 try reset_map.putNoClobber(1, 22);
942 try reset_map.putNoClobber(2, 33);
943
944 var keys = [_]i32{
945 0, 2, 1,
946 };
947
948 var values = [_]i32{
949 11, 33, 22,
950 };
951
952 var buffer = [_]i32{
953 0, 0, 0,
954 };
955
956 var it = reset_map.iterator();
957 const first_entry = it.next().?;
958 it.reset();
959
960 var count: usize = 0;
961 while (it.next()) |entry| : (count += 1) {
962 buffer[@intCast(usize, entry.key)] = entry.value;
963 }
964 testing.expect(count == 3);
965 testing.expect(it.next() == null);
966
967 for (buffer) |v, i| {
968 testing.expect(buffer[@intCast(usize, keys[i])] == values[i]);
969 }
970
971 it.reset();
972 count = 0;
973 while (it.next()) |entry| {
974 buffer[@intCast(usize, entry.key)] = entry.value;
975 count += 1;
976 if (count >= 2) break;
977 }
978
979 for (buffer[0..2]) |v, i| {
980 testing.expect(buffer[@intCast(usize, keys[i])] == values[i]);
981 }
982
983 it.reset();
984 var entry = it.next().?;
985 testing.expect(entry.key == first_entry.key);
986 testing.expect(entry.value == first_entry.value);
987}
988
989test "ensure capacity" {
990 var map = AutoArrayHashMap(i32, i32).init(std.testing.allocator);
991 defer map.deinit();
992
993 try map.ensureCapacity(20);
994 const initial_capacity = map.capacity();
995 testing.expect(initial_capacity >= 20);
996 var i: i32 = 0;
997 while (i < 20) : (i += 1) {
998 testing.expect(map.fetchPutAssumeCapacity(i, i + 10) == null);
999 }
1000 // shouldn't resize from putAssumeCapacity
1001 testing.expect(initial_capacity == map.capacity());
1002}
1003
1004test "clone" {
1005 var original = AutoArrayHashMap(i32, i32).init(std.testing.allocator);
1006 defer original.deinit();
1007
1008 // put more than `linear_scan_max` so we can test that the index header is properly cloned
1009 var i: u8 = 0;
1010 while (i < 10) : (i += 1) {
1011 try original.putNoClobber(i, i * 10);
1012 }
1013
1014 var copy = try original.clone();
1015 defer copy.deinit();
1016
1017 i = 0;
1018 while (i < 10) : (i += 1) {
1019 testing.expect(copy.get(i).? == i * 10);
1020 }
1021}
1022
1023pub fn getHashPtrAddrFn(comptime K: type) (fn (K) u32) {
1024 return struct {
1025 fn hash(key: K) u32 {
1026 return getAutoHashFn(usize)(@ptrToInt(key));
1027 }
1028 }.hash;
1029}
1030
1031pub fn getTrivialEqlFn(comptime K: type) (fn (K, K) bool) {
1032 return struct {
1033 fn eql(a: K, b: K) bool {
1034 return a == b;
1035 }
1036 }.eql;
1037}
1038
1039pub fn getAutoHashFn(comptime K: type) (fn (K) u32) {
1040 return struct {
1041 fn hash(key: K) u32 {
1042 if (comptime trait.hasUniqueRepresentation(K)) {
1043 return @truncate(u32, Wyhash.hash(0, std.mem.asBytes(&key)));
1044 } else {
1045 var hasher = Wyhash.init(0);
1046 autoHash(&hasher, key);
1047 return @truncate(u32, hasher.final());
1048 }
1049 }
1050 }.hash;
1051}
1052
1053pub fn getAutoEqlFn(comptime K: type) (fn (K, K) bool) {
1054 return struct {
1055 fn eql(a: K, b: K) bool {
1056 return meta.eql(a, b);
1057 }
1058 }.eql;
1059}
1060
1061pub fn autoEqlIsCheap(comptime K: type) bool {
1062 return switch (@typeInfo(K)) {
1063 .Bool,
1064 .Int,
1065 .Float,
1066 .Pointer,
1067 .ComptimeFloat,
1068 .ComptimeInt,
1069 .Enum,
1070 .Fn,
1071 .ErrorSet,
1072 .AnyFrame,
1073 .EnumLiteral,
1074 => true,
1075 else => false,
1076 };
1077}
1078
1079pub fn getAutoHashStratFn(comptime K: type, comptime strategy: std.hash.Strategy) (fn (K) u32) {
1080 return struct {
1081 fn hash(key: K) u32 {
1082 var hasher = Wyhash.init(0);
1083 std.hash.autoHashStrat(&hasher, key, strategy);
1084 return @truncate(u32, hasher.final());
1085 }
1086 }.hash;
1087}
lib/std/buf_set.zig+2-1
......@@ -20,7 +20,8 @@ pub const BufSet = struct {
2020 }
2121
2222 pub fn deinit(self: *BufSet) void {
23 for (self.hash_map.items()) |entry| {
23 var it = self.hash_map.iterator();
24 while (it.next()) |entry| {
2425 self.free(entry.key);
2526 }
2627 self.hash_map.deinit();
lib/std/builtin.zig+1
......@@ -261,6 +261,7 @@ pub const TypeInfo = union(enum) {
261261 name: []const u8,
262262 field_type: type,
263263 default_value: anytype,
264 is_comptime: bool,
264265 };
265266
266267 /// This data structure is used by the Zig language code generation and
lib/std/c.zig+5
......@@ -330,3 +330,8 @@ pub const FILE = @Type(.Opaque);
330330pub extern "c" fn dlopen(path: [*:0]const u8, mode: c_int) ?*c_void;
331331pub extern "c" fn dlclose(handle: *c_void) c_int;
332332pub extern "c" fn dlsym(handle: ?*c_void, symbol: [*:0]const u8) ?*c_void;
333
334pub extern "c" fn sync() void;
335pub extern "c" fn syncfs(fd: c_int) c_int;
336pub extern "c" fn fsync(fd: c_int) c_int;
337pub extern "c" fn fdatasync(fd: c_int) c_int;
lib/std/child_process.zig+2-2
......@@ -44,10 +44,10 @@ pub const ChildProcess = struct {
4444 stderr_behavior: StdIo,
4545
4646 /// Set to change the user id when spawning the child process.
47 uid: if (builtin.os.tag == .windows) void else ?u32,
47 uid: if (builtin.os.tag == .windows or builtin.os.tag == .wasi) void else ?os.uid_t,
4848
4949 /// Set to change the group id when spawning the child process.
50 gid: if (builtin.os.tag == .windows) void else ?u32,
50 gid: if (builtin.os.tag == .windows or builtin.os.tag == .wasi) void else ?os.gid_t,
5151
5252 /// Set to change the current working directory when spawning the child process.
5353 cwd: ?[]const u8,
lib/std/fmt.zig+17
......@@ -66,6 +66,7 @@ fn peekIsAlign(comptime fmt: []const u8) bool {
6666/// - output numeric value in hexadecimal notation
6767/// - `s`: print a pointer-to-many as a c-string, use zero-termination
6868/// - `B` and `Bi`: output a memory size in either metric (1000) or power-of-two (1024) based notation. works for both float and integer values.
69/// - `e` and `E`: if printing a string, escape non-printable characters
6970/// - `e`: output floating point value in scientific notation
7071/// - `d`: output numeric value in decimal notation
7172/// - `b`: output integer value in binary notation
......@@ -599,6 +600,16 @@ pub fn formatText(
599600 try formatInt(c, 16, fmt[0] == 'X', FormatOptions{ .width = 2, .fill = '0' }, writer);
600601 }
601602 return;
603 } else if (comptime (std.mem.eql(u8, fmt, "e") or std.mem.eql(u8, fmt, "E"))) {
604 for (bytes) |c| {
605 if (std.ascii.isPrint(c)) {
606 try writer.writeByte(c);
607 } else {
608 try writer.writeAll("\\x");
609 try formatInt(c, 16, fmt[0] == 'E', FormatOptions{ .width = 2, .fill = '0' }, writer);
610 }
611 }
612 return;
602613 } else {
603614 @compileError("Unknown format string: '" ++ fmt ++ "'");
604615 }
......@@ -1319,6 +1330,12 @@ test "slice" {
13191330 try testFmt("buf: Test\n Other text", "buf: {s}\n Other text", .{"Test"});
13201331}
13211332
1333test "escape non-printable" {
1334 try testFmt("abc", "{e}", .{"abc"});
1335 try testFmt("ab\\xffc", "{e}", .{"ab\xffc"});
1336 try testFmt("ab\\xFFc", "{E}", .{"ab\xffc"});
1337}
1338
13221339test "pointer" {
13231340 {
13241341 const value = @intToPtr(*align(1) i32, 0xdeadbeef);
lib/std/fmt/parse_float.zig+4-1
......@@ -37,7 +37,9 @@
3737const std = @import("../std.zig");
3838const ascii = std.ascii;
3939
40const max_digits = 25;
40// The mantissa field in FloatRepr is 64bit wide and holds only 19 digits
41// without overflowing
42const max_digits = 19;
4143
4244const f64_plus_zero: u64 = 0x0000000000000000;
4345const f64_minus_zero: u64 = 0x8000000000000000;
......@@ -409,6 +411,7 @@ test "fmt.parseFloat" {
409411 expect(approxEq(T, try parseFloat(T, "123142.1"), 123142.1, epsilon));
410412 expect(approxEq(T, try parseFloat(T, "-123142.1124"), @as(T, -123142.1124), epsilon));
411413 expect(approxEq(T, try parseFloat(T, "0.7062146892655368"), @as(T, 0.7062146892655368), epsilon));
414 expect(approxEq(T, try parseFloat(T, "2.71828182845904523536"), @as(T, 2.718281828459045), epsilon));
412415 }
413416 }
414417}
lib/std/hash_map.zig+846-697
......@@ -4,91 +4,94 @@
44// The MIT license requires this copyright notice to be included in all copies
55// and substantial portions of the software.
66const std = @import("std.zig");
7const debug = std.debug;
7const builtin = @import("builtin");
88const assert = debug.assert;
9const testing = std.testing;
9const autoHash = std.hash.autoHash;
10const debug = std.debug;
11const warn = debug.warn;
1012const math = std.math;
1113const mem = std.mem;
1214const meta = std.meta;
1315const trait = meta.trait;
14const autoHash = std.hash.autoHash;
15const Wyhash = std.hash.Wyhash;
1616const Allocator = mem.Allocator;
17const builtin = @import("builtin");
18const hash_map = @This();
17const Wyhash = std.hash.Wyhash;
18
19pub fn getAutoHashFn(comptime K: type) (fn (K) u64) {
20 return struct {
21 fn hash(key: K) u64 {
22 if (comptime trait.hasUniqueRepresentation(K)) {
23 return Wyhash.hash(0, std.mem.asBytes(&key));
24 } else {
25 var hasher = Wyhash.init(0);
26 autoHash(&hasher, key);
27 return hasher.final();
28 }
29 }
30 }.hash;
31}
32
33pub fn getAutoEqlFn(comptime K: type) (fn (K, K) bool) {
34 return struct {
35 fn eql(a: K, b: K) bool {
36 return meta.eql(a, b);
37 }
38 }.eql;
39}
1940
2041pub fn AutoHashMap(comptime K: type, comptime V: type) type {
21 return HashMap(K, V, getAutoHashFn(K), getAutoEqlFn(K), autoEqlIsCheap(K));
42 return HashMap(K, V, getAutoHashFn(K), getAutoEqlFn(K), DefaultMaxLoadPercentage);
2243}
2344
2445pub fn AutoHashMapUnmanaged(comptime K: type, comptime V: type) type {
25 return HashMapUnmanaged(K, V, getAutoHashFn(K), getAutoEqlFn(K), autoEqlIsCheap(K));
46 return HashMapUnmanaged(K, V, getAutoHashFn(K), getAutoEqlFn(K), DefaultMaxLoadPercentage);
2647}
2748
2849/// Builtin hashmap for strings as keys.
2950pub fn StringHashMap(comptime V: type) type {
30 return HashMap([]const u8, V, hashString, eqlString, true);
51 return HashMap([]const u8, V, hashString, eqlString, DefaultMaxLoadPercentage);
3152}
3253
3354pub fn StringHashMapUnmanaged(comptime V: type) type {
34 return HashMapUnmanaged([]const u8, V, hashString, eqlString, true);
55 return HashMapUnmanaged([]const u8, V, hashString, eqlString, DefaultMaxLoadPercentage);
3556}
3657
3758pub fn eqlString(a: []const u8, b: []const u8) bool {
3859 return mem.eql(u8, a, b);
3960}
4061
41pub fn hashString(s: []const u8) u32 {
42 return @truncate(u32, std.hash.Wyhash.hash(0, s));
62pub fn hashString(s: []const u8) u64 {
63 return std.hash.Wyhash.hash(0, s);
4364}
4465
45/// Insertion order is preserved.
46/// Deletions perform a "swap removal" on the entries list.
47/// Modifying the hash map while iterating is allowed, however one must understand
48/// the (well defined) behavior when mixing insertions and deletions with iteration.
66pub const DefaultMaxLoadPercentage = 80;
67
68/// General purpose hash table.
69/// No order is guaranteed and any modification invalidates live iterators.
70/// It provides fast operations (lookup, insertion, deletion) with quite high
71/// load factors (up to 80% by default) for a low memory usage.
4972/// For a hash map that can be initialized directly that does not store an Allocator
5073/// field, see `HashMapUnmanaged`.
51/// When `store_hash` is `false`, this data structure is biased towards cheap `eql`
52/// functions. It does not store each item's hash in the table. Setting `store_hash`
53/// to `true` incurs slightly more memory cost by storing each key's hash in the table
54/// but only has to call `eql` for hash collisions.
74/// If iterating over the table entries is a strong usecase and needs to be fast,
75/// prefer the alternative `std.ArrayHashMap`.
5576pub fn HashMap(
5677 comptime K: type,
5778 comptime V: type,
58 comptime hash: fn (key: K) u32,
59 comptime eql: fn (a: K, b: K) bool,
60 comptime store_hash: bool,
79 comptime hashFn: fn (key: K) u64,
80 comptime eqlFn: fn (a: K, b: K) bool,
81 comptime MaxLoadPercentage: u64,
6182) type {
6283 return struct {
6384 unmanaged: Unmanaged,
6485 allocator: *Allocator,
6586
66 pub const Unmanaged = HashMapUnmanaged(K, V, hash, eql, store_hash);
87 pub const Unmanaged = HashMapUnmanaged(K, V, hashFn, eqlFn, MaxLoadPercentage);
6788 pub const Entry = Unmanaged.Entry;
6889 pub const Hash = Unmanaged.Hash;
90 pub const Iterator = Unmanaged.Iterator;
91 pub const Size = Unmanaged.Size;
6992 pub const GetOrPutResult = Unmanaged.GetOrPutResult;
7093
71 /// Deprecated. Iterate using `items`.
72 pub const Iterator = struct {
73 hm: *const Self,
74 /// Iterator through the entry array.
75 index: usize,
76
77 pub fn next(it: *Iterator) ?*Entry {
78 if (it.index >= it.hm.unmanaged.entries.items.len) return null;
79 const result = &it.hm.unmanaged.entries.items[it.index];
80 it.index += 1;
81 return result;
82 }
83
84 /// Reset the iterator to the initial index
85 pub fn reset(it: *Iterator) void {
86 it.index = 0;
87 }
88 };
89
9094 const Self = @This();
91 const Index = Unmanaged.Index;
9295
9396 pub fn init(allocator: *Allocator) Self {
9497 return .{
......@@ -110,17 +113,12 @@ pub fn HashMap(
110113 return self.unmanaged.clearAndFree(self.allocator);
111114 }
112115
113 /// Deprecated. Use `items().len`.
114116 pub fn count(self: Self) usize {
115 return self.items().len;
117 return self.unmanaged.count();
116118 }
117119
118 /// Deprecated. Iterate using `items`.
119120 pub fn iterator(self: *const Self) Iterator {
120 return Iterator{
121 .hm = self,
122 .index = 0,
123 };
121 return self.unmanaged.iterator();
124122 }
125123
126124 /// If key exists this function cannot fail.
......@@ -150,13 +148,13 @@ pub fn HashMap(
150148
151149 /// Increases capacity, guaranteeing that insertions up until the
152150 /// `expected_count` will not cause an allocation, and therefore cannot fail.
153 pub fn ensureCapacity(self: *Self, new_capacity: usize) !void {
154 return self.unmanaged.ensureCapacity(self.allocator, new_capacity);
151 pub fn ensureCapacity(self: *Self, expected_count: Size) !void {
152 return self.unmanaged.ensureCapacity(self.allocator, expected_count);
155153 }
156154
157155 /// Returns the number of total elements which may be present before it is
158156 /// no longer guaranteed that no allocations will be performed.
159 pub fn capacity(self: *Self) usize {
157 pub fn capacity(self: *Self) Size {
160158 return self.unmanaged.capacity();
161159 }
162160
......@@ -197,18 +195,14 @@ pub fn HashMap(
197195 return self.unmanaged.fetchPutAssumeCapacity(key, value);
198196 }
199197
200 pub fn getEntry(self: Self, key: K) ?*Entry {
201 return self.unmanaged.getEntry(key);
202 }
203
204 pub fn getIndex(self: Self, key: K) ?usize {
205 return self.unmanaged.getIndex(key);
206 }
207
208198 pub fn get(self: Self, key: K) ?V {
209199 return self.unmanaged.get(key);
210200 }
211201
202 pub fn getEntry(self: Self, key: K) ?*Entry {
203 return self.unmanaged.getEntry(key);
204 }
205
212206 pub fn contains(self: Self, key: K) bool {
213207 return self.unmanaged.contains(key);
214208 }
......@@ -225,10 +219,6 @@ pub fn HashMap(
225219 return self.unmanaged.removeAssertDiscard(key);
226220 }
227221
228 pub fn items(self: Self) []Entry {
229 return self.unmanaged.items();
230 }
231
232222 pub fn clone(self: Self) !Self {
233223 var other = try self.unmanaged.clone(self.allocator);
234224 return other.promote(self.allocator);
......@@ -236,63 +226,152 @@ pub fn HashMap(
236226 };
237227}
238228
239/// General purpose hash table.
240/// Insertion order is preserved.
241/// Deletions perform a "swap removal" on the entries list.
242/// Modifying the hash map while iterating is allowed, however one must understand
243/// the (well defined) behavior when mixing insertions and deletions with iteration.
244/// This type does not store an Allocator field - the Allocator must be passed in
245/// with each function call that requires it. See `HashMap` for a type that stores
246/// an Allocator field for convenience.
247/// Can be initialized directly using the default field values.
248/// This type is designed to have low overhead for small numbers of entries. When
249/// `store_hash` is `false` and the number of entries in the map is less than 9,
250/// the overhead cost of using `HashMapUnmanaged` rather than `std.ArrayList` is
251/// only a single pointer-sized integer.
252/// When `store_hash` is `false`, this data structure is biased towards cheap `eql`
253/// functions. It does not store each item's hash in the table. Setting `store_hash`
254/// to `true` incurs slightly more memory cost by storing each key's hash in the table
255/// but guarantees only one call to `eql` per insertion/deletion.
229/// A HashMap based on open addressing and linear probing.
230/// A lookup or modification typically occurs only 2 cache misses.
231/// No order is guaranteed and any modification invalidates live iterators.
232/// It achieves good performance with quite high load factors (by default,
233/// grow is triggered at 80% full) and only one byte of overhead per element.
234/// The struct itself is only 16 bytes for a small footprint. This comes at
235/// the price of handling size with u32, which should be reasonnable enough
236/// for almost all uses.
237/// Deletions are achieved with tombstones.
256238pub fn HashMapUnmanaged(
257239 comptime K: type,
258240 comptime V: type,
259 comptime hash: fn (key: K) u32,
260 comptime eql: fn (a: K, b: K) bool,
261 comptime store_hash: bool,
241 hashFn: fn (key: K) u64,
242 eqlFn: fn (a: K, b: K) bool,
243 comptime MaxLoadPercentage: u64,
262244) type {
245 comptime assert(MaxLoadPercentage > 0 and MaxLoadPercentage < 100);
246
263247 return struct {
264 /// It is permitted to access this field directly.
265 entries: std.ArrayListUnmanaged(Entry) = .{},
266
267 /// When entries length is less than `linear_scan_max`, this remains `null`.
268 /// Once entries length grows big enough, this field is allocated. There is
269 /// an IndexHeader followed by an array of Index(I) structs, where I is defined
270 /// by how many total indexes there are.
271 index_header: ?*IndexHeader = null,
272
273 /// Modifying the key is illegal behavior.
274 /// Modifying the value is allowed.
275 /// Entry pointers become invalid whenever this HashMap is modified,
276 /// unless `ensureCapacity` was previously used.
248 const Self = @This();
249
250 // This is actually a midway pointer to the single buffer containing
251 // a `Header` field, the `Metadata`s and `Entry`s.
252 // At `-@sizeOf(Header)` is the Header field.
253 // At `sizeOf(Metadata) * capacity + offset`, which is pointed to by
254 // self.header().entries, is the array of entries.
255 // This means that the hashmap only holds one live allocation, to
256 // reduce memory fragmentation and struct size.
257 /// Pointer to the metadata.
258 metadata: ?[*]Metadata = null,
259
260 /// Current number of elements in the hashmap.
261 size: Size = 0,
262
263 // Having a countdown to grow reduces the number of instructions to
264 // execute when determining if the hashmap has enough capacity already.
265 /// Number of available slots before a grow is needed to satisfy the
266 /// `MaxLoadPercentage`.
267 available: Size = 0,
268
269 // This is purely empirical and not a /very smart magic constant™/.
270 /// Capacity of the first grow when bootstrapping the hashmap.
271 const MinimalCapacity = 8;
272
273 // This hashmap is specially designed for sizes that fit in a u32.
274 const Size = u32;
275
276 // u64 hashes guarantee us that the fingerprint bits will never be used
277 // to compute the index of a slot, maximizing the use of entropy.
278 const Hash = u64;
279
277280 pub const Entry = struct {
278 /// This field is `void` if `store_hash` is `false`.
279 hash: Hash,
280281 key: K,
281282 value: V,
282283 };
283284
284 pub const Hash = if (store_hash) u32 else void;
285 const Header = packed struct {
286 entries: [*]Entry,
287 capacity: Size,
288 };
289
290 /// Metadata for a slot. It can be in three states: empty, used or
291 /// tombstone. Tombstones indicate that an entry was previously used,
292 /// they are a simple way to handle removal.
293 /// To this state, we add 6 bits from the slot's key hash. These are
294 /// used as a fast way to disambiguate between entries without
295 /// having to use the equality function. If two fingerprints are
296 /// different, we know that we don't have to compare the keys at all.
297 /// The 6 bits are the highest ones from a 64 bit hash. This way, not
298 /// only we use the `log2(capacity)` lowest bits from the hash to determine
299 /// a slot index, but we use 6 more bits to quickly resolve collisions
300 /// when multiple elements with different hashes end up wanting to be in / the same slot.
301 /// Not using the equality function means we don't have to read into
302 /// the entries array, avoiding a likely cache miss.
303 const Metadata = packed struct {
304 const FingerPrint = u6;
305
306 used: u1 = 0,
307 tombstone: u1 = 0,
308 fingerprint: FingerPrint = 0,
309
310 pub fn isUsed(self: Metadata) bool {
311 return self.used == 1;
312 }
313
314 pub fn isTombstone(self: Metadata) bool {
315 return self.tombstone == 1;
316 }
317
318 pub fn takeFingerprint(hash: Hash) FingerPrint {
319 const hash_bits = @typeInfo(Hash).Int.bits;
320 const fp_bits = @typeInfo(FingerPrint).Int.bits;
321 return @truncate(FingerPrint, hash >> (hash_bits - fp_bits));
322 }
323
324 pub fn fill(self: *Metadata, fp: FingerPrint) void {
325 self.used = 1;
326 self.tombstone = 0;
327 self.fingerprint = fp;
328 }
329
330 pub fn remove(self: *Metadata) void {
331 self.used = 0;
332 self.tombstone = 1;
333 self.fingerprint = 0;
334 }
335 };
336
337 comptime {
338 assert(@sizeOf(Metadata) == 1);
339 assert(@alignOf(Metadata) == 1);
340 }
341
342 const Iterator = struct {
343 hm: *const Self,
344 index: Size = 0,
345
346 pub fn next(it: *Iterator) ?*Entry {
347 assert(it.index <= it.hm.capacity());
348 if (it.hm.size == 0) return null;
349
350 const cap = it.hm.capacity();
351 const end = it.hm.metadata.? + cap;
352 var metadata = it.hm.metadata.? + it.index;
353
354 while (metadata != end) : ({
355 metadata += 1;
356 it.index += 1;
357 }) {
358 if (metadata[0].isUsed()) {
359 const entry = &it.hm.entries()[it.index];
360 it.index += 1;
361 return entry;
362 }
363 }
364
365 return null;
366 }
367 };
285368
286369 pub const GetOrPutResult = struct {
287370 entry: *Entry,
288371 found_existing: bool,
289372 };
290373
291 pub const Managed = HashMap(K, V, hash, eql, store_hash);
292
293 const Self = @This();
294
295 const linear_scan_max = 8;
374 pub const Managed = HashMap(K, V, hashFn, eqlFn, MaxLoadPercentage);
296375
297376 pub fn promote(self: Self, allocator: *Allocator) Managed {
298377 return .{
......@@ -301,167 +380,156 @@ pub fn HashMapUnmanaged(
301380 };
302381 }
303382
383 fn isUnderMaxLoadPercentage(size: Size, cap: Size) bool {
384 return size * 100 < MaxLoadPercentage * cap;
385 }
386
387 pub fn init(allocator: *Allocator) Self {
388 return .{};
389 }
390
304391 pub fn deinit(self: *Self, allocator: *Allocator) void {
305 self.entries.deinit(allocator);
306 if (self.index_header) |header| {
307 header.free(allocator);
308 }
392 self.deallocate(allocator);
309393 self.* = undefined;
310394 }
311395
312 pub fn clearRetainingCapacity(self: *Self) void {
313 self.entries.items.len = 0;
314 if (self.index_header) |header| {
315 header.max_distance_from_start_index = 0;
316 switch (header.capacityIndexType()) {
317 .u8 => mem.set(Index(u8), header.indexes(u8), Index(u8).empty),
318 .u16 => mem.set(Index(u16), header.indexes(u16), Index(u16).empty),
319 .u32 => mem.set(Index(u32), header.indexes(u32), Index(u32).empty),
320 .usize => mem.set(Index(usize), header.indexes(usize), Index(usize).empty),
321 }
322 }
323 }
396 fn deallocate(self: *Self, allocator: *Allocator) void {
397 if (self.metadata == null) return;
324398
325 pub fn clearAndFree(self: *Self, allocator: *Allocator) void {
326 self.entries.shrink(allocator, 0);
327 if (self.index_header) |header| {
328 header.free(allocator);
329 self.index_header = null;
330 }
399 const cap = self.capacity();
400 const meta_size = @sizeOf(Header) + cap * @sizeOf(Metadata);
401
402 const alignment = @alignOf(Entry) - 1;
403 const entries_size = @as(usize, cap) * @sizeOf(Entry) + alignment;
404
405 const total_size = meta_size + entries_size;
406
407 var slice: []u8 = undefined;
408 slice.ptr = @intToPtr([*]u8, @ptrToInt(self.header()));
409 slice.len = total_size;
410 allocator.free(slice);
411
412 self.metadata = null;
413 self.available = 0;
331414 }
332415
333 /// If key exists this function cannot fail.
334 /// If there is an existing item with `key`, then the result
335 /// `Entry` pointer points to it, and found_existing is true.
336 /// Otherwise, puts a new item with undefined value, and
337 /// the `Entry` pointer points to it. Caller should then initialize
338 /// the value (but not the key).
339 pub fn getOrPut(self: *Self, allocator: *Allocator, key: K) !GetOrPutResult {
340 self.ensureCapacity(allocator, self.entries.items.len + 1) catch |err| {
341 // "If key exists this function cannot fail."
342 return GetOrPutResult{
343 .entry = self.getEntry(key) orelse return err,
344 .found_existing = true,
345 };
346 };
347 return self.getOrPutAssumeCapacity(key);
416 fn capacityForSize(size: Size) Size {
417 var new_cap = @truncate(u32, (@as(u64, size) * 100) / MaxLoadPercentage + 1);
418 new_cap = math.ceilPowerOfTwo(u32, new_cap) catch unreachable;
419 return new_cap;
348420 }
349421
350 /// If there is an existing item with `key`, then the result
351 /// `Entry` pointer points to it, and found_existing is true.
352 /// Otherwise, puts a new item with undefined value, and
353 /// the `Entry` pointer points to it. Caller should then initialize
354 /// the value (but not the key).
355 /// If a new entry needs to be stored, this function asserts there
356 /// is enough capacity to store it.
357 pub fn getOrPutAssumeCapacity(self: *Self, key: K) GetOrPutResult {
358 const header = self.index_header orelse {
359 // Linear scan.
360 const h = if (store_hash) hash(key) else {};
361 for (self.entries.items) |*item| {
362 if (item.hash == h and eql(key, item.key)) {
363 return GetOrPutResult{
364 .entry = item,
365 .found_existing = true,
366 };
367 }
368 }
369 const new_entry = self.entries.addOneAssumeCapacity();
370 new_entry.* = .{
371 .hash = if (store_hash) h else {},
372 .key = key,
373 .value = undefined,
374 };
375 return GetOrPutResult{
376 .entry = new_entry,
377 .found_existing = false,
378 };
379 };
422 pub fn ensureCapacity(self: *Self, allocator: *Allocator, new_size: Size) !void {
423 if (new_size > self.size)
424 try self.growIfNeeded(allocator, new_size - self.size);
425 }
380426
381 switch (header.capacityIndexType()) {
382 .u8 => return self.getOrPutInternal(key, header, u8),
383 .u16 => return self.getOrPutInternal(key, header, u16),
384 .u32 => return self.getOrPutInternal(key, header, u32),
385 .usize => return self.getOrPutInternal(key, header, usize),
427 pub fn clearRetainingCapacity(self: *Self) void {
428 if (self.metadata) |_| {
429 self.initMetadatas();
430 self.size = 0;
431 self.available = 0;
386432 }
387433 }
388434
389 pub fn getOrPutValue(self: *Self, allocator: *Allocator, key: K, value: V) !*Entry {
390 const res = try self.getOrPut(allocator, key);
391 if (!res.found_existing)
392 res.entry.value = value;
435 pub fn clearAndFree(self: *Self, allocator: *Allocator) void {
436 self.deallocate(allocator);
437 self.size = 0;
438 self.available = 0;
439 }
393440
394 return res.entry;
441 pub fn count(self: *const Self) Size {
442 return self.size;
395443 }
396444
397 /// Increases capacity, guaranteeing that insertions up until the
398 /// `expected_count` will not cause an allocation, and therefore cannot fail.
399 pub fn ensureCapacity(self: *Self, allocator: *Allocator, new_capacity: usize) !void {
400 try self.entries.ensureCapacity(allocator, new_capacity);
401 if (new_capacity <= linear_scan_max) return;
402
403 // Ensure that the indexes will be at most 60% full if
404 // `new_capacity` items are put into it.
405 const needed_len = new_capacity * 5 / 3;
406 if (self.index_header) |header| {
407 if (needed_len > header.indexes_len) {
408 // An overflow here would mean the amount of memory required would not
409 // be representable in the address space.
410 const new_indexes_len = math.ceilPowerOfTwo(usize, needed_len) catch unreachable;
411 const new_header = try IndexHeader.alloc(allocator, new_indexes_len);
412 self.insertAllEntriesIntoNewHeader(new_header);
413 header.free(allocator);
414 self.index_header = new_header;
415 }
416 } else {
417 // An overflow here would mean the amount of memory required would not
418 // be representable in the address space.
419 const new_indexes_len = math.ceilPowerOfTwo(usize, needed_len) catch unreachable;
420 const header = try IndexHeader.alloc(allocator, new_indexes_len);
421 self.insertAllEntriesIntoNewHeader(header);
422 self.index_header = header;
423 }
445 fn header(self: *const Self) *Header {
446 return @ptrCast(*Header, @ptrCast([*]Header, self.metadata.?) - 1);
424447 }
425448
426 /// Returns the number of total elements which may be present before it is
427 /// no longer guaranteed that no allocations will be performed.
428 pub fn capacity(self: Self) usize {
429 const entry_cap = self.entries.capacity;
430 const header = self.index_header orelse return math.min(linear_scan_max, entry_cap);
431 const indexes_cap = (header.indexes_len + 1) * 3 / 4;
432 return math.min(entry_cap, indexes_cap);
449 fn entries(self: *const Self) [*]Entry {
450 return self.header().entries;
433451 }
434452
435 /// Clobbers any existing data. To detect if a put would clobber
436 /// existing data, see `getOrPut`.
437 pub fn put(self: *Self, allocator: *Allocator, key: K, value: V) !void {
438 const result = try self.getOrPut(allocator, key);
439 result.entry.value = value;
453 pub fn capacity(self: *const Self) Size {
454 if (self.metadata == null) return 0;
455
456 return self.header().capacity;
440457 }
441458
442 /// Inserts a key-value pair into the hash map, asserting that no previous
443 /// entry with the same key is already present
459 pub fn iterator(self: *const Self) Iterator {
460 return .{ .hm = self };
461 }
462
463 /// Insert an entry in the map. Assumes it is not already present.
444464 pub fn putNoClobber(self: *Self, allocator: *Allocator, key: K, value: V) !void {
445 const result = try self.getOrPut(allocator, key);
446 assert(!result.found_existing);
447 result.entry.value = value;
465 assert(!self.contains(key));
466 try self.growIfNeeded(allocator, 1);
467
468 self.putAssumeCapacityNoClobber(key, value);
448469 }
449470
450 /// Asserts there is enough capacity to store the new key-value pair.
451 /// Clobbers any existing data. To detect if a put would clobber
452 /// existing data, see `getOrPutAssumeCapacity`.
453471 pub fn putAssumeCapacity(self: *Self, key: K, value: V) void {
454 const result = self.getOrPutAssumeCapacity(key);
455 result.entry.value = value;
472 const hash = hashFn(key);
473 const mask = self.capacity() - 1;
474 const fingerprint = Metadata.takeFingerprint(hash);
475 var idx = @truncate(usize, hash & mask);
476
477 var first_tombstone_idx: usize = self.capacity(); // invalid index
478 var metadata = self.metadata.? + idx;
479 while (metadata[0].isUsed() or metadata[0].isTombstone()) {
480 if (metadata[0].isUsed() and metadata[0].fingerprint == fingerprint) {
481 const entry = &self.entries()[idx];
482 if (eqlFn(entry.key, key)) {
483 return;
484 }
485 } else if (first_tombstone_idx == self.capacity() and metadata[0].isTombstone()) {
486 first_tombstone_idx = idx;
487 }
488
489 idx = (idx + 1) & mask;
490 metadata = self.metadata.? + idx;
491 }
492
493 if (first_tombstone_idx < self.capacity()) {
494 // Cheap try to lower probing lengths after deletions. Recycle a tombstone.
495 idx = first_tombstone_idx;
496 metadata = self.metadata.? + idx;
497 } else {
498 // We're using a slot previously free.
499 self.available -= 1;
500 }
501
502 metadata[0].fill(fingerprint);
503 const entry = &self.entries()[idx];
504 entry.* = .{ .key = key, .value = undefined };
505 self.size += 1;
456506 }
457507
458 /// Asserts there is enough capacity to store the new key-value pair.
459 /// Asserts that it does not clobber any existing data.
460 /// To detect if a put would clobber existing data, see `getOrPutAssumeCapacity`.
508 /// Insert an entry in the map. Assumes it is not already present,
509 /// and that no allocation is needed.
461510 pub fn putAssumeCapacityNoClobber(self: *Self, key: K, value: V) void {
462 const result = self.getOrPutAssumeCapacity(key);
463 assert(!result.found_existing);
464 result.entry.value = value;
511 assert(!self.contains(key));
512
513 const hash = hashFn(key);
514 const mask = self.capacity() - 1;
515 var idx = @truncate(usize, hash & mask);
516
517 var metadata = self.metadata.? + idx;
518 while (metadata[0].isUsed()) {
519 idx = (idx + 1) & mask;
520 metadata = self.metadata.? + idx;
521 }
522
523 if (!metadata[0].isTombstone()) {
524 assert(self.available > 0);
525 self.available -= 1;
526 }
527
528 const fingerprint = Metadata.takeFingerprint(hash);
529 metadata[0].fill(fingerprint);
530 self.entries()[idx] = Entry{ .key = key, .value = value };
531
532 self.size += 1;
465533 }
466534
467535 /// Inserts a new `Entry` into the hash map, returning the previous one, if any.
......@@ -488,400 +556,622 @@ pub fn HashMapUnmanaged(
488556 }
489557
490558 pub fn getEntry(self: Self, key: K) ?*Entry {
491 const index = self.getIndex(key) orelse return null;
492 return &self.entries.items[index];
493 }
559 if (self.size == 0) {
560 return null;
561 }
494562
495 pub fn getIndex(self: Self, key: K) ?usize {
496 const header = self.index_header orelse {
497 // Linear scan.
498 const h = if (store_hash) hash(key) else {};
499 for (self.entries.items) |*item, i| {
500 if (item.hash == h and eql(key, item.key)) {
501 return i;
563 const hash = hashFn(key);
564 const mask = self.capacity() - 1;
565 const fingerprint = Metadata.takeFingerprint(hash);
566 var idx = @truncate(usize, hash & mask);
567
568 var metadata = self.metadata.? + idx;
569 while (metadata[0].isUsed() or metadata[0].isTombstone()) {
570 if (metadata[0].isUsed() and metadata[0].fingerprint == fingerprint) {
571 const entry = &self.entries()[idx];
572 if (eqlFn(entry.key, key)) {
573 return entry;
502574 }
503575 }
504 return null;
505 };
506 switch (header.capacityIndexType()) {
507 .u8 => return self.getInternal(key, header, u8),
508 .u16 => return self.getInternal(key, header, u16),
509 .u32 => return self.getInternal(key, header, u32),
510 .usize => return self.getInternal(key, header, usize),
576 idx = (idx + 1) & mask;
577 metadata = self.metadata.? + idx;
511578 }
512 }
513579
514 pub fn get(self: Self, key: K) ?V {
515 return if (self.getEntry(key)) |entry| entry.value else null;
580 return null;
516581 }
517582
518 pub fn contains(self: Self, key: K) bool {
519 return self.getEntry(key) != null;
583 /// Insert an entry if the associated key is not already present, otherwise update preexisting value.
584 /// Returns true if the key was already present.
585 pub fn put(self: *Self, allocator: *Allocator, key: K, value: V) !void {
586 const result = try self.getOrPut(allocator, key);
587 result.entry.value = value;
520588 }
521589
522 /// If there is an `Entry` with a matching key, it is deleted from
523 /// the hash map, and then returned from this function.
524 pub fn remove(self: *Self, key: K) ?Entry {
525 const header = self.index_header orelse {
526 // Linear scan.
527 const h = if (store_hash) hash(key) else {};
528 for (self.entries.items) |item, i| {
529 if (item.hash == h and eql(key, item.key)) {
530 return self.entries.swapRemove(i);
590 /// Get an optional pointer to the value associated with key, if present.
591 pub fn get(self: Self, key: K) ?V {
592 if (self.size == 0) {
593 return null;
594 }
595
596 const hash = hashFn(key);
597 const mask = self.capacity() - 1;
598 const fingerprint = Metadata.takeFingerprint(hash);
599 var idx = @truncate(usize, hash & mask);
600
601 var metadata = self.metadata.? + idx;
602 while (metadata[0].isUsed() or metadata[0].isTombstone()) {
603 if (metadata[0].isUsed() and metadata[0].fingerprint == fingerprint) {
604 const entry = &self.entries()[idx];
605 if (eqlFn(entry.key, key)) {
606 return entry.value;
531607 }
532608 }
533 return null;
534 };
535 switch (header.capacityIndexType()) {
536 .u8 => return self.removeInternal(key, header, u8),
537 .u16 => return self.removeInternal(key, header, u16),
538 .u32 => return self.removeInternal(key, header, u32),
539 .usize => return self.removeInternal(key, header, usize),
609 idx = (idx + 1) & mask;
610 metadata = self.metadata.? + idx;
540611 }
541 }
542612
543 /// Asserts there is an `Entry` with matching key, deletes it from the hash map,
544 /// and discards it.
545 pub fn removeAssertDiscard(self: *Self, key: K) void {
546 assert(self.remove(key) != null);
613 return null;
547614 }
548615
549 pub fn items(self: Self) []Entry {
550 return self.entries.items;
616 pub fn getOrPut(self: *Self, allocator: *Allocator, key: K) !GetOrPutResult {
617 try self.growIfNeeded(allocator, 1);
618
619 return self.getOrPutAssumeCapacity(key);
551620 }
552621
553 pub fn clone(self: Self, allocator: *Allocator) !Self {
554 var other: Self = .{};
555 try other.entries.appendSlice(allocator, self.entries.items);
622 pub fn getOrPutAssumeCapacity(self: *Self, key: K) GetOrPutResult {
623 const hash = hashFn(key);
624 const mask = self.capacity() - 1;
625 const fingerprint = Metadata.takeFingerprint(hash);
626 var idx = @truncate(usize, hash & mask);
627
628 var first_tombstone_idx: usize = self.capacity(); // invalid index
629 var metadata = self.metadata.? + idx;
630 while (metadata[0].isUsed() or metadata[0].isTombstone()) {
631 if (metadata[0].isUsed() and metadata[0].fingerprint == fingerprint) {
632 const entry = &self.entries()[idx];
633 if (eqlFn(entry.key, key)) {
634 return GetOrPutResult{ .entry = entry, .found_existing = true };
635 }
636 } else if (first_tombstone_idx == self.capacity() and metadata[0].isTombstone()) {
637 first_tombstone_idx = idx;
638 }
556639
557 if (self.index_header) |header| {
558 const new_header = try IndexHeader.alloc(allocator, header.indexes_len);
559 other.insertAllEntriesIntoNewHeader(new_header);
560 other.index_header = new_header;
640 idx = (idx + 1) & mask;
641 metadata = self.metadata.? + idx;
561642 }
562 return other;
643
644 if (first_tombstone_idx < self.capacity()) {
645 // Cheap try to lower probing lengths after deletions. Recycle a tombstone.
646 idx = first_tombstone_idx;
647 metadata = self.metadata.? + idx;
648 } else {
649 // We're using a slot previously free.
650 self.available -= 1;
651 }
652
653 metadata[0].fill(fingerprint);
654 const entry = &self.entries()[idx];
655 entry.* = .{ .key = key, .value = undefined };
656 self.size += 1;
657
658 return GetOrPutResult{ .entry = entry, .found_existing = false };
563659 }
564660
565 fn removeInternal(self: *Self, key: K, header: *IndexHeader, comptime I: type) ?Entry {
566 const indexes = header.indexes(I);
567 const h = hash(key);
568 const start_index = header.constrainIndex(h);
569 var roll_over: usize = 0;
570 while (roll_over <= header.max_distance_from_start_index) : (roll_over += 1) {
571 const index_index = header.constrainIndex(start_index + roll_over);
572 var index = &indexes[index_index];
573 if (index.isEmpty())
574 return null;
575
576 const entry = &self.entries.items[index.entry_index];
577
578 const hash_match = if (store_hash) h == entry.hash else true;
579 if (!hash_match or !eql(key, entry.key))
580 continue;
581
582 const removed_entry = self.entries.swapRemove(index.entry_index);
583 if (self.entries.items.len > 0 and self.entries.items.len != index.entry_index) {
584 // Because of the swap remove, now we need to update the index that was
585 // pointing to the last entry and is now pointing to this removed item slot.
586 self.updateEntryIndex(header, self.entries.items.len, index.entry_index, I, indexes);
587 }
661 pub fn getOrPutValue(self: *Self, allocator: *Allocator, key: K, value: V) !*Entry {
662 const res = try self.getOrPut(allocator, key);
663 if (!res.found_existing) res.entry.value = value;
664 return res.entry;
665 }
588666
589 // Now we have to shift over the following indexes.
590 roll_over += 1;
591 while (roll_over < header.indexes_len) : (roll_over += 1) {
592 const next_index_index = header.constrainIndex(start_index + roll_over);
593 const next_index = &indexes[next_index_index];
594 if (next_index.isEmpty() or next_index.distance_from_start_index == 0) {
595 index.setEmpty();
667 /// Return true if there is a value associated with key in the map.
668 pub fn contains(self: *const Self, key: K) bool {
669 return self.get(key) != null;
670 }
671
672 /// If there is an `Entry` with a matching key, it is deleted from
673 /// the hash map, and then returned from this function.
674 pub fn remove(self: *Self, key: K) ?Entry {
675 if (self.size == 0) return null;
676
677 const hash = hashFn(key);
678 const mask = self.capacity() - 1;
679 const fingerprint = Metadata.takeFingerprint(hash);
680 var idx = @truncate(usize, hash & mask);
681
682 var metadata = self.metadata.? + idx;
683 while (metadata[0].isUsed() or metadata[0].isTombstone()) {
684 if (metadata[0].isUsed() and metadata[0].fingerprint == fingerprint) {
685 const entry = &self.entries()[idx];
686 if (eqlFn(entry.key, key)) {
687 const removed_entry = entry.*;
688 metadata[0].remove();
689 entry.* = undefined;
690 self.size -= 1;
596691 return removed_entry;
597692 }
598 index.* = next_index.*;
599 index.distance_from_start_index -= 1;
600 index = next_index;
601693 }
602 unreachable;
694 idx = (idx + 1) & mask;
695 metadata = self.metadata.? + idx;
603696 }
697
604698 return null;
605699 }
606700
607 fn updateEntryIndex(
608 self: *Self,
609 header: *IndexHeader,
610 old_entry_index: usize,
611 new_entry_index: usize,
612 comptime I: type,
613 indexes: []Index(I),
614 ) void {
615 const h = if (store_hash) self.entries.items[new_entry_index].hash else hash(self.entries.items[new_entry_index].key);
616 const start_index = header.constrainIndex(h);
617 var roll_over: usize = 0;
618 while (roll_over <= header.max_distance_from_start_index) : (roll_over += 1) {
619 const index_index = header.constrainIndex(start_index + roll_over);
620 const index = &indexes[index_index];
621 if (index.entry_index == old_entry_index) {
622 index.entry_index = @intCast(I, new_entry_index);
623 return;
701 /// Asserts there is an `Entry` with matching key, deletes it from the hash map,
702 /// and discards it.
703 pub fn removeAssertDiscard(self: *Self, key: K) void {
704 assert(self.contains(key));
705
706 const hash = hashFn(key);
707 const mask = self.capacity() - 1;
708 const fingerprint = Metadata.takeFingerprint(hash);
709 var idx = @truncate(usize, hash & mask);
710
711 var metadata = self.metadata.? + idx;
712 while (metadata[0].isUsed() or metadata[0].isTombstone()) {
713 if (metadata[0].isUsed() and metadata[0].fingerprint == fingerprint) {
714 const entry = &self.entries()[idx];
715 if (eqlFn(entry.key, key)) {
716 metadata[0].remove();
717 entry.* = undefined;
718 self.size -= 1;
719 return;
720 }
624721 }
722 idx = (idx + 1) & mask;
723 metadata = self.metadata.? + idx;
625724 }
725
626726 unreachable;
627727 }
628728
629 /// Must ensureCapacity before calling this.
630 fn getOrPutInternal(self: *Self, key: K, header: *IndexHeader, comptime I: type) GetOrPutResult {
631 const indexes = header.indexes(I);
632 const h = hash(key);
633 const start_index = header.constrainIndex(h);
634 var roll_over: usize = 0;
635 var distance_from_start_index: usize = 0;
636 while (roll_over <= header.indexes_len) : ({
637 roll_over += 1;
638 distance_from_start_index += 1;
639 }) {
640 const index_index = header.constrainIndex(start_index + roll_over);
641 const index = indexes[index_index];
642 if (index.isEmpty()) {
643 indexes[index_index] = .{
644 .distance_from_start_index = @intCast(I, distance_from_start_index),
645 .entry_index = @intCast(I, self.entries.items.len),
646 };
647 header.maybeBumpMax(distance_from_start_index);
648 const new_entry = self.entries.addOneAssumeCapacity();
649 new_entry.* = .{
650 .hash = if (store_hash) h else {},
651 .key = key,
652 .value = undefined,
653 };
654 return .{
655 .found_existing = false,
656 .entry = new_entry,
657 };
658 }
729 fn initMetadatas(self: *Self) void {
730 @memset(@ptrCast([*]u8, self.metadata.?), 0, @sizeOf(Metadata) * self.capacity());
731 }
659732
660 // This pointer survives the following append because we call
661 // entries.ensureCapacity before getOrPutInternal.
662 const entry = &self.entries.items[index.entry_index];
663 const hash_match = if (store_hash) h == entry.hash else true;
664 if (hash_match and eql(key, entry.key)) {
665 return .{
666 .found_existing = true,
667 .entry = entry,
668 };
669 }
670 if (index.distance_from_start_index < distance_from_start_index) {
671 // In this case, we did not find the item. We will put a new entry.
672 // However, we will use this index for the new entry, and move
673 // the previous index down the line, to keep the max_distance_from_start_index
674 // as small as possible.
675 indexes[index_index] = .{
676 .distance_from_start_index = @intCast(I, distance_from_start_index),
677 .entry_index = @intCast(I, self.entries.items.len),
678 };
679 header.maybeBumpMax(distance_from_start_index);
680 const new_entry = self.entries.addOneAssumeCapacity();
681 new_entry.* = .{
682 .hash = if (store_hash) h else {},
683 .key = key,
684 .value = undefined,
685 };
686
687 distance_from_start_index = index.distance_from_start_index;
688 var prev_entry_index = index.entry_index;
689
690 // Find somewhere to put the index we replaced by shifting
691 // following indexes backwards.
692 roll_over += 1;
693 distance_from_start_index += 1;
694 while (roll_over < header.indexes_len) : ({
695 roll_over += 1;
696 distance_from_start_index += 1;
697 }) {
698 const next_index_index = header.constrainIndex(start_index + roll_over);
699 const next_index = indexes[next_index_index];
700 if (next_index.isEmpty()) {
701 header.maybeBumpMax(distance_from_start_index);
702 indexes[next_index_index] = .{
703 .entry_index = prev_entry_index,
704 .distance_from_start_index = @intCast(I, distance_from_start_index),
705 };
706 return .{
707 .found_existing = false,
708 .entry = new_entry,
709 };
710 }
711 if (next_index.distance_from_start_index < distance_from_start_index) {
712 header.maybeBumpMax(distance_from_start_index);
713 indexes[next_index_index] = .{
714 .entry_index = prev_entry_index,
715 .distance_from_start_index = @intCast(I, distance_from_start_index),
716 };
717 distance_from_start_index = next_index.distance_from_start_index;
718 prev_entry_index = next_index.entry_index;
719 }
720 }
721 unreachable;
722 }
723 }
724 unreachable;
733 // This counts the number of occupied slots, used + tombstones, which is
734 // what has to stay under the MaxLoadPercentage of capacity.
735 fn load(self: *const Self) Size {
736 const max_load = (self.capacity() * MaxLoadPercentage) / 100;
737 assert(max_load >= self.available);
738 return @truncate(Size, max_load - self.available);
725739 }
726740
727 fn getInternal(self: Self, key: K, header: *IndexHeader, comptime I: type) ?usize {
728 const indexes = header.indexes(I);
729 const h = hash(key);
730 const start_index = header.constrainIndex(h);
731 var roll_over: usize = 0;
732 while (roll_over <= header.max_distance_from_start_index) : (roll_over += 1) {
733 const index_index = header.constrainIndex(start_index + roll_over);
734 const index = indexes[index_index];
735 if (index.isEmpty())
736 return null;
737
738 const entry = &self.entries.items[index.entry_index];
739 const hash_match = if (store_hash) h == entry.hash else true;
740 if (hash_match and eql(key, entry.key))
741 return index.entry_index;
741 fn growIfNeeded(self: *Self, allocator: *Allocator, new_count: Size) !void {
742 if (new_count > self.available) {
743 try self.grow(allocator, capacityForSize(self.load() + new_count));
742744 }
743 return null;
744745 }
745746
746 fn insertAllEntriesIntoNewHeader(self: *Self, header: *IndexHeader) void {
747 switch (header.capacityIndexType()) {
748 .u8 => return self.insertAllEntriesIntoNewHeaderGeneric(header, u8),
749 .u16 => return self.insertAllEntriesIntoNewHeaderGeneric(header, u16),
750 .u32 => return self.insertAllEntriesIntoNewHeaderGeneric(header, u32),
751 .usize => return self.insertAllEntriesIntoNewHeaderGeneric(header, usize),
747 pub fn clone(self: Self, allocator: *Allocator) !Self {
748 var other = Self{};
749 if (self.size == 0)
750 return other;
751
752 const new_cap = capacityForSize(self.size);
753 try other.allocate(allocator, new_cap);
754 other.initMetadatas();
755 other.available = @truncate(u32, (new_cap * MaxLoadPercentage) / 100);
756
757 var i: Size = 0;
758 var metadata = self.metadata.?;
759 var entr = self.entries();
760 while (i < self.capacity()) : (i += 1) {
761 if (metadata[i].isUsed()) {
762 const entry = &entr[i];
763 other.putAssumeCapacityNoClobber(entry.key, entry.value);
764 if (other.size == self.size)
765 break;
766 }
752767 }
768
769 return other;
753770 }
754771
755 fn insertAllEntriesIntoNewHeaderGeneric(self: *Self, header: *IndexHeader, comptime I: type) void {
756 const indexes = header.indexes(I);
757 entry_loop: for (self.entries.items) |entry, i| {
758 const h = if (store_hash) entry.hash else hash(entry.key);
759 const start_index = header.constrainIndex(h);
760 var entry_index = i;
761 var roll_over: usize = 0;
762 var distance_from_start_index: usize = 0;
763 while (roll_over < header.indexes_len) : ({
764 roll_over += 1;
765 distance_from_start_index += 1;
766 }) {
767 const index_index = header.constrainIndex(start_index + roll_over);
768 const next_index = indexes[index_index];
769 if (next_index.isEmpty()) {
770 header.maybeBumpMax(distance_from_start_index);
771 indexes[index_index] = .{
772 .distance_from_start_index = @intCast(I, distance_from_start_index),
773 .entry_index = @intCast(I, entry_index),
774 };
775 continue :entry_loop;
776 }
777 if (next_index.distance_from_start_index < distance_from_start_index) {
778 header.maybeBumpMax(distance_from_start_index);
779 indexes[index_index] = .{
780 .distance_from_start_index = @intCast(I, distance_from_start_index),
781 .entry_index = @intCast(I, entry_index),
782 };
783 distance_from_start_index = next_index.distance_from_start_index;
784 entry_index = next_index.entry_index;
772 fn grow(self: *Self, allocator: *Allocator, new_capacity: Size) !void {
773 const new_cap = std.math.max(new_capacity, MinimalCapacity);
774 assert(new_cap > self.capacity());
775 assert(std.math.isPowerOfTwo(new_cap));
776
777 var map = Self{};
778 defer map.deinit(allocator);
779 try map.allocate(allocator, new_cap);
780 map.initMetadatas();
781 map.available = @truncate(u32, (new_cap * MaxLoadPercentage) / 100);
782
783 if (self.size != 0) {
784 const old_capacity = self.capacity();
785 var i: Size = 0;
786 var metadata = self.metadata.?;
787 var entr = self.entries();
788 while (i < old_capacity) : (i += 1) {
789 if (metadata[i].isUsed()) {
790 const entry = &entr[i];
791 map.putAssumeCapacityNoClobber(entry.key, entry.value);
792 if (map.size == self.size)
793 break;
785794 }
786795 }
787 unreachable;
788796 }
797
798 self.size = 0;
799 std.mem.swap(Self, self, &map);
800 }
801
802 fn allocate(self: *Self, allocator: *Allocator, new_capacity: Size) !void {
803 const meta_size = @sizeOf(Header) + new_capacity * @sizeOf(Metadata);
804
805 const alignment = @alignOf(Entry) - 1;
806 const entries_size = @as(usize, new_capacity) * @sizeOf(Entry) + alignment;
807
808 const total_size = meta_size + entries_size;
809
810 const slice = try allocator.alignedAlloc(u8, @alignOf(Header), total_size);
811 const ptr = @ptrToInt(slice.ptr);
812
813 const metadata = ptr + @sizeOf(Header);
814 var entry_ptr = ptr + meta_size;
815 entry_ptr = (entry_ptr + alignment) & ~@as(usize, alignment);
816 assert(entry_ptr + @as(usize, new_capacity) * @sizeOf(Entry) <= ptr + total_size);
817
818 const hdr = @intToPtr(*Header, ptr);
819 hdr.entries = @intToPtr([*]Entry, entry_ptr);
820 hdr.capacity = new_capacity;
821 self.metadata = @intToPtr([*]Metadata, metadata);
789822 }
790823 };
791824}
792825
793const CapacityIndexType = enum { u8, u16, u32, usize };
826const testing = std.testing;
827const expect = std.testing.expect;
828const expectEqual = std.testing.expectEqual;
829
830test "std.hash_map basic usage" {
831 var map = AutoHashMap(u32, u32).init(std.testing.allocator);
832 defer map.deinit();
833
834 const count = 5;
835 var i: u32 = 0;
836 var total: u32 = 0;
837 while (i < count) : (i += 1) {
838 try map.put(i, i);
839 total += i;
840 }
841
842 var sum: u32 = 0;
843 var it = map.iterator();
844 while (it.next()) |kv| {
845 sum += kv.key;
846 }
847 expect(sum == total);
848
849 i = 0;
850 sum = 0;
851 while (i < count) : (i += 1) {
852 expectEqual(map.get(i).?, i);
853 sum += map.get(i).?;
854 }
855 expectEqual(total, sum);
856}
857
858test "std.hash_map ensureCapacity" {
859 var map = AutoHashMap(i32, i32).init(std.testing.allocator);
860 defer map.deinit();
794861
795fn capacityIndexType(indexes_len: usize) CapacityIndexType {
796 if (indexes_len < math.maxInt(u8))
797 return .u8;
798 if (indexes_len < math.maxInt(u16))
799 return .u16;
800 if (indexes_len < math.maxInt(u32))
801 return .u32;
802 return .usize;
862 try map.ensureCapacity(20);
863 const initial_capacity = map.capacity();
864 testing.expect(initial_capacity >= 20);
865 var i: i32 = 0;
866 while (i < 20) : (i += 1) {
867 testing.expect(map.fetchPutAssumeCapacity(i, i + 10) == null);
868 }
869 // shouldn't resize from putAssumeCapacity
870 testing.expect(initial_capacity == map.capacity());
803871}
804872
805fn capacityIndexSize(indexes_len: usize) usize {
806 switch (capacityIndexType(indexes_len)) {
807 .u8 => return @sizeOf(Index(u8)),
808 .u16 => return @sizeOf(Index(u16)),
809 .u32 => return @sizeOf(Index(u32)),
810 .usize => return @sizeOf(Index(usize)),
873test "std.hash_map ensureCapacity with tombstones" {
874 var map = AutoHashMap(i32, i32).init(std.testing.allocator);
875 defer map.deinit();
876
877 var i: i32 = 0;
878 while (i < 100) : (i += 1) {
879 try map.ensureCapacity(@intCast(u32, map.count() + 1));
880 map.putAssumeCapacity(i, i);
881 // Remove to create tombstones that still count as load in the hashmap.
882 _ = map.remove(i);
811883 }
812884}
813885
814fn Index(comptime I: type) type {
815 return extern struct {
816 entry_index: I,
817 distance_from_start_index: I,
886test "std.hash_map clearRetainingCapacity" {
887 var map = AutoHashMap(u32, u32).init(std.testing.allocator);
888 defer map.deinit();
889
890 map.clearRetainingCapacity();
818891
819 const Self = @This();
892 try map.put(1, 1);
893 expectEqual(map.get(1).?, 1);
894 expectEqual(map.count(), 1);
820895
821 const empty = Self{
822 .entry_index = math.maxInt(I),
823 .distance_from_start_index = undefined,
824 };
896 const cap = map.capacity();
897 expect(cap > 0);
898
899 map.clearRetainingCapacity();
900 map.clearRetainingCapacity();
901 expectEqual(map.count(), 0);
902 expectEqual(map.capacity(), cap);
903 expect(!map.contains(1));
904}
905
906test "std.hash_map grow" {
907 var map = AutoHashMap(u32, u32).init(std.testing.allocator);
908 defer map.deinit();
825909
826 fn isEmpty(idx: Self) bool {
827 return idx.entry_index == math.maxInt(I);
910 const growTo = 12456;
911
912 var i: u32 = 0;
913 while (i < growTo) : (i += 1) {
914 try map.put(i, i);
915 }
916 expectEqual(map.count(), growTo);
917
918 i = 0;
919 var it = map.iterator();
920 while (it.next()) |kv| {
921 expectEqual(kv.key, kv.value);
922 i += 1;
923 }
924 expectEqual(i, growTo);
925
926 i = 0;
927 while (i < growTo) : (i += 1) {
928 expectEqual(map.get(i).?, i);
929 }
930}
931
932test "std.hash_map clone" {
933 var map = AutoHashMap(u32, u32).init(std.testing.allocator);
934 defer map.deinit();
935
936 var a = try map.clone();
937 defer a.deinit();
938
939 expectEqual(a.count(), 0);
940
941 try a.put(1, 1);
942 try a.put(2, 2);
943 try a.put(3, 3);
944
945 var b = try a.clone();
946 defer b.deinit();
947
948 expectEqual(b.count(), 3);
949 expectEqual(b.get(1), 1);
950 expectEqual(b.get(2), 2);
951 expectEqual(b.get(3), 3);
952}
953
954test "std.hash_map ensureCapacity with existing elements" {
955 var map = AutoHashMap(u32, u32).init(std.testing.allocator);
956 defer map.deinit();
957
958 try map.put(0, 0);
959 expectEqual(map.count(), 1);
960 expectEqual(map.capacity(), @TypeOf(map).Unmanaged.MinimalCapacity);
961
962 try map.ensureCapacity(65);
963 expectEqual(map.count(), 1);
964 expectEqual(map.capacity(), 128);
965}
966
967test "std.hash_map ensureCapacity satisfies max load factor" {
968 var map = AutoHashMap(u32, u32).init(std.testing.allocator);
969 defer map.deinit();
970
971 try map.ensureCapacity(127);
972 expectEqual(map.capacity(), 256);
973}
974
975test "std.hash_map remove" {
976 var map = AutoHashMap(u32, u32).init(std.testing.allocator);
977 defer map.deinit();
978
979 var i: u32 = 0;
980 while (i < 16) : (i += 1) {
981 try map.put(i, i);
982 }
983
984 i = 0;
985 while (i < 16) : (i += 1) {
986 if (i % 3 == 0) {
987 _ = map.remove(i);
828988 }
989 }
990 expectEqual(map.count(), 10);
991 var it = map.iterator();
992 while (it.next()) |kv| {
993 expectEqual(kv.key, kv.value);
994 expect(kv.key % 3 != 0);
995 }
829996
830 fn setEmpty(idx: *Self) void {
831 idx.entry_index = math.maxInt(I);
997 i = 0;
998 while (i < 16) : (i += 1) {
999 if (i % 3 == 0) {
1000 expect(!map.contains(i));
1001 } else {
1002 expectEqual(map.get(i).?, i);
8321003 }
833 };
1004 }
8341005}
8351006
836/// This struct is trailed by an array of `Index(I)`, where `I`
837/// and the array length are determined by `indexes_len`.
838const IndexHeader = struct {
839 max_distance_from_start_index: usize,
840 indexes_len: usize,
1007test "std.hash_map reverse removes" {
1008 var map = AutoHashMap(u32, u32).init(std.testing.allocator);
1009 defer map.deinit();
8411010
842 fn constrainIndex(header: IndexHeader, i: usize) usize {
843 // This is an optimization for modulo of power of two integers;
844 // it requires `indexes_len` to always be a power of two.
845 return i & (header.indexes_len - 1);
1011 var i: u32 = 0;
1012 while (i < 16) : (i += 1) {
1013 try map.putNoClobber(i, i);
8461014 }
8471015
848 fn indexes(header: *IndexHeader, comptime I: type) []Index(I) {
849 const start = @ptrCast([*]Index(I), @ptrCast([*]u8, header) + @sizeOf(IndexHeader));
850 return start[0..header.indexes_len];
1016 i = 16;
1017 while (i > 0) : (i -= 1) {
1018 _ = map.remove(i - 1);
1019 expect(!map.contains(i - 1));
1020 var j: u32 = 0;
1021 while (j < i - 1) : (j += 1) {
1022 expectEqual(map.get(j).?, j);
1023 }
8511024 }
8521025
853 fn capacityIndexType(header: IndexHeader) CapacityIndexType {
854 return hash_map.capacityIndexType(header.indexes_len);
1026 expectEqual(map.count(), 0);
1027}
1028
1029test "std.hash_map multiple removes on same metadata" {
1030 var map = AutoHashMap(u32, u32).init(std.testing.allocator);
1031 defer map.deinit();
1032
1033 var i: u32 = 0;
1034 while (i < 16) : (i += 1) {
1035 try map.put(i, i);
8551036 }
8561037
857 fn maybeBumpMax(header: *IndexHeader, distance_from_start_index: usize) void {
858 if (distance_from_start_index > header.max_distance_from_start_index) {
859 header.max_distance_from_start_index = distance_from_start_index;
1038 _ = map.remove(7);
1039 _ = map.remove(15);
1040 _ = map.remove(14);
1041 _ = map.remove(13);
1042 expect(!map.contains(7));
1043 expect(!map.contains(15));
1044 expect(!map.contains(14));
1045 expect(!map.contains(13));
1046
1047 i = 0;
1048 while (i < 13) : (i += 1) {
1049 if (i == 7) {
1050 expect(!map.contains(i));
1051 } else {
1052 expectEqual(map.get(i).?, i);
8601053 }
8611054 }
8621055
863 fn alloc(allocator: *Allocator, len: usize) !*IndexHeader {
864 const index_size = hash_map.capacityIndexSize(len);
865 const nbytes = @sizeOf(IndexHeader) + index_size * len;
866 const bytes = try allocator.allocAdvanced(u8, @alignOf(IndexHeader), nbytes, .exact);
867 @memset(bytes.ptr + @sizeOf(IndexHeader), 0xff, bytes.len - @sizeOf(IndexHeader));
868 const result = @ptrCast(*IndexHeader, bytes.ptr);
869 result.* = .{
870 .max_distance_from_start_index = 0,
871 .indexes_len = len,
872 };
873 return result;
1056 try map.put(15, 15);
1057 try map.put(13, 13);
1058 try map.put(14, 14);
1059 try map.put(7, 7);
1060 i = 0;
1061 while (i < 16) : (i += 1) {
1062 expectEqual(map.get(i).?, i);
1063 }
1064}
1065
1066test "std.hash_map put and remove loop in random order" {
1067 var map = AutoHashMap(u32, u32).init(std.testing.allocator);
1068 defer map.deinit();
1069
1070 var keys = std.ArrayList(u32).init(std.testing.allocator);
1071 defer keys.deinit();
1072
1073 const size = 32;
1074 const iterations = 100;
1075
1076 var i: u32 = 0;
1077 while (i < size) : (i += 1) {
1078 try keys.append(i);
1079 }
1080 var rng = std.rand.DefaultPrng.init(0);
1081
1082 while (i < iterations) : (i += 1) {
1083 std.rand.Random.shuffle(&rng.random, u32, keys.items);
1084
1085 for (keys.items) |key| {
1086 try map.put(key, key);
1087 }
1088 expectEqual(map.count(), size);
1089
1090 for (keys.items) |key| {
1091 _ = map.remove(key);
1092 }
1093 expectEqual(map.count(), 0);
1094 }
1095}
1096
1097test "std.hash_map remove one million elements in random order" {
1098 const Map = AutoHashMap(u32, u32);
1099 const n = 1000 * 1000;
1100 var map = Map.init(std.heap.page_allocator);
1101 defer map.deinit();
1102
1103 var keys = std.ArrayList(u32).init(std.heap.page_allocator);
1104 defer keys.deinit();
1105
1106 var i: u32 = 0;
1107 while (i < n) : (i += 1) {
1108 keys.append(i) catch unreachable;
1109 }
1110
1111 var rng = std.rand.DefaultPrng.init(0);
1112 std.rand.Random.shuffle(&rng.random, u32, keys.items);
1113
1114 for (keys.items) |key| {
1115 map.put(key, key) catch unreachable;
1116 }
1117
1118 std.rand.Random.shuffle(&rng.random, u32, keys.items);
1119 i = 0;
1120 while (i < n) : (i += 1) {
1121 const key = keys.items[i];
1122 _ = map.remove(key);
1123 }
1124}
1125
1126test "std.hash_map put" {
1127 var map = AutoHashMap(u32, u32).init(std.testing.allocator);
1128 defer map.deinit();
1129
1130 var i: u32 = 0;
1131 while (i < 16) : (i += 1) {
1132 _ = try map.put(i, i);
1133 }
1134
1135 i = 0;
1136 while (i < 16) : (i += 1) {
1137 expectEqual(map.get(i).?, i);
1138 }
1139
1140 i = 0;
1141 while (i < 16) : (i += 1) {
1142 try map.put(i, i * 16 + 1);
1143 }
1144
1145 i = 0;
1146 while (i < 16) : (i += 1) {
1147 expectEqual(map.get(i).?, i * 16 + 1);
1148 }
1149}
1150
1151test "std.hash_map getOrPut" {
1152 var map = AutoHashMap(u32, u32).init(std.testing.allocator);
1153 defer map.deinit();
1154
1155 var i: u32 = 0;
1156 while (i < 10) : (i += 1) {
1157 try map.put(i * 2, 2);
8741158 }
8751159
876 fn free(header: *IndexHeader, allocator: *Allocator) void {
877 const index_size = hash_map.capacityIndexSize(header.indexes_len);
878 const ptr = @ptrCast([*]u8, header);
879 const slice = ptr[0 .. @sizeOf(IndexHeader) + header.indexes_len * index_size];
880 allocator.free(slice);
1160 i = 0;
1161 while (i < 20) : (i += 1) {
1162 var n = try map.getOrPutValue(i, 1);
8811163 }
882};
8831164
884test "basic hash map usage" {
1165 i = 0;
1166 var sum = i;
1167 while (i < 20) : (i += 1) {
1168 sum += map.get(i).?;
1169 }
1170
1171 expectEqual(sum, 30);
1172}
1173
1174test "std.hash_map basic hash map usage" {
8851175 var map = AutoHashMap(i32, i32).init(std.testing.allocator);
8861176 defer map.deinit();
8871177
......@@ -925,85 +1215,10 @@ test "basic hash map usage" {
9251215 map.removeAssertDiscard(3);
9261216}
9271217
928test "iterator hash map" {
929 // https://github.com/ziglang/zig/issues/5127
930 if (std.Target.current.cpu.arch == .mips) return error.SkipZigTest;
931
932 var reset_map = AutoHashMap(i32, i32).init(std.testing.allocator);
933 defer reset_map.deinit();
934
935 // test ensureCapacity with a 0 parameter
936 try reset_map.ensureCapacity(0);
937
938 try reset_map.putNoClobber(0, 11);
939 try reset_map.putNoClobber(1, 22);
940 try reset_map.putNoClobber(2, 33);
941
942 var keys = [_]i32{
943 0, 2, 1,
944 };
945
946 var values = [_]i32{
947 11, 33, 22,
948 };
949
950 var buffer = [_]i32{
951 0, 0, 0,
952 };
953
954 var it = reset_map.iterator();
955 const first_entry = it.next().?;
956 it.reset();
957
958 var count: usize = 0;
959 while (it.next()) |entry| : (count += 1) {
960 buffer[@intCast(usize, entry.key)] = entry.value;
961 }
962 testing.expect(count == 3);
963 testing.expect(it.next() == null);
964
965 for (buffer) |v, i| {
966 testing.expect(buffer[@intCast(usize, keys[i])] == values[i]);
967 }
968
969 it.reset();
970 count = 0;
971 while (it.next()) |entry| {
972 buffer[@intCast(usize, entry.key)] = entry.value;
973 count += 1;
974 if (count >= 2) break;
975 }
976
977 for (buffer[0..2]) |v, i| {
978 testing.expect(buffer[@intCast(usize, keys[i])] == values[i]);
979 }
980
981 it.reset();
982 var entry = it.next().?;
983 testing.expect(entry.key == first_entry.key);
984 testing.expect(entry.value == first_entry.value);
985}
986
987test "ensure capacity" {
988 var map = AutoHashMap(i32, i32).init(std.testing.allocator);
989 defer map.deinit();
990
991 try map.ensureCapacity(20);
992 const initial_capacity = map.capacity();
993 testing.expect(initial_capacity >= 20);
994 var i: i32 = 0;
995 while (i < 20) : (i += 1) {
996 testing.expect(map.fetchPutAssumeCapacity(i, i + 10) == null);
997 }
998 // shouldn't resize from putAssumeCapacity
999 testing.expect(initial_capacity == map.capacity());
1000}
1001
1002test "clone" {
1218test "std.hash_map clone" {
10031219 var original = AutoHashMap(i32, i32).init(std.testing.allocator);
10041220 defer original.deinit();
10051221
1006 // put more than `linear_scan_max` so we can test that the index header is properly cloned
10071222 var i: u8 = 0;
10081223 while (i < 10) : (i += 1) {
10091224 try original.putNoClobber(i, i * 10);
......@@ -1017,69 +1232,3 @@ test "clone" {
10171232 testing.expect(copy.get(i).? == i * 10);
10181233 }
10191234}
1020
1021pub fn getHashPtrAddrFn(comptime K: type) (fn (K) u32) {
1022 return struct {
1023 fn hash(key: K) u32 {
1024 return getAutoHashFn(usize)(@ptrToInt(key));
1025 }
1026 }.hash;
1027}
1028
1029pub fn getTrivialEqlFn(comptime K: type) (fn (K, K) bool) {
1030 return struct {
1031 fn eql(a: K, b: K) bool {
1032 return a == b;
1033 }
1034 }.eql;
1035}
1036
1037pub fn getAutoHashFn(comptime K: type) (fn (K) u32) {
1038 return struct {
1039 fn hash(key: K) u32 {
1040 if (comptime trait.hasUniqueRepresentation(K)) {
1041 return @truncate(u32, Wyhash.hash(0, std.mem.asBytes(&key)));
1042 } else {
1043 var hasher = Wyhash.init(0);
1044 autoHash(&hasher, key);
1045 return @truncate(u32, hasher.final());
1046 }
1047 }
1048 }.hash;
1049}
1050
1051pub fn getAutoEqlFn(comptime K: type) (fn (K, K) bool) {
1052 return struct {
1053 fn eql(a: K, b: K) bool {
1054 return meta.eql(a, b);
1055 }
1056 }.eql;
1057}
1058
1059pub fn autoEqlIsCheap(comptime K: type) bool {
1060 return switch (@typeInfo(K)) {
1061 .Bool,
1062 .Int,
1063 .Float,
1064 .Pointer,
1065 .ComptimeFloat,
1066 .ComptimeInt,
1067 .Enum,
1068 .Fn,
1069 .ErrorSet,
1070 .AnyFrame,
1071 .EnumLiteral,
1072 => true,
1073 else => false,
1074 };
1075}
1076
1077pub fn getAutoHashStratFn(comptime K: type, comptime strategy: std.hash.Strategy) (fn (K) u32) {
1078 return struct {
1079 fn hash(key: K) u32 {
1080 var hasher = Wyhash.init(0);
1081 std.hash.autoHashStrat(&hasher, key, strategy);
1082 return @truncate(u32, hasher.final());
1083 }
1084 }.hash;
1085}
lib/std/heap/general_purpose_allocator.zig+3-2
......@@ -325,7 +325,8 @@ pub fn GeneralPurposeAllocator(comptime config: Config) type {
325325 break;
326326 }
327327 }
328 for (self.large_allocations.items()) |*large_alloc| {
328 var it = self.large_allocations.iterator();
329 while (it.next()) |large_alloc| {
329330 log.err("Memory leak detected: {}", .{large_alloc.value.getStackTrace()});
330331 leaks = true;
331332 }
......@@ -584,7 +585,7 @@ pub fn GeneralPurposeAllocator(comptime config: Config) type {
584585 if (new_aligned_size > largest_bucket_object_size) {
585586 try self.large_allocations.ensureCapacity(
586587 self.backing_allocator,
587 self.large_allocations.entries.items.len + 1,
588 self.large_allocations.count() + 1,
588589 );
589590
590591 const slice = try self.backing_allocator.allocFn(self.backing_allocator, len, ptr_align, len_align, ret_addr);
lib/std/http/headers.zig+5-4
......@@ -123,9 +123,9 @@ pub const Headers = struct {
123123
124124 pub fn deinit(self: *Self) void {
125125 {
126 for (self.index.items()) |*entry| {
127 const dex = &entry.value;
128 dex.deinit(self.allocator);
126 var it = self.index.iterator();
127 while (it.next()) |entry| {
128 entry.value.deinit(self.allocator);
129129 self.allocator.free(entry.key);
130130 }
131131 self.index.deinit(self.allocator);
......@@ -333,7 +333,8 @@ pub const Headers = struct {
333333
334334 fn rebuildIndex(self: *Self) void {
335335 // clear out the indexes
336 for (self.index.items()) |*entry| {
336 var it = self.index.iterator();
337 while (it.next()) |entry| {
337338 entry.value.shrinkRetainingCapacity(0);
338339 }
339340 // fill up indexes again; we know capacity is fine from before
lib/std/io.zig+9
......@@ -169,6 +169,15 @@ pub const BitOutStream = BitWriter;
169169/// Deprecated: use `bitWriter`
170170pub const bitOutStream = bitWriter;
171171
172pub const AutoIndentingStream = @import("io/auto_indenting_stream.zig").AutoIndentingStream;
173pub const autoIndentingStream = @import("io/auto_indenting_stream.zig").autoIndentingStream;
174
175pub const ChangeDetectionStream = @import("io/change_detection_stream.zig").ChangeDetectionStream;
176pub const changeDetectionStream = @import("io/change_detection_stream.zig").changeDetectionStream;
177
178pub const FindByteOutStream = @import("io/find_byte_out_stream.zig").FindByteOutStream;
179pub const findByteOutStream = @import("io/find_byte_out_stream.zig").findByteOutStream;
180
172181pub const Packing = @import("io/serialization.zig").Packing;
173182
174183pub const Serializer = @import("io/serialization.zig").Serializer;
lib/std/io/auto_indenting_stream.zig created+148
......@@ -0,0 +1,148 @@
1const std = @import("../std.zig");
2const io = std.io;
3const mem = std.mem;
4const assert = std.debug.assert;
5
6/// Automatically inserts indentation of written data by keeping
7/// track of the current indentation level
8pub fn AutoIndentingStream(comptime UnderlyingWriter: type) type {
9 return struct {
10 const Self = @This();
11 pub const Error = UnderlyingWriter.Error;
12 pub const Writer = io.Writer(*Self, Error, write);
13
14 underlying_writer: UnderlyingWriter,
15
16 indent_count: usize = 0,
17 indent_delta: usize,
18 current_line_empty: bool = true,
19 indent_one_shot_count: usize = 0, // automatically popped when applied
20 applied_indent: usize = 0, // the most recently applied indent
21 indent_next_line: usize = 0, // not used until the next line
22
23 pub fn writer(self: *Self) Writer {
24 return .{ .context = self };
25 }
26
27 pub fn write(self: *Self, bytes: []const u8) Error!usize {
28 if (bytes.len == 0)
29 return @as(usize, 0);
30
31 try self.applyIndent();
32 return self.writeNoIndent(bytes);
33 }
34
35 // Change the indent delta without changing the final indentation level
36 pub fn setIndentDelta(self: *Self, indent_delta: usize) void {
37 if (self.indent_delta == indent_delta) {
38 return;
39 } else if (self.indent_delta > indent_delta) {
40 assert(self.indent_delta % indent_delta == 0);
41 self.indent_count = self.indent_count * (self.indent_delta / indent_delta);
42 } else {
43 // assert that the current indentation (in spaces) in a multiple of the new delta
44 assert((self.indent_count * self.indent_delta) % indent_delta == 0);
45 self.indent_count = self.indent_count / (indent_delta / self.indent_delta);
46 }
47 self.indent_delta = indent_delta;
48 }
49
50 fn writeNoIndent(self: *Self, bytes: []const u8) Error!usize {
51 if (bytes.len == 0)
52 return @as(usize, 0);
53
54 try self.underlying_writer.writeAll(bytes);
55 if (bytes[bytes.len - 1] == '\n')
56 self.resetLine();
57 return bytes.len;
58 }
59
60 pub fn insertNewline(self: *Self) Error!void {
61 _ = try self.writeNoIndent("\n");
62 }
63
64 fn resetLine(self: *Self) void {
65 self.current_line_empty = true;
66 self.indent_next_line = 0;
67 }
68
69 /// Insert a newline unless the current line is blank
70 pub fn maybeInsertNewline(self: *Self) Error!void {
71 if (!self.current_line_empty)
72 try self.insertNewline();
73 }
74
75 /// Push default indentation
76 pub fn pushIndent(self: *Self) void {
77 // Doesn't actually write any indentation.
78 // Just primes the stream to be able to write the correct indentation if it needs to.
79 self.indent_count += 1;
80 }
81
82 /// Push an indent that is automatically popped after being applied
83 pub fn pushIndentOneShot(self: *Self) void {
84 self.indent_one_shot_count += 1;
85 self.pushIndent();
86 }
87
88 /// Turns all one-shot indents into regular indents
89 /// Returns number of indents that must now be manually popped
90 pub fn lockOneShotIndent(self: *Self) usize {
91 var locked_count = self.indent_one_shot_count;
92 self.indent_one_shot_count = 0;
93 return locked_count;
94 }
95
96 /// Push an indent that should not take effect until the next line
97 pub fn pushIndentNextLine(self: *Self) void {
98 self.indent_next_line += 1;
99 self.pushIndent();
100 }
101
102 pub fn popIndent(self: *Self) void {
103 assert(self.indent_count != 0);
104 self.indent_count -= 1;
105
106 if (self.indent_next_line > 0)
107 self.indent_next_line -= 1;
108 }
109
110 /// Writes ' ' bytes if the current line is empty
111 fn applyIndent(self: *Self) Error!void {
112 const current_indent = self.currentIndent();
113 if (self.current_line_empty and current_indent > 0) {
114 try self.underlying_writer.writeByteNTimes(' ', current_indent);
115 self.applied_indent = current_indent;
116 }
117
118 self.indent_count -= self.indent_one_shot_count;
119 self.indent_one_shot_count = 0;
120 self.current_line_empty = false;
121 }
122
123 /// Checks to see if the most recent indentation exceeds the currently pushed indents
124 pub fn isLineOverIndented(self: *Self) bool {
125 if (self.current_line_empty) return false;
126 return self.applied_indent > self.currentIndent();
127 }
128
129 fn currentIndent(self: *Self) usize {
130 var indent_current: usize = 0;
131 if (self.indent_count > 0) {
132 const indent_count = self.indent_count - self.indent_next_line;
133 indent_current = indent_count * self.indent_delta;
134 }
135 return indent_current;
136 }
137 };
138}
139
140pub fn autoIndentingStream(
141 indent_delta: usize,
142 underlying_writer: anytype,
143) AutoIndentingStream(@TypeOf(underlying_writer)) {
144 return AutoIndentingStream(@TypeOf(underlying_writer)){
145 .underlying_writer = underlying_writer,
146 .indent_delta = indent_delta,
147 };
148}
lib/std/io/change_detection_stream.zig created+55
......@@ -0,0 +1,55 @@
1const std = @import("../std.zig");
2const io = std.io;
3const mem = std.mem;
4const assert = std.debug.assert;
5
6/// Used to detect if the data written to a stream differs from a source buffer
7pub fn ChangeDetectionStream(comptime WriterType: type) type {
8 return struct {
9 const Self = @This();
10 pub const Error = WriterType.Error;
11 pub const Writer = io.Writer(*Self, Error, write);
12
13 anything_changed: bool,
14 underlying_writer: WriterType,
15 source_index: usize,
16 source: []const u8,
17
18 pub fn writer(self: *Self) Writer {
19 return .{ .context = self };
20 }
21
22 fn write(self: *Self, bytes: []const u8) Error!usize {
23 if (!self.anything_changed) {
24 const end = self.source_index + bytes.len;
25 if (end > self.source.len) {
26 self.anything_changed = true;
27 } else {
28 const src_slice = self.source[self.source_index..end];
29 self.source_index += bytes.len;
30 if (!mem.eql(u8, bytes, src_slice)) {
31 self.anything_changed = true;
32 }
33 }
34 }
35
36 return self.underlying_writer.write(bytes);
37 }
38
39 pub fn changeDetected(self: *Self) bool {
40 return self.anything_changed or (self.source_index != self.source.len);
41 }
42 };
43}
44
45pub fn changeDetectionStream(
46 source: []const u8,
47 underlying_writer: anytype,
48) ChangeDetectionStream(@TypeOf(underlying_writer)) {
49 return ChangeDetectionStream(@TypeOf(underlying_writer)){
50 .anything_changed = false,
51 .underlying_writer = underlying_writer,
52 .source_index = 0,
53 .source = source,
54 };
55}
lib/std/io/find_byte_out_stream.zig created+40
......@@ -0,0 +1,40 @@
1const std = @import("../std.zig");
2const io = std.io;
3const assert = std.debug.assert;
4
5/// An OutStream that returns whether the given character has been written to it.
6/// The contents are not written to anything.
7pub fn FindByteOutStream(comptime UnderlyingWriter: type) type {
8 return struct {
9 const Self = @This();
10 pub const Error = UnderlyingWriter.Error;
11 pub const Writer = io.Writer(*Self, Error, write);
12
13 underlying_writer: UnderlyingWriter,
14 byte_found: bool,
15 byte: u8,
16
17 pub fn writer(self: *Self) Writer {
18 return .{ .context = self };
19 }
20
21 fn write(self: *Self, bytes: []const u8) Error!usize {
22 if (!self.byte_found) {
23 self.byte_found = blk: {
24 for (bytes) |b|
25 if (b == self.byte) break :blk true;
26 break :blk false;
27 };
28 }
29 return self.underlying_writer.write(bytes);
30 }
31 };
32}
33
34pub fn findByteOutStream(byte: u8, underlying_writer: anytype) FindByteOutStream(@TypeOf(underlying_writer)) {
35 return FindByteOutStream(@TypeOf(underlying_writer)){
36 .underlying_writer = underlying_writer,
37 .byte = byte,
38 .byte_found = false,
39 };
40}
lib/std/meta.zig+8-8
......@@ -705,34 +705,34 @@ pub fn Vector(comptime len: u32, comptime child: type) type {
705705pub fn cast(comptime DestType: type, target: anytype) DestType {
706706 const TargetType = @TypeOf(target);
707707 switch (@typeInfo(DestType)) {
708 .Pointer => {
708 .Pointer => |dest_ptr| {
709709 switch (@typeInfo(TargetType)) {
710710 .Int, .ComptimeInt => {
711711 return @intToPtr(DestType, target);
712712 },
713713 .Pointer => |ptr| {
714 return @ptrCast(DestType, @alignCast(ptr.alignment, target));
714 return @ptrCast(DestType, @alignCast(dest_ptr.alignment, target));
715715 },
716716 .Optional => |opt| {
717717 if (@typeInfo(opt.child) == .Pointer) {
718 return @ptrCast(DestType, @alignCast(@alignOf(opt.child.Child), target));
718 return @ptrCast(DestType, @alignCast(dest_ptr, target));
719719 }
720720 },
721721 else => {},
722722 }
723723 },
724 .Optional => |opt| {
725 if (@typeInfo(opt.child) == .Pointer) {
724 .Optional => |dest_opt| {
725 if (@typeInfo(dest_opt.child) == .Pointer) {
726726 switch (@typeInfo(TargetType)) {
727727 .Int, .ComptimeInt => {
728728 return @intToPtr(DestType, target);
729729 },
730 .Pointer => |ptr| {
731 return @ptrCast(DestType, @alignCast(ptr.alignment, target));
730 .Pointer => {
731 return @ptrCast(DestType, @alignCast(@alignOf(dest_opt.child.Child), target));
732732 },
733733 .Optional => |target_opt| {
734734 if (@typeInfo(target_opt.child) == .Pointer) {
735 return @ptrCast(DestType, @alignCast(@alignOf(target_opt.child.Child), target));
735 return @ptrCast(DestType, @alignCast(@alignOf(dest_opt.child.Child), target));
736736 }
737737 },
738738 else => {},
lib/std/meta/trailer_flags.zig+1
......@@ -46,6 +46,7 @@ pub fn TrailerFlags(comptime Fields: type) type {
4646 ??struct_field.field_type,
4747 @as(?struct_field.field_type, null),
4848 ),
49 .is_comptime = false,
4950 };
5051 }
5152 break :blk @Type(.{
lib/std/net.zig+4-1
......@@ -1164,7 +1164,7 @@ fn linuxLookupNameFromDnsSearch(
11641164 }
11651165
11661166 const search = if (rc.search.isNull() or dots >= rc.ndots or mem.endsWith(u8, name, "."))
1167 &[_]u8{}
1167 ""
11681168 else
11691169 rc.search.span();
11701170
......@@ -1641,6 +1641,9 @@ pub const StreamServer = struct {
16411641 /// by the socket buffer limits, not by the system memory.
16421642 SystemResources,
16431643
1644 /// Socket is not listening for new connections.
1645 SocketNotListening,
1646
16441647 ProtocolFailure,
16451648
16461649 /// Firewall rules forbid connection.
lib/std/os.zig+98-8
......@@ -2512,13 +2512,14 @@ pub fn readlinkatZ(dirfd: fd_t, file_path: [*:0]const u8, out_buffer: []u8) Read
25122512 }
25132513}
25142514
2515pub const SetIdError = error{
2516 ResourceLimitReached,
2515pub const SetEidError = error{
25172516 InvalidUserId,
25182517 PermissionDenied,
2519} || UnexpectedError;
2518};
25202519
2521pub fn setuid(uid: u32) SetIdError!void {
2520pub const SetIdError = error{ResourceLimitReached} || SetEidError || UnexpectedError;
2521
2522pub fn setuid(uid: uid_t) SetIdError!void {
25222523 switch (errno(system.setuid(uid))) {
25232524 0 => return,
25242525 EAGAIN => return error.ResourceLimitReached,
......@@ -2528,7 +2529,16 @@ pub fn setuid(uid: u32) SetIdError!void {
25282529 }
25292530}
25302531
2531pub fn setreuid(ruid: u32, euid: u32) SetIdError!void {
2532pub fn seteuid(uid: uid_t) SetEidError!void {
2533 switch (errno(system.seteuid(uid))) {
2534 0 => return,
2535 EINVAL => return error.InvalidUserId,
2536 EPERM => return error.PermissionDenied,
2537 else => |err| return unexpectedErrno(err),
2538 }
2539}
2540
2541pub fn setreuid(ruid: uid_t, euid: uid_t) SetIdError!void {
25322542 switch (errno(system.setreuid(ruid, euid))) {
25332543 0 => return,
25342544 EAGAIN => return error.ResourceLimitReached,
......@@ -2538,7 +2548,7 @@ pub fn setreuid(ruid: u32, euid: u32) SetIdError!void {
25382548 }
25392549}
25402550
2541pub fn setgid(gid: u32) SetIdError!void {
2551pub fn setgid(gid: gid_t) SetIdError!void {
25422552 switch (errno(system.setgid(gid))) {
25432553 0 => return,
25442554 EAGAIN => return error.ResourceLimitReached,
......@@ -2548,7 +2558,16 @@ pub fn setgid(gid: u32) SetIdError!void {
25482558 }
25492559}
25502560
2551pub fn setregid(rgid: u32, egid: u32) SetIdError!void {
2561pub fn setegid(uid: uid_t) SetEidError!void {
2562 switch (errno(system.setegid(uid))) {
2563 0 => return,
2564 EINVAL => return error.InvalidUserId,
2565 EPERM => return error.PermissionDenied,
2566 else => |err| return unexpectedErrno(err),
2567 }
2568}
2569
2570pub fn setregid(rgid: gid_t, egid: gid_t) SetIdError!void {
25522571 switch (errno(system.setregid(rgid, egid))) {
25532572 0 => return,
25542573 EAGAIN => return error.ResourceLimitReached,
......@@ -2802,6 +2821,9 @@ pub const AcceptError = error{
28022821 /// by the socket buffer limits, not by the system memory.
28032822 SystemResources,
28042823
2824 /// Socket is not listening for new connections.
2825 SocketNotListening,
2826
28052827 ProtocolFailure,
28062828
28072829 /// Firewall rules forbid connection.
......@@ -2870,7 +2892,7 @@ pub fn accept(
28702892 EBADF => unreachable, // always a race condition
28712893 ECONNABORTED => return error.ConnectionAborted,
28722894 EFAULT => unreachable,
2873 EINVAL => unreachable,
2895 EINVAL => return error.SocketNotListening,
28742896 ENOTSOCK => unreachable,
28752897 EMFILE => return error.ProcessFdQuotaExceeded,
28762898 ENFILE => return error.SystemFdQuotaExceeded,
......@@ -5328,3 +5350,71 @@ pub fn signalfd(fd: fd_t, mask: *const sigset_t, flags: u32) !fd_t {
53285350 else => |err| return std.os.unexpectedErrno(err),
53295351 }
53305352}
5353
5354pub const SyncError = error{
5355 InputOutput,
5356 NoSpaceLeft,
5357 DiskQuota,
5358 AccessDenied,
5359} || UnexpectedError;
5360
5361/// Write all pending file contents and metadata modifications to all filesystems.
5362pub fn sync() void {
5363 system.sync();
5364}
5365
5366/// Write all pending file contents and metadata modifications to the filesystem which contains the specified file.
5367pub fn syncfs(fd: fd_t) SyncError!void {
5368 const rc = system.syncfs(fd);
5369 switch (errno(rc)) {
5370 0 => return,
5371 EBADF, EINVAL, EROFS => unreachable,
5372 EIO => return error.InputOutput,
5373 ENOSPC => return error.NoSpaceLeft,
5374 EDQUOT => return error.DiskQuota,
5375 else => |err| return std.os.unexpectedErrno(err),
5376 }
5377}
5378
5379/// Write all pending file contents and metadata modifications for the specified file descriptor to the underlying filesystem.
5380pub fn fsync(fd: fd_t) SyncError!void {
5381 if (std.Target.current.os.tag == .windows) {
5382 if (windows.kernel32.FlushFileBuffers(fd) != 0)
5383 return;
5384 switch (windows.kernel32.GetLastError()) {
5385 .SUCCESS => return,
5386 .INVALID_HANDLE => unreachable,
5387 .ACCESS_DENIED => return error.AccessDenied, // a sync was performed but the system couldn't update the access time
5388 .UNEXP_NET_ERR => return error.InputOutput,
5389 else => return error.InputOutput,
5390 }
5391 }
5392 const rc = system.fsync(fd);
5393 switch (errno(rc)) {
5394 0 => return,
5395 EBADF, EINVAL, EROFS => unreachable,
5396 EIO => return error.InputOutput,
5397 ENOSPC => return error.NoSpaceLeft,
5398 EDQUOT => return error.DiskQuota,
5399 else => |err| return std.os.unexpectedErrno(err),
5400 }
5401}
5402
5403/// Write all pending file contents for the specified file descriptor to the underlying filesystem, but not necessarily the metadata.
5404pub fn fdatasync(fd: fd_t) SyncError!void {
5405 if (std.Target.current.os.tag == .windows) {
5406 return fsync(fd) catch |err| switch (err) {
5407 SyncError.AccessDenied => return, // fdatasync doesn't promise that the access time was synced
5408 else => return err,
5409 };
5410 }
5411 const rc = system.fdatasync(fd);
5412 switch (errno(rc)) {
5413 0 => return,
5414 EBADF, EINVAL, EROFS => unreachable,
5415 EIO => return error.InputOutput,
5416 ENOSPC => return error.NoSpaceLeft,
5417 EDQUOT => return error.DiskQuota,
5418 else => |err| return std.os.unexpectedErrno(err),
5419 }
5420}
lib/std/os/bits/darwin.zig+6-2
......@@ -7,9 +7,13 @@ const std = @import("../../std.zig");
77const assert = std.debug.assert;
88const maxInt = std.math.maxInt;
99
10// See: https://opensource.apple.com/source/xnu/xnu-6153.141.1/bsd/sys/_types.h.auto.html
11// TODO: audit mode_t/pid_t, should likely be u16/i32
1012pub const fd_t = c_int;
1113pub const pid_t = c_int;
1214pub const mode_t = c_uint;
15pub const uid_t = u32;
16pub const gid_t = u32;
1317
1418pub const in_port_t = u16;
1519pub const sa_family_t = u8;
......@@ -79,8 +83,8 @@ pub const Stat = extern struct {
7983 mode: u16,
8084 nlink: u16,
8185 ino: ino_t,
82 uid: u32,
83 gid: u32,
86 uid: uid_t,
87 gid: gid_t,
8488 rdev: i32,
8589 atimesec: isize,
8690 atimensec: isize,
lib/std/os/bits/dragonfly.zig+10-3
......@@ -9,10 +9,17 @@ const maxInt = std.math.maxInt;
99pub fn S_ISCHR(m: u32) bool {
1010 return m & S_IFMT == S_IFCHR;
1111}
12
13// See:
14// - https://gitweb.dragonflybsd.org/dragonfly.git/blob/HEAD:/include/unistd.h
15// - https://gitweb.dragonflybsd.org/dragonfly.git/blob/HEAD:/sys/sys/types.h
16// TODO: mode_t should probably be changed to a u16, audit pid_t/off_t as well
1217pub const fd_t = c_int;
1318pub const pid_t = c_int;
1419pub const off_t = c_long;
1520pub const mode_t = c_uint;
21pub const uid_t = u32;
22pub const gid_t = u32;
1623
1724pub const ENOTSUP = EOPNOTSUPP;
1825pub const EWOULDBLOCK = EAGAIN;
......@@ -151,8 +158,8 @@ pub const Stat = extern struct {
151158 dev: c_uint,
152159 mode: c_ushort,
153160 padding1: u16,
154 uid: c_uint,
155 gid: c_uint,
161 uid: uid_t,
162 gid: gid_t,
156163 rdev: c_uint,
157164 atim: timespec,
158165 mtim: timespec,
......@@ -511,7 +518,7 @@ pub const siginfo_t = extern struct {
511518 si_errno: c_int,
512519 si_code: c_int,
513520 si_pid: c_int,
514 si_uid: c_uint,
521 si_uid: uid_t,
515522 si_status: c_int,
516523 si_addr: ?*c_void,
517524 si_value: union_sigval,
lib/std/os/bits/freebsd.zig+6-2
......@@ -6,8 +6,12 @@
66const std = @import("../../std.zig");
77const maxInt = std.math.maxInt;
88
9// See https://svnweb.freebsd.org/base/head/sys/sys/_types.h?view=co
10// TODO: audit pid_t/mode_t. They should likely be i32 and u16, respectively
911pub const fd_t = c_int;
1012pub const pid_t = c_int;
13pub const uid_t = u32;
14pub const gid_t = u32;
1115pub const mode_t = c_uint;
1216
1317pub const socklen_t = u32;
......@@ -128,8 +132,8 @@ pub const Stat = extern struct {
128132
129133 mode: u16,
130134 __pad0: u16,
131 uid: u32,
132 gid: u32,
135 uid: uid_t,
136 gid: gid_t,
133137 __pad1: u32,
134138 rdev: u64,
135139
lib/std/os/bits/linux.zig+4-4
......@@ -29,7 +29,7 @@ const is_mips = builtin.arch.isMIPS();
2929
3030pub const pid_t = i32;
3131pub const fd_t = i32;
32pub const uid_t = i32;
32pub const uid_t = u32;
3333pub const gid_t = u32;
3434pub const clock_t = isize;
3535
......@@ -853,7 +853,7 @@ pub const signalfd_siginfo = extern struct {
853853 errno: i32,
854854 code: i32,
855855 pid: u32,
856 uid: u32,
856 uid: uid_t,
857857 fd: i32,
858858 tid: u32,
859859 band: u32,
......@@ -1491,10 +1491,10 @@ pub const Statx = extern struct {
14911491 nlink: u32,
14921492
14931493 /// User ID of owner
1494 uid: u32,
1494 uid: uid_t,
14951495
14961496 /// Group ID of owner
1497 gid: u32,
1497 gid: gid_t,
14981498
14991499 /// File type and mode
15001500 mode: u16,
lib/std/os/bits/linux/x86_64.zig+3-2
......@@ -7,6 +7,7 @@
77const std = @import("../../../std.zig");
88const pid_t = linux.pid_t;
99const uid_t = linux.uid_t;
10const gid_t = linux.gid_t;
1011const clock_t = linux.clock_t;
1112const stack_t = linux.stack_t;
1213const sigset_t = linux.sigset_t;
......@@ -523,8 +524,8 @@ pub const Stat = extern struct {
523524 nlink: usize,
524525
525526 mode: u32,
526 uid: u32,
527 gid: u32,
527 uid: uid_t,
528 gid: gid_t,
528529 __pad0: u32,
529530 rdev: u64,
530531 size: off_t,
lib/std/os/linux.zig+56-26
......@@ -655,7 +655,7 @@ pub fn nanosleep(req: *const timespec, rem: ?*timespec) usize {
655655 return syscall2(.nanosleep, @ptrToInt(req), @ptrToInt(rem));
656656}
657657
658pub fn setuid(uid: u32) usize {
658pub fn setuid(uid: uid_t) usize {
659659 if (@hasField(SYS, "setuid32")) {
660660 return syscall1(.setuid32, uid);
661661 } else {
......@@ -663,7 +663,7 @@ pub fn setuid(uid: u32) usize {
663663 }
664664}
665665
666pub fn setgid(gid: u32) usize {
666pub fn setgid(gid: gid_t) usize {
667667 if (@hasField(SYS, "setgid32")) {
668668 return syscall1(.setgid32, gid);
669669 } else {
......@@ -671,7 +671,7 @@ pub fn setgid(gid: u32) usize {
671671 }
672672}
673673
674pub fn setreuid(ruid: u32, euid: u32) usize {
674pub fn setreuid(ruid: uid_t, euid: uid_t) usize {
675675 if (@hasField(SYS, "setreuid32")) {
676676 return syscall2(.setreuid32, ruid, euid);
677677 } else {
......@@ -679,7 +679,7 @@ pub fn setreuid(ruid: u32, euid: u32) usize {
679679 }
680680}
681681
682pub fn setregid(rgid: u32, egid: u32) usize {
682pub fn setregid(rgid: gid_t, egid: gid_t) usize {
683683 if (@hasField(SYS, "setregid32")) {
684684 return syscall2(.setregid32, rgid, egid);
685685 } else {
......@@ -687,47 +687,61 @@ pub fn setregid(rgid: u32, egid: u32) usize {
687687 }
688688}
689689
690pub fn getuid() u32 {
690pub fn getuid() uid_t {
691691 if (@hasField(SYS, "getuid32")) {
692 return @as(u32, syscall0(.getuid32));
692 return @as(uid_t, syscall0(.getuid32));
693693 } else {
694 return @as(u32, syscall0(.getuid));
694 return @as(uid_t, syscall0(.getuid));
695695 }
696696}
697697
698pub fn getgid() u32 {
698pub fn getgid() gid_t {
699699 if (@hasField(SYS, "getgid32")) {
700 return @as(u32, syscall0(.getgid32));
700 return @as(gid_t, syscall0(.getgid32));
701701 } else {
702 return @as(u32, syscall0(.getgid));
702 return @as(gid_t, syscall0(.getgid));
703703 }
704704}
705705
706pub fn geteuid() u32 {
706pub fn geteuid() uid_t {
707707 if (@hasField(SYS, "geteuid32")) {
708 return @as(u32, syscall0(.geteuid32));
708 return @as(uid_t, syscall0(.geteuid32));
709709 } else {
710 return @as(u32, syscall0(.geteuid));
710 return @as(uid_t, syscall0(.geteuid));
711711 }
712712}
713713
714pub fn getegid() u32 {
714pub fn getegid() gid_t {
715715 if (@hasField(SYS, "getegid32")) {
716 return @as(u32, syscall0(.getegid32));
716 return @as(gid_t, syscall0(.getegid32));
717717 } else {
718 return @as(u32, syscall0(.getegid));
718 return @as(gid_t, syscall0(.getegid));
719719 }
720720}
721721
722pub fn seteuid(euid: u32) usize {
723 return setreuid(std.math.maxInt(u32), euid);
722pub fn seteuid(euid: uid_t) usize {
723 // We use setresuid here instead of setreuid to ensure that the saved uid
724 // is not changed. This is what musl and recent glibc versions do as well.
725 //
726 // The setresuid(2) man page says that if -1 is passed the corresponding
727 // id will not be changed. Since uid_t is unsigned, this wraps around to the
728 // max value in C.
729 comptime assert(@typeInfo(uid_t) == .Int and !@typeInfo(uid_t).Int.is_signed);
730 return setresuid(std.math.maxInt(uid_t), euid, std.math.maxInt(uid_t));
724731}
725732
726pub fn setegid(egid: u32) usize {
727 return setregid(std.math.maxInt(u32), egid);
733pub fn setegid(egid: gid_t) usize {
734 // We use setresgid here instead of setregid to ensure that the saved uid
735 // is not changed. This is what musl and recent glibc versions do as well.
736 //
737 // The setresgid(2) man page says that if -1 is passed the corresponding
738 // id will not be changed. Since gid_t is unsigned, this wraps around to the
739 // max value in C.
740 comptime assert(@typeInfo(uid_t) == .Int and !@typeInfo(uid_t).Int.is_signed);
741 return setresgid(std.math.maxInt(gid_t), egid, std.math.maxInt(gid_t));
728742}
729743
730pub fn getresuid(ruid: *u32, euid: *u32, suid: *u32) usize {
744pub fn getresuid(ruid: *uid_t, euid: *uid_t, suid: *uid_t) usize {
731745 if (@hasField(SYS, "getresuid32")) {
732746 return syscall3(.getresuid32, @ptrToInt(ruid), @ptrToInt(euid), @ptrToInt(suid));
733747 } else {
......@@ -735,7 +749,7 @@ pub fn getresuid(ruid: *u32, euid: *u32, suid: *u32) usize {
735749 }
736750}
737751
738pub fn getresgid(rgid: *u32, egid: *u32, sgid: *u32) usize {
752pub fn getresgid(rgid: *gid_t, egid: *gid_t, sgid: *gid_t) usize {
739753 if (@hasField(SYS, "getresgid32")) {
740754 return syscall3(.getresgid32, @ptrToInt(rgid), @ptrToInt(egid), @ptrToInt(sgid));
741755 } else {
......@@ -743,7 +757,7 @@ pub fn getresgid(rgid: *u32, egid: *u32, sgid: *u32) usize {
743757 }
744758}
745759
746pub fn setresuid(ruid: u32, euid: u32, suid: u32) usize {
760pub fn setresuid(ruid: uid_t, euid: uid_t, suid: uid_t) usize {
747761 if (@hasField(SYS, "setresuid32")) {
748762 return syscall3(.setresuid32, ruid, euid, suid);
749763 } else {
......@@ -751,7 +765,7 @@ pub fn setresuid(ruid: u32, euid: u32, suid: u32) usize {
751765 }
752766}
753767
754pub fn setresgid(rgid: u32, egid: u32, sgid: u32) usize {
768pub fn setresgid(rgid: gid_t, egid: gid_t, sgid: gid_t) usize {
755769 if (@hasField(SYS, "setresgid32")) {
756770 return syscall3(.setresgid32, rgid, egid, sgid);
757771 } else {
......@@ -759,7 +773,7 @@ pub fn setresgid(rgid: u32, egid: u32, sgid: u32) usize {
759773 }
760774}
761775
762pub fn getgroups(size: usize, list: *u32) usize {
776pub fn getgroups(size: usize, list: *gid_t) usize {
763777 if (@hasField(SYS, "getgroups32")) {
764778 return syscall2(.getgroups32, size, @ptrToInt(list));
765779 } else {
......@@ -767,7 +781,7 @@ pub fn getgroups(size: usize, list: *u32) usize {
767781 }
768782}
769783
770pub fn setgroups(size: usize, list: *const u32) usize {
784pub fn setgroups(size: usize, list: *const gid_t) usize {
771785 if (@hasField(SYS, "setgroups32")) {
772786 return syscall2(.setgroups32, size, @ptrToInt(list));
773787 } else {
......@@ -1226,6 +1240,22 @@ pub fn bpf(cmd: BPF.Cmd, attr: *BPF.Attr, size: u32) usize {
12261240 return syscall3(.bpf, @enumToInt(cmd), @ptrToInt(attr), size);
12271241}
12281242
1243pub fn sync() void {
1244 _ = syscall0(.sync);
1245}
1246
1247pub fn syncfs(fd: fd_t) usize {
1248 return syscall1(.syncfs, @bitCast(usize, @as(isize, fd)));
1249}
1250
1251pub fn fsync(fd: fd_t) usize {
1252 return syscall1(.fsync, @bitCast(usize, @as(isize, fd)));
1253}
1254
1255pub fn fdatasync(fd: fd_t) usize {
1256 return syscall1(.fdatasync, @bitCast(usize, @as(isize, fd)));
1257}
1258
12291259test "" {
12301260 if (builtin.os.tag == .linux) {
12311261 _ = @import("linux/test.zig");
lib/std/os/test.zig+36
......@@ -555,3 +555,39 @@ test "signalfd" {
555555 return error.SkipZigTest;
556556 _ = std.os.signalfd;
557557}
558
559test "sync" {
560 if (builtin.os.tag != .linux)
561 return error.SkipZigTest;
562
563 var tmp = tmpDir(.{});
564 defer tmp.cleanup();
565
566 const test_out_file = "os_tmp_test";
567 const file = try tmp.dir.createFile(test_out_file, .{});
568 defer {
569 file.close();
570 tmp.dir.deleteFile(test_out_file) catch {};
571 }
572
573 os.sync();
574 try os.syncfs(file.handle);
575}
576
577test "fsync" {
578 if (builtin.os.tag != .linux and builtin.os.tag != .windows)
579 return error.SkipZigTest;
580
581 var tmp = tmpDir(.{});
582 defer tmp.cleanup();
583
584 const test_out_file = "os_tmp_test";
585 const file = try tmp.dir.createFile(test_out_file, .{});
586 defer {
587 file.close();
588 tmp.dir.deleteFile(test_out_file) catch {};
589 }
590
591 try os.fsync(file.handle);
592 try os.fdatasync(file.handle);
593}
lib/std/os/windows/kernel32.zig+2
......@@ -287,3 +287,5 @@ pub extern "kernel32" fn K32GetWsChangesEx(hProcess: HANDLE, lpWatchInfoEx: PPSA
287287pub extern "kernel32" fn K32InitializeProcessForWsWatch(hProcess: HANDLE) callconv(.Stdcall) BOOL;
288288pub extern "kernel32" fn K32QueryWorkingSet(hProcess: HANDLE, pv: PVOID, cb: DWORD) callconv(.Stdcall) BOOL;
289289pub extern "kernel32" fn K32QueryWorkingSetEx(hProcess: HANDLE, pv: PVOID, cb: DWORD) callconv(.Stdcall) BOOL;
290
291pub extern "kernel32" fn FlushFileBuffers(hFile: HANDLE) callconv(.Stdcall) BOOL;
lib/std/process.zig+4-4
......@@ -578,8 +578,8 @@ fn testWindowsCmdLine(input_cmd_line: [*]const u8, expected_args: []const []cons
578578}
579579
580580pub const UserInfo = struct {
581 uid: u32,
582 gid: u32,
581 uid: os.uid_t,
582 gid: os.gid_t,
583583};
584584
585585/// POSIX function which gets a uid from username.
......@@ -607,8 +607,8 @@ pub fn posixGetUserInfo(name: []const u8) !UserInfo {
607607 var buf: [std.mem.page_size]u8 = undefined;
608608 var name_index: usize = 0;
609609 var state = State.Start;
610 var uid: u32 = 0;
611 var gid: u32 = 0;
610 var uid: os.uid_t = 0;
611 var gid: os.gid_t = 0;
612612
613613 while (true) {
614614 const amt_read = try reader.read(buf[0..]);
lib/std/progress.zig+3-3
......@@ -197,7 +197,7 @@ pub const Progress = struct {
197197 var maybe_node: ?*Node = &self.root;
198198 while (maybe_node) |node| {
199199 if (need_ellipse) {
200 self.bufWrite(&end, "...", .{});
200 self.bufWrite(&end, "... ", .{});
201201 }
202202 need_ellipse = false;
203203 if (node.name.len != 0 or node.estimated_total_items != null) {
......@@ -218,7 +218,7 @@ pub const Progress = struct {
218218 maybe_node = node.recently_updated_child;
219219 }
220220 if (need_ellipse) {
221 self.bufWrite(&end, "...", .{});
221 self.bufWrite(&end, "... ", .{});
222222 }
223223 }
224224
......@@ -253,7 +253,7 @@ pub const Progress = struct {
253253 const bytes_needed_for_esc_codes_at_end = if (std.builtin.os.tag == .windows) 0 else 11;
254254 const max_end = self.output_buffer.len - bytes_needed_for_esc_codes_at_end;
255255 if (end.* > max_end) {
256 const suffix = "...";
256 const suffix = "... ";
257257 self.columns_written = self.columns_written - (end.* - max_end) + suffix.len;
258258 std.mem.copy(u8, self.output_buffer[max_end..], suffix);
259259 end.* = max_end + suffix.len;
lib/std/special/test_runner.zig+1-1
......@@ -40,7 +40,7 @@ pub fn main() anyerror!void {
4040 test_node.activate();
4141 progress.refresh();
4242 if (progress.terminal == null) {
43 std.debug.print("{}/{} {}...", .{ i + 1, test_fn_list.len, test_fn.name });
43 std.debug.print("{}/{} {}... ", .{ i + 1, test_fn_list.len, test_fn.name });
4444 }
4545 const result = if (test_fn.async_frame_size) |size| switch (io_mode) {
4646 .evented => blk: {
lib/std/std.zig+7
......@@ -3,11 +3,15 @@
33// This file is part of [zig](https://ziglang.org/), which is MIT licensed.
44// The MIT license requires this copyright notice to be included in all copies
55// and substantial portions of the software.
6pub const ArrayHashMap = array_hash_map.ArrayHashMap;
7pub const ArrayHashMapUnmanaged = array_hash_map.ArrayHashMapUnmanaged;
68pub const ArrayList = @import("array_list.zig").ArrayList;
79pub const ArrayListAligned = @import("array_list.zig").ArrayListAligned;
810pub const ArrayListAlignedUnmanaged = @import("array_list.zig").ArrayListAlignedUnmanaged;
911pub const ArrayListSentineled = @import("array_list_sentineled.zig").ArrayListSentineled;
1012pub const ArrayListUnmanaged = @import("array_list.zig").ArrayListUnmanaged;
13pub const AutoArrayHashMap = array_hash_map.AutoArrayHashMap;
14pub const AutoArrayHashMapUnmanaged = array_hash_map.AutoArrayHashMapUnmanaged;
1115pub const AutoHashMap = hash_map.AutoHashMap;
1216pub const AutoHashMapUnmanaged = hash_map.AutoHashMapUnmanaged;
1317pub const BloomFilter = @import("bloom_filter.zig").BloomFilter;
......@@ -32,10 +36,13 @@ pub const SinglyLinkedList = @import("linked_list.zig").SinglyLinkedList;
3236pub const SpinLock = @import("spinlock.zig").SpinLock;
3337pub const StringHashMap = hash_map.StringHashMap;
3438pub const StringHashMapUnmanaged = hash_map.StringHashMapUnmanaged;
39pub const StringArrayHashMap = array_hash_map.StringArrayHashMap;
40pub const StringArrayHashMapUnmanaged = array_hash_map.StringArrayHashMapUnmanaged;
3541pub const TailQueue = @import("linked_list.zig").TailQueue;
3642pub const Target = @import("target.zig").Target;
3743pub const Thread = @import("thread.zig").Thread;
3844
45pub const array_hash_map = @import("array_hash_map.zig");
3946pub const atomic = @import("atomic.zig");
4047pub const base64 = @import("base64.zig");
4148pub const build = @import("build.zig");
lib/std/target.zig+1-1
......@@ -101,7 +101,7 @@ pub const Target = struct {
101101
102102 /// Latest Windows version that the Zig Standard Library is aware of
103103 pub const latest = WindowsVersion.win10_20h1;
104
104
105105 pub const Range = struct {
106106 min: WindowsVersion,
107107 max: WindowsVersion,
lib/std/zig/parser_test.zig+114-6
......@@ -615,6 +615,17 @@ test "zig fmt: infix operator and then multiline string literal" {
615615 );
616616}
617617
618test "zig fmt: infix operator and then multiline string literal" {
619 try testCanonical(
620 \\const x = "" ++
621 \\ \\ hi0
622 \\ \\ hi1
623 \\ \\ hi2
624 \\;
625 \\
626 );
627}
628
618629test "zig fmt: C pointers" {
619630 try testCanonical(
620631 \\const Ptr = [*c]i32;
......@@ -885,6 +896,28 @@ test "zig fmt: 2nd arg multiline string" {
885896 );
886897}
887898
899test "zig fmt: 2nd arg multiline string many args" {
900 try testCanonical(
901 \\comptime {
902 \\ cases.addAsm("hello world linux x86_64",
903 \\ \\.text
904 \\ , "Hello, world!\n", "Hello, world!\n");
905 \\}
906 \\
907 );
908}
909
910test "zig fmt: final arg multiline string" {
911 try testCanonical(
912 \\comptime {
913 \\ cases.addAsm("hello world linux x86_64", "Hello, world!\n",
914 \\ \\.text
915 \\ );
916 \\}
917 \\
918 );
919}
920
888921test "zig fmt: if condition wraps" {
889922 try testTransform(
890923 \\comptime {
......@@ -915,6 +948,11 @@ test "zig fmt: if condition wraps" {
915948 \\ var a = if (a) |*f| x: {
916949 \\ break :x &a.b;
917950 \\ } else |err| err;
951 \\ var a = if (cond and
952 \\ cond) |*f|
953 \\ x: {
954 \\ break :x &a.b;
955 \\ } else |err| err;
918956 \\}
919957 ,
920958 \\comptime {
......@@ -951,6 +989,35 @@ test "zig fmt: if condition wraps" {
951989 \\ var a = if (a) |*f| x: {
952990 \\ break :x &a.b;
953991 \\ } else |err| err;
992 \\ var a = if (cond and
993 \\ cond) |*f|
994 \\ x: {
995 \\ break :x &a.b;
996 \\ } else |err| err;
997 \\}
998 \\
999 );
1000}
1001
1002test "zig fmt: if condition has line break but must not wrap" {
1003 try testCanonical(
1004 \\comptime {
1005 \\ if (self.user_input_options.put(
1006 \\ name,
1007 \\ UserInputOption{
1008 \\ .name = name,
1009 \\ .used = false,
1010 \\ },
1011 \\ ) catch unreachable) |*prev_value| {
1012 \\ foo();
1013 \\ bar();
1014 \\ }
1015 \\ if (put(
1016 \\ a,
1017 \\ b,
1018 \\ )) {
1019 \\ foo();
1020 \\ }
9541021 \\}
9551022 \\
9561023 );
......@@ -977,6 +1044,18 @@ test "zig fmt: if condition has line break but must not wrap" {
9771044 );
9781045}
9791046
1047test "zig fmt: function call with multiline argument" {
1048 try testCanonical(
1049 \\comptime {
1050 \\ self.user_input_options.put(name, UserInputOption{
1051 \\ .name = name,
1052 \\ .used = false,
1053 \\ });
1054 \\}
1055 \\
1056 );
1057}
1058
9801059test "zig fmt: same-line doc comment on variable declaration" {
9811060 try testTransform(
9821061 \\pub const MAP_ANONYMOUS = 0x1000; /// allocated from memory, swap space
......@@ -1228,7 +1307,7 @@ test "zig fmt: array literal with hint" {
12281307 \\const a = []u8{
12291308 \\ 1, 2,
12301309 \\ 3, //
1231 \\ 4,
1310 \\ 4,
12321311 \\ 5, 6,
12331312 \\ 7,
12341313 \\};
......@@ -1293,7 +1372,7 @@ test "zig fmt: multiline string parameter in fn call with trailing comma" {
12931372 \\ \\ZIG_C_HEADER_FILES {}
12941373 \\ \\ZIG_DIA_GUIDS_LIB {}
12951374 \\ \\
1296 \\ ,
1375 \\ ,
12971376 \\ std.cstr.toSliceConst(c.ZIG_CMAKE_BINARY_DIR),
12981377 \\ std.cstr.toSliceConst(c.ZIG_CXX_COMPILER),
12991378 \\ std.cstr.toSliceConst(c.ZIG_DIA_GUIDS_LIB),
......@@ -2885,20 +2964,20 @@ test "zig fmt: multiline string in array" {
28852964 try testCanonical(
28862965 \\const Foo = [][]const u8{
28872966 \\ \\aaa
2888 \\,
2967 \\ ,
28892968 \\ \\bbb
28902969 \\};
28912970 \\
28922971 \\fn bar() void {
28932972 \\ const Foo = [][]const u8{
28942973 \\ \\aaa
2895 \\ ,
2974 \\ ,
28962975 \\ \\bbb
28972976 \\ };
28982977 \\ const Bar = [][]const u8{ // comment here
28992978 \\ \\aaa
29002979 \\ \\
2901 \\ , // and another comment can go here
2980 \\ , // and another comment can go here
29022981 \\ \\bbb
29032982 \\ };
29042983 \\}
......@@ -3214,6 +3293,34 @@ test "zig fmt: C var args" {
32143293 );
32153294}
32163295
3296test "zig fmt: Only indent multiline string literals in function calls" {
3297 try testCanonical(
3298 \\test "zig fmt:" {
3299 \\ try testTransform(
3300 \\ \\const X = struct {
3301 \\ \\ foo: i32, bar: i8 };
3302 \\ ,
3303 \\ \\const X = struct {
3304 \\ \\ foo: i32, bar: i8
3305 \\ \\};
3306 \\ \\
3307 \\ );
3308 \\}
3309 \\
3310 );
3311}
3312
3313test "zig fmt: Don't add extra newline after if" {
3314 try testCanonical(
3315 \\pub fn atomicSymLink(allocator: *Allocator, existing_path: []const u8, new_path: []const u8) !void {
3316 \\ if (cwd().symLink(existing_path, new_path, .{})) {
3317 \\ return;
3318 \\ }
3319 \\}
3320 \\
3321 );
3322}
3323
32173324const std = @import("std");
32183325const mem = std.mem;
32193326const warn = std.debug.warn;
......@@ -3256,7 +3363,8 @@ fn testParse(source: []const u8, allocator: *mem.Allocator, anything_changed: *b
32563363 var buffer = std.ArrayList(u8).init(allocator);
32573364 errdefer buffer.deinit();
32583365
3259 anything_changed.* = try std.zig.render(allocator, buffer.outStream(), tree);
3366 const outStream = buffer.outStream();
3367 anything_changed.* = try std.zig.render(allocator, outStream, tree);
32603368 return buffer.toOwnedSlice();
32613369}
32623370fn testTransform(source: []const u8, expected_source: []const u8) !void {
lib/std/zig/render.zig+763-912
......@@ -6,10 +6,12 @@
66const std = @import("../std.zig");
77const assert = std.debug.assert;
88const mem = std.mem;
9const meta = std.meta;
910const ast = std.zig.ast;
1011const Token = std.zig.Token;
1112
1213const indent_delta = 4;
14const asm_indent_delta = 2;
1315
1416pub const Error = error{
1517 /// Ran out of memory allocating call stack frames to complete rendering.
......@@ -21,70 +23,32 @@ pub fn render(allocator: *mem.Allocator, stream: anytype, tree: *ast.Tree) (@Typ
2123 // cannot render an invalid tree
2224 std.debug.assert(tree.errors.len == 0);
2325
24 // make a passthrough stream that checks whether something changed
25 const MyStream = struct {
26 const MyStream = @This();
27 const StreamError = @TypeOf(stream).Error;
28
29 child_stream: @TypeOf(stream),
30 anything_changed: bool,
31 source_index: usize,
32 source: []const u8,
33
34 fn write(self: *MyStream, bytes: []const u8) StreamError!usize {
35 if (!self.anything_changed) {
36 const end = self.source_index + bytes.len;
37 if (end > self.source.len) {
38 self.anything_changed = true;
39 } else {
40 const src_slice = self.source[self.source_index..end];
41 self.source_index += bytes.len;
42 if (!mem.eql(u8, bytes, src_slice)) {
43 self.anything_changed = true;
44 }
45 }
46 }
47
48 return self.child_stream.write(bytes);
49 }
50 };
51 var my_stream = MyStream{
52 .child_stream = stream,
53 .anything_changed = false,
54 .source_index = 0,
55 .source = tree.source,
56 };
57 const my_stream_stream: std.io.Writer(*MyStream, MyStream.StreamError, MyStream.write) = .{
58 .context = &my_stream,
59 };
26 var change_detection_stream = std.io.changeDetectionStream(tree.source, stream);
27 var auto_indenting_stream = std.io.autoIndentingStream(indent_delta, change_detection_stream.writer());
6028
61 try renderRoot(allocator, my_stream_stream, tree);
29 try renderRoot(allocator, &auto_indenting_stream, tree);
6230
63 if (my_stream.source_index != my_stream.source.len) {
64 my_stream.anything_changed = true;
65 }
66
67 return my_stream.anything_changed;
31 return change_detection_stream.changeDetected();
6832}
6933
7034fn renderRoot(
7135 allocator: *mem.Allocator,
72 stream: anytype,
36 ais: anytype,
7337 tree: *ast.Tree,
74) (@TypeOf(stream).Error || Error)!void {
38) (@TypeOf(ais.*).Error || Error)!void {
39
7540 // render all the line comments at the beginning of the file
7641 for (tree.token_ids) |token_id, i| {
7742 if (token_id != .LineComment) break;
7843 const token_loc = tree.token_locs[i];
79 try stream.print("{}\n", .{mem.trimRight(u8, tree.tokenSliceLoc(token_loc), " ")});
44 try ais.writer().print("{}\n", .{mem.trimRight(u8, tree.tokenSliceLoc(token_loc), " ")});
8045 const next_token = tree.token_locs[i + 1];
8146 const loc = tree.tokenLocationLoc(token_loc.end, next_token);
8247 if (loc.line >= 2) {
83 try stream.writeByte('\n');
48 try ais.insertNewline();
8449 }
8550 }
8651
87 var start_col: usize = 0;
8852 var decl_i: ast.NodeIndex = 0;
8953 const root_decls = tree.root_node.decls();
9054
......@@ -145,7 +109,7 @@ fn renderRoot(
145109 // If there's no next reformatted `decl`, just copy the
146110 // remaining input tokens and bail out.
147111 const start = tree.token_locs[copy_start_token_index].start;
148 try copyFixingWhitespace(stream, tree.source[start..]);
112 try copyFixingWhitespace(ais, tree.source[start..]);
149113 return;
150114 }
151115 decl = root_decls[decl_i];
......@@ -186,26 +150,25 @@ fn renderRoot(
186150
187151 const start = tree.token_locs[copy_start_token_index].start;
188152 const end = tree.token_locs[copy_end_token_index].start;
189 try copyFixingWhitespace(stream, tree.source[start..end]);
153 try copyFixingWhitespace(ais, tree.source[start..end]);
190154 }
191155
192 try renderTopLevelDecl(allocator, stream, tree, 0, &start_col, decl);
156 try renderTopLevelDecl(allocator, ais, tree, decl);
193157 decl_i += 1;
194158 if (decl_i >= root_decls.len) return;
195 try renderExtraNewline(tree, stream, &start_col, root_decls[decl_i]);
159 try renderExtraNewline(tree, ais, root_decls[decl_i]);
196160 }
197161}
198162
199fn renderExtraNewline(tree: *ast.Tree, stream: anytype, start_col: *usize, node: *ast.Node) @TypeOf(stream).Error!void {
200 return renderExtraNewlineToken(tree, stream, start_col, node.firstToken());
163fn renderExtraNewline(tree: *ast.Tree, ais: anytype, node: *ast.Node) @TypeOf(ais.*).Error!void {
164 return renderExtraNewlineToken(tree, ais, node.firstToken());
201165}
202166
203167fn renderExtraNewlineToken(
204168 tree: *ast.Tree,
205 stream: anytype,
206 start_col: *usize,
169 ais: anytype,
207170 first_token: ast.TokenIndex,
208) @TypeOf(stream).Error!void {
171) @TypeOf(ais.*).Error!void {
209172 var prev_token = first_token;
210173 if (prev_token == 0) return;
211174 var newline_threshold: usize = 2;
......@@ -218,28 +181,27 @@ fn renderExtraNewlineToken(
218181 const prev_token_end = tree.token_locs[prev_token - 1].end;
219182 const loc = tree.tokenLocation(prev_token_end, first_token);
220183 if (loc.line >= newline_threshold) {
221 try stream.writeByte('\n');
222 start_col.* = 0;
184 try ais.insertNewline();
223185 }
224186}
225187
226fn renderTopLevelDecl(allocator: *mem.Allocator, stream: anytype, tree: *ast.Tree, indent: usize, start_col: *usize, decl: *ast.Node) (@TypeOf(stream).Error || Error)!void {
227 try renderContainerDecl(allocator, stream, tree, indent, start_col, decl, .Newline);
188fn renderTopLevelDecl(allocator: *mem.Allocator, ais: anytype, tree: *ast.Tree, decl: *ast.Node) (@TypeOf(ais.*).Error || Error)!void {
189 try renderContainerDecl(allocator, ais, tree, decl, .Newline);
228190}
229191
230fn renderContainerDecl(allocator: *mem.Allocator, stream: anytype, tree: *ast.Tree, indent: usize, start_col: *usize, decl: *ast.Node, space: Space) (@TypeOf(stream).Error || Error)!void {
192fn renderContainerDecl(allocator: *mem.Allocator, ais: anytype, tree: *ast.Tree, decl: *ast.Node, space: Space) (@TypeOf(ais.*).Error || Error)!void {
231193 switch (decl.tag) {
232194 .FnProto => {
233195 const fn_proto = @fieldParentPtr(ast.Node.FnProto, "base", decl);
234196
235 try renderDocComments(tree, stream, fn_proto, fn_proto.getDocComments(), indent, start_col);
197 try renderDocComments(tree, ais, fn_proto, fn_proto.getDocComments());
236198
237199 if (fn_proto.getBodyNode()) |body_node| {
238 try renderExpression(allocator, stream, tree, indent, start_col, decl, .Space);
239 try renderExpression(allocator, stream, tree, indent, start_col, body_node, space);
200 try renderExpression(allocator, ais, tree, decl, .Space);
201 try renderExpression(allocator, ais, tree, body_node, space);
240202 } else {
241 try renderExpression(allocator, stream, tree, indent, start_col, decl, .None);
242 try renderToken(tree, stream, tree.nextToken(decl.lastToken()), indent, start_col, space);
203 try renderExpression(allocator, ais, tree, decl, .None);
204 try renderToken(tree, ais, tree.nextToken(decl.lastToken()), space);
243205 }
244206 },
245207
......@@ -247,35 +209,35 @@ fn renderContainerDecl(allocator: *mem.Allocator, stream: anytype, tree: *ast.Tr
247209 const use_decl = @fieldParentPtr(ast.Node.Use, "base", decl);
248210
249211 if (use_decl.visib_token) |visib_token| {
250 try renderToken(tree, stream, visib_token, indent, start_col, .Space); // pub
212 try renderToken(tree, ais, visib_token, .Space); // pub
251213 }
252 try renderToken(tree, stream, use_decl.use_token, indent, start_col, .Space); // usingnamespace
253 try renderExpression(allocator, stream, tree, indent, start_col, use_decl.expr, .None);
254 try renderToken(tree, stream, use_decl.semicolon_token, indent, start_col, space); // ;
214 try renderToken(tree, ais, use_decl.use_token, .Space); // usingnamespace
215 try renderExpression(allocator, ais, tree, use_decl.expr, .None);
216 try renderToken(tree, ais, use_decl.semicolon_token, space); // ;
255217 },
256218
257219 .VarDecl => {
258220 const var_decl = @fieldParentPtr(ast.Node.VarDecl, "base", decl);
259221
260 try renderDocComments(tree, stream, var_decl, var_decl.getDocComments(), indent, start_col);
261 try renderVarDecl(allocator, stream, tree, indent, start_col, var_decl);
222 try renderDocComments(tree, ais, var_decl, var_decl.getDocComments());
223 try renderVarDecl(allocator, ais, tree, var_decl);
262224 },
263225
264226 .TestDecl => {
265227 const test_decl = @fieldParentPtr(ast.Node.TestDecl, "base", decl);
266228
267 try renderDocComments(tree, stream, test_decl, test_decl.doc_comments, indent, start_col);
268 try renderToken(tree, stream, test_decl.test_token, indent, start_col, .Space);
269 try renderExpression(allocator, stream, tree, indent, start_col, test_decl.name, .Space);
270 try renderExpression(allocator, stream, tree, indent, start_col, test_decl.body_node, space);
229 try renderDocComments(tree, ais, test_decl, test_decl.doc_comments);
230 try renderToken(tree, ais, test_decl.test_token, .Space);
231 try renderExpression(allocator, ais, tree, test_decl.name, .Space);
232 try renderExpression(allocator, ais, tree, test_decl.body_node, space);
271233 },
272234
273235 .ContainerField => {
274236 const field = @fieldParentPtr(ast.Node.ContainerField, "base", decl);
275237
276 try renderDocComments(tree, stream, field, field.doc_comments, indent, start_col);
238 try renderDocComments(tree, ais, field, field.doc_comments);
277239 if (field.comptime_token) |t| {
278 try renderToken(tree, stream, t, indent, start_col, .Space); // comptime
240 try renderToken(tree, ais, t, .Space); // comptime
279241 }
280242
281243 const src_has_trailing_comma = blk: {
......@@ -288,68 +250,67 @@ fn renderContainerDecl(allocator: *mem.Allocator, stream: anytype, tree: *ast.Tr
288250 const last_token_space: Space = if (src_has_trailing_comma) .None else space;
289251
290252 if (field.type_expr == null and field.value_expr == null) {
291 try renderToken(tree, stream, field.name_token, indent, start_col, last_token_space); // name
253 try renderToken(tree, ais, field.name_token, last_token_space); // name
292254 } else if (field.type_expr != null and field.value_expr == null) {
293 try renderToken(tree, stream, field.name_token, indent, start_col, .None); // name
294 try renderToken(tree, stream, tree.nextToken(field.name_token), indent, start_col, .Space); // :
255 try renderToken(tree, ais, field.name_token, .None); // name
256 try renderToken(tree, ais, tree.nextToken(field.name_token), .Space); // :
295257
296258 if (field.align_expr) |align_value_expr| {
297 try renderExpression(allocator, stream, tree, indent, start_col, field.type_expr.?, .Space); // type
259 try renderExpression(allocator, ais, tree, field.type_expr.?, .Space); // type
298260 const lparen_token = tree.prevToken(align_value_expr.firstToken());
299261 const align_kw = tree.prevToken(lparen_token);
300262 const rparen_token = tree.nextToken(align_value_expr.lastToken());
301 try renderToken(tree, stream, align_kw, indent, start_col, .None); // align
302 try renderToken(tree, stream, lparen_token, indent, start_col, .None); // (
303 try renderExpression(allocator, stream, tree, indent, start_col, align_value_expr, .None); // alignment
304 try renderToken(tree, stream, rparen_token, indent, start_col, last_token_space); // )
263 try renderToken(tree, ais, align_kw, .None); // align
264 try renderToken(tree, ais, lparen_token, .None); // (
265 try renderExpression(allocator, ais, tree, align_value_expr, .None); // alignment
266 try renderToken(tree, ais, rparen_token, last_token_space); // )
305267 } else {
306 try renderExpression(allocator, stream, tree, indent, start_col, field.type_expr.?, last_token_space); // type
268 try renderExpression(allocator, ais, tree, field.type_expr.?, last_token_space); // type
307269 }
308270 } else if (field.type_expr == null and field.value_expr != null) {
309 try renderToken(tree, stream, field.name_token, indent, start_col, .Space); // name
310 try renderToken(tree, stream, tree.nextToken(field.name_token), indent, start_col, .Space); // =
311 try renderExpression(allocator, stream, tree, indent, start_col, field.value_expr.?, last_token_space); // value
271 try renderToken(tree, ais, field.name_token, .Space); // name
272 try renderToken(tree, ais, tree.nextToken(field.name_token), .Space); // =
273 try renderExpression(allocator, ais, tree, field.value_expr.?, last_token_space); // value
312274 } else {
313 try renderToken(tree, stream, field.name_token, indent, start_col, .None); // name
314 try renderToken(tree, stream, tree.nextToken(field.name_token), indent, start_col, .Space); // :
275 try renderToken(tree, ais, field.name_token, .None); // name
276 try renderToken(tree, ais, tree.nextToken(field.name_token), .Space); // :
315277
316278 if (field.align_expr) |align_value_expr| {
317 try renderExpression(allocator, stream, tree, indent, start_col, field.type_expr.?, .Space); // type
279 try renderExpression(allocator, ais, tree, field.type_expr.?, .Space); // type
318280 const lparen_token = tree.prevToken(align_value_expr.firstToken());
319281 const align_kw = tree.prevToken(lparen_token);
320282 const rparen_token = tree.nextToken(align_value_expr.lastToken());
321 try renderToken(tree, stream, align_kw, indent, start_col, .None); // align
322 try renderToken(tree, stream, lparen_token, indent, start_col, .None); // (
323 try renderExpression(allocator, stream, tree, indent, start_col, align_value_expr, .None); // alignment
324 try renderToken(tree, stream, rparen_token, indent, start_col, .Space); // )
283 try renderToken(tree, ais, align_kw, .None); // align
284 try renderToken(tree, ais, lparen_token, .None); // (
285 try renderExpression(allocator, ais, tree, align_value_expr, .None); // alignment
286 try renderToken(tree, ais, rparen_token, .Space); // )
325287 } else {
326 try renderExpression(allocator, stream, tree, indent, start_col, field.type_expr.?, .Space); // type
288 try renderExpression(allocator, ais, tree, field.type_expr.?, .Space); // type
327289 }
328 try renderToken(tree, stream, tree.prevToken(field.value_expr.?.firstToken()), indent, start_col, .Space); // =
329 try renderExpression(allocator, stream, tree, indent, start_col, field.value_expr.?, last_token_space); // value
290 try renderToken(tree, ais, tree.prevToken(field.value_expr.?.firstToken()), .Space); // =
291 try renderExpression(allocator, ais, tree, field.value_expr.?, last_token_space); // value
330292 }
331293
332294 if (src_has_trailing_comma) {
333295 const comma = tree.nextToken(field.lastToken());
334 try renderToken(tree, stream, comma, indent, start_col, space);
296 try renderToken(tree, ais, comma, space);
335297 }
336298 },
337299
338300 .Comptime => {
339301 assert(!decl.requireSemiColon());
340 try renderExpression(allocator, stream, tree, indent, start_col, decl, space);
302 try renderExpression(allocator, ais, tree, decl, space);
341303 },
342304
343305 .DocComment => {
344306 const comment = @fieldParentPtr(ast.Node.DocComment, "base", decl);
345307 const kind = tree.token_ids[comment.first_line];
346 try renderToken(tree, stream, comment.first_line, indent, start_col, .Newline);
308 try renderToken(tree, ais, comment.first_line, .Newline);
347309 var tok_i = comment.first_line + 1;
348310 while (true) : (tok_i += 1) {
349311 const tok_id = tree.token_ids[tok_i];
350312 if (tok_id == kind) {
351 try stream.writeByteNTimes(' ', indent);
352 try renderToken(tree, stream, tok_i, indent, start_col, .Newline);
313 try renderToken(tree, ais, tok_i, .Newline);
353314 } else if (tok_id == .LineComment) {
354315 continue;
355316 } else {
......@@ -363,13 +324,11 @@ fn renderContainerDecl(allocator: *mem.Allocator, stream: anytype, tree: *ast.Tr
363324
364325fn renderExpression(
365326 allocator: *mem.Allocator,
366 stream: anytype,
327 ais: anytype,
367328 tree: *ast.Tree,
368 indent: usize,
369 start_col: *usize,
370329 base: *ast.Node,
371330 space: Space,
372) (@TypeOf(stream).Error || Error)!void {
331) (@TypeOf(ais.*).Error || Error)!void {
373332 switch (base.tag) {
374333 .Identifier,
375334 .IntegerLiteral,
......@@ -383,18 +342,18 @@ fn renderExpression(
383342 .UndefinedLiteral,
384343 => {
385344 const casted_node = base.cast(ast.Node.OneToken).?;
386 return renderToken(tree, stream, casted_node.token, indent, start_col, space);
345 return renderToken(tree, ais, casted_node.token, space);
387346 },
388347
389348 .AnyType => {
390349 const any_type = base.castTag(.AnyType).?;
391350 if (mem.eql(u8, tree.tokenSlice(any_type.token), "var")) {
392351 // TODO remove in next release cycle
393 try stream.writeAll("anytype");
394 if (space == .Comma) try stream.writeAll(",\n");
352 try ais.writer().writeAll("anytype");
353 if (space == .Comma) try ais.writer().writeAll(",\n");
395354 return;
396355 }
397 return renderToken(tree, stream, any_type.token, indent, start_col, space);
356 return renderToken(tree, ais, any_type.token, space);
398357 },
399358
400359 .Block, .LabeledBlock => {
......@@ -424,65 +383,65 @@ fn renderExpression(
424383 };
425384
426385 if (block.label) |label| {
427 try renderToken(tree, stream, label, indent, start_col, Space.None);
428 try renderToken(tree, stream, tree.nextToken(label), indent, start_col, Space.Space);
386 try renderToken(tree, ais, label, Space.None);
387 try renderToken(tree, ais, tree.nextToken(label), Space.Space);
429388 }
430389
431390 if (block.statements.len == 0) {
432 try renderToken(tree, stream, block.lbrace, indent + indent_delta, start_col, Space.None);
433 return renderToken(tree, stream, block.rbrace, indent, start_col, space);
391 ais.pushIndentNextLine();
392 defer ais.popIndent();
393 try renderToken(tree, ais, block.lbrace, Space.None);
434394 } else {
435 const block_indent = indent + indent_delta;
436 try renderToken(tree, stream, block.lbrace, block_indent, start_col, Space.Newline);
395 ais.pushIndentNextLine();
396 defer ais.popIndent();
397
398 try renderToken(tree, ais, block.lbrace, Space.Newline);
437399
438400 for (block.statements) |statement, i| {
439 try stream.writeByteNTimes(' ', block_indent);
440 try renderStatement(allocator, stream, tree, block_indent, start_col, statement);
401 try renderStatement(allocator, ais, tree, statement);
441402
442403 if (i + 1 < block.statements.len) {
443 try renderExtraNewline(tree, stream, start_col, block.statements[i + 1]);
404 try renderExtraNewline(tree, ais, block.statements[i + 1]);
444405 }
445406 }
446
447 try stream.writeByteNTimes(' ', indent);
448 return renderToken(tree, stream, block.rbrace, indent, start_col, space);
449407 }
408 return renderToken(tree, ais, block.rbrace, space);
450409 },
451410
452411 .Defer => {
453412 const defer_node = @fieldParentPtr(ast.Node.Defer, "base", base);
454413
455 try renderToken(tree, stream, defer_node.defer_token, indent, start_col, Space.Space);
414 try renderToken(tree, ais, defer_node.defer_token, Space.Space);
456415 if (defer_node.payload) |payload| {
457 try renderExpression(allocator, stream, tree, indent, start_col, payload, Space.Space);
416 try renderExpression(allocator, ais, tree, payload, Space.Space);
458417 }
459 return renderExpression(allocator, stream, tree, indent, start_col, defer_node.expr, space);
418 return renderExpression(allocator, ais, tree, defer_node.expr, space);
460419 },
461420 .Comptime => {
462421 const comptime_node = @fieldParentPtr(ast.Node.Comptime, "base", base);
463422
464 try renderToken(tree, stream, comptime_node.comptime_token, indent, start_col, Space.Space);
465 return renderExpression(allocator, stream, tree, indent, start_col, comptime_node.expr, space);
423 try renderToken(tree, ais, comptime_node.comptime_token, Space.Space);
424 return renderExpression(allocator, ais, tree, comptime_node.expr, space);
466425 },
467426 .Nosuspend => {
468427 const nosuspend_node = @fieldParentPtr(ast.Node.Nosuspend, "base", base);
469428 if (mem.eql(u8, tree.tokenSlice(nosuspend_node.nosuspend_token), "noasync")) {
470429 // TODO: remove this
471 try stream.writeAll("nosuspend ");
430 try ais.writer().writeAll("nosuspend ");
472431 } else {
473 try renderToken(tree, stream, nosuspend_node.nosuspend_token, indent, start_col, Space.Space);
432 try renderToken(tree, ais, nosuspend_node.nosuspend_token, Space.Space);
474433 }
475 return renderExpression(allocator, stream, tree, indent, start_col, nosuspend_node.expr, space);
434 return renderExpression(allocator, ais, tree, nosuspend_node.expr, space);
476435 },
477436
478437 .Suspend => {
479438 const suspend_node = @fieldParentPtr(ast.Node.Suspend, "base", base);
480439
481440 if (suspend_node.body) |body| {
482 try renderToken(tree, stream, suspend_node.suspend_token, indent, start_col, Space.Space);
483 return renderExpression(allocator, stream, tree, indent, start_col, body, space);
441 try renderToken(tree, ais, suspend_node.suspend_token, Space.Space);
442 return renderExpression(allocator, ais, tree, body, space);
484443 } else {
485 return renderToken(tree, stream, suspend_node.suspend_token, indent, start_col, space);
444 return renderToken(tree, ais, suspend_node.suspend_token, space);
486445 }
487446 },
488447
......@@ -490,26 +449,21 @@ fn renderExpression(
490449 const infix_op_node = @fieldParentPtr(ast.Node.Catch, "base", base);
491450
492451 const op_space = Space.Space;
493 try renderExpression(allocator, stream, tree, indent, start_col, infix_op_node.lhs, op_space);
452 try renderExpression(allocator, ais, tree, infix_op_node.lhs, op_space);
494453
495454 const after_op_space = blk: {
496 const loc = tree.tokenLocation(tree.token_locs[infix_op_node.op_token].end, tree.nextToken(infix_op_node.op_token));
497 break :blk if (loc.line == 0) op_space else Space.Newline;
455 const same_line = tree.tokensOnSameLine(infix_op_node.op_token, tree.nextToken(infix_op_node.op_token));
456 break :blk if (same_line) op_space else Space.Newline;
498457 };
499458
500 try renderToken(tree, stream, infix_op_node.op_token, indent, start_col, after_op_space);
501 if (after_op_space == Space.Newline and
502 tree.token_ids[tree.nextToken(infix_op_node.op_token)] != .MultilineStringLiteralLine)
503 {
504 try stream.writeByteNTimes(' ', indent + indent_delta);
505 start_col.* = indent + indent_delta;
506 }
459 try renderToken(tree, ais, infix_op_node.op_token, after_op_space);
507460
508461 if (infix_op_node.payload) |payload| {
509 try renderExpression(allocator, stream, tree, indent, start_col, payload, Space.Space);
462 try renderExpression(allocator, ais, tree, payload, Space.Space);
510463 }
511464
512 return renderExpression(allocator, stream, tree, indent, start_col, infix_op_node.rhs, space);
465 ais.pushIndentOneShot();
466 return renderExpression(allocator, ais, tree, infix_op_node.rhs, space);
513467 },
514468
515469 .Add,
......@@ -561,22 +515,16 @@ fn renderExpression(
561515 .Period, .ErrorUnion, .Range => Space.None,
562516 else => Space.Space,
563517 };
564 try renderExpression(allocator, stream, tree, indent, start_col, infix_op_node.lhs, op_space);
518 try renderExpression(allocator, ais, tree, infix_op_node.lhs, op_space);
565519
566520 const after_op_space = blk: {
567521 const loc = tree.tokenLocation(tree.token_locs[infix_op_node.op_token].end, tree.nextToken(infix_op_node.op_token));
568522 break :blk if (loc.line == 0) op_space else Space.Newline;
569523 };
570524
571 try renderToken(tree, stream, infix_op_node.op_token, indent, start_col, after_op_space);
572 if (after_op_space == Space.Newline and
573 tree.token_ids[tree.nextToken(infix_op_node.op_token)] != .MultilineStringLiteralLine)
574 {
575 try stream.writeByteNTimes(' ', indent + indent_delta);
576 start_col.* = indent + indent_delta;
577 }
578
579 return renderExpression(allocator, stream, tree, indent, start_col, infix_op_node.rhs, space);
525 try renderToken(tree, ais, infix_op_node.op_token, after_op_space);
526 ais.pushIndentOneShot();
527 return renderExpression(allocator, ais, tree, infix_op_node.rhs, space);
580528 },
581529
582530 .BitNot,
......@@ -587,8 +535,8 @@ fn renderExpression(
587535 .AddressOf,
588536 => {
589537 const casted_node = @fieldParentPtr(ast.Node.SimplePrefixOp, "base", base);
590 try renderToken(tree, stream, casted_node.op_token, indent, start_col, Space.None);
591 return renderExpression(allocator, stream, tree, indent, start_col, casted_node.rhs, space);
538 try renderToken(tree, ais, casted_node.op_token, Space.None);
539 return renderExpression(allocator, ais, tree, casted_node.rhs, space);
592540 },
593541
594542 .Try,
......@@ -596,18 +544,16 @@ fn renderExpression(
596544 .Await,
597545 => {
598546 const casted_node = @fieldParentPtr(ast.Node.SimplePrefixOp, "base", base);
599 try renderToken(tree, stream, casted_node.op_token, indent, start_col, Space.Space);
600 return renderExpression(allocator, stream, tree, indent, start_col, casted_node.rhs, space);
547 try renderToken(tree, ais, casted_node.op_token, Space.Space);
548 return renderExpression(allocator, ais, tree, casted_node.rhs, space);
601549 },
602550
603551 .ArrayType => {
604552 const array_type = @fieldParentPtr(ast.Node.ArrayType, "base", base);
605553 return renderArrayType(
606554 allocator,
607 stream,
555 ais,
608556 tree,
609 indent,
610 start_col,
611557 array_type.op_token,
612558 array_type.rhs,
613559 array_type.len_expr,
......@@ -619,10 +565,8 @@ fn renderExpression(
619565 const array_type = @fieldParentPtr(ast.Node.ArrayTypeSentinel, "base", base);
620566 return renderArrayType(
621567 allocator,
622 stream,
568 ais,
623569 tree,
624 indent,
625 start_col,
626570 array_type.op_token,
627571 array_type.rhs,
628572 array_type.len_expr,
......@@ -635,111 +579,111 @@ fn renderExpression(
635579 const ptr_type = @fieldParentPtr(ast.Node.PtrType, "base", base);
636580 const op_tok_id = tree.token_ids[ptr_type.op_token];
637581 switch (op_tok_id) {
638 .Asterisk, .AsteriskAsterisk => try stream.writeByte('*'),
582 .Asterisk, .AsteriskAsterisk => try ais.writer().writeByte('*'),
639583 .LBracket => if (tree.token_ids[ptr_type.op_token + 2] == .Identifier)
640 try stream.writeAll("[*c")
584 try ais.writer().writeAll("[*c")
641585 else
642 try stream.writeAll("[*"),
586 try ais.writer().writeAll("[*"),
643587 else => unreachable,
644588 }
645589 if (ptr_type.ptr_info.sentinel) |sentinel| {
646590 const colon_token = tree.prevToken(sentinel.firstToken());
647 try renderToken(tree, stream, colon_token, indent, start_col, Space.None); // :
591 try renderToken(tree, ais, colon_token, Space.None); // :
648592 const sentinel_space = switch (op_tok_id) {
649593 .LBracket => Space.None,
650594 else => Space.Space,
651595 };
652 try renderExpression(allocator, stream, tree, indent, start_col, sentinel, sentinel_space);
596 try renderExpression(allocator, ais, tree, sentinel, sentinel_space);
653597 }
654598 switch (op_tok_id) {
655599 .Asterisk, .AsteriskAsterisk => {},
656 .LBracket => try stream.writeByte(']'),
600 .LBracket => try ais.writer().writeByte(']'),
657601 else => unreachable,
658602 }
659603 if (ptr_type.ptr_info.allowzero_token) |allowzero_token| {
660 try renderToken(tree, stream, allowzero_token, indent, start_col, Space.Space); // allowzero
604 try renderToken(tree, ais, allowzero_token, Space.Space); // allowzero
661605 }
662606 if (ptr_type.ptr_info.align_info) |align_info| {
663607 const lparen_token = tree.prevToken(align_info.node.firstToken());
664608 const align_token = tree.prevToken(lparen_token);
665609
666 try renderToken(tree, stream, align_token, indent, start_col, Space.None); // align
667 try renderToken(tree, stream, lparen_token, indent, start_col, Space.None); // (
610 try renderToken(tree, ais, align_token, Space.None); // align
611 try renderToken(tree, ais, lparen_token, Space.None); // (
668612
669 try renderExpression(allocator, stream, tree, indent, start_col, align_info.node, Space.None);
613 try renderExpression(allocator, ais, tree, align_info.node, Space.None);
670614
671615 if (align_info.bit_range) |bit_range| {
672616 const colon1 = tree.prevToken(bit_range.start.firstToken());
673617 const colon2 = tree.prevToken(bit_range.end.firstToken());
674618
675 try renderToken(tree, stream, colon1, indent, start_col, Space.None); // :
676 try renderExpression(allocator, stream, tree, indent, start_col, bit_range.start, Space.None);
677 try renderToken(tree, stream, colon2, indent, start_col, Space.None); // :
678 try renderExpression(allocator, stream, tree, indent, start_col, bit_range.end, Space.None);
619 try renderToken(tree, ais, colon1, Space.None); // :
620 try renderExpression(allocator, ais, tree, bit_range.start, Space.None);
621 try renderToken(tree, ais, colon2, Space.None); // :
622 try renderExpression(allocator, ais, tree, bit_range.end, Space.None);
679623
680624 const rparen_token = tree.nextToken(bit_range.end.lastToken());
681 try renderToken(tree, stream, rparen_token, indent, start_col, Space.Space); // )
625 try renderToken(tree, ais, rparen_token, Space.Space); // )
682626 } else {
683627 const rparen_token = tree.nextToken(align_info.node.lastToken());
684 try renderToken(tree, stream, rparen_token, indent, start_col, Space.Space); // )
628 try renderToken(tree, ais, rparen_token, Space.Space); // )
685629 }
686630 }
687631 if (ptr_type.ptr_info.const_token) |const_token| {
688 try renderToken(tree, stream, const_token, indent, start_col, Space.Space); // const
632 try renderToken(tree, ais, const_token, Space.Space); // const
689633 }
690634 if (ptr_type.ptr_info.volatile_token) |volatile_token| {
691 try renderToken(tree, stream, volatile_token, indent, start_col, Space.Space); // volatile
635 try renderToken(tree, ais, volatile_token, Space.Space); // volatile
692636 }
693 return renderExpression(allocator, stream, tree, indent, start_col, ptr_type.rhs, space);
637 return renderExpression(allocator, ais, tree, ptr_type.rhs, space);
694638 },
695639
696640 .SliceType => {
697641 const slice_type = @fieldParentPtr(ast.Node.SliceType, "base", base);
698 try renderToken(tree, stream, slice_type.op_token, indent, start_col, Space.None); // [
642 try renderToken(tree, ais, slice_type.op_token, Space.None); // [
699643 if (slice_type.ptr_info.sentinel) |sentinel| {
700644 const colon_token = tree.prevToken(sentinel.firstToken());
701 try renderToken(tree, stream, colon_token, indent, start_col, Space.None); // :
702 try renderExpression(allocator, stream, tree, indent, start_col, sentinel, Space.None);
703 try renderToken(tree, stream, tree.nextToken(sentinel.lastToken()), indent, start_col, Space.None); // ]
645 try renderToken(tree, ais, colon_token, Space.None); // :
646 try renderExpression(allocator, ais, tree, sentinel, Space.None);
647 try renderToken(tree, ais, tree.nextToken(sentinel.lastToken()), Space.None); // ]
704648 } else {
705 try renderToken(tree, stream, tree.nextToken(slice_type.op_token), indent, start_col, Space.None); // ]
649 try renderToken(tree, ais, tree.nextToken(slice_type.op_token), Space.None); // ]
706650 }
707651
708652 if (slice_type.ptr_info.allowzero_token) |allowzero_token| {
709 try renderToken(tree, stream, allowzero_token, indent, start_col, Space.Space); // allowzero
653 try renderToken(tree, ais, allowzero_token, Space.Space); // allowzero
710654 }
711655 if (slice_type.ptr_info.align_info) |align_info| {
712656 const lparen_token = tree.prevToken(align_info.node.firstToken());
713657 const align_token = tree.prevToken(lparen_token);
714658
715 try renderToken(tree, stream, align_token, indent, start_col, Space.None); // align
716 try renderToken(tree, stream, lparen_token, indent, start_col, Space.None); // (
659 try renderToken(tree, ais, align_token, Space.None); // align
660 try renderToken(tree, ais, lparen_token, Space.None); // (
717661
718 try renderExpression(allocator, stream, tree, indent, start_col, align_info.node, Space.None);
662 try renderExpression(allocator, ais, tree, align_info.node, Space.None);
719663
720664 if (align_info.bit_range) |bit_range| {
721665 const colon1 = tree.prevToken(bit_range.start.firstToken());
722666 const colon2 = tree.prevToken(bit_range.end.firstToken());
723667
724 try renderToken(tree, stream, colon1, indent, start_col, Space.None); // :
725 try renderExpression(allocator, stream, tree, indent, start_col, bit_range.start, Space.None);
726 try renderToken(tree, stream, colon2, indent, start_col, Space.None); // :
727 try renderExpression(allocator, stream, tree, indent, start_col, bit_range.end, Space.None);
668 try renderToken(tree, ais, colon1, Space.None); // :
669 try renderExpression(allocator, ais, tree, bit_range.start, Space.None);
670 try renderToken(tree, ais, colon2, Space.None); // :
671 try renderExpression(allocator, ais, tree, bit_range.end, Space.None);
728672
729673 const rparen_token = tree.nextToken(bit_range.end.lastToken());
730 try renderToken(tree, stream, rparen_token, indent, start_col, Space.Space); // )
674 try renderToken(tree, ais, rparen_token, Space.Space); // )
731675 } else {
732676 const rparen_token = tree.nextToken(align_info.node.lastToken());
733 try renderToken(tree, stream, rparen_token, indent, start_col, Space.Space); // )
677 try renderToken(tree, ais, rparen_token, Space.Space); // )
734678 }
735679 }
736680 if (slice_type.ptr_info.const_token) |const_token| {
737 try renderToken(tree, stream, const_token, indent, start_col, Space.Space);
681 try renderToken(tree, ais, const_token, Space.Space);
738682 }
739683 if (slice_type.ptr_info.volatile_token) |volatile_token| {
740 try renderToken(tree, stream, volatile_token, indent, start_col, Space.Space);
684 try renderToken(tree, ais, volatile_token, Space.Space);
741685 }
742 return renderExpression(allocator, stream, tree, indent, start_col, slice_type.rhs, space);
686 return renderExpression(allocator, ais, tree, slice_type.rhs, space);
743687 },
744688
745689 .ArrayInitializer, .ArrayInitializerDot => {
......@@ -768,27 +712,33 @@ fn renderExpression(
768712
769713 if (exprs.len == 0) {
770714 switch (lhs) {
771 .dot => |dot| try renderToken(tree, stream, dot, indent, start_col, Space.None),
772 .node => |node| try renderExpression(allocator, stream, tree, indent, start_col, node, Space.None),
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);
773723 }
774 try renderToken(tree, stream, lbrace, indent, start_col, Space.None);
775 return renderToken(tree, stream, rtoken, indent, start_col, space);
776 }
777724
778 if (exprs.len == 1 and tree.token_ids[exprs[0].lastToken() + 1] == .RBrace) {
725 return renderToken(tree, ais, rtoken, space);
726 }
727 if (exprs.len == 1 and tree.token_ids[exprs[0].*.lastToken() + 1] == .RBrace) {
779728 const expr = exprs[0];
729
780730 switch (lhs) {
781 .dot => |dot| try renderToken(tree, stream, dot, indent, start_col, Space.None),
782 .node => |node| try renderExpression(allocator, stream, tree, indent, start_col, node, Space.None),
731 .dot => |dot| try renderToken(tree, ais, dot, Space.None),
732 .node => |node| try renderExpression(allocator, ais, tree, node, Space.None),
783733 }
784 try renderToken(tree, stream, lbrace, indent, start_col, Space.None);
785 try renderExpression(allocator, stream, tree, indent, start_col, expr, Space.None);
786 return renderToken(tree, stream, rtoken, indent, start_col, space);
734 try renderToken(tree, ais, lbrace, Space.None);
735 try renderExpression(allocator, ais, tree, expr, Space.None);
736 return renderToken(tree, ais, rtoken, space);
787737 }
788738
789739 switch (lhs) {
790 .dot => |dot| try renderToken(tree, stream, dot, indent, start_col, Space.None),
791 .node => |node| try renderExpression(allocator, stream, tree, indent, start_col, node, Space.None),
740 .dot => |dot| try renderToken(tree, ais, dot, Space.None),
741 .node => |node| try renderExpression(allocator, ais, tree, node, Space.None),
792742 }
793743
794744 // scan to find row size
......@@ -830,79 +780,70 @@ fn renderExpression(
830780 var expr_widths = widths[0 .. widths.len - row_size];
831781 var column_widths = widths[widths.len - row_size ..];
832782
833 // Null stream for counting the printed length of each expression
783 // Null ais for counting the printed length of each expression
834784 var counting_stream = std.io.countingOutStream(std.io.null_out_stream);
785 var auto_indenting_stream = std.io.autoIndentingStream(indent_delta, counting_stream.writer());
835786
836787 for (exprs) |expr, i| {
837788 counting_stream.bytes_written = 0;
838 var dummy_col: usize = 0;
839 try renderExpression(allocator, counting_stream.outStream(), tree, indent, &dummy_col, expr, Space.None);
789 try renderExpression(allocator, &auto_indenting_stream, tree, expr, Space.None);
840790 const width = @intCast(usize, counting_stream.bytes_written);
841791 const col = i % row_size;
842792 column_widths[col] = std.math.max(column_widths[col], width);
843793 expr_widths[i] = width;
844794 }
845795
846 var new_indent = indent + indent_delta;
796 {
797 ais.pushIndentNextLine();
798 defer ais.popIndent();
799 try renderToken(tree, ais, lbrace, Space.Newline);
847800
848 if (tree.token_ids[tree.nextToken(lbrace)] != .MultilineStringLiteralLine) {
849 try renderToken(tree, stream, lbrace, new_indent, start_col, Space.Newline);
850 try stream.writeByteNTimes(' ', new_indent);
851 } else {
852 new_indent -= indent_delta;
853 try renderToken(tree, stream, lbrace, new_indent, start_col, Space.None);
854 }
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);
855806
856 var col: usize = 1;
857 for (exprs) |expr, i| {
858 if (i + 1 < exprs.len) {
859 const next_expr = exprs[i + 1];
860 try renderExpression(allocator, stream, tree, new_indent, start_col, expr, Space.None);
807 const comma = tree.nextToken(expr.*.lastToken());
861808
862 const comma = tree.nextToken(expr.lastToken());
809 if (col != row_size) {
810 try renderToken(tree, ais, comma, Space.Space); // ,
863811
864 if (col != row_size) {
865 try renderToken(tree, stream, comma, new_indent, start_col, Space.Space); // ,
812 const padding = column_widths[i % row_size] - expr_widths[i];
813 try ais.writer().writeByteNTimes(' ', padding);
866814
867 const padding = column_widths[i % row_size] - expr_widths[i];
868 try stream.writeByteNTimes(' ', padding);
815 col += 1;
816 continue;
817 }
818 col = 1;
869819
870 col += 1;
871 continue;
872 }
873 col = 1;
820 if (tree.token_ids[tree.nextToken(comma)] != .MultilineStringLiteralLine) {
821 try renderToken(tree, ais, comma, Space.Newline); // ,
822 } else {
823 try renderToken(tree, ais, comma, Space.None); // ,
824 }
874825
875 if (tree.token_ids[tree.nextToken(comma)] != .MultilineStringLiteralLine) {
876 try renderToken(tree, stream, comma, new_indent, start_col, Space.Newline); // ,
826 try renderExtraNewline(tree, ais, next_expr);
877827 } else {
878 try renderToken(tree, stream, comma, new_indent, start_col, Space.None); // ,
879 }
880
881 try renderExtraNewline(tree, stream, start_col, next_expr);
882 if (next_expr.tag != .MultilineStringLiteral) {
883 try stream.writeByteNTimes(' ', new_indent);
828 try renderExpression(allocator, ais, tree, expr, Space.Comma); // ,
884829 }
885 } else {
886 try renderExpression(allocator, stream, tree, new_indent, start_col, expr, Space.Comma); // ,
887830 }
888831 }
889 if (exprs[exprs.len - 1].tag != .MultilineStringLiteral) {
890 try stream.writeByteNTimes(' ', indent);
891 }
892 return renderToken(tree, stream, rtoken, indent, start_col, space);
832 return renderToken(tree, ais, rtoken, space);
893833 } else {
894 try renderToken(tree, stream, lbrace, indent, start_col, Space.Space);
834 try renderToken(tree, ais, lbrace, Space.Space);
895835 for (exprs) |expr, i| {
896836 if (i + 1 < exprs.len) {
897 try renderExpression(allocator, stream, tree, indent, start_col, expr, Space.None);
898 const comma = tree.nextToken(expr.lastToken());
899 try renderToken(tree, stream, comma, indent, start_col, Space.Space); // ,
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); // ,
900841 } else {
901 try renderExpression(allocator, stream, tree, indent, start_col, expr, Space.Space);
842 try renderExpression(allocator, ais, tree, expr, Space.Space);
902843 }
903844 }
904845
905 return renderToken(tree, stream, rtoken, indent, start_col, space);
846 return renderToken(tree, ais, rtoken, space);
906847 }
907848 },
908849
......@@ -932,11 +873,17 @@ fn renderExpression(
932873
933874 if (field_inits.len == 0) {
934875 switch (lhs) {
935 .dot => |dot| try renderToken(tree, stream, dot, indent, start_col, Space.None),
936 .node => |node| try renderExpression(allocator, stream, tree, indent, start_col, node, Space.None),
876 .dot => |dot| try renderToken(tree, ais, dot, Space.None),
877 .node => |node| try renderExpression(allocator, ais, tree, node, Space.None),
937878 }
938 try renderToken(tree, stream, lbrace, indent + indent_delta, start_col, Space.None);
939 return renderToken(tree, stream, rtoken, indent, start_col, space);
879
880 {
881 ais.pushIndentNextLine();
882 defer ais.popIndent();
883 try renderToken(tree, ais, lbrace, Space.None);
884 }
885
886 return renderToken(tree, ais, rtoken, space);
940887 }
941888
942889 const src_has_trailing_comma = blk: {
......@@ -952,9 +899,10 @@ fn renderExpression(
952899 const expr_outputs_one_line = blk: {
953900 // render field expressions until a LF is found
954901 for (field_inits) |field_init| {
955 var find_stream = FindByteOutStream.init('\n');
956 var dummy_col: usize = 0;
957 try renderExpression(allocator, find_stream.outStream(), tree, 0, &dummy_col, field_init, Space.None);
902 var find_stream = std.io.findByteOutStream('\n', std.io.null_out_stream);
903 var auto_indenting_stream = std.io.autoIndentingStream(indent_delta, find_stream.writer());
904
905 try renderExpression(allocator, &auto_indenting_stream, tree, field_init, Space.None);
958906 if (find_stream.byte_found) break :blk false;
959907 }
960908 break :blk true;
......@@ -967,7 +915,6 @@ fn renderExpression(
967915 .StructInitializer,
968916 .StructInitializerDot,
969917 => break :blk,
970
971918 else => {},
972919 }
973920
......@@ -977,76 +924,78 @@ fn renderExpression(
977924 }
978925
979926 switch (lhs) {
980 .dot => |dot| try renderToken(tree, stream, dot, indent, start_col, Space.None),
981 .node => |node| try renderExpression(allocator, stream, tree, indent, start_col, node, Space.None),
927 .dot => |dot| try renderToken(tree, ais, dot, Space.None),
928 .node => |node| try renderExpression(allocator, ais, tree, node, Space.None),
982929 }
983 try renderToken(tree, stream, lbrace, indent, start_col, Space.Space);
984 try renderExpression(allocator, stream, tree, indent, start_col, &field_init.base, Space.Space);
985 return renderToken(tree, stream, rtoken, indent, start_col, space);
930 try renderToken(tree, ais, lbrace, Space.Space);
931 try renderExpression(allocator, ais, tree, &field_init.base, Space.Space);
932 return renderToken(tree, ais, rtoken, space);
986933 }
987934
988935 if (!src_has_trailing_comma and src_same_line and expr_outputs_one_line) {
989936 // render all on one line, no trailing comma
990937 switch (lhs) {
991 .dot => |dot| try renderToken(tree, stream, dot, indent, start_col, Space.None),
992 .node => |node| try renderExpression(allocator, stream, tree, indent, start_col, node, Space.None),
938 .dot => |dot| try renderToken(tree, ais, dot, Space.None),
939 .node => |node| try renderExpression(allocator, ais, tree, node, Space.None),
993940 }
994 try renderToken(tree, stream, lbrace, indent, start_col, Space.Space);
941 try renderToken(tree, ais, lbrace, Space.Space);
995942
996943 for (field_inits) |field_init, i| {
997944 if (i + 1 < field_inits.len) {
998 try renderExpression(allocator, stream, tree, indent, start_col, field_init, Space.None);
945 try renderExpression(allocator, ais, tree, field_init, Space.None);
999946
1000947 const comma = tree.nextToken(field_init.lastToken());
1001 try renderToken(tree, stream, comma, indent, start_col, Space.Space);
948 try renderToken(tree, ais, comma, Space.Space);
1002949 } else {
1003 try renderExpression(allocator, stream, tree, indent, start_col, field_init, Space.Space);
950 try renderExpression(allocator, ais, tree, field_init, Space.Space);
1004951 }
1005952 }
1006953
1007 return renderToken(tree, stream, rtoken, indent, start_col, space);
954 return renderToken(tree, ais, rtoken, space);
1008955 }
1009956
1010 const new_indent = indent + indent_delta;
957 {
958 switch (lhs) {
959 .dot => |dot| try renderToken(tree, ais, dot, Space.None),
960 .node => |node| try renderExpression(allocator, ais, tree, node, Space.None),
961 }
1011962
1012 switch (lhs) {
1013 .dot => |dot| try renderToken(tree, stream, dot, new_indent, start_col, Space.None),
1014 .node => |node| try renderExpression(allocator, stream, tree, new_indent, start_col, node, Space.None),
1015 }
1016 try renderToken(tree, stream, lbrace, new_indent, start_col, Space.Newline);
963 ais.pushIndentNextLine();
964 defer ais.popIndent();
1017965
1018 for (field_inits) |field_init, i| {
1019 try stream.writeByteNTimes(' ', new_indent);
966 try renderToken(tree, ais, lbrace, Space.Newline);
1020967
1021 if (i + 1 < field_inits.len) {
1022 try renderExpression(allocator, stream, tree, new_indent, start_col, field_init, Space.None);
968 for (field_inits) |field_init, i| {
969 if (i + 1 < field_inits.len) {
970 const next_field_init = field_inits[i + 1];
971 try renderExpression(allocator, ais, tree, field_init, Space.None);
1023972
1024 const comma = tree.nextToken(field_init.lastToken());
1025 try renderToken(tree, stream, comma, new_indent, start_col, Space.Newline);
973 const comma = tree.nextToken(field_init.lastToken());
974 try renderToken(tree, ais, comma, Space.Newline);
1026975
1027 try renderExtraNewline(tree, stream, start_col, field_inits[i + 1]);
1028 } else {
1029 try renderExpression(allocator, stream, tree, new_indent, start_col, field_init, Space.Comma);
976 try renderExtraNewline(tree, ais, next_field_init);
977 } else {
978 try renderExpression(allocator, ais, tree, field_init, Space.Comma);
979 }
1030980 }
1031981 }
1032982
1033 try stream.writeByteNTimes(' ', indent);
1034 return renderToken(tree, stream, rtoken, indent, start_col, space);
983 return renderToken(tree, ais, rtoken, space);
1035984 },
1036985
1037986 .Call => {
1038987 const call = @fieldParentPtr(ast.Node.Call, "base", base);
1039988 if (call.async_token) |async_token| {
1040 try renderToken(tree, stream, async_token, indent, start_col, Space.Space);
989 try renderToken(tree, ais, async_token, Space.Space);
1041990 }
1042991
1043 try renderExpression(allocator, stream, tree, indent, start_col, call.lhs, Space.None);
992 try renderExpression(allocator, ais, tree, call.lhs, Space.None);
1044993
1045994 const lparen = tree.nextToken(call.lhs.lastToken());
1046995
1047996 if (call.params_len == 0) {
1048 try renderToken(tree, stream, lparen, indent, start_col, Space.None);
1049 return renderToken(tree, stream, call.rtoken, indent, start_col, space);
997 try renderToken(tree, ais, lparen, Space.None);
998 return renderToken(tree, ais, call.rtoken, space);
1050999 }
10511000
10521001 const src_has_trailing_comma = blk: {
......@@ -1055,43 +1004,41 @@ fn renderExpression(
10551004 };
10561005
10571006 if (src_has_trailing_comma) {
1058 const new_indent = indent + indent_delta;
1059 try renderToken(tree, stream, lparen, new_indent, start_col, Space.Newline);
1007 try renderToken(tree, ais, lparen, Space.Newline);
10601008
10611009 const params = call.params();
10621010 for (params) |param_node, i| {
1063 const param_node_new_indent = if (param_node.tag == .MultilineStringLiteral) blk: {
1064 break :blk indent;
1065 } else blk: {
1066 try stream.writeByteNTimes(' ', new_indent);
1067 break :blk new_indent;
1068 };
1011 ais.pushIndent();
1012 defer ais.popIndent();
10691013
10701014 if (i + 1 < params.len) {
1071 try renderExpression(allocator, stream, tree, param_node_new_indent, start_col, param_node, Space.None);
1015 const next_node = params[i + 1];
1016 try renderExpression(allocator, ais, tree, param_node, Space.None);
10721017 const comma = tree.nextToken(param_node.lastToken());
1073 try renderToken(tree, stream, comma, new_indent, start_col, Space.Newline); // ,
1074 try renderExtraNewline(tree, stream, start_col, params[i + 1]);
1018 try renderToken(tree, ais, comma, Space.Newline); // ,
1019 try renderExtraNewline(tree, ais, next_node);
10751020 } else {
1076 try renderExpression(allocator, stream, tree, param_node_new_indent, start_col, param_node, Space.Comma);
1077 try stream.writeByteNTimes(' ', indent);
1078 return renderToken(tree, stream, call.rtoken, indent, start_col, space);
1021 try renderExpression(allocator, ais, tree, param_node, Space.Comma);
10791022 }
10801023 }
1024 return renderToken(tree, ais, call.rtoken, space);
10811025 }
10821026
1083 try renderToken(tree, stream, lparen, indent, start_col, Space.None); // (
1027 try renderToken(tree, ais, lparen, Space.None); // (
10841028
10851029 const params = call.params();
10861030 for (params) |param_node, i| {
1087 try renderExpression(allocator, stream, tree, indent, start_col, param_node, Space.None);
1031 if (param_node.*.tag == .MultilineStringLiteral) ais.pushIndentOneShot();
1032
1033 try renderExpression(allocator, ais, tree, param_node, Space.None);
10881034
10891035 if (i + 1 < params.len) {
1036 const next_param = params[i + 1];
10901037 const comma = tree.nextToken(param_node.lastToken());
1091 try renderToken(tree, stream, comma, indent, start_col, Space.Space);
1038 try renderToken(tree, ais, comma, Space.Space);
10921039 }
10931040 }
1094 return renderToken(tree, stream, call.rtoken, indent, start_col, space);
1041 return renderToken(tree, ais, call.rtoken, space);
10951042 },
10961043
10971044 .ArrayAccess => {
......@@ -1100,26 +1047,25 @@ fn renderExpression(
11001047 const lbracket = tree.nextToken(suffix_op.lhs.lastToken());
11011048 const rbracket = tree.nextToken(suffix_op.index_expr.lastToken());
11021049
1103 try renderExpression(allocator, stream, tree, indent, start_col, suffix_op.lhs, Space.None);
1104 try renderToken(tree, stream, lbracket, indent, start_col, Space.None); // [
1050 try renderExpression(allocator, ais, tree, suffix_op.lhs, Space.None);
1051 try renderToken(tree, ais, lbracket, Space.None); // [
11051052
11061053 const starts_with_comment = tree.token_ids[lbracket + 1] == .LineComment;
11071054 const ends_with_comment = tree.token_ids[rbracket - 1] == .LineComment;
1108 const new_indent = if (ends_with_comment) indent + indent_delta else indent;
1109 const new_space = if (ends_with_comment) Space.Newline else Space.None;
1110 try renderExpression(allocator, stream, tree, new_indent, start_col, suffix_op.index_expr, new_space);
1111 if (starts_with_comment) {
1112 try stream.writeByte('\n');
1113 }
1114 if (ends_with_comment or starts_with_comment) {
1115 try stream.writeByteNTimes(' ', indent);
1055 {
1056 const new_space = if (ends_with_comment) Space.Newline else Space.None;
1057
1058 ais.pushIndent();
1059 defer ais.popIndent();
1060 try renderExpression(allocator, ais, tree, suffix_op.index_expr, new_space);
11161061 }
1117 return renderToken(tree, stream, rbracket, indent, start_col, space); // ]
1062 if (starts_with_comment) try ais.maybeInsertNewline();
1063 return renderToken(tree, ais, rbracket, space); // ]
11181064 },
1065
11191066 .Slice => {
11201067 const suffix_op = base.castTag(.Slice).?;
1121
1122 try renderExpression(allocator, stream, tree, indent, start_col, suffix_op.lhs, Space.None);
1068 try renderExpression(allocator, ais, tree, suffix_op.lhs, Space.None);
11231069
11241070 const lbracket = tree.prevToken(suffix_op.start.firstToken());
11251071 const dotdot = tree.nextToken(suffix_op.start.lastToken());
......@@ -1129,32 +1075,33 @@ fn renderExpression(
11291075 const after_start_space = if (after_start_space_bool) Space.Space else Space.None;
11301076 const after_op_space = if (suffix_op.end != null) after_start_space else Space.None;
11311077
1132 try renderToken(tree, stream, lbracket, indent, start_col, Space.None); // [
1133 try renderExpression(allocator, stream, tree, indent, start_col, suffix_op.start, after_start_space);
1134 try renderToken(tree, stream, dotdot, indent, start_col, after_op_space); // ..
1078 try renderToken(tree, ais, lbracket, Space.None); // [
1079 try renderExpression(allocator, ais, tree, suffix_op.start, after_start_space);
1080 try renderToken(tree, ais, dotdot, after_op_space); // ..
11351081 if (suffix_op.end) |end| {
11361082 const after_end_space = if (suffix_op.sentinel != null) Space.Space else Space.None;
1137 try renderExpression(allocator, stream, tree, indent, start_col, end, after_end_space);
1083 try renderExpression(allocator, ais, tree, end, after_end_space);
11381084 }
11391085 if (suffix_op.sentinel) |sentinel| {
11401086 const colon = tree.prevToken(sentinel.firstToken());
1141 try renderToken(tree, stream, colon, indent, start_col, Space.None); // :
1142 try renderExpression(allocator, stream, tree, indent, start_col, sentinel, Space.None);
1087 try renderToken(tree, ais, colon, Space.None); // :
1088 try renderExpression(allocator, ais, tree, sentinel, Space.None);
11431089 }
1144 return renderToken(tree, stream, suffix_op.rtoken, indent, start_col, space); // ]
1090 return renderToken(tree, ais, suffix_op.rtoken, space); // ]
11451091 },
1092
11461093 .Deref => {
11471094 const suffix_op = base.castTag(.Deref).?;
11481095
1149 try renderExpression(allocator, stream, tree, indent, start_col, suffix_op.lhs, Space.None);
1150 return renderToken(tree, stream, suffix_op.rtoken, indent, start_col, space); // .*
1096 try renderExpression(allocator, ais, tree, suffix_op.lhs, Space.None);
1097 return renderToken(tree, ais, suffix_op.rtoken, space); // .*
11511098 },
11521099 .UnwrapOptional => {
11531100 const suffix_op = base.castTag(.UnwrapOptional).?;
11541101
1155 try renderExpression(allocator, stream, tree, indent, start_col, suffix_op.lhs, Space.None);
1156 try renderToken(tree, stream, tree.prevToken(suffix_op.rtoken), indent, start_col, Space.None); // .
1157 return renderToken(tree, stream, suffix_op.rtoken, indent, start_col, space); // ?
1102 try renderExpression(allocator, ais, tree, suffix_op.lhs, Space.None);
1103 try renderToken(tree, ais, tree.prevToken(suffix_op.rtoken), Space.None); // .
1104 return renderToken(tree, ais, suffix_op.rtoken, space); // ?
11581105 },
11591106
11601107 .Break => {
......@@ -1163,145 +1110,152 @@ fn renderExpression(
11631110 const maybe_label = flow_expr.getLabel();
11641111
11651112 if (maybe_label == null and maybe_rhs == null) {
1166 return renderToken(tree, stream, flow_expr.ltoken, indent, start_col, space); // break
1113 return renderToken(tree, ais, flow_expr.ltoken, space); // break
11671114 }
11681115
1169 try renderToken(tree, stream, flow_expr.ltoken, indent, start_col, Space.Space); // break
1116 try renderToken(tree, ais, flow_expr.ltoken, Space.Space); // break
11701117 if (maybe_label) |label| {
11711118 const colon = tree.nextToken(flow_expr.ltoken);
1172 try renderToken(tree, stream, colon, indent, start_col, Space.None); // :
1119 try renderToken(tree, ais, colon, Space.None); // :
11731120
11741121 if (maybe_rhs == null) {
1175 return renderToken(tree, stream, label, indent, start_col, space); // label
1122 return renderToken(tree, ais, label, space); // label
11761123 }
1177 try renderToken(tree, stream, label, indent, start_col, Space.Space); // label
1124 try renderToken(tree, ais, label, Space.Space); // label
11781125 }
1179 return renderExpression(allocator, stream, tree, indent, start_col, maybe_rhs.?, space);
1126 return renderExpression(allocator, ais, tree, maybe_rhs.?, space);
11801127 },
11811128
11821129 .Continue => {
11831130 const flow_expr = base.castTag(.Continue).?;
11841131 if (flow_expr.getLabel()) |label| {
1185 try renderToken(tree, stream, flow_expr.ltoken, indent, start_col, Space.Space); // continue
1132 try renderToken(tree, ais, flow_expr.ltoken, Space.Space); // continue
11861133 const colon = tree.nextToken(flow_expr.ltoken);
1187 try renderToken(tree, stream, colon, indent, start_col, Space.None); // :
1188 return renderToken(tree, stream, label, indent, start_col, space); // label
1134 try renderToken(tree, ais, colon, Space.None); // :
1135 return renderToken(tree, ais, label, space); // label
11891136 } else {
1190 return renderToken(tree, stream, flow_expr.ltoken, indent, start_col, space); // continue
1137 return renderToken(tree, ais, flow_expr.ltoken, space); // continue
11911138 }
11921139 },
11931140
11941141 .Return => {
11951142 const flow_expr = base.castTag(.Return).?;
11961143 if (flow_expr.getRHS()) |rhs| {
1197 try renderToken(tree, stream, flow_expr.ltoken, indent, start_col, Space.Space);
1198 return renderExpression(allocator, stream, tree, indent, start_col, rhs, space);
1144 try renderToken(tree, ais, flow_expr.ltoken, Space.Space);
1145 return renderExpression(allocator, ais, tree, rhs, space);
11991146 } else {
1200 return renderToken(tree, stream, flow_expr.ltoken, indent, start_col, space);
1147 return renderToken(tree, ais, flow_expr.ltoken, space);
12011148 }
12021149 },
12031150
12041151 .Payload => {
12051152 const payload = @fieldParentPtr(ast.Node.Payload, "base", base);
12061153
1207 try renderToken(tree, stream, payload.lpipe, indent, start_col, Space.None);
1208 try renderExpression(allocator, stream, tree, indent, start_col, payload.error_symbol, Space.None);
1209 return renderToken(tree, stream, payload.rpipe, indent, start_col, space);
1154 try renderToken(tree, ais, payload.lpipe, Space.None);
1155 try renderExpression(allocator, ais, tree, payload.error_symbol, Space.None);
1156 return renderToken(tree, ais, payload.rpipe, space);
12101157 },
12111158
12121159 .PointerPayload => {
12131160 const payload = @fieldParentPtr(ast.Node.PointerPayload, "base", base);
12141161
1215 try renderToken(tree, stream, payload.lpipe, indent, start_col, Space.None);
1162 try renderToken(tree, ais, payload.lpipe, Space.None);
12161163 if (payload.ptr_token) |ptr_token| {
1217 try renderToken(tree, stream, ptr_token, indent, start_col, Space.None);
1164 try renderToken(tree, ais, ptr_token, Space.None);
12181165 }
1219 try renderExpression(allocator, stream, tree, indent, start_col, payload.value_symbol, Space.None);
1220 return renderToken(tree, stream, payload.rpipe, indent, start_col, space);
1166 try renderExpression(allocator, ais, tree, payload.value_symbol, Space.None);
1167 return renderToken(tree, ais, payload.rpipe, space);
12211168 },
12221169
12231170 .PointerIndexPayload => {
12241171 const payload = @fieldParentPtr(ast.Node.PointerIndexPayload, "base", base);
12251172
1226 try renderToken(tree, stream, payload.lpipe, indent, start_col, Space.None);
1173 try renderToken(tree, ais, payload.lpipe, Space.None);
12271174 if (payload.ptr_token) |ptr_token| {
1228 try renderToken(tree, stream, ptr_token, indent, start_col, Space.None);
1175 try renderToken(tree, ais, ptr_token, Space.None);
12291176 }
1230 try renderExpression(allocator, stream, tree, indent, start_col, payload.value_symbol, Space.None);
1177 try renderExpression(allocator, ais, tree, payload.value_symbol, Space.None);
12311178
12321179 if (payload.index_symbol) |index_symbol| {
12331180 const comma = tree.nextToken(payload.value_symbol.lastToken());
12341181
1235 try renderToken(tree, stream, comma, indent, start_col, Space.Space);
1236 try renderExpression(allocator, stream, tree, indent, start_col, index_symbol, Space.None);
1182 try renderToken(tree, ais, comma, Space.Space);
1183 try renderExpression(allocator, ais, tree, index_symbol, Space.None);
12371184 }
12381185
1239 return renderToken(tree, stream, payload.rpipe, indent, start_col, space);
1186 return renderToken(tree, ais, payload.rpipe, space);
12401187 },
12411188
12421189 .GroupedExpression => {
12431190 const grouped_expr = @fieldParentPtr(ast.Node.GroupedExpression, "base", base);
12441191
1245 try renderToken(tree, stream, grouped_expr.lparen, indent, start_col, Space.None);
1246 try renderExpression(allocator, stream, tree, indent, start_col, grouped_expr.expr, Space.None);
1247 return renderToken(tree, stream, grouped_expr.rparen, indent, start_col, space);
1192 try renderToken(tree, ais, grouped_expr.lparen, Space.None);
1193 {
1194 ais.pushIndentOneShot();
1195 try renderExpression(allocator, ais, tree, grouped_expr.expr, Space.None);
1196 }
1197 return renderToken(tree, ais, grouped_expr.rparen, space);
12481198 },
12491199
12501200 .FieldInitializer => {
12511201 const field_init = @fieldParentPtr(ast.Node.FieldInitializer, "base", base);
12521202
1253 try renderToken(tree, stream, field_init.period_token, indent, start_col, Space.None); // .
1254 try renderToken(tree, stream, field_init.name_token, indent, start_col, Space.Space); // name
1255 try renderToken(tree, stream, tree.nextToken(field_init.name_token), indent, start_col, Space.Space); // =
1256 return renderExpression(allocator, stream, tree, indent, start_col, field_init.expr, space);
1203 try renderToken(tree, ais, field_init.period_token, Space.None); // .
1204 try renderToken(tree, ais, field_init.name_token, Space.Space); // name
1205 try renderToken(tree, ais, tree.nextToken(field_init.name_token), Space.Space); // =
1206 return renderExpression(allocator, ais, tree, field_init.expr, space);
12571207 },
12581208
12591209 .ContainerDecl => {
12601210 const container_decl = @fieldParentPtr(ast.Node.ContainerDecl, "base", base);
12611211
12621212 if (container_decl.layout_token) |layout_token| {
1263 try renderToken(tree, stream, layout_token, indent, start_col, Space.Space);
1213 try renderToken(tree, ais, layout_token, Space.Space);
12641214 }
12651215
12661216 switch (container_decl.init_arg_expr) {
12671217 .None => {
1268 try renderToken(tree, stream, container_decl.kind_token, indent, start_col, Space.Space); // union
1218 try renderToken(tree, ais, container_decl.kind_token, Space.Space); // union
12691219 },
12701220 .Enum => |enum_tag_type| {
1271 try renderToken(tree, stream, container_decl.kind_token, indent, start_col, Space.None); // union
1221 try renderToken(tree, ais, container_decl.kind_token, Space.None); // union
12721222
12731223 const lparen = tree.nextToken(container_decl.kind_token);
12741224 const enum_token = tree.nextToken(lparen);
12751225
1276 try renderToken(tree, stream, lparen, indent, start_col, Space.None); // (
1277 try renderToken(tree, stream, enum_token, indent, start_col, Space.None); // enum
1226 try renderToken(tree, ais, lparen, Space.None); // (
1227 try renderToken(tree, ais, enum_token, Space.None); // enum
12781228
12791229 if (enum_tag_type) |expr| {
1280 try renderToken(tree, stream, tree.nextToken(enum_token), indent, start_col, Space.None); // (
1281 try renderExpression(allocator, stream, tree, indent, start_col, expr, Space.None);
1230 try renderToken(tree, ais, tree.nextToken(enum_token), Space.None); // (
1231 try renderExpression(allocator, ais, tree, expr, Space.None);
12821232
12831233 const rparen = tree.nextToken(expr.lastToken());
1284 try renderToken(tree, stream, rparen, indent, start_col, Space.None); // )
1285 try renderToken(tree, stream, tree.nextToken(rparen), indent, start_col, Space.Space); // )
1234 try renderToken(tree, ais, rparen, Space.None); // )
1235 try renderToken(tree, ais, tree.nextToken(rparen), Space.Space); // )
12861236 } else {
1287 try renderToken(tree, stream, tree.nextToken(enum_token), indent, start_col, Space.Space); // )
1237 try renderToken(tree, ais, tree.nextToken(enum_token), Space.Space); // )
12881238 }
12891239 },
12901240 .Type => |type_expr| {
1291 try renderToken(tree, stream, container_decl.kind_token, indent, start_col, Space.None); // union
1241 try renderToken(tree, ais, container_decl.kind_token, Space.None); // union
12921242
12931243 const lparen = tree.nextToken(container_decl.kind_token);
12941244 const rparen = tree.nextToken(type_expr.lastToken());
12951245
1296 try renderToken(tree, stream, lparen, indent, start_col, Space.None); // (
1297 try renderExpression(allocator, stream, tree, indent, start_col, type_expr, Space.None);
1298 try renderToken(tree, stream, rparen, indent, start_col, Space.Space); // )
1246 try renderToken(tree, ais, lparen, Space.None); // (
1247 try renderExpression(allocator, ais, tree, type_expr, Space.None);
1248 try renderToken(tree, ais, rparen, Space.Space); // )
12991249 },
13001250 }
13011251
13021252 if (container_decl.fields_and_decls_len == 0) {
1303 try renderToken(tree, stream, container_decl.lbrace_token, indent + indent_delta, start_col, Space.None); // {
1304 return renderToken(tree, stream, container_decl.rbrace_token, indent, start_col, space); // }
1253 {
1254 ais.pushIndentNextLine();
1255 defer ais.popIndent();
1256 try renderToken(tree, ais, container_decl.lbrace_token, Space.None); // {
1257 }
1258 return renderToken(tree, ais, container_decl.rbrace_token, space); // }
13051259 }
13061260
13071261 const src_has_trailing_comma = blk: {
......@@ -1332,43 +1286,39 @@ fn renderExpression(
13321286
13331287 if (src_has_trailing_comma or !src_has_only_fields) {
13341288 // One declaration per line
1335 const new_indent = indent + indent_delta;
1336 try renderToken(tree, stream, container_decl.lbrace_token, new_indent, start_col, .Newline); // {
1289 ais.pushIndentNextLine();
1290 defer ais.popIndent();
1291 try renderToken(tree, ais, container_decl.lbrace_token, .Newline); // {
13371292
13381293 for (fields_and_decls) |decl, i| {
1339 try stream.writeByteNTimes(' ', new_indent);
1340 try renderContainerDecl(allocator, stream, tree, new_indent, start_col, decl, .Newline);
1294 try renderContainerDecl(allocator, ais, tree, decl, .Newline);
13411295
13421296 if (i + 1 < fields_and_decls.len) {
1343 try renderExtraNewline(tree, stream, start_col, fields_and_decls[i + 1]);
1297 try renderExtraNewline(tree, ais, fields_and_decls[i + 1]);
13441298 }
13451299 }
1346
1347 try stream.writeByteNTimes(' ', indent);
13481300 } else if (src_has_newline) {
13491301 // All the declarations on the same line, but place the items on
13501302 // their own line
1351 try renderToken(tree, stream, container_decl.lbrace_token, indent, start_col, .Newline); // {
1303 try renderToken(tree, ais, container_decl.lbrace_token, .Newline); // {
13521304
1353 const new_indent = indent + indent_delta;
1354 try stream.writeByteNTimes(' ', new_indent);
1305 ais.pushIndent();
1306 defer ais.popIndent();
13551307
13561308 for (fields_and_decls) |decl, i| {
13571309 const space_after_decl: Space = if (i + 1 >= fields_and_decls.len) .Newline else .Space;
1358 try renderContainerDecl(allocator, stream, tree, new_indent, start_col, decl, space_after_decl);
1310 try renderContainerDecl(allocator, ais, tree, decl, space_after_decl);
13591311 }
1360
1361 try stream.writeByteNTimes(' ', indent);
13621312 } else {
13631313 // All the declarations on the same line
1364 try renderToken(tree, stream, container_decl.lbrace_token, indent, start_col, .Space); // {
1314 try renderToken(tree, ais, container_decl.lbrace_token, .Space); // {
13651315
13661316 for (fields_and_decls) |decl| {
1367 try renderContainerDecl(allocator, stream, tree, indent, start_col, decl, .Space);
1317 try renderContainerDecl(allocator, ais, tree, decl, .Space);
13681318 }
13691319 }
13701320
1371 return renderToken(tree, stream, container_decl.rbrace_token, indent, start_col, space); // }
1321 return renderToken(tree, ais, container_decl.rbrace_token, space); // }
13721322 },
13731323
13741324 .ErrorSetDecl => {
......@@ -1377,9 +1327,9 @@ fn renderExpression(
13771327 const lbrace = tree.nextToken(err_set_decl.error_token);
13781328
13791329 if (err_set_decl.decls_len == 0) {
1380 try renderToken(tree, stream, err_set_decl.error_token, indent, start_col, Space.None);
1381 try renderToken(tree, stream, lbrace, indent, start_col, Space.None);
1382 return renderToken(tree, stream, err_set_decl.rbrace_token, indent, start_col, space);
1330 try renderToken(tree, ais, err_set_decl.error_token, Space.None);
1331 try renderToken(tree, ais, lbrace, Space.None);
1332 return renderToken(tree, ais, err_set_decl.rbrace_token, space);
13831333 }
13841334
13851335 if (err_set_decl.decls_len == 1) blk: {
......@@ -1393,13 +1343,13 @@ fn renderExpression(
13931343 break :blk;
13941344 }
13951345
1396 try renderToken(tree, stream, err_set_decl.error_token, indent, start_col, Space.None); // error
1397 try renderToken(tree, stream, lbrace, indent, start_col, Space.None); // {
1398 try renderExpression(allocator, stream, tree, indent, start_col, node, Space.None);
1399 return renderToken(tree, stream, err_set_decl.rbrace_token, indent, start_col, space); // }
1346 try renderToken(tree, ais, err_set_decl.error_token, Space.None); // error
1347 try renderToken(tree, ais, lbrace, Space.None); // {
1348 try renderExpression(allocator, ais, tree, node, Space.None);
1349 return renderToken(tree, ais, err_set_decl.rbrace_token, space); // }
14001350 }
14011351
1402 try renderToken(tree, stream, err_set_decl.error_token, indent, start_col, Space.None); // error
1352 try renderToken(tree, ais, err_set_decl.error_token, Space.None); // error
14031353
14041354 const src_has_trailing_comma = blk: {
14051355 const maybe_comma = tree.prevToken(err_set_decl.rbrace_token);
......@@ -1407,72 +1357,66 @@ fn renderExpression(
14071357 };
14081358
14091359 if (src_has_trailing_comma) {
1410 try renderToken(tree, stream, lbrace, indent, start_col, Space.Newline); // {
1411 const new_indent = indent + indent_delta;
1412
1413 const decls = err_set_decl.decls();
1414 for (decls) |node, i| {
1415 try stream.writeByteNTimes(' ', new_indent);
1416
1417 if (i + 1 < decls.len) {
1418 try renderExpression(allocator, stream, tree, new_indent, start_col, node, Space.None);
1419 try renderToken(tree, stream, tree.nextToken(node.lastToken()), new_indent, start_col, Space.Newline); // ,
1420
1421 try renderExtraNewline(tree, stream, start_col, decls[i + 1]);
1422 } else {
1423 try renderExpression(allocator, stream, tree, new_indent, start_col, node, Space.Comma);
1360 {
1361 ais.pushIndent();
1362 defer ais.popIndent();
1363
1364 try renderToken(tree, ais, lbrace, Space.Newline); // {
1365 const decls = err_set_decl.decls();
1366 for (decls) |node, i| {
1367 if (i + 1 < decls.len) {
1368 try renderExpression(allocator, ais, tree, node, Space.None);
1369 try renderToken(tree, ais, tree.nextToken(node.lastToken()), Space.Newline); // ,
1370
1371 try renderExtraNewline(tree, ais, decls[i + 1]);
1372 } else {
1373 try renderExpression(allocator, ais, tree, node, Space.Comma);
1374 }
14241375 }
14251376 }
14261377
1427 try stream.writeByteNTimes(' ', indent);
1428 return renderToken(tree, stream, err_set_decl.rbrace_token, indent, start_col, space); // }
1378 return renderToken(tree, ais, err_set_decl.rbrace_token, space); // }
14291379 } else {
1430 try renderToken(tree, stream, lbrace, indent, start_col, Space.Space); // {
1380 try renderToken(tree, ais, lbrace, Space.Space); // {
14311381
14321382 const decls = err_set_decl.decls();
14331383 for (decls) |node, i| {
14341384 if (i + 1 < decls.len) {
1435 try renderExpression(allocator, stream, tree, indent, start_col, node, Space.None);
1385 try renderExpression(allocator, ais, tree, node, Space.None);
14361386
14371387 const comma_token = tree.nextToken(node.lastToken());
14381388 assert(tree.token_ids[comma_token] == .Comma);
1439 try renderToken(tree, stream, comma_token, indent, start_col, Space.Space); // ,
1440 try renderExtraNewline(tree, stream, start_col, decls[i + 1]);
1389 try renderToken(tree, ais, comma_token, Space.Space); // ,
1390 try renderExtraNewline(tree, ais, decls[i + 1]);
14411391 } else {
1442 try renderExpression(allocator, stream, tree, indent, start_col, node, Space.Space);
1392 try renderExpression(allocator, ais, tree, node, Space.Space);
14431393 }
14441394 }
14451395
1446 return renderToken(tree, stream, err_set_decl.rbrace_token, indent, start_col, space); // }
1396 return renderToken(tree, ais, err_set_decl.rbrace_token, space); // }
14471397 }
14481398 },
14491399
14501400 .ErrorTag => {
14511401 const tag = @fieldParentPtr(ast.Node.ErrorTag, "base", base);
14521402
1453 try renderDocComments(tree, stream, tag, tag.doc_comments, indent, start_col);
1454 return renderToken(tree, stream, tag.name_token, indent, start_col, space); // name
1403 try renderDocComments(tree, ais, tag, tag.doc_comments);
1404 return renderToken(tree, ais, tag.name_token, space); // name
14551405 },
14561406
14571407 .MultilineStringLiteral => {
1458 // TODO: Don't indent in this function, but let the caller indent.
1459 // If this has been implemented, a lot of hacky solutions in i.e. ArrayInit and FunctionCall can be removed
14601408 const multiline_str_literal = @fieldParentPtr(ast.Node.MultilineStringLiteral, "base", base);
14611409
1462 var skip_first_indent = true;
1463 if (tree.token_ids[multiline_str_literal.firstToken() - 1] != .LineComment) {
1464 try stream.print("\n", .{});
1465 skip_first_indent = false;
1466 }
1467
1468 for (multiline_str_literal.lines()) |t| {
1469 if (!skip_first_indent) {
1470 try stream.writeByteNTimes(' ', indent + indent_delta);
1410 {
1411 const locked_indents = ais.lockOneShotIndent();
1412 defer {
1413 var i: u8 = 0;
1414 while (i < locked_indents) : (i += 1) ais.popIndent();
14711415 }
1472 try renderToken(tree, stream, t, indent, start_col, Space.None);
1473 skip_first_indent = false;
1416 try ais.maybeInsertNewline();
1417
1418 for (multiline_str_literal.lines()) |t| try renderToken(tree, ais, t, Space.None);
14741419 }
1475 try stream.writeByteNTimes(' ', indent);
14761420 },
14771421
14781422 .BuiltinCall => {
......@@ -1480,9 +1424,9 @@ fn renderExpression(
14801424
14811425 // TODO remove after 0.7.0 release
14821426 if (mem.eql(u8, tree.tokenSlice(builtin_call.builtin_token), "@OpaqueType"))
1483 return stream.writeAll("@Type(.Opaque)");
1427 return ais.writer().writeAll("@Type(.Opaque)");
14841428
1485 try renderToken(tree, stream, builtin_call.builtin_token, indent, start_col, Space.None); // @name
1429 try renderToken(tree, ais, builtin_call.builtin_token, Space.None); // @name
14861430
14871431 const src_params_trailing_comma = blk: {
14881432 if (builtin_call.params_len < 2) break :blk false;
......@@ -1494,31 +1438,30 @@ fn renderExpression(
14941438 const lparen = tree.nextToken(builtin_call.builtin_token);
14951439
14961440 if (!src_params_trailing_comma) {
1497 try renderToken(tree, stream, lparen, indent, start_col, Space.None); // (
1441 try renderToken(tree, ais, lparen, Space.None); // (
14981442
14991443 // render all on one line, no trailing comma
15001444 const params = builtin_call.params();
15011445 for (params) |param_node, i| {
1502 try renderExpression(allocator, stream, tree, indent, start_col, param_node, Space.None);
1446 try renderExpression(allocator, ais, tree, param_node, Space.None);
15031447
15041448 if (i + 1 < params.len) {
15051449 const comma_token = tree.nextToken(param_node.lastToken());
1506 try renderToken(tree, stream, comma_token, indent, start_col, Space.Space); // ,
1450 try renderToken(tree, ais, comma_token, Space.Space); // ,
15071451 }
15081452 }
15091453 } else {
15101454 // one param per line
1511 const new_indent = indent + indent_delta;
1512 try renderToken(tree, stream, lparen, new_indent, start_col, Space.Newline); // (
1455 ais.pushIndent();
1456 defer ais.popIndent();
1457 try renderToken(tree, ais, lparen, Space.Newline); // (
15131458
15141459 for (builtin_call.params()) |param_node| {
1515 try stream.writeByteNTimes(' ', new_indent);
1516 try renderExpression(allocator, stream, tree, indent, start_col, param_node, Space.Comma);
1460 try renderExpression(allocator, ais, tree, param_node, Space.Comma);
15171461 }
1518 try stream.writeByteNTimes(' ', indent);
15191462 }
15201463
1521 return renderToken(tree, stream, builtin_call.rparen_token, indent, start_col, space); // )
1464 return renderToken(tree, ais, builtin_call.rparen_token, space); // )
15221465 },
15231466
15241467 .FnProto => {
......@@ -1528,24 +1471,24 @@ fn renderExpression(
15281471 const visib_token = tree.token_ids[visib_token_index];
15291472 assert(visib_token == .Keyword_pub or visib_token == .Keyword_export);
15301473
1531 try renderToken(tree, stream, visib_token_index, indent, start_col, Space.Space); // pub
1474 try renderToken(tree, ais, visib_token_index, Space.Space); // pub
15321475 }
15331476
15341477 if (fn_proto.getExternExportInlineToken()) |extern_export_inline_token| {
15351478 if (fn_proto.getIsExternPrototype() == null)
1536 try renderToken(tree, stream, extern_export_inline_token, indent, start_col, Space.Space); // extern/export/inline
1479 try renderToken(tree, ais, extern_export_inline_token, Space.Space); // extern/export/inline
15371480 }
15381481
15391482 if (fn_proto.getLibName()) |lib_name| {
1540 try renderExpression(allocator, stream, tree, indent, start_col, lib_name, Space.Space);
1483 try renderExpression(allocator, ais, tree, lib_name, Space.Space);
15411484 }
15421485
15431486 const lparen = if (fn_proto.getNameToken()) |name_token| blk: {
1544 try renderToken(tree, stream, fn_proto.fn_token, indent, start_col, Space.Space); // fn
1545 try renderToken(tree, stream, name_token, indent, start_col, Space.None); // name
1487 try renderToken(tree, ais, fn_proto.fn_token, Space.Space); // fn
1488 try renderToken(tree, ais, name_token, Space.None); // name
15461489 break :blk tree.nextToken(name_token);
15471490 } else blk: {
1548 try renderToken(tree, stream, fn_proto.fn_token, indent, start_col, Space.Space); // fn
1491 try renderToken(tree, ais, fn_proto.fn_token, Space.Space); // fn
15491492 break :blk tree.nextToken(fn_proto.fn_token);
15501493 };
15511494 assert(tree.token_ids[lparen] == .LParen);
......@@ -1572,47 +1515,45 @@ fn renderExpression(
15721515 };
15731516
15741517 if (!src_params_trailing_comma) {
1575 try renderToken(tree, stream, lparen, indent, start_col, Space.None); // (
1518 try renderToken(tree, ais, lparen, Space.None); // (
15761519
15771520 // render all on one line, no trailing comma
15781521 for (fn_proto.params()) |param_decl, i| {
1579 try renderParamDecl(allocator, stream, tree, indent, start_col, param_decl, Space.None);
1522 try renderParamDecl(allocator, ais, tree, param_decl, Space.None);
15801523
15811524 if (i + 1 < fn_proto.params_len or fn_proto.getVarArgsToken() != null) {
15821525 const comma = tree.nextToken(param_decl.lastToken());
1583 try renderToken(tree, stream, comma, indent, start_col, Space.Space); // ,
1526 try renderToken(tree, ais, comma, Space.Space); // ,
15841527 }
15851528 }
15861529 if (fn_proto.getVarArgsToken()) |var_args_token| {
1587 try renderToken(tree, stream, var_args_token, indent, start_col, Space.None);
1530 try renderToken(tree, ais, var_args_token, Space.None);
15881531 }
15891532 } else {
15901533 // one param per line
1591 const new_indent = indent + indent_delta;
1592 try renderToken(tree, stream, lparen, new_indent, start_col, Space.Newline); // (
1534 ais.pushIndent();
1535 defer ais.popIndent();
1536 try renderToken(tree, ais, lparen, Space.Newline); // (
15931537
15941538 for (fn_proto.params()) |param_decl| {
1595 try stream.writeByteNTimes(' ', new_indent);
1596 try renderParamDecl(allocator, stream, tree, new_indent, start_col, param_decl, Space.Comma);
1539 try renderParamDecl(allocator, ais, tree, param_decl, Space.Comma);
15971540 }
15981541 if (fn_proto.getVarArgsToken()) |var_args_token| {
1599 try stream.writeByteNTimes(' ', new_indent);
1600 try renderToken(tree, stream, var_args_token, new_indent, start_col, Space.Comma);
1542 try renderToken(tree, ais, var_args_token, Space.Comma);
16011543 }
1602 try stream.writeByteNTimes(' ', indent);
16031544 }
16041545
1605 try renderToken(tree, stream, rparen, indent, start_col, Space.Space); // )
1546 try renderToken(tree, ais, rparen, Space.Space); // )
16061547
16071548 if (fn_proto.getAlignExpr()) |align_expr| {
16081549 const align_rparen = tree.nextToken(align_expr.lastToken());
16091550 const align_lparen = tree.prevToken(align_expr.firstToken());
16101551 const align_kw = tree.prevToken(align_lparen);
16111552
1612 try renderToken(tree, stream, align_kw, indent, start_col, Space.None); // align
1613 try renderToken(tree, stream, align_lparen, indent, start_col, Space.None); // (
1614 try renderExpression(allocator, stream, tree, indent, start_col, align_expr, Space.None);
1615 try renderToken(tree, stream, align_rparen, indent, start_col, Space.Space); // )
1553 try renderToken(tree, ais, align_kw, Space.None); // align
1554 try renderToken(tree, ais, align_lparen, Space.None); // (
1555 try renderExpression(allocator, ais, tree, align_expr, Space.None);
1556 try renderToken(tree, ais, align_rparen, Space.Space); // )
16161557 }
16171558
16181559 if (fn_proto.getSectionExpr()) |section_expr| {
......@@ -1620,10 +1561,10 @@ fn renderExpression(
16201561 const section_lparen = tree.prevToken(section_expr.firstToken());
16211562 const section_kw = tree.prevToken(section_lparen);
16221563
1623 try renderToken(tree, stream, section_kw, indent, start_col, Space.None); // section
1624 try renderToken(tree, stream, section_lparen, indent, start_col, Space.None); // (
1625 try renderExpression(allocator, stream, tree, indent, start_col, section_expr, Space.None);
1626 try renderToken(tree, stream, section_rparen, indent, start_col, Space.Space); // )
1564 try renderToken(tree, ais, section_kw, Space.None); // section
1565 try renderToken(tree, ais, section_lparen, Space.None); // (
1566 try renderExpression(allocator, ais, tree, section_expr, Space.None);
1567 try renderToken(tree, ais, section_rparen, Space.Space); // )
16271568 }
16281569
16291570 if (fn_proto.getCallconvExpr()) |callconv_expr| {
......@@ -1631,23 +1572,23 @@ fn renderExpression(
16311572 const callconv_lparen = tree.prevToken(callconv_expr.firstToken());
16321573 const callconv_kw = tree.prevToken(callconv_lparen);
16331574
1634 try renderToken(tree, stream, callconv_kw, indent, start_col, Space.None); // callconv
1635 try renderToken(tree, stream, callconv_lparen, indent, start_col, Space.None); // (
1636 try renderExpression(allocator, stream, tree, indent, start_col, callconv_expr, Space.None);
1637 try renderToken(tree, stream, callconv_rparen, indent, start_col, Space.Space); // )
1575 try renderToken(tree, ais, callconv_kw, Space.None); // callconv
1576 try renderToken(tree, ais, callconv_lparen, Space.None); // (
1577 try renderExpression(allocator, ais, tree, callconv_expr, Space.None);
1578 try renderToken(tree, ais, callconv_rparen, Space.Space); // )
16381579 } else if (fn_proto.getIsExternPrototype() != null) {
1639 try stream.writeAll("callconv(.C) ");
1580 try ais.writer().writeAll("callconv(.C) ");
16401581 } else if (fn_proto.getIsAsync() != null) {
1641 try stream.writeAll("callconv(.Async) ");
1582 try ais.writer().writeAll("callconv(.Async) ");
16421583 }
16431584
16441585 switch (fn_proto.return_type) {
16451586 .Explicit => |node| {
1646 return renderExpression(allocator, stream, tree, indent, start_col, node, space);
1587 return renderExpression(allocator, ais, tree, node, space);
16471588 },
16481589 .InferErrorSet => |node| {
1649 try renderToken(tree, stream, tree.prevToken(node.firstToken()), indent, start_col, Space.None); // !
1650 return renderExpression(allocator, stream, tree, indent, start_col, node, space);
1590 try renderToken(tree, ais, tree.prevToken(node.firstToken()), Space.None); // !
1591 return renderExpression(allocator, ais, tree, node, space);
16511592 },
16521593 .Invalid => unreachable,
16531594 }
......@@ -1657,11 +1598,11 @@ fn renderExpression(
16571598 const anyframe_type = @fieldParentPtr(ast.Node.AnyFrameType, "base", base);
16581599
16591600 if (anyframe_type.result) |result| {
1660 try renderToken(tree, stream, anyframe_type.anyframe_token, indent, start_col, Space.None); // anyframe
1661 try renderToken(tree, stream, result.arrow_token, indent, start_col, Space.None); // ->
1662 return renderExpression(allocator, stream, tree, indent, start_col, result.return_type, space);
1601 try renderToken(tree, ais, anyframe_type.anyframe_token, Space.None); // anyframe
1602 try renderToken(tree, ais, result.arrow_token, Space.None); // ->
1603 return renderExpression(allocator, ais, tree, result.return_type, space);
16631604 } else {
1664 return renderToken(tree, stream, anyframe_type.anyframe_token, indent, start_col, space); // anyframe
1605 return renderToken(tree, ais, anyframe_type.anyframe_token, space); // anyframe
16651606 }
16661607 },
16671608
......@@ -1670,38 +1611,38 @@ fn renderExpression(
16701611 .Switch => {
16711612 const switch_node = @fieldParentPtr(ast.Node.Switch, "base", base);
16721613
1673 try renderToken(tree, stream, switch_node.switch_token, indent, start_col, Space.Space); // switch
1674 try renderToken(tree, stream, tree.nextToken(switch_node.switch_token), indent, start_col, Space.None); // (
1614 try renderToken(tree, ais, switch_node.switch_token, Space.Space); // switch
1615 try renderToken(tree, ais, tree.nextToken(switch_node.switch_token), Space.None); // (
16751616
16761617 const rparen = tree.nextToken(switch_node.expr.lastToken());
16771618 const lbrace = tree.nextToken(rparen);
16781619
16791620 if (switch_node.cases_len == 0) {
1680 try renderExpression(allocator, stream, tree, indent, start_col, switch_node.expr, Space.None);
1681 try renderToken(tree, stream, rparen, indent, start_col, Space.Space); // )
1682 try renderToken(tree, stream, lbrace, indent, start_col, Space.None); // {
1683 return renderToken(tree, stream, switch_node.rbrace, indent, start_col, space); // }
1621 try renderExpression(allocator, ais, tree, switch_node.expr, Space.None);
1622 try renderToken(tree, ais, rparen, Space.Space); // )
1623 try renderToken(tree, ais, lbrace, Space.None); // {
1624 return renderToken(tree, ais, switch_node.rbrace, space); // }
16841625 }
16851626
1686 try renderExpression(allocator, stream, tree, indent, start_col, switch_node.expr, Space.None);
1687
1688 const new_indent = indent + indent_delta;
1627 try renderExpression(allocator, ais, tree, switch_node.expr, Space.None);
1628 try renderToken(tree, ais, rparen, Space.Space); // )
16891629
1690 try renderToken(tree, stream, rparen, indent, start_col, Space.Space); // )
1691 try renderToken(tree, stream, lbrace, new_indent, start_col, Space.Newline); // {
1630 {
1631 ais.pushIndentNextLine();
1632 defer ais.popIndent();
1633 try renderToken(tree, ais, lbrace, Space.Newline); // {
16921634
1693 const cases = switch_node.cases();
1694 for (cases) |node, i| {
1695 try stream.writeByteNTimes(' ', new_indent);
1696 try renderExpression(allocator, stream, tree, new_indent, start_col, node, Space.Comma);
1635 const cases = switch_node.cases();
1636 for (cases) |node, i| {
1637 try renderExpression(allocator, ais, tree, node, Space.Comma);
16971638
1698 if (i + 1 < cases.len) {
1699 try renderExtraNewline(tree, stream, start_col, cases[i + 1]);
1639 if (i + 1 < cases.len) {
1640 try renderExtraNewline(tree, ais, cases[i + 1]);
1641 }
17001642 }
17011643 }
17021644
1703 try stream.writeByteNTimes(' ', indent);
1704 return renderToken(tree, stream, switch_node.rbrace, indent, start_col, space); // }
1645 return renderToken(tree, ais, switch_node.rbrace, space); // }
17051646 },
17061647
17071648 .SwitchCase => {
......@@ -1718,43 +1659,41 @@ fn renderExpression(
17181659 const items = switch_case.items();
17191660 for (items) |node, i| {
17201661 if (i + 1 < items.len) {
1721 try renderExpression(allocator, stream, tree, indent, start_col, node, Space.None);
1662 try renderExpression(allocator, ais, tree, node, Space.None);
17221663
17231664 const comma_token = tree.nextToken(node.lastToken());
1724 try renderToken(tree, stream, comma_token, indent, start_col, Space.Space); // ,
1725 try renderExtraNewline(tree, stream, start_col, items[i + 1]);
1665 try renderToken(tree, ais, comma_token, Space.Space); // ,
1666 try renderExtraNewline(tree, ais, items[i + 1]);
17261667 } else {
1727 try renderExpression(allocator, stream, tree, indent, start_col, node, Space.Space);
1668 try renderExpression(allocator, ais, tree, node, Space.Space);
17281669 }
17291670 }
17301671 } else {
17311672 const items = switch_case.items();
17321673 for (items) |node, i| {
17331674 if (i + 1 < items.len) {
1734 try renderExpression(allocator, stream, tree, indent, start_col, node, Space.None);
1675 try renderExpression(allocator, ais, tree, node, Space.None);
17351676
17361677 const comma_token = tree.nextToken(node.lastToken());
1737 try renderToken(tree, stream, comma_token, indent, start_col, Space.Newline); // ,
1738 try renderExtraNewline(tree, stream, start_col, items[i + 1]);
1739 try stream.writeByteNTimes(' ', indent);
1678 try renderToken(tree, ais, comma_token, Space.Newline); // ,
1679 try renderExtraNewline(tree, ais, items[i + 1]);
17401680 } else {
1741 try renderExpression(allocator, stream, tree, indent, start_col, node, Space.Comma);
1742 try stream.writeByteNTimes(' ', indent);
1681 try renderExpression(allocator, ais, tree, node, Space.Comma);
17431682 }
17441683 }
17451684 }
17461685
1747 try renderToken(tree, stream, switch_case.arrow_token, indent, start_col, Space.Space); // =>
1686 try renderToken(tree, ais, switch_case.arrow_token, Space.Space); // =>
17481687
17491688 if (switch_case.payload) |payload| {
1750 try renderExpression(allocator, stream, tree, indent, start_col, payload, Space.Space);
1689 try renderExpression(allocator, ais, tree, payload, Space.Space);
17511690 }
17521691
1753 return renderExpression(allocator, stream, tree, indent, start_col, switch_case.expr, space);
1692 return renderExpression(allocator, ais, tree, switch_case.expr, space);
17541693 },
17551694 .SwitchElse => {
17561695 const switch_else = @fieldParentPtr(ast.Node.SwitchElse, "base", base);
1757 return renderToken(tree, stream, switch_else.token, indent, start_col, space);
1696 return renderToken(tree, ais, switch_else.token, space);
17581697 },
17591698 .Else => {
17601699 const else_node = @fieldParentPtr(ast.Node.Else, "base", base);
......@@ -1763,37 +1702,37 @@ fn renderExpression(
17631702 const same_line = body_is_block or tree.tokensOnSameLine(else_node.else_token, else_node.body.lastToken());
17641703
17651704 const after_else_space = if (same_line or else_node.payload != null) Space.Space else Space.Newline;
1766 try renderToken(tree, stream, else_node.else_token, indent, start_col, after_else_space);
1705 try renderToken(tree, ais, else_node.else_token, after_else_space);
17671706
17681707 if (else_node.payload) |payload| {
17691708 const payload_space = if (same_line) Space.Space else Space.Newline;
1770 try renderExpression(allocator, stream, tree, indent, start_col, payload, payload_space);
1709 try renderExpression(allocator, ais, tree, payload, payload_space);
17711710 }
17721711
17731712 if (same_line) {
1774 return renderExpression(allocator, stream, tree, indent, start_col, else_node.body, space);
1713 return renderExpression(allocator, ais, tree, else_node.body, space);
1714 } else {
1715 ais.pushIndent();
1716 defer ais.popIndent();
1717 return renderExpression(allocator, ais, tree, else_node.body, space);
17751718 }
1776
1777 try stream.writeByteNTimes(' ', indent + indent_delta);
1778 start_col.* = indent + indent_delta;
1779 return renderExpression(allocator, stream, tree, indent, start_col, else_node.body, space);
17801719 },
17811720
17821721 .While => {
17831722 const while_node = @fieldParentPtr(ast.Node.While, "base", base);
17841723
17851724 if (while_node.label) |label| {
1786 try renderToken(tree, stream, label, indent, start_col, Space.None); // label
1787 try renderToken(tree, stream, tree.nextToken(label), indent, start_col, Space.Space); // :
1725 try renderToken(tree, ais, label, Space.None); // label
1726 try renderToken(tree, ais, tree.nextToken(label), Space.Space); // :
17881727 }
17891728
17901729 if (while_node.inline_token) |inline_token| {
1791 try renderToken(tree, stream, inline_token, indent, start_col, Space.Space); // inline
1730 try renderToken(tree, ais, inline_token, Space.Space); // inline
17921731 }
17931732
1794 try renderToken(tree, stream, while_node.while_token, indent, start_col, Space.Space); // while
1795 try renderToken(tree, stream, tree.nextToken(while_node.while_token), indent, start_col, Space.None); // (
1796 try renderExpression(allocator, stream, tree, indent, start_col, while_node.condition, Space.None);
1733 try renderToken(tree, ais, while_node.while_token, Space.Space); // while
1734 try renderToken(tree, ais, tree.nextToken(while_node.while_token), Space.None); // (
1735 try renderExpression(allocator, ais, tree, while_node.condition, Space.None);
17971736
17981737 const cond_rparen = tree.nextToken(while_node.condition.lastToken());
17991738
......@@ -1815,12 +1754,12 @@ fn renderExpression(
18151754
18161755 {
18171756 const rparen_space = if (while_node.payload != null or while_node.continue_expr != null) Space.Space else block_start_space;
1818 try renderToken(tree, stream, cond_rparen, indent, start_col, rparen_space); // )
1757 try renderToken(tree, ais, cond_rparen, rparen_space); // )
18191758 }
18201759
18211760 if (while_node.payload) |payload| {
1822 const payload_space = if (while_node.continue_expr != null) Space.Space else block_start_space;
1823 try renderExpression(allocator, stream, tree, indent, start_col, payload, payload_space);
1761 const payload_space = Space.Space; //if (while_node.continue_expr != null) Space.Space else block_start_space;
1762 try renderExpression(allocator, ais, tree, payload, payload_space);
18241763 }
18251764
18261765 if (while_node.continue_expr) |continue_expr| {
......@@ -1828,29 +1767,22 @@ fn renderExpression(
18281767 const lparen = tree.prevToken(continue_expr.firstToken());
18291768 const colon = tree.prevToken(lparen);
18301769
1831 try renderToken(tree, stream, colon, indent, start_col, Space.Space); // :
1832 try renderToken(tree, stream, lparen, indent, start_col, Space.None); // (
1770 try renderToken(tree, ais, colon, Space.Space); // :
1771 try renderToken(tree, ais, lparen, Space.None); // (
18331772
1834 try renderExpression(allocator, stream, tree, indent, start_col, continue_expr, Space.None);
1773 try renderExpression(allocator, ais, tree, continue_expr, Space.None);
18351774
1836 try renderToken(tree, stream, rparen, indent, start_col, block_start_space); // )
1775 try renderToken(tree, ais, rparen, block_start_space); // )
18371776 }
18381777
1839 var new_indent = indent;
1840 if (block_start_space == Space.Newline) {
1841 new_indent += indent_delta;
1842 try stream.writeByteNTimes(' ', new_indent);
1843 start_col.* = new_indent;
1778 {
1779 if (!body_is_block) ais.pushIndent();
1780 defer if (!body_is_block) ais.popIndent();
1781 try renderExpression(allocator, ais, tree, while_node.body, after_body_space);
18441782 }
18451783
1846 try renderExpression(allocator, stream, tree, indent, start_col, while_node.body, after_body_space);
1847
18481784 if (while_node.@"else") |@"else"| {
1849 if (after_body_space == Space.Newline) {
1850 try stream.writeByteNTimes(' ', indent);
1851 start_col.* = indent;
1852 }
1853 return renderExpression(allocator, stream, tree, indent, start_col, &@"else".base, space);
1785 return renderExpression(allocator, ais, tree, &@"else".base, space);
18541786 }
18551787 },
18561788
......@@ -1858,17 +1790,17 @@ fn renderExpression(
18581790 const for_node = @fieldParentPtr(ast.Node.For, "base", base);
18591791
18601792 if (for_node.label) |label| {
1861 try renderToken(tree, stream, label, indent, start_col, Space.None); // label
1862 try renderToken(tree, stream, tree.nextToken(label), indent, start_col, Space.Space); // :
1793 try renderToken(tree, ais, label, Space.None); // label
1794 try renderToken(tree, ais, tree.nextToken(label), Space.Space); // :
18631795 }
18641796
18651797 if (for_node.inline_token) |inline_token| {
1866 try renderToken(tree, stream, inline_token, indent, start_col, Space.Space); // inline
1798 try renderToken(tree, ais, inline_token, Space.Space); // inline
18671799 }
18681800
1869 try renderToken(tree, stream, for_node.for_token, indent, start_col, Space.Space); // for
1870 try renderToken(tree, stream, tree.nextToken(for_node.for_token), indent, start_col, Space.None); // (
1871 try renderExpression(allocator, stream, tree, indent, start_col, for_node.array_expr, Space.None);
1801 try renderToken(tree, ais, for_node.for_token, Space.Space); // for
1802 try renderToken(tree, ais, tree.nextToken(for_node.for_token), Space.None); // (
1803 try renderExpression(allocator, ais, tree, for_node.array_expr, Space.None);
18721804
18731805 const rparen = tree.nextToken(for_node.array_expr.lastToken());
18741806
......@@ -1876,10 +1808,10 @@ fn renderExpression(
18761808 const src_one_line_to_body = !body_is_block and tree.tokensOnSameLine(rparen, for_node.body.firstToken());
18771809 const body_on_same_line = body_is_block or src_one_line_to_body;
18781810
1879 try renderToken(tree, stream, rparen, indent, start_col, Space.Space); // )
1811 try renderToken(tree, ais, rparen, Space.Space); // )
18801812
18811813 const space_after_payload = if (body_on_same_line) Space.Space else Space.Newline;
1882 try renderExpression(allocator, stream, tree, indent, start_col, for_node.payload, space_after_payload); // |x|
1814 try renderExpression(allocator, ais, tree, for_node.payload, space_after_payload); // |x|
18831815
18841816 const space_after_body = blk: {
18851817 if (for_node.@"else") |@"else"| {
......@@ -1894,13 +1826,14 @@ fn renderExpression(
18941826 }
18951827 };
18961828
1897 const body_indent = if (body_on_same_line) indent else indent + indent_delta;
1898 if (!body_on_same_line) try stream.writeByteNTimes(' ', body_indent);
1899 try renderExpression(allocator, stream, tree, body_indent, start_col, for_node.body, space_after_body); // { body }
1829 {
1830 if (!body_on_same_line) ais.pushIndent();
1831 defer if (!body_on_same_line) ais.popIndent();
1832 try renderExpression(allocator, ais, tree, for_node.body, space_after_body); // { body }
1833 }
19001834
19011835 if (for_node.@"else") |@"else"| {
1902 if (space_after_body == Space.Newline) try stream.writeByteNTimes(' ', indent);
1903 return renderExpression(allocator, stream, tree, indent, start_col, &@"else".base, space); // else
1836 return renderExpression(allocator, ais, tree, &@"else".base, space); // else
19041837 }
19051838 },
19061839
......@@ -1910,29 +1843,29 @@ fn renderExpression(
19101843 const lparen = tree.nextToken(if_node.if_token);
19111844 const rparen = tree.nextToken(if_node.condition.lastToken());
19121845
1913 try renderToken(tree, stream, if_node.if_token, indent, start_col, Space.Space); // if
1914 try renderToken(tree, stream, lparen, indent, start_col, Space.None); // (
1846 try renderToken(tree, ais, if_node.if_token, Space.Space); // if
1847 try renderToken(tree, ais, lparen, Space.None); // (
19151848
1916 try renderExpression(allocator, stream, tree, indent, start_col, if_node.condition, Space.None); // condition
1849 try renderExpression(allocator, ais, tree, if_node.condition, Space.None); // condition
19171850
19181851 const body_is_if_block = if_node.body.tag == .If;
19191852 const body_is_block = nodeIsBlock(if_node.body);
19201853
19211854 if (body_is_if_block) {
1922 try renderExtraNewline(tree, stream, start_col, if_node.body);
1855 try renderExtraNewline(tree, ais, if_node.body);
19231856 } else if (body_is_block) {
19241857 const after_rparen_space = if (if_node.payload == null) Space.BlockStart else Space.Space;
1925 try renderToken(tree, stream, rparen, indent, start_col, after_rparen_space); // )
1858 try renderToken(tree, ais, rparen, after_rparen_space); // )
19261859
19271860 if (if_node.payload) |payload| {
1928 try renderExpression(allocator, stream, tree, indent, start_col, payload, Space.BlockStart); // |x|
1861 try renderExpression(allocator, ais, tree, payload, Space.BlockStart); // |x|
19291862 }
19301863
19311864 if (if_node.@"else") |@"else"| {
1932 try renderExpression(allocator, stream, tree, indent, start_col, if_node.body, Space.SpaceOrOutdent);
1933 return renderExpression(allocator, stream, tree, indent, start_col, &@"else".base, space);
1865 try renderExpression(allocator, ais, tree, if_node.body, Space.SpaceOrOutdent);
1866 return renderExpression(allocator, ais, tree, &@"else".base, space);
19341867 } else {
1935 return renderExpression(allocator, stream, tree, indent, start_col, if_node.body, space);
1868 return renderExpression(allocator, ais, tree, if_node.body, space);
19361869 }
19371870 }
19381871
......@@ -1940,186 +1873,184 @@ fn renderExpression(
19401873
19411874 if (src_has_newline) {
19421875 const after_rparen_space = if (if_node.payload == null) Space.Newline else Space.Space;
1943 try renderToken(tree, stream, rparen, indent, start_col, after_rparen_space); // )
1876 try renderToken(tree, ais, rparen, after_rparen_space); // )
19441877
19451878 if (if_node.payload) |payload| {
1946 try renderExpression(allocator, stream, tree, indent, start_col, payload, Space.Newline);
1879 try renderExpression(allocator, ais, tree, payload, Space.Newline);
19471880 }
19481881
1949 const new_indent = indent + indent_delta;
1950 try stream.writeByteNTimes(' ', new_indent);
1951
19521882 if (if_node.@"else") |@"else"| {
19531883 const else_is_block = nodeIsBlock(@"else".body);
1954 try renderExpression(allocator, stream, tree, new_indent, start_col, if_node.body, Space.Newline);
1955 try stream.writeByteNTimes(' ', indent);
1884
1885 {
1886 ais.pushIndent();
1887 defer ais.popIndent();
1888 try renderExpression(allocator, ais, tree, if_node.body, Space.Newline);
1889 }
19561890
19571891 if (else_is_block) {
1958 try renderToken(tree, stream, @"else".else_token, indent, start_col, Space.Space); // else
1892 try renderToken(tree, ais, @"else".else_token, Space.Space); // else
19591893
19601894 if (@"else".payload) |payload| {
1961 try renderExpression(allocator, stream, tree, indent, start_col, payload, Space.Space);
1895 try renderExpression(allocator, ais, tree, payload, Space.Space);
19621896 }
19631897
1964 return renderExpression(allocator, stream, tree, indent, start_col, @"else".body, space);
1898 return renderExpression(allocator, ais, tree, @"else".body, space);
19651899 } else {
19661900 const after_else_space = if (@"else".payload == null) Space.Newline else Space.Space;
1967 try renderToken(tree, stream, @"else".else_token, indent, start_col, after_else_space); // else
1901 try renderToken(tree, ais, @"else".else_token, after_else_space); // else
19681902
19691903 if (@"else".payload) |payload| {
1970 try renderExpression(allocator, stream, tree, indent, start_col, payload, Space.Newline);
1904 try renderExpression(allocator, ais, tree, payload, Space.Newline);
19711905 }
1972 try stream.writeByteNTimes(' ', new_indent);
19731906
1974 return renderExpression(allocator, stream, tree, new_indent, start_col, @"else".body, space);
1907 ais.pushIndent();
1908 defer ais.popIndent();
1909 return renderExpression(allocator, ais, tree, @"else".body, space);
19751910 }
19761911 } else {
1977 return renderExpression(allocator, stream, tree, new_indent, start_col, if_node.body, space);
1912 ais.pushIndent();
1913 defer ais.popIndent();
1914 return renderExpression(allocator, ais, tree, if_node.body, space);
19781915 }
19791916 }
19801917
1981 try renderToken(tree, stream, rparen, indent, start_col, Space.Space); // )
1918 // Single line if statement
1919
1920 try renderToken(tree, ais, rparen, Space.Space); // )
19821921
19831922 if (if_node.payload) |payload| {
1984 try renderExpression(allocator, stream, tree, indent, start_col, payload, Space.Space);
1923 try renderExpression(allocator, ais, tree, payload, Space.Space);
19851924 }
19861925
19871926 if (if_node.@"else") |@"else"| {
1988 try renderExpression(allocator, stream, tree, indent, start_col, if_node.body, Space.Space);
1989 try renderToken(tree, stream, @"else".else_token, indent, start_col, Space.Space);
1927 try renderExpression(allocator, ais, tree, if_node.body, Space.Space);
1928 try renderToken(tree, ais, @"else".else_token, Space.Space);
19901929
19911930 if (@"else".payload) |payload| {
1992 try renderExpression(allocator, stream, tree, indent, start_col, payload, Space.Space);
1931 try renderExpression(allocator, ais, tree, payload, Space.Space);
19931932 }
19941933
1995 return renderExpression(allocator, stream, tree, indent, start_col, @"else".body, space);
1934 return renderExpression(allocator, ais, tree, @"else".body, space);
19961935 } else {
1997 return renderExpression(allocator, stream, tree, indent, start_col, if_node.body, space);
1936 return renderExpression(allocator, ais, tree, if_node.body, space);
19981937 }
19991938 },
20001939
20011940 .Asm => {
20021941 const asm_node = @fieldParentPtr(ast.Node.Asm, "base", base);
20031942
2004 try renderToken(tree, stream, asm_node.asm_token, indent, start_col, Space.Space); // asm
1943 try renderToken(tree, ais, asm_node.asm_token, Space.Space); // asm
20051944
20061945 if (asm_node.volatile_token) |volatile_token| {
2007 try renderToken(tree, stream, volatile_token, indent, start_col, Space.Space); // volatile
2008 try renderToken(tree, stream, tree.nextToken(volatile_token), indent, start_col, Space.None); // (
1946 try renderToken(tree, ais, volatile_token, Space.Space); // volatile
1947 try renderToken(tree, ais, tree.nextToken(volatile_token), Space.None); // (
20091948 } else {
2010 try renderToken(tree, stream, tree.nextToken(asm_node.asm_token), indent, start_col, Space.None); // (
1949 try renderToken(tree, ais, tree.nextToken(asm_node.asm_token), Space.None); // (
20111950 }
20121951
2013 if (asm_node.outputs.len == 0 and asm_node.inputs.len == 0 and asm_node.clobbers.len == 0) {
2014 try renderExpression(allocator, stream, tree, indent, start_col, asm_node.template, Space.None);
2015 return renderToken(tree, stream, asm_node.rparen, indent, start_col, space);
2016 }
1952 asmblk: {
1953 ais.pushIndent();
1954 defer ais.popIndent();
20171955
2018 try renderExpression(allocator, stream, tree, indent, start_col, asm_node.template, Space.Newline);
1956 if (asm_node.outputs.len == 0 and asm_node.inputs.len == 0 and asm_node.clobbers.len == 0) {
1957 try renderExpression(allocator, ais, tree, asm_node.template, Space.None);
1958 break :asmblk;
1959 }
20191960
2020 const indent_once = indent + indent_delta;
1961 try renderExpression(allocator, ais, tree, asm_node.template, Space.Newline);
20211962
2022 if (asm_node.template.tag == .MultilineStringLiteral) {
2023 // After rendering a multiline string literal the cursor is
2024 // already offset by indent
2025 try stream.writeByteNTimes(' ', indent_delta);
2026 } else {
2027 try stream.writeByteNTimes(' ', indent_once);
2028 }
1963 ais.setIndentDelta(asm_indent_delta);
1964 defer ais.setIndentDelta(indent_delta);
20291965
2030 const colon1 = tree.nextToken(asm_node.template.lastToken());
2031 const indent_extra = indent_once + 2;
1966 const colon1 = tree.nextToken(asm_node.template.lastToken());
20321967
2033 const colon2 = if (asm_node.outputs.len == 0) blk: {
2034 try renderToken(tree, stream, colon1, indent, start_col, Space.Newline); // :
2035 try stream.writeByteNTimes(' ', indent_once);
1968 const colon2 = if (asm_node.outputs.len == 0) blk: {
1969 try renderToken(tree, ais, colon1, Space.Newline); // :
20361970
2037 break :blk tree.nextToken(colon1);
2038 } else blk: {
2039 try renderToken(tree, stream, colon1, indent, start_col, Space.Space); // :
2040
2041 for (asm_node.outputs) |*asm_output, i| {
2042 if (i + 1 < asm_node.outputs.len) {
2043 const next_asm_output = asm_node.outputs[i + 1];
2044 try renderAsmOutput(allocator, stream, tree, indent_extra, start_col, asm_output, Space.None);
2045
2046 const comma = tree.prevToken(next_asm_output.firstToken());
2047 try renderToken(tree, stream, comma, indent_extra, start_col, Space.Newline); // ,
2048 try renderExtraNewlineToken(tree, stream, start_col, next_asm_output.firstToken());
2049
2050 try stream.writeByteNTimes(' ', indent_extra);
2051 } else if (asm_node.inputs.len == 0 and asm_node.clobbers.len == 0) {
2052 try renderAsmOutput(allocator, stream, tree, indent_extra, start_col, asm_output, Space.Newline);
2053 try stream.writeByteNTimes(' ', indent);
2054 return renderToken(tree, stream, asm_node.rparen, indent, start_col, space);
2055 } else {
2056 try renderAsmOutput(allocator, stream, tree, indent_extra, start_col, asm_output, Space.Newline);
2057 try stream.writeByteNTimes(' ', indent_once);
2058 const comma_or_colon = tree.nextToken(asm_output.lastToken());
2059 break :blk switch (tree.token_ids[comma_or_colon]) {
2060 .Comma => tree.nextToken(comma_or_colon),
2061 else => comma_or_colon,
2062 };
2063 }
2064 }
2065 unreachable;
2066 };
1971 break :blk tree.nextToken(colon1);
1972 } else blk: {
1973 try renderToken(tree, ais, colon1, Space.Space); // :
20671974
2068 const colon3 = if (asm_node.inputs.len == 0) blk: {
2069 try renderToken(tree, stream, colon2, indent, start_col, Space.Newline); // :
2070 try stream.writeByteNTimes(' ', indent_once);
1975 ais.pushIndent();
1976 defer ais.popIndent();
20711977
2072 break :blk tree.nextToken(colon2);
2073 } else blk: {
2074 try renderToken(tree, stream, colon2, indent, start_col, Space.Space); // :
2075
2076 for (asm_node.inputs) |*asm_input, i| {
2077 if (i + 1 < asm_node.inputs.len) {
2078 const next_asm_input = &asm_node.inputs[i + 1];
2079 try renderAsmInput(allocator, stream, tree, indent_extra, start_col, asm_input, Space.None);
2080
2081 const comma = tree.prevToken(next_asm_input.firstToken());
2082 try renderToken(tree, stream, comma, indent_extra, start_col, Space.Newline); // ,
2083 try renderExtraNewlineToken(tree, stream, start_col, next_asm_input.firstToken());
2084
2085 try stream.writeByteNTimes(' ', indent_extra);
2086 } else if (asm_node.clobbers.len == 0) {
2087 try renderAsmInput(allocator, stream, tree, indent_extra, start_col, asm_input, Space.Newline);
2088 try stream.writeByteNTimes(' ', indent);
2089 return renderToken(tree, stream, asm_node.rparen, indent, start_col, space); // )
2090 } else {
2091 try renderAsmInput(allocator, stream, tree, indent_extra, start_col, asm_input, Space.Newline);
2092 try stream.writeByteNTimes(' ', indent_once);
2093 const comma_or_colon = tree.nextToken(asm_input.lastToken());
2094 break :blk switch (tree.token_ids[comma_or_colon]) {
2095 .Comma => tree.nextToken(comma_or_colon),
2096 else => comma_or_colon,
2097 };
1978 for (asm_node.outputs) |*asm_output, i| {
1979 if (i + 1 < asm_node.outputs.len) {
1980 const next_asm_output = asm_node.outputs[i + 1];
1981 try renderAsmOutput(allocator, ais, tree, asm_output, Space.None);
1982
1983 const comma = tree.prevToken(next_asm_output.firstToken());
1984 try renderToken(tree, ais, comma, Space.Newline); // ,
1985 try renderExtraNewlineToken(tree, ais, next_asm_output.firstToken());
1986 } else if (asm_node.inputs.len == 0 and asm_node.clobbers.len == 0) {
1987 try renderAsmOutput(allocator, ais, tree, asm_output, Space.Newline);
1988 break :asmblk;
1989 } else {
1990 try renderAsmOutput(allocator, ais, tree, asm_output, Space.Newline);
1991 const comma_or_colon = tree.nextToken(asm_output.lastToken());
1992 break :blk switch (tree.token_ids[comma_or_colon]) {
1993 .Comma => tree.nextToken(comma_or_colon),
1994 else => comma_or_colon,
1995 };
1996 }
20981997 }
2099 }
2100 unreachable;
2101 };
1998 unreachable;
1999 };
21022000
2103 try renderToken(tree, stream, colon3, indent, start_col, Space.Space); // :
2001 const colon3 = if (asm_node.inputs.len == 0) blk: {
2002 try renderToken(tree, ais, colon2, Space.Newline); // :
2003 break :blk tree.nextToken(colon2);
2004 } else blk: {
2005 try renderToken(tree, ais, colon2, Space.Space); // :
2006 ais.pushIndent();
2007 defer ais.popIndent();
2008 for (asm_node.inputs) |*asm_input, i| {
2009 if (i + 1 < asm_node.inputs.len) {
2010 const next_asm_input = &asm_node.inputs[i + 1];
2011 try renderAsmInput(allocator, ais, tree, asm_input, Space.None);
2012
2013 const comma = tree.prevToken(next_asm_input.firstToken());
2014 try renderToken(tree, ais, comma, Space.Newline); // ,
2015 try renderExtraNewlineToken(tree, ais, next_asm_input.firstToken());
2016 } else if (asm_node.clobbers.len == 0) {
2017 try renderAsmInput(allocator, ais, tree, asm_input, Space.Newline);
2018 break :asmblk;
2019 } else {
2020 try renderAsmInput(allocator, ais, tree, asm_input, Space.Newline);
2021 const comma_or_colon = tree.nextToken(asm_input.lastToken());
2022 break :blk switch (tree.token_ids[comma_or_colon]) {
2023 .Comma => tree.nextToken(comma_or_colon),
2024 else => comma_or_colon,
2025 };
2026 }
2027 }
2028 unreachable;
2029 };
21042030
2105 for (asm_node.clobbers) |clobber_node, i| {
2106 if (i + 1 >= asm_node.clobbers.len) {
2107 try renderExpression(allocator, stream, tree, indent_extra, start_col, clobber_node, Space.Newline);
2108 try stream.writeByteNTimes(' ', indent);
2109 return renderToken(tree, stream, asm_node.rparen, indent, start_col, space);
2110 } else {
2111 try renderExpression(allocator, stream, tree, indent_extra, start_col, clobber_node, Space.None);
2112 const comma = tree.nextToken(clobber_node.lastToken());
2113 try renderToken(tree, stream, comma, indent_once, start_col, Space.Space); // ,
2031 try renderToken(tree, ais, colon3, Space.Space); // :
2032 ais.pushIndent();
2033 defer ais.popIndent();
2034 for (asm_node.clobbers) |clobber_node, i| {
2035 if (i + 1 >= asm_node.clobbers.len) {
2036 try renderExpression(allocator, ais, tree, clobber_node, Space.Newline);
2037 break :asmblk;
2038 } else {
2039 try renderExpression(allocator, ais, tree, clobber_node, Space.None);
2040 const comma = tree.nextToken(clobber_node.lastToken());
2041 try renderToken(tree, ais, comma, Space.Space); // ,
2042 }
21142043 }
21152044 }
2045
2046 return renderToken(tree, ais, asm_node.rparen, space);
21162047 },
21172048
21182049 .EnumLiteral => {
21192050 const enum_literal = @fieldParentPtr(ast.Node.EnumLiteral, "base", base);
21202051
2121 try renderToken(tree, stream, enum_literal.dot, indent, start_col, Space.None); // .
2122 return renderToken(tree, stream, enum_literal.name, indent, start_col, space); // name
2052 try renderToken(tree, ais, enum_literal.dot, Space.None); // .
2053 return renderToken(tree, ais, enum_literal.name, space); // name
21232054 },
21242055
21252056 .ContainerField,
......@@ -2133,118 +2064,115 @@ fn renderExpression(
21332064
21342065fn renderArrayType(
21352066 allocator: *mem.Allocator,
2136 stream: anytype,
2067 ais: anytype,
21372068 tree: *ast.Tree,
2138 indent: usize,
2139 start_col: *usize,
21402069 lbracket: ast.TokenIndex,
21412070 rhs: *ast.Node,
21422071 len_expr: *ast.Node,
21432072 opt_sentinel: ?*ast.Node,
21442073 space: Space,
2145) (@TypeOf(stream).Error || Error)!void {
2074) (@TypeOf(ais.*).Error || Error)!void {
21462075 const rbracket = tree.nextToken(if (opt_sentinel) |sentinel|
21472076 sentinel.lastToken()
21482077 else
21492078 len_expr.lastToken());
21502079
2151 try renderToken(tree, stream, lbracket, indent, start_col, Space.None); // [
2152
21532080 const starts_with_comment = tree.token_ids[lbracket + 1] == .LineComment;
21542081 const ends_with_comment = tree.token_ids[rbracket - 1] == .LineComment;
2155 const new_indent = if (ends_with_comment) indent + indent_delta else indent;
21562082 const new_space = if (ends_with_comment) Space.Newline else Space.None;
2157 try renderExpression(allocator, stream, tree, new_indent, start_col, len_expr, new_space);
2158 if (starts_with_comment) {
2159 try stream.writeByte('\n');
2160 }
2161 if (ends_with_comment or starts_with_comment) {
2162 try stream.writeByteNTimes(' ', indent);
2163 }
2164 if (opt_sentinel) |sentinel| {
2165 const colon_token = tree.prevToken(sentinel.firstToken());
2166 try renderToken(tree, stream, colon_token, indent, start_col, Space.None); // :
2167 try renderExpression(allocator, stream, tree, indent, start_col, sentinel, Space.None);
2083 {
2084 const do_indent = (starts_with_comment or ends_with_comment);
2085 if (do_indent) ais.pushIndent();
2086 defer if (do_indent) ais.popIndent();
2087
2088 try renderToken(tree, ais, lbracket, Space.None); // [
2089 try renderExpression(allocator, ais, tree, len_expr, new_space);
2090
2091 if (starts_with_comment) {
2092 try ais.maybeInsertNewline();
2093 }
2094 if (opt_sentinel) |sentinel| {
2095 const colon_token = tree.prevToken(sentinel.firstToken());
2096 try renderToken(tree, ais, colon_token, Space.None); // :
2097 try renderExpression(allocator, ais, tree, sentinel, Space.None);
2098 }
2099 if (starts_with_comment) {
2100 try ais.maybeInsertNewline();
2101 }
21682102 }
2169 try renderToken(tree, stream, rbracket, indent, start_col, Space.None); // ]
2103 try renderToken(tree, ais, rbracket, Space.None); // ]
21702104
2171 return renderExpression(allocator, stream, tree, indent, start_col, rhs, space);
2105 return renderExpression(allocator, ais, tree, rhs, space);
21722106}
21732107
21742108fn renderAsmOutput(
21752109 allocator: *mem.Allocator,
2176 stream: anytype,
2110 ais: anytype,
21772111 tree: *ast.Tree,
2178 indent: usize,
2179 start_col: *usize,
21802112 asm_output: *const ast.Node.Asm.Output,
21812113 space: Space,
2182) (@TypeOf(stream).Error || Error)!void {
2183 try stream.writeAll("[");
2184 try renderExpression(allocator, stream, tree, indent, start_col, asm_output.symbolic_name, Space.None);
2185 try stream.writeAll("] ");
2186 try renderExpression(allocator, stream, tree, indent, start_col, asm_output.constraint, Space.None);
2187 try stream.writeAll(" (");
2114) (@TypeOf(ais.*).Error || Error)!void {
2115 try ais.writer().writeAll("[");
2116 try renderExpression(allocator, ais, tree, asm_output.symbolic_name, Space.None);
2117 try ais.writer().writeAll("] ");
2118 try renderExpression(allocator, ais, tree, asm_output.constraint, Space.None);
2119 try ais.writer().writeAll(" (");
21882120
21892121 switch (asm_output.kind) {
21902122 ast.Node.Asm.Output.Kind.Variable => |variable_name| {
2191 try renderExpression(allocator, stream, tree, indent, start_col, &variable_name.base, Space.None);
2123 try renderExpression(allocator, ais, tree, &variable_name.base, Space.None);
21922124 },
21932125 ast.Node.Asm.Output.Kind.Return => |return_type| {
2194 try stream.writeAll("-> ");
2195 try renderExpression(allocator, stream, tree, indent, start_col, return_type, Space.None);
2126 try ais.writer().writeAll("-> ");
2127 try renderExpression(allocator, ais, tree, return_type, Space.None);
21962128 },
21972129 }
21982130
2199 return renderToken(tree, stream, asm_output.lastToken(), indent, start_col, space); // )
2131 return renderToken(tree, ais, asm_output.lastToken(), space); // )
22002132}
22012133
22022134fn renderAsmInput(
22032135 allocator: *mem.Allocator,
2204 stream: anytype,
2136 ais: anytype,
22052137 tree: *ast.Tree,
2206 indent: usize,
2207 start_col: *usize,
22082138 asm_input: *const ast.Node.Asm.Input,
22092139 space: Space,
2210) (@TypeOf(stream).Error || Error)!void {
2211 try stream.writeAll("[");
2212 try renderExpression(allocator, stream, tree, indent, start_col, asm_input.symbolic_name, Space.None);
2213 try stream.writeAll("] ");
2214 try renderExpression(allocator, stream, tree, indent, start_col, asm_input.constraint, Space.None);
2215 try stream.writeAll(" (");
2216 try renderExpression(allocator, stream, tree, indent, start_col, asm_input.expr, Space.None);
2217 return renderToken(tree, stream, asm_input.lastToken(), indent, start_col, space); // )
2140) (@TypeOf(ais.*).Error || Error)!void {
2141 try ais.writer().writeAll("[");
2142 try renderExpression(allocator, ais, tree, asm_input.symbolic_name, Space.None);
2143 try ais.writer().writeAll("] ");
2144 try renderExpression(allocator, ais, tree, asm_input.constraint, Space.None);
2145 try ais.writer().writeAll(" (");
2146 try renderExpression(allocator, ais, tree, asm_input.expr, Space.None);
2147 return renderToken(tree, ais, asm_input.lastToken(), space); // )
22182148}
22192149
22202150fn renderVarDecl(
22212151 allocator: *mem.Allocator,
2222 stream: anytype,
2152 ais: anytype,
22232153 tree: *ast.Tree,
2224 indent: usize,
2225 start_col: *usize,
22262154 var_decl: *ast.Node.VarDecl,
2227) (@TypeOf(stream).Error || Error)!void {
2155) (@TypeOf(ais.*).Error || Error)!void {
22282156 if (var_decl.getVisibToken()) |visib_token| {
2229 try renderToken(tree, stream, visib_token, indent, start_col, Space.Space); // pub
2157 try renderToken(tree, ais, visib_token, Space.Space); // pub
22302158 }
22312159
22322160 if (var_decl.getExternExportToken()) |extern_export_token| {
2233 try renderToken(tree, stream, extern_export_token, indent, start_col, Space.Space); // extern
2161 try renderToken(tree, ais, extern_export_token, Space.Space); // extern
22342162
22352163 if (var_decl.getLibName()) |lib_name| {
2236 try renderExpression(allocator, stream, tree, indent, start_col, lib_name, Space.Space); // "lib"
2164 try renderExpression(allocator, ais, tree, lib_name, Space.Space); // "lib"
22372165 }
22382166 }
22392167
22402168 if (var_decl.getComptimeToken()) |comptime_token| {
2241 try renderToken(tree, stream, comptime_token, indent, start_col, Space.Space); // comptime
2169 try renderToken(tree, ais, comptime_token, Space.Space); // comptime
22422170 }
22432171
22442172 if (var_decl.getThreadLocalToken()) |thread_local_token| {
2245 try renderToken(tree, stream, thread_local_token, indent, start_col, Space.Space); // threadlocal
2173 try renderToken(tree, ais, thread_local_token, Space.Space); // threadlocal
22462174 }
2247 try renderToken(tree, stream, var_decl.mut_token, indent, start_col, Space.Space); // var
2175 try renderToken(tree, ais, var_decl.mut_token, Space.Space); // var
22482176
22492177 const name_space = if (var_decl.getTypeNode() == null and
22502178 (var_decl.getAlignNode() != null or
......@@ -2253,95 +2181,92 @@ fn renderVarDecl(
22532181 Space.Space
22542182 else
22552183 Space.None;
2256 try renderToken(tree, stream, var_decl.name_token, indent, start_col, name_space);
2184 try renderToken(tree, ais, var_decl.name_token, name_space);
22572185
22582186 if (var_decl.getTypeNode()) |type_node| {
2259 try renderToken(tree, stream, tree.nextToken(var_decl.name_token), indent, start_col, Space.Space);
2187 try renderToken(tree, ais, tree.nextToken(var_decl.name_token), Space.Space);
22602188 const s = if (var_decl.getAlignNode() != null or
22612189 var_decl.getSectionNode() != null or
22622190 var_decl.getInitNode() != null) Space.Space else Space.None;
2263 try renderExpression(allocator, stream, tree, indent, start_col, type_node, s);
2191 try renderExpression(allocator, ais, tree, type_node, s);
22642192 }
22652193
22662194 if (var_decl.getAlignNode()) |align_node| {
22672195 const lparen = tree.prevToken(align_node.firstToken());
22682196 const align_kw = tree.prevToken(lparen);
22692197 const rparen = tree.nextToken(align_node.lastToken());
2270 try renderToken(tree, stream, align_kw, indent, start_col, Space.None); // align
2271 try renderToken(tree, stream, lparen, indent, start_col, Space.None); // (
2272 try renderExpression(allocator, stream, tree, indent, start_col, align_node, Space.None);
2198 try renderToken(tree, ais, align_kw, Space.None); // align
2199 try renderToken(tree, ais, lparen, Space.None); // (
2200 try renderExpression(allocator, ais, tree, align_node, Space.None);
22732201 const s = if (var_decl.getSectionNode() != null or var_decl.getInitNode() != null) Space.Space else Space.None;
2274 try renderToken(tree, stream, rparen, indent, start_col, s); // )
2202 try renderToken(tree, ais, rparen, s); // )
22752203 }
22762204
22772205 if (var_decl.getSectionNode()) |section_node| {
22782206 const lparen = tree.prevToken(section_node.firstToken());
22792207 const section_kw = tree.prevToken(lparen);
22802208 const rparen = tree.nextToken(section_node.lastToken());
2281 try renderToken(tree, stream, section_kw, indent, start_col, Space.None); // linksection
2282 try renderToken(tree, stream, lparen, indent, start_col, Space.None); // (
2283 try renderExpression(allocator, stream, tree, indent, start_col, section_node, Space.None);
2209 try renderToken(tree, ais, section_kw, Space.None); // linksection
2210 try renderToken(tree, ais, lparen, Space.None); // (
2211 try renderExpression(allocator, ais, tree, section_node, Space.None);
22842212 const s = if (var_decl.getInitNode() != null) Space.Space else Space.None;
2285 try renderToken(tree, stream, rparen, indent, start_col, s); // )
2213 try renderToken(tree, ais, rparen, s); // )
22862214 }
22872215
22882216 if (var_decl.getInitNode()) |init_node| {
22892217 const s = if (init_node.tag == .MultilineStringLiteral) Space.None else Space.Space;
2290 try renderToken(tree, stream, var_decl.getEqToken().?, indent, start_col, s); // =
2291 try renderExpression(allocator, stream, tree, indent, start_col, init_node, Space.None);
2218 try renderToken(tree, ais, var_decl.getEqToken().?, s); // =
2219 ais.pushIndentOneShot();
2220 try renderExpression(allocator, ais, tree, init_node, Space.None);
22922221 }
22932222
2294 try renderToken(tree, stream, var_decl.semicolon_token, indent, start_col, Space.Newline);
2223 try renderToken(tree, ais, var_decl.semicolon_token, Space.Newline);
22952224}
22962225
22972226fn renderParamDecl(
22982227 allocator: *mem.Allocator,
2299 stream: anytype,
2228 ais: anytype,
23002229 tree: *ast.Tree,
2301 indent: usize,
2302 start_col: *usize,
23032230 param_decl: ast.Node.FnProto.ParamDecl,
23042231 space: Space,
2305) (@TypeOf(stream).Error || Error)!void {
2306 try renderDocComments(tree, stream, param_decl, param_decl.doc_comments, indent, start_col);
2232) (@TypeOf(ais.*).Error || Error)!void {
2233 try renderDocComments(tree, ais, param_decl, param_decl.doc_comments);
23072234
23082235 if (param_decl.comptime_token) |comptime_token| {
2309 try renderToken(tree, stream, comptime_token, indent, start_col, Space.Space);
2236 try renderToken(tree, ais, comptime_token, Space.Space);
23102237 }
23112238 if (param_decl.noalias_token) |noalias_token| {
2312 try renderToken(tree, stream, noalias_token, indent, start_col, Space.Space);
2239 try renderToken(tree, ais, noalias_token, Space.Space);
23132240 }
23142241 if (param_decl.name_token) |name_token| {
2315 try renderToken(tree, stream, name_token, indent, start_col, Space.None);
2316 try renderToken(tree, stream, tree.nextToken(name_token), indent, start_col, Space.Space); // :
2242 try renderToken(tree, ais, name_token, Space.None);
2243 try renderToken(tree, ais, tree.nextToken(name_token), Space.Space); // :
23172244 }
23182245 switch (param_decl.param_type) {
2319 .any_type, .type_expr => |node| try renderExpression(allocator, stream, tree, indent, start_col, node, space),
2246 .any_type, .type_expr => |node| try renderExpression(allocator, ais, tree, node, space),
23202247 }
23212248}
23222249
23232250fn renderStatement(
23242251 allocator: *mem.Allocator,
2325 stream: anytype,
2252 ais: anytype,
23262253 tree: *ast.Tree,
2327 indent: usize,
2328 start_col: *usize,
23292254 base: *ast.Node,
2330) (@TypeOf(stream).Error || Error)!void {
2255) (@TypeOf(ais.*).Error || Error)!void {
23312256 switch (base.tag) {
23322257 .VarDecl => {
23332258 const var_decl = @fieldParentPtr(ast.Node.VarDecl, "base", base);
2334 try renderVarDecl(allocator, stream, tree, indent, start_col, var_decl);
2259 try renderVarDecl(allocator, ais, tree, var_decl);
23352260 },
23362261 else => {
23372262 if (base.requireSemiColon()) {
2338 try renderExpression(allocator, stream, tree, indent, start_col, base, Space.None);
2263 try renderExpression(allocator, ais, tree, base, Space.None);
23392264
23402265 const semicolon_index = tree.nextToken(base.lastToken());
23412266 assert(tree.token_ids[semicolon_index] == .Semicolon);
2342 try renderToken(tree, stream, semicolon_index, indent, start_col, Space.Newline);
2267 try renderToken(tree, ais, semicolon_index, Space.Newline);
23432268 } else {
2344 try renderExpression(allocator, stream, tree, indent, start_col, base, Space.Newline);
2269 try renderExpression(allocator, ais, tree, base, Space.Newline);
23452270 }
23462271 },
23472272 }
......@@ -2360,24 +2285,19 @@ const Space = enum {
23602285
23612286fn renderTokenOffset(
23622287 tree: *ast.Tree,
2363 stream: anytype,
2288 ais: anytype,
23642289 token_index: ast.TokenIndex,
2365 indent: usize,
2366 start_col: *usize,
23672290 space: Space,
23682291 token_skip_bytes: usize,
2369) (@TypeOf(stream).Error || Error)!void {
2292) (@TypeOf(ais.*).Error || Error)!void {
23702293 if (space == Space.BlockStart) {
2371 if (start_col.* < indent + indent_delta)
2372 return renderToken(tree, stream, token_index, indent, start_col, Space.Space);
2373 try renderToken(tree, stream, token_index, indent, start_col, Space.Newline);
2374 try stream.writeByteNTimes(' ', indent);
2375 start_col.* = indent;
2376 return;
2294 // If placing the lbrace on the current line would cause an uggly gap then put the lbrace on the next line
2295 const new_space = if (ais.isLineOverIndented()) Space.Newline else Space.Space;
2296 return renderToken(tree, ais, token_index, new_space);
23772297 }
23782298
23792299 var token_loc = tree.token_locs[token_index];
2380 try stream.writeAll(mem.trimRight(u8, tree.tokenSliceLoc(token_loc)[token_skip_bytes..], " "));
2300 try ais.writer().writeAll(mem.trimRight(u8, tree.tokenSliceLoc(token_loc)[token_skip_bytes..], " "));
23812301
23822302 if (space == Space.NoComment)
23832303 return;
......@@ -2386,20 +2306,20 @@ fn renderTokenOffset(
23862306 var next_token_loc = tree.token_locs[token_index + 1];
23872307
23882308 if (space == Space.Comma) switch (next_token_id) {
2389 .Comma => return renderToken(tree, stream, token_index + 1, indent, start_col, Space.Newline),
2309 .Comma => return renderToken(tree, ais, token_index + 1, Space.Newline),
23902310 .LineComment => {
2391 try stream.writeAll(", ");
2392 return renderToken(tree, stream, token_index + 1, indent, start_col, Space.Newline);
2311 try ais.writer().writeAll(", ");
2312 return renderToken(tree, ais, token_index + 1, Space.Newline);
23932313 },
23942314 else => {
23952315 if (token_index + 2 < tree.token_ids.len and
23962316 tree.token_ids[token_index + 2] == .MultilineStringLiteralLine)
23972317 {
2398 try stream.writeAll(",");
2318 try ais.writer().writeAll(",");
23992319 return;
24002320 } else {
2401 try stream.writeAll(",\n");
2402 start_col.* = 0;
2321 try ais.writer().writeAll(",");
2322 try ais.insertNewline();
24032323 return;
24042324 }
24052325 },
......@@ -2423,15 +2343,14 @@ fn renderTokenOffset(
24232343 if (next_token_id == .MultilineStringLiteralLine) {
24242344 return;
24252345 } else {
2426 try stream.writeAll("\n");
2427 start_col.* = 0;
2346 try ais.insertNewline();
24282347 return;
24292348 }
24302349 },
24312350 Space.Space, Space.SpaceOrOutdent => {
24322351 if (next_token_id == .MultilineStringLiteralLine)
24332352 return;
2434 try stream.writeByte(' ');
2353 try ais.writer().writeByte(' ');
24352354 return;
24362355 },
24372356 Space.NoComment, Space.Comma, Space.BlockStart => unreachable,
......@@ -2448,8 +2367,7 @@ fn renderTokenOffset(
24482367 next_token_id = tree.token_ids[token_index + offset];
24492368 next_token_loc = tree.token_locs[token_index + offset];
24502369 if (next_token_id != .LineComment) {
2451 try stream.writeByte('\n');
2452 start_col.* = 0;
2370 try ais.insertNewline();
24532371 return;
24542372 }
24552373 },
......@@ -2462,7 +2380,7 @@ fn renderTokenOffset(
24622380
24632381 var loc = tree.tokenLocationLoc(token_loc.end, next_token_loc);
24642382 if (loc.line == 0) {
2465 try stream.print(" {}", .{mem.trimRight(u8, tree.tokenSliceLoc(next_token_loc), " ")});
2383 try ais.writer().print(" {}", .{mem.trimRight(u8, tree.tokenSliceLoc(next_token_loc), " ")});
24662384 offset = 2;
24672385 token_loc = next_token_loc;
24682386 next_token_loc = tree.token_locs[token_index + offset];
......@@ -2470,26 +2388,16 @@ fn renderTokenOffset(
24702388 if (next_token_id != .LineComment) {
24712389 switch (space) {
24722390 Space.None, Space.Space => {
2473 try stream.writeByte('\n');
2474 const after_comment_token = tree.token_ids[token_index + offset];
2475 const next_line_indent = switch (after_comment_token) {
2476 .RParen, .RBrace, .RBracket => indent,
2477 else => indent + indent_delta,
2478 };
2479 try stream.writeByteNTimes(' ', next_line_indent);
2480 start_col.* = next_line_indent;
2391 try ais.insertNewline();
24812392 },
24822393 Space.SpaceOrOutdent => {
2483 try stream.writeByte('\n');
2484 try stream.writeByteNTimes(' ', indent);
2485 start_col.* = indent;
2394 try ais.insertNewline();
24862395 },
24872396 Space.Newline => {
24882397 if (next_token_id == .MultilineStringLiteralLine) {
24892398 return;
24902399 } else {
2491 try stream.writeAll("\n");
2492 start_col.* = 0;
2400 try ais.insertNewline();
24932401 return;
24942402 }
24952403 },
......@@ -2505,10 +2413,9 @@ fn renderTokenOffset(
25052413 // translate-c doesn't generate correct newlines
25062414 // in generated code (loc.line == 0) so treat that case
25072415 // as though there was meant to be a newline between the tokens
2508 const newline_count = if (loc.line <= 1) @as(u8, 1) else @as(u8, 2);
2509 try stream.writeByteNTimes('\n', newline_count);
2510 try stream.writeByteNTimes(' ', indent);
2511 try stream.writeAll(mem.trimRight(u8, tree.tokenSliceLoc(next_token_loc), " "));
2416 var newline_count = if (loc.line <= 1) @as(u8, 1) else @as(u8, 2);
2417 while (newline_count > 0) : (newline_count -= 1) try ais.insertNewline();
2418 try ais.writer().writeAll(mem.trimRight(u8, tree.tokenSliceLoc(next_token_loc), " "));
25122419
25132420 offset += 1;
25142421 token_loc = next_token_loc;
......@@ -2520,32 +2427,15 @@ fn renderTokenOffset(
25202427 if (next_token_id == .MultilineStringLiteralLine) {
25212428 return;
25222429 } else {
2523 try stream.writeAll("\n");
2524 start_col.* = 0;
2430 try ais.insertNewline();
25252431 return;
25262432 }
25272433 },
25282434 Space.None, Space.Space => {
2529 try stream.writeByte('\n');
2530
2531 const after_comment_token = tree.token_ids[token_index + offset];
2532 const next_line_indent = switch (after_comment_token) {
2533 .RParen, .RBrace, .RBracket => blk: {
2534 if (indent > indent_delta) {
2535 break :blk indent - indent_delta;
2536 } else {
2537 break :blk 0;
2538 }
2539 },
2540 else => indent,
2541 };
2542 try stream.writeByteNTimes(' ', next_line_indent);
2543 start_col.* = next_line_indent;
2435 try ais.insertNewline();
25442436 },
25452437 Space.SpaceOrOutdent => {
2546 try stream.writeByte('\n');
2547 try stream.writeByteNTimes(' ', indent);
2548 start_col.* = indent;
2438 try ais.insertNewline();
25492439 },
25502440 Space.NoNewline => {},
25512441 Space.NoComment, Space.Comma, Space.BlockStart => unreachable,
......@@ -2558,46 +2448,38 @@ fn renderTokenOffset(
25582448
25592449fn renderToken(
25602450 tree: *ast.Tree,
2561 stream: anytype,
2451 ais: anytype,
25622452 token_index: ast.TokenIndex,
2563 indent: usize,
2564 start_col: *usize,
25652453 space: Space,
2566) (@TypeOf(stream).Error || Error)!void {
2567 return renderTokenOffset(tree, stream, token_index, indent, start_col, space, 0);
2454) (@TypeOf(ais.*).Error || Error)!void {
2455 return renderTokenOffset(tree, ais, token_index, space, 0);
25682456}
25692457
25702458fn renderDocComments(
25712459 tree: *ast.Tree,
2572 stream: anytype,
2460 ais: anytype,
25732461 node: anytype,
25742462 doc_comments: ?*ast.Node.DocComment,
2575 indent: usize,
2576 start_col: *usize,
2577) (@TypeOf(stream).Error || Error)!void {
2463) (@TypeOf(ais.*).Error || Error)!void {
25782464 const comment = doc_comments orelse return;
2579 return renderDocCommentsToken(tree, stream, comment, node.firstToken(), indent, start_col);
2465 return renderDocCommentsToken(tree, ais, comment, node.firstToken());
25802466}
25812467
25822468fn renderDocCommentsToken(
25832469 tree: *ast.Tree,
2584 stream: anytype,
2470 ais: anytype,
25852471 comment: *ast.Node.DocComment,
25862472 first_token: ast.TokenIndex,
2587 indent: usize,
2588 start_col: *usize,
2589) (@TypeOf(stream).Error || Error)!void {
2473) (@TypeOf(ais.*).Error || Error)!void {
25902474 var tok_i = comment.first_line;
25912475 while (true) : (tok_i += 1) {
25922476 switch (tree.token_ids[tok_i]) {
25932477 .DocComment, .ContainerDocComment => {
25942478 if (comment.first_line < first_token) {
2595 try renderToken(tree, stream, tok_i, indent, start_col, Space.Newline);
2596 try stream.writeByteNTimes(' ', indent);
2479 try renderToken(tree, ais, tok_i, Space.Newline);
25972480 } else {
2598 try renderToken(tree, stream, tok_i, indent, start_col, Space.NoComment);
2599 try stream.writeAll("\n");
2600 try stream.writeByteNTimes(' ', indent);
2481 try renderToken(tree, ais, tok_i, Space.NoComment);
2482 try ais.insertNewline();
26012483 }
26022484 },
26032485 .LineComment => continue,
......@@ -2669,41 +2551,10 @@ fn nodeCausesSliceOpSpace(base: *ast.Node) bool {
26692551 };
26702552}
26712553
2672/// A `std.io.OutStream` that returns whether the given character has been written to it.
2673/// The contents are not written to anything.
2674const FindByteOutStream = struct {
2675 byte_found: bool,
2676 byte: u8,
2677
2678 pub const Error = error{};
2679 pub const OutStream = std.io.OutStream(*FindByteOutStream, Error, write);
2680
2681 pub fn init(byte: u8) FindByteOutStream {
2682 return FindByteOutStream{
2683 .byte = byte,
2684 .byte_found = false,
2685 };
2686 }
2687
2688 pub fn write(self: *FindByteOutStream, bytes: []const u8) Error!usize {
2689 if (self.byte_found) return bytes.len;
2690 self.byte_found = blk: {
2691 for (bytes) |b|
2692 if (b == self.byte) break :blk true;
2693 break :blk false;
2694 };
2695 return bytes.len;
2696 }
2697
2698 pub fn outStream(self: *FindByteOutStream) OutStream {
2699 return .{ .context = self };
2700 }
2701};
2702
2703fn copyFixingWhitespace(stream: anytype, slice: []const u8) @TypeOf(stream).Error!void {
2554fn copyFixingWhitespace(ais: anytype, slice: []const u8) @TypeOf(ais.*).Error!void {
27042555 for (slice) |byte| switch (byte) {
2705 '\t' => try stream.writeAll(" "),
2556 '\t' => try ais.writer().writeAll(" "),
27062557 '\r' => {},
2707 else => try stream.writeByte(byte),
2558 else => try ais.writer().writeByte(byte),
27082559 };
27092560}
lib/std/zig/tokenizer.zig+2-1
......@@ -1175,6 +1175,7 @@ pub const Tokenizer = struct {
11751175 },
11761176 .num_dot_dec => switch (c) {
11771177 '.' => {
1178 result.id = .IntegerLiteral;
11781179 self.index -= 1;
11791180 state = .start;
11801181 break;
......@@ -1183,7 +1184,6 @@ pub const Tokenizer = struct {
11831184 state = .float_exponent_unsigned;
11841185 },
11851186 '0'...'9' => {
1186 result.id = .FloatLiteral;
11871187 state = .float_fraction_dec;
11881188 },
11891189 else => {
......@@ -1769,6 +1769,7 @@ test "tokenizer - number literals decimal" {
17691769 testTokenize("7", &[_]Token.Id{.IntegerLiteral});
17701770 testTokenize("8", &[_]Token.Id{.IntegerLiteral});
17711771 testTokenize("9", &[_]Token.Id{.IntegerLiteral});
1772 testTokenize("1..", &[_]Token.Id{ .IntegerLiteral, .Ellipsis2 });
17721773 testTokenize("0a", &[_]Token.Id{ .Invalid, .Identifier });
17731774 testTokenize("9b", &[_]Token.Id{ .Invalid, .Identifier });
17741775 testTokenize("1z", &[_]Token.Id{ .Invalid, .Identifier });
src-self-hosted/Module.zig+242-130
......@@ -36,17 +36,17 @@ bin_file_path: []const u8,
3636/// It's rare for a decl to be exported, so we save memory by having a sparse map of
3737/// Decl pointers to details about them being exported.
3838/// The Export memory is owned by the `export_owners` table; the slice itself is owned by this table.
39decl_exports: std.AutoHashMapUnmanaged(*Decl, []*Export) = .{},
39decl_exports: std.AutoArrayHashMapUnmanaged(*Decl, []*Export) = .{},
4040/// We track which export is associated with the given symbol name for quick
4141/// detection of symbol collisions.
42symbol_exports: std.StringHashMapUnmanaged(*Export) = .{},
42symbol_exports: std.StringArrayHashMapUnmanaged(*Export) = .{},
4343/// This models the Decls that perform exports, so that `decl_exports` can be updated when a Decl
4444/// is modified. Note that the key of this table is not the Decl being exported, but the Decl that
4545/// is performing the export of another Decl.
4646/// This table owns the Export memory.
47export_owners: std.AutoHashMapUnmanaged(*Decl, []*Export) = .{},
47export_owners: std.AutoArrayHashMapUnmanaged(*Decl, []*Export) = .{},
4848/// Maps fully qualified namespaced names to the Decl struct for them.
49decl_table: std.HashMapUnmanaged(Scope.NameHash, *Decl, Scope.name_hash_hash, Scope.name_hash_eql, false) = .{},
49decl_table: std.ArrayHashMapUnmanaged(Scope.NameHash, *Decl, Scope.name_hash_hash, Scope.name_hash_eql, false) = .{},
5050
5151link_error_flags: link.File.ErrorFlags = .{},
5252
......@@ -57,13 +57,13 @@ work_queue: std.fifo.LinearFifo(WorkItem, .Dynamic),
5757/// The ErrorMsg memory is owned by the decl, using Module's allocator.
5858/// Note that a Decl can succeed but the Fn it represents can fail. In this case,
5959/// a Decl can have a failed_decls entry but have analysis status of success.
60failed_decls: std.AutoHashMapUnmanaged(*Decl, *ErrorMsg) = .{},
60failed_decls: std.AutoArrayHashMapUnmanaged(*Decl, *ErrorMsg) = .{},
6161/// Using a map here for consistency with the other fields here.
6262/// The ErrorMsg memory is owned by the `Scope`, using Module's allocator.
63failed_files: std.AutoHashMapUnmanaged(*Scope, *ErrorMsg) = .{},
63failed_files: std.AutoArrayHashMapUnmanaged(*Scope, *ErrorMsg) = .{},
6464/// Using a map here for consistency with the other fields here.
6565/// The ErrorMsg memory is owned by the `Export`, using Module's allocator.
66failed_exports: std.AutoHashMapUnmanaged(*Export, *ErrorMsg) = .{},
66failed_exports: std.AutoArrayHashMapUnmanaged(*Export, *ErrorMsg) = .{},
6767
6868/// Incrementing integer used to compare against the corresponding Decl
6969/// field to determine whether a Decl's status applies to an ongoing update, or a
......@@ -125,7 +125,7 @@ pub const Decl = struct {
125125 /// mapping them to an address in the output file.
126126 /// Memory owned by this decl, using Module's allocator.
127127 name: [*:0]const u8,
128 /// The direct parent container of the Decl. This is either a `Scope.File` or `Scope.ZIRModule`.
128 /// The direct parent container of the Decl. This is either a `Scope.Container` or `Scope.ZIRModule`.
129129 /// Reference to externally owned memory.
130130 scope: *Scope,
131131 /// The AST Node decl index or ZIR Inst index that contains this declaration.
......@@ -201,9 +201,9 @@ pub const Decl = struct {
201201 /// typed_value may need to be regenerated.
202202 dependencies: DepsTable = .{},
203203
204 /// The reason this is not `std.AutoHashMapUnmanaged` is a workaround for
204 /// The reason this is not `std.AutoArrayHashMapUnmanaged` is a workaround for
205205 /// stage1 compiler giving me: `error: struct 'Module.Decl' depends on itself`
206 pub const DepsTable = std.HashMapUnmanaged(*Decl, void, std.hash_map.getAutoHashFn(*Decl), std.hash_map.getAutoEqlFn(*Decl), false);
206 pub const DepsTable = std.ArrayHashMapUnmanaged(*Decl, void, std.array_hash_map.getAutoHashFn(*Decl), std.array_hash_map.getAutoEqlFn(*Decl), false);
207207
208208 pub fn destroy(self: *Decl, gpa: *Allocator) void {
209209 gpa.free(mem.spanZ(self.name));
......@@ -217,9 +217,10 @@ pub const Decl = struct {
217217
218218 pub fn src(self: Decl) usize {
219219 switch (self.scope.tag) {
220 .file => {
221 const file = @fieldParentPtr(Scope.File, "base", self.scope);
222 const tree = file.contents.tree;
220 .container => {
221 const container = @fieldParentPtr(Scope.Container, "base", self.scope);
222 const tree = container.file_scope.contents.tree;
223 // TODO Container should have it's own decls()
223224 const decl_node = tree.root_node.decls()[self.src_index];
224225 return tree.token_locs[decl_node.firstToken()].start;
225226 },
......@@ -229,7 +230,7 @@ pub const Decl = struct {
229230 const src_decl = module.decls[self.src_index];
230231 return src_decl.inst.src;
231232 },
232 .block => unreachable,
233 .file, .block => unreachable,
233234 .gen_zir => unreachable,
234235 .local_val => unreachable,
235236 .local_ptr => unreachable,
......@@ -359,6 +360,7 @@ pub const Scope = struct {
359360 .local_ptr => return self.cast(LocalPtr).?.gen_zir.arena,
360361 .zir_module => return &self.cast(ZIRModule).?.contents.module.arena.allocator,
361362 .file => unreachable,
363 .container => unreachable,
362364 }
363365 }
364366
......@@ -368,15 +370,16 @@ pub const Scope = struct {
368370 return switch (self.tag) {
369371 .block => self.cast(Block).?.decl,
370372 .gen_zir => self.cast(GenZIR).?.decl,
371 .local_val => return self.cast(LocalVal).?.gen_zir.decl,
372 .local_ptr => return self.cast(LocalPtr).?.gen_zir.decl,
373 .local_val => self.cast(LocalVal).?.gen_zir.decl,
374 .local_ptr => self.cast(LocalPtr).?.gen_zir.decl,
373375 .decl => self.cast(DeclAnalysis).?.decl,
374376 .zir_module => null,
375377 .file => null,
378 .container => null,
376379 };
377380 }
378381
379 /// Asserts the scope has a parent which is a ZIRModule or File and
382 /// Asserts the scope has a parent which is a ZIRModule or Container and
380383 /// returns it.
381384 pub fn namespace(self: *Scope) *Scope {
382385 switch (self.tag) {
......@@ -385,7 +388,8 @@ pub const Scope = struct {
385388 .local_val => return self.cast(LocalVal).?.gen_zir.decl.scope,
386389 .local_ptr => return self.cast(LocalPtr).?.gen_zir.decl.scope,
387390 .decl => return self.cast(DeclAnalysis).?.decl.scope,
388 .zir_module, .file => return self,
391 .file => return &self.cast(File).?.root_container.base,
392 .zir_module, .container => return self,
389393 }
390394 }
391395
......@@ -399,8 +403,9 @@ pub const Scope = struct {
399403 .local_val => unreachable,
400404 .local_ptr => unreachable,
401405 .decl => unreachable,
406 .file => unreachable,
402407 .zir_module => return self.cast(ZIRModule).?.fullyQualifiedNameHash(name),
403 .file => return self.cast(File).?.fullyQualifiedNameHash(name),
408 .container => return self.cast(Container).?.fullyQualifiedNameHash(name),
404409 }
405410 }
406411
......@@ -409,11 +414,12 @@ pub const Scope = struct {
409414 switch (self.tag) {
410415 .file => return self.cast(File).?.contents.tree,
411416 .zir_module => unreachable,
412 .decl => return self.cast(DeclAnalysis).?.decl.scope.cast(File).?.contents.tree,
413 .block => return self.cast(Block).?.decl.scope.cast(File).?.contents.tree,
414 .gen_zir => return self.cast(GenZIR).?.decl.scope.cast(File).?.contents.tree,
415 .local_val => return self.cast(LocalVal).?.gen_zir.decl.scope.cast(File).?.contents.tree,
416 .local_ptr => return self.cast(LocalPtr).?.gen_zir.decl.scope.cast(File).?.contents.tree,
417 .decl => return self.cast(DeclAnalysis).?.decl.scope.cast(Container).?.file_scope.contents.tree,
418 .block => return self.cast(Block).?.decl.scope.cast(Container).?.file_scope.contents.tree,
419 .gen_zir => return self.cast(GenZIR).?.decl.scope.cast(Container).?.file_scope.contents.tree,
420 .local_val => return self.cast(LocalVal).?.gen_zir.decl.scope.cast(Container).?.file_scope.contents.tree,
421 .local_ptr => return self.cast(LocalPtr).?.gen_zir.decl.scope.cast(Container).?.file_scope.contents.tree,
422 .container => return self.cast(Container).?.file_scope.contents.tree,
417423 }
418424 }
419425
......@@ -427,13 +433,15 @@ pub const Scope = struct {
427433 .decl => unreachable,
428434 .zir_module => unreachable,
429435 .file => unreachable,
436 .container => unreachable,
430437 };
431438 }
432439
433 /// Asserts the scope has a parent which is a ZIRModule or File and
440 /// Asserts the scope has a parent which is a ZIRModule, Contaienr or File and
434441 /// returns the sub_file_path field.
435442 pub fn subFilePath(base: *Scope) []const u8 {
436443 switch (base.tag) {
444 .container => return @fieldParentPtr(Container, "base", base).file_scope.sub_file_path,
437445 .file => return @fieldParentPtr(File, "base", base).sub_file_path,
438446 .zir_module => return @fieldParentPtr(ZIRModule, "base", base).sub_file_path,
439447 .block => unreachable,
......@@ -453,11 +461,13 @@ pub const Scope = struct {
453461 .local_val => unreachable,
454462 .local_ptr => unreachable,
455463 .decl => unreachable,
464 .container => unreachable,
456465 }
457466 }
458467
459468 pub fn getSource(base: *Scope, module: *Module) ![:0]const u8 {
460469 switch (base.tag) {
470 .container => return @fieldParentPtr(Container, "base", base).file_scope.getSource(module),
461471 .file => return @fieldParentPtr(File, "base", base).getSource(module),
462472 .zir_module => return @fieldParentPtr(ZIRModule, "base", base).getSource(module),
463473 .gen_zir => unreachable,
......@@ -471,8 +481,9 @@ pub const Scope = struct {
471481 /// Asserts the scope is a namespace Scope and removes the Decl from the namespace.
472482 pub fn removeDecl(base: *Scope, child: *Decl) void {
473483 switch (base.tag) {
474 .file => return @fieldParentPtr(File, "base", base).removeDecl(child),
484 .container => return @fieldParentPtr(Container, "base", base).removeDecl(child),
475485 .zir_module => return @fieldParentPtr(ZIRModule, "base", base).removeDecl(child),
486 .file => unreachable,
476487 .block => unreachable,
477488 .gen_zir => unreachable,
478489 .local_val => unreachable,
......@@ -499,6 +510,7 @@ pub const Scope = struct {
499510 .local_val => unreachable,
500511 .local_ptr => unreachable,
501512 .decl => unreachable,
513 .container => unreachable,
502514 }
503515 }
504516
......@@ -515,6 +527,8 @@ pub const Scope = struct {
515527 zir_module,
516528 /// .zig source code.
517529 file,
530 /// struct, enum or union, every .file contains one of these.
531 container,
518532 block,
519533 decl,
520534 gen_zir,
......@@ -522,6 +536,33 @@ pub const Scope = struct {
522536 local_ptr,
523537 };
524538
539 pub const Container = struct {
540 pub const base_tag: Tag = .container;
541 base: Scope = Scope{ .tag = base_tag },
542
543 file_scope: *Scope.File,
544
545 /// Direct children of the file.
546 decls: std.AutoArrayHashMapUnmanaged(*Decl, void),
547
548 // TODO implement container types and put this in a status union
549 // ty: Type
550
551 pub fn deinit(self: *Container, gpa: *Allocator) void {
552 self.decls.deinit(gpa);
553 self.* = undefined;
554 }
555
556 pub fn removeDecl(self: *Container, child: *Decl) void {
557 _ = self.decls.remove(child);
558 }
559
560 pub fn fullyQualifiedNameHash(self: *Container, name: []const u8) NameHash {
561 // TODO container scope qualified names.
562 return std.zig.hashSrc(name);
563 }
564 };
565
525566 pub const File = struct {
526567 pub const base_tag: Tag = .file;
527568 base: Scope = Scope{ .tag = base_tag },
......@@ -544,8 +585,7 @@ pub const Scope = struct {
544585 loaded_success,
545586 },
546587
547 /// Direct children of the file.
548 decls: ArrayListUnmanaged(*Decl),
588 root_container: Container,
549589
550590 pub fn unload(self: *File, gpa: *Allocator) void {
551591 switch (self.status) {
......@@ -569,20 +609,11 @@ pub const Scope = struct {
569609 }
570610
571611 pub fn deinit(self: *File, gpa: *Allocator) void {
572 self.decls.deinit(gpa);
612 self.root_container.deinit(gpa);
573613 self.unload(gpa);
574614 self.* = undefined;
575615 }
576616
577 pub fn removeDecl(self: *File, child: *Decl) void {
578 for (self.decls.items) |item, i| {
579 if (item == child) {
580 _ = self.decls.swapRemove(i);
581 return;
582 }
583 }
584 }
585
586617 pub fn dumpSrc(self: *File, src: usize) void {
587618 const loc = std.zig.findLineColumn(self.source.bytes, src);
588619 std.debug.print("{}:{}:{}\n", .{ self.sub_file_path, loc.line + 1, loc.column + 1 });
......@@ -604,11 +635,6 @@ pub const Scope = struct {
604635 .bytes => |bytes| return bytes,
605636 }
606637 }
607
608 pub fn fullyQualifiedNameHash(self: *File, name: []const u8) NameHash {
609 // We don't have struct scopes yet so this is currently just a simple name hash.
610 return std.zig.hashSrc(name);
611 }
612638 };
613639
614640 pub const ZIRModule = struct {
......@@ -725,6 +751,7 @@ pub const Scope = struct {
725751 /// Points to the arena allocator of DeclAnalysis
726752 arena: *Allocator,
727753 label: ?Label = null,
754 is_comptime: bool,
728755
729756 pub const Label = struct {
730757 zir_block: *zir.Inst.Block,
......@@ -860,7 +887,10 @@ pub fn init(gpa: *Allocator, options: InitOptions) !Module {
860887 .source = .{ .unloaded = {} },
861888 .contents = .{ .not_available = {} },
862889 .status = .never_loaded,
863 .decls = .{},
890 .root_container = .{
891 .file_scope = root_scope,
892 .decls = .{},
893 },
864894 };
865895 break :blk &root_scope.base;
866896 } else if (mem.endsWith(u8, options.root_pkg.root_src_path, ".zir")) {
......@@ -932,7 +962,8 @@ pub fn deinit(self: *Module) void {
932962 self.symbol_exports.deinit(gpa);
933963 self.root_scope.destroy(gpa);
934964
935 for (self.global_error_set.items()) |entry| {
965 var it = self.global_error_set.iterator();
966 while (it.next()) |entry| {
936967 gpa.free(entry.key);
937968 }
938969 self.global_error_set.deinit(gpa);
......@@ -967,7 +998,7 @@ pub fn update(self: *Module) !void {
967998 // to force a refresh we unload now.
968999 if (self.root_scope.cast(Scope.File)) |zig_file| {
9691000 zig_file.unload(self.gpa);
970 self.analyzeRootSrcFile(zig_file) catch |err| switch (err) {
1001 self.analyzeContainer(&zig_file.root_container) catch |err| switch (err) {
9711002 error.AnalysisFail => {
9721003 assert(self.totalErrorCount() != 0);
9731004 },
......@@ -1235,8 +1266,8 @@ fn astGenAndAnalyzeDecl(self: *Module, decl: *Decl) !bool {
12351266 const tracy = trace(@src());
12361267 defer tracy.end();
12371268
1238 const file_scope = decl.scope.cast(Scope.File).?;
1239 const tree = try self.getAstTree(file_scope);
1269 const container_scope = decl.scope.cast(Scope.Container).?;
1270 const tree = try self.getAstTree(container_scope);
12401271 const ast_node = tree.root_node.decls()[decl.src_index];
12411272 switch (ast_node.tag) {
12421273 .FnProto => {
......@@ -1307,7 +1338,6 @@ fn astGenAndAnalyzeDecl(self: *Module, decl: *Decl) !bool {
13071338 .return_type = return_type_inst,
13081339 .param_types = param_types,
13091340 }, .{});
1310 _ = try astgen.addZIRUnOp(self, &fn_type_scope.base, fn_src, .@"return", fn_type_inst);
13111341
13121342 // We need the memory for the Type to go into the arena for the Decl
13131343 var decl_arena = std.heap.ArenaAllocator.init(self.gpa);
......@@ -1320,10 +1350,11 @@ fn astGenAndAnalyzeDecl(self: *Module, decl: *Decl) !bool {
13201350 .decl = decl,
13211351 .instructions = .{},
13221352 .arena = &decl_arena.allocator,
1353 .is_comptime = false,
13231354 };
13241355 defer block_scope.instructions.deinit(self.gpa);
13251356
1326 const fn_type = try zir_sema.analyzeBodyValueAsType(self, &block_scope, .{
1357 const fn_type = try zir_sema.analyzeBodyValueAsType(self, &block_scope, fn_type_inst, .{
13271358 .instructions = fn_type_scope.instructions.items,
13281359 });
13291360 const new_func = try decl_arena.allocator.create(Fn);
......@@ -1457,6 +1488,7 @@ fn astGenAndAnalyzeDecl(self: *Module, decl: *Decl) !bool {
14571488 .decl = decl,
14581489 .instructions = .{},
14591490 .arena = &decl_arena.allocator,
1491 .is_comptime = true,
14601492 };
14611493 defer block_scope.instructions.deinit(self.gpa);
14621494
......@@ -1489,35 +1521,7 @@ fn astGenAndAnalyzeDecl(self: *Module, decl: *Decl) !bool {
14891521 return self.failNode(&block_scope.base, sect_expr, "TODO implement function section expression", .{});
14901522 }
14911523
1492 const explicit_type = blk: {
1493 const type_node = var_decl.getTypeNode() orelse
1494 break :blk null;
1495
1496 // Temporary arena for the zir instructions.
1497 var type_scope_arena = std.heap.ArenaAllocator.init(self.gpa);
1498 defer type_scope_arena.deinit();
1499 var type_scope: Scope.GenZIR = .{
1500 .decl = decl,
1501 .arena = &type_scope_arena.allocator,
1502 .parent = decl.scope,
1503 };
1504 defer type_scope.instructions.deinit(self.gpa);
1505
1506 const src = tree.token_locs[type_node.firstToken()].start;
1507 const type_type = try astgen.addZIRInstConst(self, &type_scope.base, src, .{
1508 .ty = Type.initTag(.type),
1509 .val = Value.initTag(.type_type),
1510 });
1511 const var_type = try astgen.expr(self, &type_scope.base, .{ .ty = type_type }, type_node);
1512 _ = try astgen.addZIRUnOp(self, &type_scope.base, src, .@"return", var_type);
1513
1514 break :blk try zir_sema.analyzeBodyValueAsType(self, &block_scope, .{
1515 .instructions = type_scope.instructions.items,
1516 });
1517 };
1518
1519 var var_type: Type = undefined;
1520 const value: ?Value = if (var_decl.getInitNode()) |init_node| blk: {
1524 const var_info: struct { ty: Type, val: ?Value } = if (var_decl.getInitNode()) |init_node| vi: {
15211525 var gen_scope_arena = std.heap.ArenaAllocator.init(self.gpa);
15221526 defer gen_scope_arena.deinit();
15231527 var gen_scope: Scope.GenZIR = .{
......@@ -1526,11 +1530,19 @@ fn astGenAndAnalyzeDecl(self: *Module, decl: *Decl) !bool {
15261530 .parent = decl.scope,
15271531 };
15281532 defer gen_scope.instructions.deinit(self.gpa);
1529 const src = tree.token_locs[init_node.firstToken()].start;
15301533
1531 // TODO comptime scope here
1532 const init_inst = try astgen.expr(self, &gen_scope.base, .none, init_node);
1533 _ = try astgen.addZIRUnOp(self, &gen_scope.base, src, .@"return", init_inst);
1534 const init_result_loc: astgen.ResultLoc = if (var_decl.getTypeNode()) |type_node| rl: {
1535 const src = tree.token_locs[type_node.firstToken()].start;
1536 const type_type = try astgen.addZIRInstConst(self, &gen_scope.base, src, .{
1537 .ty = Type.initTag(.type),
1538 .val = Value.initTag(.type_type),
1539 });
1540 const var_type = try astgen.expr(self, &gen_scope.base, .{ .ty = type_type }, type_node);
1541 break :rl .{ .ty = var_type };
1542 } else .none;
1543
1544 const src = tree.token_locs[init_node.firstToken()].start;
1545 const init_inst = try astgen.expr(self, &gen_scope.base, init_result_loc, init_node);
15341546
15351547 var inner_block: Scope.Block = .{
15361548 .parent = null,
......@@ -1538,42 +1550,58 @@ fn astGenAndAnalyzeDecl(self: *Module, decl: *Decl) !bool {
15381550 .decl = decl,
15391551 .instructions = .{},
15401552 .arena = &gen_scope_arena.allocator,
1553 .is_comptime = true,
15411554 };
15421555 defer inner_block.instructions.deinit(self.gpa);
15431556 try zir_sema.analyzeBody(self, &inner_block.base, .{ .instructions = gen_scope.instructions.items });
15441557
1545 for (inner_block.instructions.items) |inst| {
1546 if (inst.castTag(.ret)) |ret| {
1547 const coerced = if (explicit_type) |some|
1548 try self.coerce(&inner_block.base, some, ret.operand)
1549 else
1550 ret.operand;
1551 const val = coerced.value() orelse
1552 return self.fail(&block_scope.base, inst.src, "unable to resolve comptime value", .{});
1553
1554 var_type = explicit_type orelse try ret.operand.ty.copy(block_scope.arena);
1555 break :blk try val.copy(block_scope.arena);
1556 } else {
1557 return self.fail(&block_scope.base, inst.src, "unable to resolve comptime value", .{});
1558 }
1559 }
1560 unreachable;
1558 // The result location guarantees the type coercion.
1559 const analyzed_init_inst = init_inst.analyzed_inst.?;
1560 // The is_comptime in the Scope.Block guarantees the result is comptime-known.
1561 const val = analyzed_init_inst.value().?;
1562
1563 const ty = try analyzed_init_inst.ty.copy(block_scope.arena);
1564 break :vi .{
1565 .ty = ty,
1566 .val = try val.copy(block_scope.arena),
1567 };
15611568 } else if (!is_extern) {
15621569 return self.failTok(&block_scope.base, var_decl.firstToken(), "variables must be initialized", .{});
1563 } else if (explicit_type) |some| blk: {
1564 var_type = some;
1565 break :blk null;
1570 } else if (var_decl.getTypeNode()) |type_node| vi: {
1571 // Temporary arena for the zir instructions.
1572 var type_scope_arena = std.heap.ArenaAllocator.init(self.gpa);
1573 defer type_scope_arena.deinit();
1574 var type_scope: Scope.GenZIR = .{
1575 .decl = decl,
1576 .arena = &type_scope_arena.allocator,
1577 .parent = decl.scope,
1578 };
1579 defer type_scope.instructions.deinit(self.gpa);
1580
1581 const src = tree.token_locs[type_node.firstToken()].start;
1582 const type_type = try astgen.addZIRInstConst(self, &type_scope.base, src, .{
1583 .ty = Type.initTag(.type),
1584 .val = Value.initTag(.type_type),
1585 });
1586 const var_type = try astgen.expr(self, &type_scope.base, .{ .ty = type_type }, type_node);
1587 const ty = try zir_sema.analyzeBodyValueAsType(self, &block_scope, var_type, .{
1588 .instructions = type_scope.instructions.items,
1589 });
1590 break :vi .{
1591 .ty = ty,
1592 .val = null,
1593 };
15661594 } else {
15671595 return self.failTok(&block_scope.base, var_decl.firstToken(), "unable to infer variable type", .{});
15681596 };
15691597
1570 if (is_mutable and !var_type.isValidVarType(is_extern)) {
1571 return self.failTok(&block_scope.base, var_decl.firstToken(), "variable of type '{}' must be const", .{var_type});
1598 if (is_mutable and !var_info.ty.isValidVarType(is_extern)) {
1599 return self.failTok(&block_scope.base, var_decl.firstToken(), "variable of type '{}' must be const", .{var_info.ty});
15721600 }
15731601
15741602 var type_changed = true;
15751603 if (decl.typedValueManaged()) |tvm| {
1576 type_changed = !tvm.typed_value.ty.eql(var_type);
1604 type_changed = !tvm.typed_value.ty.eql(var_info.ty);
15771605
15781606 tvm.deinit(self.gpa);
15791607 }
......@@ -1582,7 +1610,7 @@ fn astGenAndAnalyzeDecl(self: *Module, decl: *Decl) !bool {
15821610 const var_payload = try decl_arena.allocator.create(Value.Payload.Variable);
15831611 new_variable.* = .{
15841612 .owner_decl = decl,
1585 .init = value orelse undefined,
1613 .init = var_info.val orelse undefined,
15861614 .is_extern = is_extern,
15871615 .is_mutable = is_mutable,
15881616 .is_threadlocal = is_threadlocal,
......@@ -1593,7 +1621,7 @@ fn astGenAndAnalyzeDecl(self: *Module, decl: *Decl) !bool {
15931621 decl.typed_value = .{
15941622 .most_recent = .{
15951623 .typed_value = .{
1596 .ty = var_type,
1624 .ty = var_info.ty,
15971625 .val = Value.initPayload(&var_payload.base),
15981626 },
15991627 .arena = decl_arena_state,
......@@ -1628,8 +1656,7 @@ fn astGenAndAnalyzeDecl(self: *Module, decl: *Decl) !bool {
16281656 };
16291657 defer gen_scope.instructions.deinit(self.gpa);
16301658
1631 // TODO comptime scope here
1632 _ = try astgen.expr(self, &gen_scope.base, .none, comptime_decl.expr);
1659 _ = try astgen.comptimeExpr(self, &gen_scope.base, .none, comptime_decl.expr);
16331660
16341661 var block_scope: Scope.Block = .{
16351662 .parent = null,
......@@ -1637,6 +1664,7 @@ fn astGenAndAnalyzeDecl(self: *Module, decl: *Decl) !bool {
16371664 .decl = decl,
16381665 .instructions = .{},
16391666 .arena = &analysis_arena.allocator,
1667 .is_comptime = true,
16401668 };
16411669 defer block_scope.instructions.deinit(self.gpa);
16421670
......@@ -1699,10 +1727,12 @@ fn getSrcModule(self: *Module, root_scope: *Scope.ZIRModule) !*zir.Module {
16991727 }
17001728}
17011729
1702fn getAstTree(self: *Module, root_scope: *Scope.File) !*ast.Tree {
1730fn getAstTree(self: *Module, container_scope: *Scope.Container) !*ast.Tree {
17031731 const tracy = trace(@src());
17041732 defer tracy.end();
17051733
1734 const root_scope = container_scope.file_scope;
1735
17061736 switch (root_scope.status) {
17071737 .never_loaded, .unloaded_success => {
17081738 try self.failed_files.ensureCapacity(self.gpa, self.failed_files.items().len + 1);
......@@ -1744,25 +1774,25 @@ fn getAstTree(self: *Module, root_scope: *Scope.File) !*ast.Tree {
17441774 }
17451775}
17461776
1747fn analyzeRootSrcFile(self: *Module, root_scope: *Scope.File) !void {
1777fn analyzeContainer(self: *Module, container_scope: *Scope.Container) !void {
17481778 const tracy = trace(@src());
17491779 defer tracy.end();
17501780
17511781 // We may be analyzing it for the first time, or this may be
17521782 // an incremental update. This code handles both cases.
1753 const tree = try self.getAstTree(root_scope);
1783 const tree = try self.getAstTree(container_scope);
17541784 const decls = tree.root_node.decls();
17551785
17561786 try self.work_queue.ensureUnusedCapacity(decls.len);
1757 try root_scope.decls.ensureCapacity(self.gpa, decls.len);
1787 try container_scope.decls.ensureCapacity(self.gpa, decls.len);
17581788
17591789 // Keep track of the decls that we expect to see in this file so that
17601790 // we know which ones have been deleted.
1761 var deleted_decls = std.AutoHashMap(*Decl, void).init(self.gpa);
1791 var deleted_decls = std.AutoArrayHashMap(*Decl, void).init(self.gpa);
17621792 defer deleted_decls.deinit();
1763 try deleted_decls.ensureCapacity(root_scope.decls.items.len);
1764 for (root_scope.decls.items) |file_decl| {
1765 deleted_decls.putAssumeCapacityNoClobber(file_decl, {});
1793 try deleted_decls.ensureCapacity(container_scope.decls.items().len);
1794 for (container_scope.decls.items()) |entry| {
1795 deleted_decls.putAssumeCapacityNoClobber(entry.key, {});
17661796 }
17671797
17681798 for (decls) |src_decl, decl_i| {
......@@ -1774,7 +1804,7 @@ fn analyzeRootSrcFile(self: *Module, root_scope: *Scope.File) !void {
17741804
17751805 const name_loc = tree.token_locs[name_tok];
17761806 const name = tree.tokenSliceLoc(name_loc);
1777 const name_hash = root_scope.fullyQualifiedNameHash(name);
1807 const name_hash = container_scope.fullyQualifiedNameHash(name);
17781808 const contents_hash = std.zig.hashSrc(tree.getNodeSource(src_decl));
17791809 if (self.decl_table.get(name_hash)) |decl| {
17801810 // Update the AST Node index of the decl, even if its contents are unchanged, it may
......@@ -1802,8 +1832,8 @@ fn analyzeRootSrcFile(self: *Module, root_scope: *Scope.File) !void {
18021832 }
18031833 }
18041834 } else {
1805 const new_decl = try self.createNewDecl(&root_scope.base, name, decl_i, name_hash, contents_hash);
1806 root_scope.decls.appendAssumeCapacity(new_decl);
1835 const new_decl = try self.createNewDecl(&container_scope.base, name, decl_i, name_hash, contents_hash);
1836 container_scope.decls.putAssumeCapacity(new_decl, {});
18071837 if (fn_proto.getExternExportInlineToken()) |maybe_export_token| {
18081838 if (tree.token_ids[maybe_export_token] == .Keyword_export) {
18091839 self.work_queue.writeItemAssumeCapacity(.{ .analyze_decl = new_decl });
......@@ -1813,7 +1843,7 @@ fn analyzeRootSrcFile(self: *Module, root_scope: *Scope.File) !void {
18131843 } else if (src_decl.castTag(.VarDecl)) |var_decl| {
18141844 const name_loc = tree.token_locs[var_decl.name_token];
18151845 const name = tree.tokenSliceLoc(name_loc);
1816 const name_hash = root_scope.fullyQualifiedNameHash(name);
1846 const name_hash = container_scope.fullyQualifiedNameHash(name);
18171847 const contents_hash = std.zig.hashSrc(tree.getNodeSource(src_decl));
18181848 if (self.decl_table.get(name_hash)) |decl| {
18191849 // Update the AST Node index of the decl, even if its contents are unchanged, it may
......@@ -1829,8 +1859,8 @@ fn analyzeRootSrcFile(self: *Module, root_scope: *Scope.File) !void {
18291859 decl.contents_hash = contents_hash;
18301860 }
18311861 } else {
1832 const new_decl = try self.createNewDecl(&root_scope.base, name, decl_i, name_hash, contents_hash);
1833 root_scope.decls.appendAssumeCapacity(new_decl);
1862 const new_decl = try self.createNewDecl(&container_scope.base, name, decl_i, name_hash, contents_hash);
1863 container_scope.decls.putAssumeCapacity(new_decl, {});
18341864 if (var_decl.getExternExportToken()) |maybe_export_token| {
18351865 if (tree.token_ids[maybe_export_token] == .Keyword_export) {
18361866 self.work_queue.writeItemAssumeCapacity(.{ .analyze_decl = new_decl });
......@@ -1842,11 +1872,11 @@ fn analyzeRootSrcFile(self: *Module, root_scope: *Scope.File) !void {
18421872 const name = try std.fmt.allocPrint(self.gpa, "__comptime_{}", .{name_index});
18431873 defer self.gpa.free(name);
18441874
1845 const name_hash = root_scope.fullyQualifiedNameHash(name);
1875 const name_hash = container_scope.fullyQualifiedNameHash(name);
18461876 const contents_hash = std.zig.hashSrc(tree.getNodeSource(src_decl));
18471877
1848 const new_decl = try self.createNewDecl(&root_scope.base, name, decl_i, name_hash, contents_hash);
1849 root_scope.decls.appendAssumeCapacity(new_decl);
1878 const new_decl = try self.createNewDecl(&container_scope.base, name, decl_i, name_hash, contents_hash);
1879 container_scope.decls.putAssumeCapacity(new_decl, {});
18501880 self.work_queue.writeItemAssumeCapacity(.{ .analyze_decl = new_decl });
18511881 } else if (src_decl.castTag(.ContainerField)) |container_field| {
18521882 log.err("TODO: analyze container field", .{});
......@@ -1879,7 +1909,7 @@ fn analyzeRootZIRModule(self: *Module, root_scope: *Scope.ZIRModule) !void {
18791909
18801910 // Keep track of the decls that we expect to see in this file so that
18811911 // we know which ones have been deleted.
1882 var deleted_decls = std.AutoHashMap(*Decl, void).init(self.gpa);
1912 var deleted_decls = std.AutoArrayHashMap(*Decl, void).init(self.gpa);
18831913 defer deleted_decls.deinit();
18841914 try deleted_decls.ensureCapacity(self.decl_table.items().len);
18851915 for (self.decl_table.items()) |entry| {
......@@ -2007,6 +2037,7 @@ fn analyzeFnBody(self: *Module, decl: *Decl, func: *Fn) !void {
20072037 .decl = decl,
20082038 .instructions = .{},
20092039 .arena = &arena.allocator,
2040 .is_comptime = false,
20102041 };
20112042 defer inner_block.instructions.deinit(self.gpa);
20122043
......@@ -2088,16 +2119,23 @@ pub fn getErrorValue(self: *Module, name: []const u8) !std.StringHashMapUnmanage
20882119 errdefer self.global_error_set.removeAssertDiscard(name);
20892120
20902121 gop.entry.key = try self.gpa.dupe(u8, name);
2091 gop.entry.value = @intCast(u16, self.global_error_set.items().len - 1);
2122 gop.entry.value = @intCast(u16, self.global_error_set.count() - 1);
20922123 return gop.entry.*;
20932124}
20942125
2095/// TODO split this into `requireRuntimeBlock` and `requireFunctionBlock` and audit callsites.
2096pub fn requireRuntimeBlock(self: *Module, scope: *Scope, src: usize) !*Scope.Block {
2126pub fn requireFunctionBlock(self: *Module, scope: *Scope, src: usize) !*Scope.Block {
20972127 return scope.cast(Scope.Block) orelse
20982128 return self.fail(scope, src, "instruction illegal outside function body", .{});
20992129}
21002130
2131pub fn requireRuntimeBlock(self: *Module, scope: *Scope, src: usize) !*Scope.Block {
2132 const block = try self.requireFunctionBlock(scope, src);
2133 if (block.is_comptime) {
2134 return self.fail(scope, src, "unable to resolve comptime value", .{});
2135 }
2136 return block;
2137}
2138
21012139pub fn resolveConstValue(self: *Module, scope: *Scope, base: *Inst) !Value {
21022140 return (try self.resolveDefinedValue(scope, base)) orelse
21032141 return self.fail(scope, base.src, "unable to resolve comptime value", .{});
......@@ -2584,6 +2622,72 @@ pub fn analyzeIsErr(self: *Module, scope: *Scope, src: usize, operand: *Inst) In
25842622 return self.fail(scope, src, "TODO implement analysis of iserr", .{});
25852623}
25862624
2625pub fn analyzeSlice(self: *Module, scope: *Scope, src: usize, array_ptr: *Inst, start: *Inst, end_opt: ?*Inst, sentinel_opt: ?*Inst) InnerError!*Inst {
2626 const ptr_child = switch (array_ptr.ty.zigTypeTag()) {
2627 .Pointer => array_ptr.ty.elemType(),
2628 else => return self.fail(scope, src, "expected pointer, found '{}'", .{array_ptr.ty}),
2629 };
2630
2631 var array_type = ptr_child;
2632 const elem_type = switch (ptr_child.zigTypeTag()) {
2633 .Array => ptr_child.elemType(),
2634 .Pointer => blk: {
2635 if (ptr_child.isSinglePointer()) {
2636 if (ptr_child.elemType().zigTypeTag() == .Array) {
2637 array_type = ptr_child.elemType();
2638 break :blk ptr_child.elemType().elemType();
2639 }
2640
2641 return self.fail(scope, src, "slice of single-item pointer", .{});
2642 }
2643 break :blk ptr_child.elemType();
2644 },
2645 else => return self.fail(scope, src, "slice of non-array type '{}'", .{ptr_child}),
2646 };
2647
2648 const slice_sentinel = if (sentinel_opt) |sentinel| blk: {
2649 const casted = try self.coerce(scope, elem_type, sentinel);
2650 break :blk try self.resolveConstValue(scope, casted);
2651 } else null;
2652
2653 var return_ptr_size: std.builtin.TypeInfo.Pointer.Size = .Slice;
2654 var return_elem_type = elem_type;
2655 if (end_opt) |end| {
2656 if (end.value()) |end_val| {
2657 if (start.value()) |start_val| {
2658 const start_u64 = start_val.toUnsignedInt();
2659 const end_u64 = end_val.toUnsignedInt();
2660 if (start_u64 > end_u64) {
2661 return self.fail(scope, src, "out of bounds slice", .{});
2662 }
2663
2664 const len = end_u64 - start_u64;
2665 const array_sentinel = if (array_type.zigTypeTag() == .Array and end_u64 == array_type.arrayLen())
2666 array_type.sentinel()
2667 else
2668 slice_sentinel;
2669 return_elem_type = try self.arrayType(scope, len, array_sentinel, elem_type);
2670 return_ptr_size = .One;
2671 }
2672 }
2673 }
2674 const return_type = try self.ptrType(
2675 scope,
2676 src,
2677 return_elem_type,
2678 if (end_opt == null) slice_sentinel else null,
2679 0, // TODO alignment
2680 0,
2681 0,
2682 !ptr_child.isConstPtr(),
2683 ptr_child.isAllowzeroPtr(),
2684 ptr_child.isVolatilePtr(),
2685 return_ptr_size,
2686 );
2687
2688 return self.fail(scope, src, "TODO implement analysis of slice", .{});
2689}
2690
25872691/// Asserts that lhs and rhs types are both numeric.
25882692pub fn cmpNumeric(
25892693 self: *Module,
......@@ -2794,6 +2898,12 @@ pub fn resolvePeerTypes(self: *Module, scope: *Scope, instructions: []*Inst) !Ty
27942898 prev_inst = next_inst;
27952899 continue;
27962900 }
2901 if (next_inst.ty.zigTypeTag() == .Undefined)
2902 continue;
2903 if (prev_inst.ty.zigTypeTag() == .Undefined) {
2904 prev_inst = next_inst;
2905 continue;
2906 }
27972907 if (prev_inst.ty.isInt() and
27982908 next_inst.ty.isInt() and
27992909 prev_inst.ty.isSignedInt() == next_inst.ty.isSignedInt())
......@@ -3045,6 +3155,7 @@ fn failWithOwnedErrorMsg(self: *Module, scope: *Scope, src: usize, err_msg: *Err
30453155 self.failed_files.putAssumeCapacityNoClobber(scope, err_msg);
30463156 },
30473157 .file => unreachable,
3158 .container => unreachable,
30483159 }
30493160 return error.AnalysisFail;
30503161}
......@@ -3432,6 +3543,7 @@ pub fn addSafetyCheck(mod: *Module, parent_block: *Scope.Block, ok: *Inst, panic
34323543 .decl = parent_block.decl,
34333544 .instructions = .{},
34343545 .arena = parent_block.arena,
3546 .is_comptime = parent_block.is_comptime,
34353547 };
34363548 defer fail_block.instructions.deinit(mod.gpa);
34373549
src-self-hosted/astgen.zig+147-42
......@@ -258,7 +258,7 @@ pub fn expr(mod: *Module, scope: *Scope, rl: ResultLoc, node: *ast.Node) InnerEr
258258 .OptionalType => return rlWrap(mod, scope, rl, try optionalType(mod, scope, node.castTag(.OptionalType).?)),
259259 .UnwrapOptional => return unwrapOptional(mod, scope, rl, node.castTag(.UnwrapOptional).?),
260260 .Block => return rlWrapVoid(mod, scope, rl, node, try blockExpr(mod, scope, node.castTag(.Block).?)),
261 .LabeledBlock => return labeledBlockExpr(mod, scope, rl, node.castTag(.LabeledBlock).?),
261 .LabeledBlock => return labeledBlockExpr(mod, scope, rl, node.castTag(.LabeledBlock).?, .block),
262262 .Break => return rlWrap(mod, scope, rl, try breakExpr(mod, scope, node.castTag(.Break).?)),
263263 .PtrType => return rlWrap(mod, scope, rl, try ptrType(mod, scope, node.castTag(.PtrType).?)),
264264 .GroupedExpression => return expr(mod, scope, rl, node.castTag(.GroupedExpression).?.expr),
......@@ -275,15 +275,16 @@ pub fn expr(mod: *Module, scope: *Scope, rl: ResultLoc, node: *ast.Node) InnerEr
275275 .ErrorType => return rlWrap(mod, scope, rl, try errorType(mod, scope, node.castTag(.ErrorType).?)),
276276 .For => return forExpr(mod, scope, rl, node.castTag(.For).?),
277277 .ArrayAccess => return arrayAccess(mod, scope, rl, node.castTag(.ArrayAccess).?),
278 .Slice => return rlWrap(mod, scope, rl, try sliceExpr(mod, scope, node.castTag(.Slice).?)),
278279 .Catch => return catchExpr(mod, scope, rl, node.castTag(.Catch).?),
280 .Comptime => return comptimeKeyword(mod, scope, rl, node.castTag(.Comptime).?),
281 .OrElse => return orelseExpr(mod, scope, rl, node.castTag(.OrElse).?),
279282
280283 .Defer => return mod.failNode(scope, node, "TODO implement astgen.expr for .Defer", .{}),
281284 .Range => return mod.failNode(scope, node, "TODO implement astgen.expr for .Range", .{}),
282 .OrElse => return mod.failNode(scope, node, "TODO implement astgen.expr for .OrElse", .{}),
283285 .Await => return mod.failNode(scope, node, "TODO implement astgen.expr for .Await", .{}),
284286 .Resume => return mod.failNode(scope, node, "TODO implement astgen.expr for .Resume", .{}),
285287 .Try => return mod.failNode(scope, node, "TODO implement astgen.expr for .Try", .{}),
286 .Slice => return mod.failNode(scope, node, "TODO implement astgen.expr for .Slice", .{}),
287288 .ArrayInitializer => return mod.failNode(scope, node, "TODO implement astgen.expr for .ArrayInitializer", .{}),
288289 .ArrayInitializerDot => return mod.failNode(scope, node, "TODO implement astgen.expr for .ArrayInitializerDot", .{}),
289290 .StructInitializer => return mod.failNode(scope, node, "TODO implement astgen.expr for .StructInitializer", .{}),
......@@ -294,11 +295,46 @@ pub fn expr(mod: *Module, scope: *Scope, rl: ResultLoc, node: *ast.Node) InnerEr
294295 .AnyType => return mod.failNode(scope, node, "TODO implement astgen.expr for .AnyType", .{}),
295296 .FnProto => return mod.failNode(scope, node, "TODO implement astgen.expr for .FnProto", .{}),
296297 .ContainerDecl => return mod.failNode(scope, node, "TODO implement astgen.expr for .ContainerDecl", .{}),
297 .Comptime => return mod.failNode(scope, node, "TODO implement astgen.expr for .Comptime", .{}),
298298 .Nosuspend => return mod.failNode(scope, node, "TODO implement astgen.expr for .Nosuspend", .{}),
299299 }
300300}
301301
302fn comptimeKeyword(mod: *Module, scope: *Scope, rl: ResultLoc, node: *ast.Node.Comptime) InnerError!*zir.Inst {
303 const tracy = trace(@src());
304 defer tracy.end();
305
306 return comptimeExpr(mod, scope, rl, node.expr);
307}
308
309pub fn comptimeExpr(mod: *Module, parent_scope: *Scope, rl: ResultLoc, node: *ast.Node) InnerError!*zir.Inst {
310 const tree = parent_scope.tree();
311 const src = tree.token_locs[node.firstToken()].start;
312
313 // Optimization for labeled blocks: don't need to have 2 layers of blocks, we can reuse the existing one.
314 if (node.castTag(.LabeledBlock)) |block_node| {
315 return labeledBlockExpr(mod, parent_scope, rl, block_node, .block_comptime);
316 }
317
318 // Make a scope to collect generated instructions in the sub-expression.
319 var block_scope: Scope.GenZIR = .{
320 .parent = parent_scope,
321 .decl = parent_scope.decl().?,
322 .arena = parent_scope.arena(),
323 .instructions = .{},
324 };
325 defer block_scope.instructions.deinit(mod.gpa);
326
327 // No need to capture the result here because block_comptime_flat implies that the final
328 // instruction is the block's result value.
329 _ = try expr(mod, &block_scope.base, rl, node);
330
331 const block = try addZIRInstBlock(mod, parent_scope, src, .block_comptime_flat, .{
332 .instructions = try block_scope.arena.dupe(*zir.Inst, block_scope.instructions.items),
333 });
334
335 return &block.base;
336}
337
302338fn breakExpr(mod: *Module, parent_scope: *Scope, node: *ast.Node.ControlFlowExpression) InnerError!*zir.Inst {
303339 const tree = parent_scope.tree();
304340 const src = tree.token_locs[node.ltoken].start;
......@@ -360,10 +396,13 @@ fn labeledBlockExpr(
360396 parent_scope: *Scope,
361397 rl: ResultLoc,
362398 block_node: *ast.Node.LabeledBlock,
399 zir_tag: zir.Inst.Tag,
363400) InnerError!*zir.Inst {
364401 const tracy = trace(@src());
365402 defer tracy.end();
366403
404 assert(zir_tag == .block or zir_tag == .block_comptime);
405
367406 const tree = parent_scope.tree();
368407 const src = tree.token_locs[block_node.lbrace].start;
369408
......@@ -373,7 +412,7 @@ fn labeledBlockExpr(
373412 const block_inst = try gen_zir.arena.create(zir.Inst.Block);
374413 block_inst.* = .{
375414 .base = .{
376 .tag = .block,
415 .tag = zir_tag,
377416 .src = src,
378417 },
379418 .positionals = .{
......@@ -751,13 +790,31 @@ fn errorType(mod: *Module, scope: *Scope, node: *ast.Node.OneToken) InnerError!*
751790}
752791
753792fn catchExpr(mod: *Module, scope: *Scope, rl: ResultLoc, node: *ast.Node.Catch) InnerError!*zir.Inst {
793 return orelseCatchExpr(mod, scope, rl, node.lhs, node.op_token, .iserr, .unwrap_err_unsafe, node.rhs, node.payload);
794}
795
796fn orelseExpr(mod: *Module, scope: *Scope, rl: ResultLoc, node: *ast.Node.SimpleInfixOp) InnerError!*zir.Inst {
797 return orelseCatchExpr(mod, scope, rl, node.lhs, node.op_token, .isnull, .unwrap_optional_unsafe, node.rhs, null);
798}
799
800fn orelseCatchExpr(
801 mod: *Module,
802 scope: *Scope,
803 rl: ResultLoc,
804 lhs: *ast.Node,
805 op_token: ast.TokenIndex,
806 cond_op: zir.Inst.Tag,
807 unwrap_op: zir.Inst.Tag,
808 rhs: *ast.Node,
809 payload_node: ?*ast.Node,
810) InnerError!*zir.Inst {
754811 const tree = scope.tree();
755 const src = tree.token_locs[node.op_token].start;
812 const src = tree.token_locs[op_token].start;
756813
757 const err_union_ptr = try expr(mod, scope, .ref, node.lhs);
758 // TODO we could avoid an unnecessary copy if .iserr took a pointer
759 const err_union = try addZIRUnOp(mod, scope, src, .deref, err_union_ptr);
760 const cond = try addZIRUnOp(mod, scope, src, .iserr, err_union);
814 const operand_ptr = try expr(mod, scope, .ref, lhs);
815 // TODO we could avoid an unnecessary copy if .iserr, .isnull took a pointer
816 const err_union = try addZIRUnOp(mod, scope, src, .deref, operand_ptr);
817 const cond = try addZIRUnOp(mod, scope, src, cond_op, err_union);
761818
762819 var block_scope: Scope.GenZIR = .{
763820 .parent = scope,
......@@ -773,7 +830,7 @@ fn catchExpr(mod: *Module, scope: *Scope, rl: ResultLoc, node: *ast.Node.Catch)
773830 .else_body = undefined, // populated below
774831 }, .{});
775832
776 const block = try addZIRInstBlock(mod, scope, src, .{
833 const block = try addZIRInstBlock(mod, scope, src, .block, .{
777834 .instructions = try block_scope.arena.dupe(*zir.Inst, block_scope.instructions.items),
778835 });
779836
......@@ -786,55 +843,55 @@ fn catchExpr(mod: *Module, scope: *Scope, rl: ResultLoc, node: *ast.Node.Catch)
786843 .inferred_ptr, .bitcasted_ptr, .block_ptr => .{ .block_ptr = block },
787844 };
788845
789 var err_scope: Scope.GenZIR = .{
846 var then_scope: Scope.GenZIR = .{
790847 .parent = scope,
791848 .decl = block_scope.decl,
792849 .arena = block_scope.arena,
793850 .instructions = .{},
794851 };
795 defer err_scope.instructions.deinit(mod.gpa);
852 defer then_scope.instructions.deinit(mod.gpa);
796853
797854 var err_val_scope: Scope.LocalVal = undefined;
798 const err_sub_scope = blk: {
799 const payload = node.payload orelse
800 break :blk &err_scope.base;
855 const then_sub_scope = blk: {
856 const payload = payload_node orelse
857 break :blk &then_scope.base;
801858
802859 const err_name = tree.tokenSlice(payload.castTag(.Payload).?.error_symbol.firstToken());
803860 if (mem.eql(u8, err_name, "_"))
804 break :blk &err_scope.base;
861 break :blk &then_scope.base;
805862
806 const unwrapped_err_ptr = try addZIRUnOp(mod, &err_scope.base, src, .unwrap_err_code, err_union_ptr);
863 const unwrapped_err_ptr = try addZIRUnOp(mod, &then_scope.base, src, .unwrap_err_code, operand_ptr);
807864 err_val_scope = .{
808 .parent = &err_scope.base,
809 .gen_zir = &err_scope,
865 .parent = &then_scope.base,
866 .gen_zir = &then_scope,
810867 .name = err_name,
811 .inst = try addZIRUnOp(mod, &err_scope.base, src, .deref, unwrapped_err_ptr),
868 .inst = try addZIRUnOp(mod, &then_scope.base, src, .deref, unwrapped_err_ptr),
812869 };
813870 break :blk &err_val_scope.base;
814871 };
815872
816 _ = try addZIRInst(mod, &err_scope.base, src, zir.Inst.Break, .{
873 _ = try addZIRInst(mod, &then_scope.base, src, zir.Inst.Break, .{
817874 .block = block,
818 .operand = try expr(mod, err_sub_scope, branch_rl, node.rhs),
875 .operand = try expr(mod, then_sub_scope, branch_rl, rhs),
819876 }, .{});
820877
821 var not_err_scope: Scope.GenZIR = .{
878 var else_scope: Scope.GenZIR = .{
822879 .parent = scope,
823880 .decl = block_scope.decl,
824881 .arena = block_scope.arena,
825882 .instructions = .{},
826883 };
827 defer not_err_scope.instructions.deinit(mod.gpa);
884 defer else_scope.instructions.deinit(mod.gpa);
828885
829 const unwrapped_payload = try addZIRUnOp(mod, &not_err_scope.base, src, .unwrap_err_unsafe, err_union_ptr);
830 _ = try addZIRInst(mod, &not_err_scope.base, src, zir.Inst.Break, .{
886 const unwrapped_payload = try addZIRUnOp(mod, &else_scope.base, src, unwrap_op, operand_ptr);
887 _ = try addZIRInst(mod, &else_scope.base, src, zir.Inst.Break, .{
831888 .block = block,
832889 .operand = unwrapped_payload,
833890 }, .{});
834891
835 condbr.positionals.then_body = .{ .instructions = try err_scope.arena.dupe(*zir.Inst, err_scope.instructions.items) };
836 condbr.positionals.else_body = .{ .instructions = try not_err_scope.arena.dupe(*zir.Inst, not_err_scope.instructions.items) };
837 return rlWrap(mod, scope, rl, &block.base);
892 condbr.positionals.then_body = .{ .instructions = try then_scope.arena.dupe(*zir.Inst, then_scope.instructions.items) };
893 condbr.positionals.else_body = .{ .instructions = try else_scope.arena.dupe(*zir.Inst, else_scope.instructions.items) };
894 return rlWrapPtr(mod, scope, rl, &block.base);
838895}
839896
840897/// Return whether the identifier names of two tokens are equal. Resolves @"" tokens without allocating.
......@@ -894,6 +951,36 @@ fn arrayAccess(mod: *Module, scope: *Scope, rl: ResultLoc, node: *ast.Node.Array
894951 return rlWrapPtr(mod, scope, rl, try addZIRInst(mod, scope, src, zir.Inst.ElemPtr, .{ .array_ptr = array_ptr, .index = index }, .{}));
895952}
896953
954fn sliceExpr(mod: *Module, scope: *Scope, node: *ast.Node.Slice) InnerError!*zir.Inst {
955 const tree = scope.tree();
956 const src = tree.token_locs[node.rtoken].start;
957
958 const usize_type = try addZIRInstConst(mod, scope, src, .{
959 .ty = Type.initTag(.type),
960 .val = Value.initTag(.usize_type),
961 });
962
963 const array_ptr = try expr(mod, scope, .ref, node.lhs);
964 const start = try expr(mod, scope, .{ .ty = usize_type }, node.start);
965
966 if (node.end == null and node.sentinel == null) {
967 return try addZIRBinOp(mod, scope, src, .slice_start, array_ptr, start);
968 }
969
970 const end = if (node.end) |end| try expr(mod, scope, .{ .ty = usize_type }, end) else null;
971 // we could get the child type here, but it is easier to just do it in semantic analysis.
972 const sentinel = if (node.sentinel) |sentinel| try expr(mod, scope, .none, sentinel) else null;
973
974 return try addZIRInst(
975 mod,
976 scope,
977 src,
978 zir.Inst.Slice,
979 .{ .array_ptr = array_ptr, .start = start },
980 .{ .end = end, .sentinel = sentinel },
981 );
982}
983
897984fn deref(mod: *Module, scope: *Scope, node: *ast.Node.SimpleSuffixOp) InnerError!*zir.Inst {
898985 const tree = scope.tree();
899986 const src = tree.token_locs[node.rtoken].start;
......@@ -946,7 +1033,7 @@ fn boolBinOp(
9461033 .else_body = undefined, // populated below
9471034 }, .{});
9481035
949 const block = try addZIRInstBlock(mod, scope, src, .{
1036 const block = try addZIRInstBlock(mod, scope, src, .block, .{
9501037 .instructions = try block_scope.arena.dupe(*zir.Inst, block_scope.instructions.items),
9511038 });
9521039
......@@ -1095,7 +1182,7 @@ fn ifExpr(mod: *Module, scope: *Scope, rl: ResultLoc, if_node: *ast.Node.If) Inn
10951182 .else_body = undefined, // populated below
10961183 }, .{});
10971184
1098 const block = try addZIRInstBlock(mod, scope, if_src, .{
1185 const block = try addZIRInstBlock(mod, scope, if_src, .block, .{
10991186 .instructions = try block_scope.arena.dupe(*zir.Inst, block_scope.instructions.items),
11001187 });
11011188
......@@ -1218,7 +1305,7 @@ fn whileExpr(mod: *Module, scope: *Scope, rl: ResultLoc, while_node: *ast.Node.W
12181305 .then_body = undefined, // populated below
12191306 .else_body = undefined, // populated below
12201307 }, .{});
1221 const cond_block = try addZIRInstBlock(mod, &loop_scope.base, while_src, .{
1308 const cond_block = try addZIRInstBlock(mod, &loop_scope.base, while_src, .block, .{
12221309 .instructions = try loop_scope.arena.dupe(*zir.Inst, continue_scope.instructions.items),
12231310 });
12241311 // TODO avoid emitting the continue expr when there
......@@ -1231,7 +1318,7 @@ fn whileExpr(mod: *Module, scope: *Scope, rl: ResultLoc, while_node: *ast.Node.W
12311318 const loop = try addZIRInstLoop(mod, &expr_scope.base, while_src, .{
12321319 .instructions = try expr_scope.arena.dupe(*zir.Inst, loop_scope.instructions.items),
12331320 });
1234 const while_block = try addZIRInstBlock(mod, scope, while_src, .{
1321 const while_block = try addZIRInstBlock(mod, scope, while_src, .block, .{
12351322 .instructions = try expr_scope.arena.dupe(*zir.Inst, expr_scope.instructions.items),
12361323 });
12371324
......@@ -1365,7 +1452,7 @@ fn forExpr(mod: *Module, scope: *Scope, rl: ResultLoc, for_node: *ast.Node.For)
13651452 .then_body = undefined, // populated below
13661453 .else_body = undefined, // populated below
13671454 }, .{});
1368 const cond_block = try addZIRInstBlock(mod, &loop_scope.base, for_src, .{
1455 const cond_block = try addZIRInstBlock(mod, &loop_scope.base, for_src, .block, .{
13691456 .instructions = try loop_scope.arena.dupe(*zir.Inst, cond_scope.instructions.items),
13701457 });
13711458
......@@ -1382,7 +1469,7 @@ fn forExpr(mod: *Module, scope: *Scope, rl: ResultLoc, for_node: *ast.Node.For)
13821469 const loop = try addZIRInstLoop(mod, &for_scope.base, for_src, .{
13831470 .instructions = try for_scope.arena.dupe(*zir.Inst, loop_scope.instructions.items),
13841471 });
1385 const for_block = try addZIRInstBlock(mod, scope, for_src, .{
1472 const for_block = try addZIRInstBlock(mod, scope, for_src, .block, .{
13861473 .instructions = try for_scope.arena.dupe(*zir.Inst, for_scope.instructions.items),
13871474 });
13881475
......@@ -2260,6 +2347,30 @@ pub fn addZIRBinOp(
22602347 return &inst.base;
22612348}
22622349
2350pub fn addZIRInstBlock(
2351 mod: *Module,
2352 scope: *Scope,
2353 src: usize,
2354 tag: zir.Inst.Tag,
2355 body: zir.Module.Body,
2356) !*zir.Inst.Block {
2357 const gen_zir = scope.getGenZIR();
2358 try gen_zir.instructions.ensureCapacity(mod.gpa, gen_zir.instructions.items.len + 1);
2359 const inst = try gen_zir.arena.create(zir.Inst.Block);
2360 inst.* = .{
2361 .base = .{
2362 .tag = tag,
2363 .src = src,
2364 },
2365 .positionals = .{
2366 .body = body,
2367 },
2368 .kw_args = .{},
2369 };
2370 gen_zir.instructions.appendAssumeCapacity(&inst.base);
2371 return inst;
2372}
2373
22632374pub fn addZIRInst(
22642375 mod: *Module,
22652376 scope: *Scope,
......@@ -2278,12 +2389,6 @@ pub fn addZIRInstConst(mod: *Module, scope: *Scope, src: usize, typed_value: Typ
22782389 return addZIRInst(mod, scope, src, zir.Inst.Const, P{ .typed_value = typed_value }, .{});
22792390}
22802391
2281/// TODO The existence of this function is a workaround for a bug in stage1.
2282pub fn addZIRInstBlock(mod: *Module, scope: *Scope, src: usize, body: zir.Module.Body) !*zir.Inst.Block {
2283 const P = std.meta.fieldInfo(zir.Inst.Block, "positionals").field_type;
2284 return addZIRInstSpecial(mod, scope, src, zir.Inst.Block, P{ .body = body }, .{});
2285}
2286
22872392/// TODO The existence of this function is a workaround for a bug in stage1.
22882393pub fn addZIRInstLoop(mod: *Module, scope: *Scope, src: usize, body: zir.Module.Body) !*zir.Inst.Loop {
22892394 const P = std.meta.fieldInfo(zir.Inst.Loop, "positionals").field_type;
src-self-hosted/codegen.zig+63-8
......@@ -132,7 +132,7 @@ pub fn generateSymbol(
132132 .Array => {
133133 // TODO populate .debug_info for the array
134134 if (typed_value.val.cast(Value.Payload.Bytes)) |payload| {
135 if (typed_value.ty.arraySentinel()) |sentinel| {
135 if (typed_value.ty.sentinel()) |sentinel| {
136136 try code.ensureCapacity(code.items.len + payload.data.len + 1);
137137 code.appendSliceAssumeCapacity(payload.data);
138138 const prev_len = code.items.len;
......@@ -359,7 +359,7 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {
359359 };
360360
361361 const Branch = struct {
362 inst_table: std.AutoHashMapUnmanaged(*ir.Inst, MCValue) = .{},
362 inst_table: std.AutoArrayHashMapUnmanaged(*ir.Inst, MCValue) = .{},
363363
364364 fn deinit(self: *Branch, gpa: *Allocator) void {
365365 self.inst_table.deinit(gpa);
......@@ -436,8 +436,8 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {
436436 try branch_stack.append(.{});
437437
438438 const src_data: struct {lbrace_src: usize, rbrace_src: usize, source: []const u8} = blk: {
439 if (module_fn.owner_decl.scope.cast(Module.Scope.File)) |scope_file| {
440 const tree = scope_file.contents.tree;
439 if (module_fn.owner_decl.scope.cast(Module.Scope.Container)) |container_scope| {
440 const tree = container_scope.file_scope.contents.tree;
441441 const fn_proto = tree.root_node.decls()[module_fn.owner_decl.src_index].castTag(.FnProto).?;
442442 const block = fn_proto.getBodyNode().?.castTag(.Block).?;
443443 const lbrace_src = tree.token_locs[block.lbrace].start;
......@@ -750,7 +750,7 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {
750750 const ptr_bits = arch.ptrBitWidth();
751751 const ptr_bytes: u64 = @divExact(ptr_bits, 8);
752752 if (abi_size <= ptr_bytes) {
753 try self.registers.ensureCapacity(self.gpa, self.registers.items().len + 1);
753 try self.registers.ensureCapacity(self.gpa, self.registers.count() + 1);
754754 if (self.allocReg(inst)) |reg| {
755755 return MCValue{ .register = registerAlias(reg, abi_size) };
756756 }
......@@ -788,7 +788,7 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {
788788 /// `reg_owner` is the instruction that gets associated with the register in the register table.
789789 /// This can have a side effect of spilling instructions to the stack to free up a register.
790790 fn copyToNewRegister(self: *Self, reg_owner: *ir.Inst, mcv: MCValue) !MCValue {
791 try self.registers.ensureCapacity(self.gpa, self.registers.items().len + 1);
791 try self.registers.ensureCapacity(self.gpa, @intCast(u32, self.registers.count() + 1));
792792
793793 const reg = self.allocReg(reg_owner) orelse b: {
794794 // We'll take over the first register. Move the instruction that was previously
......@@ -1247,7 +1247,7 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {
12471247 if (inst.base.isUnused())
12481248 return MCValue.dead;
12491249
1250 try self.registers.ensureCapacity(self.gpa, self.registers.items().len + 1);
1250 try self.registers.ensureCapacity(self.gpa, self.registers.count() + 1);
12511251
12521252 const result = self.args[self.arg_index];
12531253 self.arg_index += 1;
......@@ -1443,7 +1443,57 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {
14431443 }
14441444 } else if (self.bin_file.cast(link.File.MachO)) |macho_file| {
14451445 switch (arch) {
1446 .x86_64 => return self.fail(inst.base.src, "TODO implement codegen for call when linking with MachO for x86_64 arch", .{}),
1446 .x86_64 => {
1447 for (info.args) |mc_arg, arg_i| {
1448 const arg = inst.args[arg_i];
1449 const arg_mcv = try self.resolveInst(inst.args[arg_i]);
1450 // Here we do not use setRegOrMem even though the logic is similar, because
1451 // the function call will move the stack pointer, so the offsets are different.
1452 switch (mc_arg) {
1453 .none => continue,
1454 .register => |reg| {
1455 try self.genSetReg(arg.src, reg, arg_mcv);
1456 // TODO interact with the register allocator to mark the instruction as moved.
1457 },
1458 .stack_offset => {
1459 // Here we need to emit instructions like this:
1460 // mov qword ptr [rsp + stack_offset], x
1461 return self.fail(inst.base.src, "TODO implement calling with parameters in memory", .{});
1462 },
1463 .ptr_stack_offset => {
1464 return self.fail(inst.base.src, "TODO implement calling with MCValue.ptr_stack_offset arg", .{});
1465 },
1466 .ptr_embedded_in_code => {
1467 return self.fail(inst.base.src, "TODO implement calling with MCValue.ptr_embedded_in_code arg", .{});
1468 },
1469 .undef => unreachable,
1470 .immediate => unreachable,
1471 .unreach => unreachable,
1472 .dead => unreachable,
1473 .embedded_in_code => unreachable,
1474 .memory => unreachable,
1475 .compare_flags_signed => unreachable,
1476 .compare_flags_unsigned => unreachable,
1477 }
1478 }
1479
1480 if (inst.func.cast(ir.Inst.Constant)) |func_inst| {
1481 if (func_inst.val.cast(Value.Payload.Function)) |func_val| {
1482 const func = func_val.func;
1483 const got = &macho_file.sections.items[macho_file.got_section_index.?];
1484 const ptr_bytes = 8;
1485 const got_addr = @intCast(u32, got.addr + func.owner_decl.link.macho.offset_table_index.? * ptr_bytes);
1486 // ff 14 25 xx xx xx xx call [addr]
1487 try self.code.ensureCapacity(self.code.items.len + 7);
1488 self.code.appendSliceAssumeCapacity(&[3]u8{ 0xff, 0x14, 0x25 });
1489 mem.writeIntLittle(u32, self.code.addManyAsArrayAssumeCapacity(4), got_addr);
1490 } else {
1491 return self.fail(inst.base.src, "TODO implement calling bitcasted functions", .{});
1492 }
1493 } else {
1494 return self.fail(inst.base.src, "TODO implement calling runtime known function pointer", .{});
1495 }
1496 },
14471497 .aarch64 => return self.fail(inst.base.src, "TODO implement codegen for call when linking with MachO for aarch64 arch", .{}),
14481498 else => unreachable,
14491499 }
......@@ -2486,6 +2536,11 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {
24862536 const got = &elf_file.program_headers.items[elf_file.phdr_got_index.?];
24872537 const got_addr = got.p_vaddr + decl.link.elf.offset_table_index * ptr_bytes;
24882538 return MCValue{ .memory = got_addr };
2539 } else if (self.bin_file.cast(link.File.MachO)) |macho_file| {
2540 const decl = payload.decl;
2541 const got = &macho_file.sections.items[macho_file.got_section_index.?];
2542 const got_addr = got.addr + decl.link.macho.offset_table_index.? * ptr_bytes;
2543 return MCValue{ .memory = got_addr };
24892544 } else {
24902545 return self.fail(src, "TODO codegen non-ELF const Decl pointer", .{});
24912546 }
src-self-hosted/codegen/c.zig+3-2
......@@ -85,7 +85,7 @@ fn genArray(file: *C, decl: *Decl) !void {
8585 const name = try map(file.base.allocator, mem.span(decl.name));
8686 defer file.base.allocator.free(name);
8787 if (tv.val.cast(Value.Payload.Bytes)) |payload|
88 if (tv.ty.arraySentinel()) |sentinel|
88 if (tv.ty.sentinel()) |sentinel|
8989 if (sentinel.toUnsignedInt() == 0)
9090 try file.constants.writer().print("const char *const {} = \"{}\";\n", .{ name, payload.data })
9191 else
......@@ -110,7 +110,8 @@ const Context = struct {
110110 }
111111
112112 fn deinit(self: *Context) void {
113 for (self.inst_map.items()) |kv| {
113 var it = self.inst_map.iterator();
114 while (it.next()) |kv| {
114115 self.file.base.allocator.free(kv.value);
115116 }
116117 self.inst_map.deinit();
src-self-hosted/ir.zig+9-1
......@@ -189,7 +189,7 @@ pub const Inst = struct {
189189 }
190190
191191 pub fn cmpOperator(base: *Inst) ?std.math.CompareOperator {
192 return switch (self.base.tag) {
192 return switch (base.tag) {
193193 .cmp_lt => .lt,
194194 .cmp_lte => .lte,
195195 .cmp_eq => .eq,
......@@ -220,6 +220,14 @@ pub const Inst = struct {
220220 unreachable;
221221 }
222222
223 pub fn breakBlock(base: *Inst) ?*Block {
224 return switch (base.tag) {
225 .br => base.castTag(.br).?.block,
226 .brvoid => base.castTag(.brvoid).?.block,
227 else => null,
228 };
229 }
230
223231 pub const NoOp = struct {
224232 base: Inst,
225233
src-self-hosted/link.zig+1-1
......@@ -47,7 +47,7 @@ pub const File = struct {
4747 };
4848
4949 /// For DWARF .debug_info.
50 pub const DbgInfoTypeRelocsTable = std.HashMapUnmanaged(Type, DbgInfoTypeReloc, Type.hash, Type.eql, true);
50 pub const DbgInfoTypeRelocsTable = std.HashMapUnmanaged(Type, DbgInfoTypeReloc, Type.hash, Type.eql, std.hash_map.DefaultMaxLoadPercentage);
5151
5252 /// For DWARF .debug_info.
5353 pub const DbgInfoTypeReloc = struct {
src-self-hosted/link/Elf.zig+10-7
......@@ -1629,7 +1629,8 @@ pub fn updateDecl(self: *Elf, module: *Module, decl: *Module.Decl) !void {
16291629
16301630 var dbg_info_type_relocs: File.DbgInfoTypeRelocsTable = .{};
16311631 defer {
1632 for (dbg_info_type_relocs.items()) |*entry| {
1632 var it = dbg_info_type_relocs.iterator();
1633 while (it.next()) |entry| {
16331634 entry.value.relocs.deinit(self.base.allocator);
16341635 }
16351636 dbg_info_type_relocs.deinit(self.base.allocator);
......@@ -1655,8 +1656,8 @@ pub fn updateDecl(self: *Elf, module: *Module, decl: *Module.Decl) !void {
16551656 try dbg_line_buffer.ensureCapacity(26);
16561657
16571658 const line_off: u28 = blk: {
1658 if (decl.scope.cast(Module.Scope.File)) |scope_file| {
1659 const tree = scope_file.contents.tree;
1659 if (decl.scope.cast(Module.Scope.Container)) |container_scope| {
1660 const tree = container_scope.file_scope.contents.tree;
16601661 const file_ast_decls = tree.root_node.decls();
16611662 // TODO Look into improving the performance here by adding a token-index-to-line
16621663 // lookup table. Currently this involves scanning over the source code for newlines.
......@@ -1917,7 +1918,8 @@ pub fn updateDecl(self: *Elf, module: *Module, decl: *Module.Decl) !void {
19171918 // Now we emit the .debug_info types of the Decl. These will count towards the size of
19181919 // the buffer, so we have to do it before computing the offset, and we can't perform the actual
19191920 // relocations yet.
1920 for (dbg_info_type_relocs.items()) |*entry| {
1921 var it = dbg_info_type_relocs.iterator();
1922 while (it.next()) |entry| {
19211923 entry.value.off = @intCast(u32, dbg_info_buffer.items.len);
19221924 try self.addDbgInfoType(entry.key, &dbg_info_buffer);
19231925 }
......@@ -1925,7 +1927,8 @@ pub fn updateDecl(self: *Elf, module: *Module, decl: *Module.Decl) !void {
19251927 try self.updateDeclDebugInfoAllocation(text_block, @intCast(u32, dbg_info_buffer.items.len));
19261928
19271929 // Now that we have the offset assigned we can finally perform type relocations.
1928 for (dbg_info_type_relocs.items()) |entry| {
1930 it = dbg_info_type_relocs.iterator();
1931 while (it.next()) |entry| {
19291932 for (entry.value.relocs.items) |off| {
19301933 mem.writeInt(
19311934 u32,
......@@ -2154,8 +2157,8 @@ pub fn updateDeclLineNumber(self: *Elf, module: *Module, decl: *const Module.Dec
21542157 const tracy = trace(@src());
21552158 defer tracy.end();
21562159
2157 const scope_file = decl.scope.cast(Module.Scope.File).?;
2158 const tree = scope_file.contents.tree;
2160 const container_scope = decl.scope.cast(Module.Scope.Container).?;
2161 const tree = container_scope.file_scope.contents.tree;
21592162 const file_ast_decls = tree.root_node.decls();
21602163 // TODO Look into improving the performance here by adding a token-index-to-line
21612164 // lookup table. Currently this involves scanning over the source code for newlines.
src-self-hosted/link/MachO.zig+525-159
......@@ -18,36 +18,66 @@ const File = link.File;
1818
1919pub const base_tag: File.Tag = File.Tag.macho;
2020
21const LoadCommand = union(enum) {
22 Segment: macho.segment_command_64,
23 LinkeditData: macho.linkedit_data_command,
24 Symtab: macho.symtab_command,
25 Dysymtab: macho.dysymtab_command,
26
27 pub fn cmdsize(self: LoadCommand) u32 {
28 return switch (self) {
29 .Segment => |x| x.cmdsize,
30 .LinkeditData => |x| x.cmdsize,
31 .Symtab => |x| x.cmdsize,
32 .Dysymtab => |x| x.cmdsize,
33 };
34 }
35};
36
2137base: File,
2238
23/// List of all load command headers that are in the file.
24/// We use it to track number and size of all commands needed by the header.
25commands: std.ArrayListUnmanaged(macho.load_command) = std.ArrayListUnmanaged(macho.load_command){},
26command_file_offset: ?u64 = null,
39/// Table of all load commands
40load_commands: std.ArrayListUnmanaged(LoadCommand) = .{},
41segment_cmd_index: ?u16 = null,
42symtab_cmd_index: ?u16 = null,
43dysymtab_cmd_index: ?u16 = null,
44data_in_code_cmd_index: ?u16 = null,
2745
28/// Stored in native-endian format, depending on target endianness needs to be bswapped on read/write.
29/// Same order as in the file.
30segments: std.ArrayListUnmanaged(macho.segment_command_64) = std.ArrayListUnmanaged(macho.segment_command_64){},
31/// Section (headers) *always* follow segment (load commands) directly!
32sections: std.ArrayListUnmanaged(macho.section_64) = std.ArrayListUnmanaged(macho.section_64){},
46/// Table of all sections
47sections: std.ArrayListUnmanaged(macho.section_64) = .{},
3348
34/// Offset (index) into __TEXT segment load command.
35text_segment_offset: ?u64 = null,
36/// Offset (index) into __LINKEDIT segment load command.
37linkedit_segment_offset: ?u664 = null,
49/// __TEXT segment sections
50text_section_index: ?u16 = null,
51cstring_section_index: ?u16 = null,
52const_text_section_index: ?u16 = null,
53stubs_section_index: ?u16 = null,
54stub_helper_section_index: ?u16 = null,
55
56/// __DATA segment sections
57got_section_index: ?u16 = null,
58const_data_section_index: ?u16 = null,
3859
39/// Entry point load command
40entry_point_cmd: ?macho.entry_point_command = null,
4160entry_addr: ?u64 = null,
4261
43/// The first 4GB of process' memory is reserved for the null (__PAGEZERO) segment.
44/// This is also the start address for our binary.
45vm_start_address: u64 = 0x100000000,
62/// Table of all symbols used.
63/// Internally references string table for names (which are optional).
64symbol_table: std.ArrayListUnmanaged(macho.nlist_64) = .{},
65
66/// Table of symbol names aka the string table.
67string_table: std.ArrayListUnmanaged(u8) = .{},
4668
47seg_table_dirty: bool = false,
69/// Table of symbol vaddr values. The values is the absolute vaddr value.
70/// If the vaddr of the executable __TEXT segment vaddr changes, the entire offset
71/// table needs to be rewritten.
72offset_table: std.ArrayListUnmanaged(u64) = .{},
4873
4974error_flags: File.ErrorFlags = File.ErrorFlags{},
5075
76cmd_table_dirty: bool = false,
77
78/// Pointer to the last allocated text block
79last_text_block: ?*TextBlock = null,
80
5181/// `alloc_num / alloc_den` is the factor of padding when allocating.
5282const alloc_num = 4;
5383const alloc_den = 3;
......@@ -67,7 +97,23 @@ const LIB_SYSTEM_NAME: [*:0]const u8 = "System";
6797const LIB_SYSTEM_PATH: [*:0]const u8 = DEFAULT_LIB_SEARCH_PATH ++ "/libSystem.B.dylib";
6898
6999pub const TextBlock = struct {
70 pub const empty = TextBlock{};
100 /// Index into the symbol table
101 symbol_table_index: ?u32,
102 /// Index into offset table
103 offset_table_index: ?u32,
104 /// Size of this text block
105 size: u64,
106 /// Points to the previous and next neighbours
107 prev: ?*TextBlock,
108 next: ?*TextBlock,
109
110 pub const empty = TextBlock{
111 .symbol_table_index = null,
112 .offset_table_index = null,
113 .size = 0,
114 .prev = null,
115 .next = null,
116 };
71117};
72118
73119pub const SrcFn = struct {
......@@ -117,6 +163,12 @@ fn openFile(allocator: *Allocator, file: fs.File, options: link.Options) !MachO
117163/// Truncates the existing file contents and overwrites the contents.
118164/// Returns an error if `file` is not already open with +read +write +seek abilities.
119165fn createFile(allocator: *Allocator, file: fs.File, options: link.Options) !MachO {
166 switch (options.output_mode) {
167 .Exe => {},
168 .Obj => {},
169 .Lib => return error.TODOImplementWritingLibFiles,
170 }
171
120172 var self: MachO = .{
121173 .base = .{
122174 .file = file,
......@@ -127,104 +179,15 @@ fn createFile(allocator: *Allocator, file: fs.File, options: link.Options) !Mach
127179 };
128180 errdefer self.deinit();
129181
130 switch (options.output_mode) {
131 .Exe => {
132 // The first segment command for executables is always a __PAGEZERO segment.
133 const pagezero = .{
134 .cmd = macho.LC_SEGMENT_64,
135 .cmdsize = commandSize(@sizeOf(macho.segment_command_64)),
136 .segname = makeString("__PAGEZERO"),
137 .vmaddr = 0,
138 .vmsize = self.vm_start_address,
139 .fileoff = 0,
140 .filesize = 0,
141 .maxprot = macho.VM_PROT_NONE,
142 .initprot = macho.VM_PROT_NONE,
143 .nsects = 0,
144 .flags = 0,
145 };
146 try self.commands.append(allocator, .{
147 .cmd = pagezero.cmd,
148 .cmdsize = pagezero.cmdsize,
149 });
150 try self.segments.append(allocator, pagezero);
151 },
152 .Obj => return error.TODOImplementWritingObjFiles,
153 .Lib => return error.TODOImplementWritingLibFiles,
154 }
155
156182 try self.populateMissingMetadata();
157183
158184 return self;
159185}
160186
161fn writeMachOHeader(self: *MachO) !void {
162 var hdr: macho.mach_header_64 = undefined;
163 hdr.magic = macho.MH_MAGIC_64;
164
165 const CpuInfo = struct {
166 cpu_type: macho.cpu_type_t,
167 cpu_subtype: macho.cpu_subtype_t,
168 };
169
170 const cpu_info: CpuInfo = switch (self.base.options.target.cpu.arch) {
171 .aarch64 => .{
172 .cpu_type = macho.CPU_TYPE_ARM64,
173 .cpu_subtype = macho.CPU_SUBTYPE_ARM_ALL,
174 },
175 .x86_64 => .{
176 .cpu_type = macho.CPU_TYPE_X86_64,
177 .cpu_subtype = macho.CPU_SUBTYPE_X86_64_ALL,
178 },
179 else => return error.UnsupportedMachOArchitecture,
180 };
181 hdr.cputype = cpu_info.cpu_type;
182 hdr.cpusubtype = cpu_info.cpu_subtype;
183
184 const filetype: u32 = switch (self.base.options.output_mode) {
185 .Exe => macho.MH_EXECUTE,
186 .Obj => macho.MH_OBJECT,
187 .Lib => switch (self.base.options.link_mode) {
188 .Static => return error.TODOStaticLibMachOType,
189 .Dynamic => macho.MH_DYLIB,
190 },
191 };
192 hdr.filetype = filetype;
193
194 const ncmds = try math.cast(u32, self.commands.items.len);
195 hdr.ncmds = ncmds;
196
197 var sizeof_cmds: u32 = 0;
198 for (self.commands.items) |cmd| {
199 sizeof_cmds += cmd.cmdsize;
200 }
201 hdr.sizeofcmds = sizeof_cmds;
202
203 // TODO should these be set to something else?
204 hdr.flags = 0;
205 hdr.reserved = 0;
206
207 try self.base.file.?.pwriteAll(@ptrCast([*]const u8, &hdr)[0..@sizeOf(macho.mach_header_64)], 0);
208}
209
210187pub fn flush(self: *MachO, module: *Module) !void {
211 // Save segments first
212 {
213 const buf = try self.base.allocator.alloc(macho.segment_command_64, self.segments.items.len);
214 defer self.base.allocator.free(buf);
215
216 self.command_file_offset = @sizeOf(macho.mach_header_64);
217
218 for (buf) |*seg, i| {
219 seg.* = self.segments.items[i];
220 self.command_file_offset.? += self.segments.items[i].cmdsize;
221 }
222
223 try self.base.file.?.pwriteAll(mem.sliceAsBytes(buf), @sizeOf(macho.mach_header_64));
224 }
225
226188 switch (self.base.options.output_mode) {
227189 .Exe => {
190 var last_cmd_offset: usize = @sizeOf(macho.mach_header_64);
228191 {
229192 // Specify path to dynamic linker dyld
230193 const cmdsize = commandSize(@sizeOf(macho.dylinker_command) + mem.lenZ(DEFAULT_DYLD_PATH));
......@@ -235,18 +198,14 @@ pub fn flush(self: *MachO, module: *Module) !void {
235198 .name = @sizeOf(macho.dylinker_command),
236199 },
237200 };
238 try self.commands.append(self.base.allocator, .{
239 .cmd = macho.LC_LOAD_DYLINKER,
240 .cmdsize = cmdsize,
241 });
242201
243 try self.base.file.?.pwriteAll(mem.sliceAsBytes(load_dylinker[0..1]), self.command_file_offset.?);
202 try self.base.file.?.pwriteAll(mem.sliceAsBytes(load_dylinker[0..1]), last_cmd_offset);
244203
245 const file_offset = self.command_file_offset.? + @sizeOf(macho.dylinker_command);
204 const file_offset = last_cmd_offset + @sizeOf(macho.dylinker_command);
246205 try self.addPadding(cmdsize - @sizeOf(macho.dylinker_command), file_offset);
247206
248207 try self.base.file.?.pwriteAll(mem.spanZ(DEFAULT_DYLD_PATH), file_offset);
249 self.command_file_offset.? += cmdsize;
208 last_cmd_offset += cmdsize;
250209 }
251210
252211 {
......@@ -268,21 +227,44 @@ pub fn flush(self: *MachO, module: *Module) !void {
268227 .dylib = dylib,
269228 },
270229 };
271 try self.commands.append(self.base.allocator, .{
272 .cmd = macho.LC_LOAD_DYLIB,
273 .cmdsize = cmdsize,
274 });
275230
276 try self.base.file.?.pwriteAll(mem.sliceAsBytes(load_dylib[0..1]), self.command_file_offset.?);
231 try self.base.file.?.pwriteAll(mem.sliceAsBytes(load_dylib[0..1]), last_cmd_offset);
277232
278 const file_offset = self.command_file_offset.? + @sizeOf(macho.dylib_command);
233 const file_offset = last_cmd_offset + @sizeOf(macho.dylib_command);
279234 try self.addPadding(cmdsize - @sizeOf(macho.dylib_command), file_offset);
280235
281236 try self.base.file.?.pwriteAll(mem.spanZ(LIB_SYSTEM_PATH), file_offset);
282 self.command_file_offset.? += cmdsize;
237 last_cmd_offset += cmdsize;
283238 }
284239 },
285 .Obj => return error.TODOImplementWritingObjFiles,
240 .Obj => {
241 {
242 const symtab = &self.load_commands.items[self.symtab_cmd_index.?].Symtab;
243 symtab.nsyms = @intCast(u32, self.symbol_table.items.len);
244 const allocated_size = self.allocatedSize(symtab.stroff);
245 const needed_size = self.string_table.items.len;
246 log.debug("allocated_size = 0x{x}, needed_size = 0x{x}\n", .{ allocated_size, needed_size });
247
248 if (needed_size > allocated_size) {
249 symtab.strsize = 0;
250 symtab.stroff = @intCast(u32, self.findFreeSpace(needed_size, 1));
251 }
252 symtab.strsize = @intCast(u32, needed_size);
253
254 log.debug("writing string table from 0x{x} to 0x{x}\n", .{ symtab.stroff, symtab.stroff + symtab.strsize });
255
256 try self.base.file.?.pwriteAll(self.string_table.items, symtab.stroff);
257 }
258
259 var last_cmd_offset: usize = @sizeOf(macho.mach_header_64);
260 for (self.load_commands.items) |cmd| {
261 const cmd_to_write = [1]@TypeOf(cmd){cmd};
262 try self.base.file.?.pwriteAll(mem.sliceAsBytes(cmd_to_write[0..1]), last_cmd_offset);
263 last_cmd_offset += cmd.cmdsize();
264 }
265 const off = @sizeOf(macho.mach_header_64) + @sizeOf(macho.segment_command_64);
266 try self.base.file.?.pwriteAll(mem.sliceAsBytes(self.sections.items), off);
267 },
286268 .Lib => return error.TODOImplementWritingLibFiles,
287269 }
288270
......@@ -297,14 +279,110 @@ pub fn flush(self: *MachO, module: *Module) !void {
297279}
298280
299281pub fn deinit(self: *MachO) void {
300 self.commands.deinit(self.base.allocator);
301 self.segments.deinit(self.base.allocator);
282 self.offset_table.deinit(self.base.allocator);
283 self.string_table.deinit(self.base.allocator);
284 self.symbol_table.deinit(self.base.allocator);
302285 self.sections.deinit(self.base.allocator);
286 self.load_commands.deinit(self.base.allocator);
287}
288
289pub fn allocateDeclIndexes(self: *MachO, decl: *Module.Decl) !void {
290 if (decl.link.macho.symbol_table_index) |_| return;
291
292 try self.symbol_table.ensureCapacity(self.base.allocator, self.symbol_table.items.len + 1);
293 try self.offset_table.ensureCapacity(self.base.allocator, self.offset_table.items.len + 1);
294
295 log.debug("allocating symbol index {} for {}\n", .{ self.symbol_table.items.len, decl.name });
296 decl.link.macho.symbol_table_index = @intCast(u32, self.symbol_table.items.len);
297 _ = self.symbol_table.addOneAssumeCapacity();
298
299 decl.link.macho.offset_table_index = @intCast(u32, self.offset_table.items.len);
300 _ = self.offset_table.addOneAssumeCapacity();
301
302 self.symbol_table.items[decl.link.macho.symbol_table_index.?] = .{
303 .n_strx = 0,
304 .n_type = 0,
305 .n_sect = 0,
306 .n_desc = 0,
307 .n_value = 0,
308 };
309 self.offset_table.items[decl.link.macho.offset_table_index.?] = 0;
303310}
304311
305pub fn allocateDeclIndexes(self: *MachO, decl: *Module.Decl) !void {}
312pub fn updateDecl(self: *MachO, module: *Module, decl: *Module.Decl) !void {
313 const tracy = trace(@src());
314 defer tracy.end();
315
316 var code_buffer = std.ArrayList(u8).init(self.base.allocator);
317 defer code_buffer.deinit();
306318
307pub fn updateDecl(self: *MachO, module: *Module, decl: *Module.Decl) !void {}
319 var dbg_line_buffer = std.ArrayList(u8).init(self.base.allocator);
320 defer dbg_line_buffer.deinit();
321
322 var dbg_info_buffer = std.ArrayList(u8).init(self.base.allocator);
323 defer dbg_info_buffer.deinit();
324
325 var dbg_info_type_relocs: File.DbgInfoTypeRelocsTable = .{};
326 defer {
327 var it = dbg_info_type_relocs.iterator();
328 while (it.next()) |entry| {
329 entry.value.relocs.deinit(self.base.allocator);
330 }
331 dbg_info_type_relocs.deinit(self.base.allocator);
332 }
333
334 const typed_value = decl.typed_value.most_recent.typed_value;
335 const res = try codegen.generateSymbol(
336 &self.base,
337 decl.src(),
338 typed_value,
339 &code_buffer,
340 &dbg_line_buffer,
341 &dbg_info_buffer,
342 &dbg_info_type_relocs,
343 );
344
345 const code = switch (res) {
346 .externally_managed => |x| x,
347 .appended => code_buffer.items,
348 .fail => |em| {
349 decl.analysis = .codegen_failure;
350 try module.failed_decls.put(module.gpa, decl, em);
351 return;
352 },
353 };
354 log.debug("generated code {}\n", .{code});
355
356 const required_alignment = typed_value.ty.abiAlignment(self.base.options.target);
357 const symbol = &self.symbol_table.items[decl.link.macho.symbol_table_index.?];
358
359 const decl_name = mem.spanZ(decl.name);
360 const name_str_index = try self.makeString(decl_name);
361 const addr = try self.allocateTextBlock(&decl.link.macho, code.len, required_alignment);
362 log.debug("allocated text block for {} at 0x{x}\n", .{ decl_name, addr });
363 log.debug("updated text section {}\n", .{self.sections.items[self.text_section_index.?]});
364
365 symbol.* = .{
366 .n_strx = name_str_index,
367 .n_type = macho.N_SECT,
368 .n_sect = @intCast(u8, self.text_section_index.?) + 1,
369 .n_desc = 0,
370 .n_value = addr,
371 };
372 self.offset_table.items[decl.link.macho.offset_table_index.?] = addr;
373
374 try self.writeSymbol(decl.link.macho.symbol_table_index.?);
375
376 const text_section = self.sections.items[self.text_section_index.?];
377 const section_offset = symbol.n_value - text_section.addr;
378 const file_offset = text_section.offset + section_offset;
379 log.debug("file_offset 0x{x}\n", .{file_offset});
380 try self.base.file.?.pwriteAll(code, file_offset);
381
382 // Since we updated the vaddr and the size, each corresponding export symbol also needs to be updated.
383 const decl_exports = module.decl_exports.get(decl) orelse &[0]*Module.Export{};
384 return self.updateDeclExports(module, decl, decl_exports);
385}
308386
309387pub fn updateDeclLineNumber(self: *MachO, module: *Module, decl: *const Module.Decl) !void {}
310388
......@@ -313,51 +391,191 @@ pub fn updateDeclExports(
313391 module: *Module,
314392 decl: *const Module.Decl,
315393 exports: []const *Module.Export,
316) !void {}
394) !void {
395 const tracy = trace(@src());
396 defer tracy.end();
397
398 if (decl.link.macho.symbol_table_index == null) return;
399
400 var decl_sym = self.symbol_table.items[decl.link.macho.symbol_table_index.?];
401 // TODO implement
402 if (exports.len == 0) return;
403
404 const exp = exports[0];
405 self.entry_addr = decl_sym.n_value;
406 decl_sym.n_type |= macho.N_EXT;
407 exp.link.sym_index = 0;
408}
317409
318410pub fn freeDecl(self: *MachO, decl: *Module.Decl) void {}
319411
320412pub fn getDeclVAddr(self: *MachO, decl: *const Module.Decl) u64 {
321 @panic("TODO implement getDeclVAddr for MachO");
413 return self.symbol_table.items[decl.link.macho.symbol_table_index.?].n_value;
322414}
323415
324416pub fn populateMissingMetadata(self: *MachO) !void {
325 if (self.text_segment_offset == null) {
326 self.text_segment_offset = @intCast(u64, self.segments.items.len);
327 const file_size = alignSize(u64, self.base.options.program_code_size_hint, 0x1000);
328 log.debug("vmsize/filesize = {}", .{file_size});
329 const file_offset = 0;
330 const vm_address = self.vm_start_address; // the end of __PAGEZERO segment in VM
331 const protection = macho.VM_PROT_READ | macho.VM_PROT_EXECUTE;
332 const cmdsize = commandSize(@sizeOf(macho.segment_command_64));
333 const text_segment = .{
334 .cmd = macho.LC_SEGMENT_64,
335 .cmdsize = cmdsize,
336 .segname = makeString("__TEXT"),
337 .vmaddr = vm_address,
338 .vmsize = file_size,
339 .fileoff = 0, // __TEXT segment *always* starts at 0 file offset
340 .filesize = 0, //file_size,
341 .maxprot = protection,
342 .initprot = protection,
343 .nsects = 0,
344 .flags = 0,
345 };
346 try self.commands.append(self.base.allocator, .{
347 .cmd = macho.LC_SEGMENT_64,
348 .cmdsize = cmdsize,
417 if (self.segment_cmd_index == null) {
418 self.segment_cmd_index = @intCast(u16, self.load_commands.items.len);
419 try self.load_commands.append(self.base.allocator, .{
420 .Segment = .{
421 .cmd = macho.LC_SEGMENT_64,
422 .cmdsize = @sizeOf(macho.segment_command_64),
423 .segname = makeStaticString(""),
424 .vmaddr = 0,
425 .vmsize = 0,
426 .fileoff = 0,
427 .filesize = 0,
428 .maxprot = 0,
429 .initprot = 0,
430 .nsects = 0,
431 .flags = 0,
432 },
433 });
434 self.cmd_table_dirty = true;
435 }
436 if (self.symtab_cmd_index == null) {
437 self.symtab_cmd_index = @intCast(u16, self.load_commands.items.len);
438 try self.load_commands.append(self.base.allocator, .{
439 .Symtab = .{
440 .cmd = macho.LC_SYMTAB,
441 .cmdsize = @sizeOf(macho.symtab_command),
442 .symoff = 0,
443 .nsyms = 0,
444 .stroff = 0,
445 .strsize = 0,
446 },
349447 });
350 try self.segments.append(self.base.allocator, text_segment);
448 self.cmd_table_dirty = true;
449 }
450 if (self.text_section_index == null) {
451 self.text_section_index = @intCast(u16, self.sections.items.len);
452 const segment = &self.load_commands.items[self.segment_cmd_index.?].Segment;
453 segment.cmdsize += @sizeOf(macho.section_64);
454 segment.nsects += 1;
455
456 const file_size = self.base.options.program_code_size_hint;
457 const off = @intCast(u32, self.findFreeSpace(file_size, 1));
458 const flags = macho.S_REGULAR | macho.S_ATTR_PURE_INSTRUCTIONS | macho.S_ATTR_SOME_INSTRUCTIONS;
459
460 log.debug("found __text section free space 0x{x} to 0x{x}\n", .{ off, off + file_size });
461
462 try self.sections.append(self.base.allocator, .{
463 .sectname = makeStaticString("__text"),
464 .segname = makeStaticString("__TEXT"),
465 .addr = 0,
466 .size = file_size,
467 .offset = off,
468 .@"align" = 0x1000,
469 .reloff = 0,
470 .nreloc = 0,
471 .flags = flags,
472 .reserved1 = 0,
473 .reserved2 = 0,
474 .reserved3 = 0,
475 });
476
477 segment.vmsize += file_size;
478 segment.filesize += file_size;
479 segment.fileoff = off;
480
481 log.debug("initial text section {}\n", .{self.sections.items[self.text_section_index.?]});
482 }
483 {
484 const symtab = &self.load_commands.items[self.symtab_cmd_index.?].Symtab;
485 if (symtab.symoff == 0) {
486 const p_align = @sizeOf(macho.nlist_64);
487 const nsyms = self.base.options.symbol_count_hint;
488 const file_size = p_align * nsyms;
489 const off = @intCast(u32, self.findFreeSpace(file_size, p_align));
490 log.debug("found symbol table free space 0x{x} to 0x{x}\n", .{ off, off + file_size });
491 symtab.symoff = off;
492 symtab.nsyms = @intCast(u32, nsyms);
493 }
494 if (symtab.stroff == 0) {
495 try self.string_table.append(self.base.allocator, 0);
496 const file_size = @intCast(u32, self.string_table.items.len);
497 const off = @intCast(u32, self.findFreeSpace(file_size, 1));
498 log.debug("found string table free space 0x{x} to 0x{x}\n", .{ off, off + file_size });
499 symtab.stroff = off;
500 symtab.strsize = file_size;
501 }
351502 }
352503}
353504
354fn makeString(comptime bytes: []const u8) [16]u8 {
505fn allocateTextBlock(self: *MachO, text_block: *TextBlock, new_block_size: u64, alignment: u64) !u64 {
506 const segment = &self.load_commands.items[self.segment_cmd_index.?].Segment;
507 const text_section = &self.sections.items[self.text_section_index.?];
508 const new_block_ideal_capacity = new_block_size * alloc_num / alloc_den;
509
510 var block_placement: ?*TextBlock = null;
511 const addr = blk: {
512 if (self.last_text_block) |last| {
513 const last_symbol = self.symbol_table.items[last.symbol_table_index.?];
514 const ideal_capacity = last.size * alloc_num / alloc_den;
515 const ideal_capacity_end_addr = last_symbol.n_value + ideal_capacity;
516 const new_start_addr = mem.alignForwardGeneric(u64, ideal_capacity_end_addr, alignment);
517 block_placement = last;
518 break :blk new_start_addr;
519 } else {
520 break :blk text_section.addr;
521 }
522 };
523 log.debug("computed symbol address 0x{x}\n", .{addr});
524
525 const expand_text_section = block_placement == null or block_placement.?.next == null;
526 if (expand_text_section) {
527 const text_capacity = self.allocatedSize(text_section.offset);
528 const needed_size = (addr + new_block_size) - text_section.addr;
529 log.debug("text capacity 0x{x}, needed size 0x{x}\n", .{ text_capacity, needed_size });
530
531 if (needed_size > text_capacity) {
532 // TODO handle growth
533 }
534
535 self.last_text_block = text_block;
536 text_section.size = needed_size;
537 segment.vmsize = needed_size;
538 segment.filesize = needed_size;
539 if (alignment < text_section.@"align") {
540 text_section.@"align" = @intCast(u32, alignment);
541 }
542 }
543 text_block.size = new_block_size;
544
545 if (text_block.prev) |prev| {
546 prev.next = text_block.next;
547 }
548 if (text_block.next) |next| {
549 next.prev = text_block.prev;
550 }
551
552 if (block_placement) |big_block| {
553 text_block.prev = big_block;
554 text_block.next = big_block.next;
555 big_block.next = text_block;
556 } else {
557 text_block.prev = null;
558 text_block.next = null;
559 }
560
561 return addr;
562}
563
564fn makeStaticString(comptime bytes: []const u8) [16]u8 {
355565 var buf = [_]u8{0} ** 16;
356 if (bytes.len > buf.len) @compileError("MachO segment/section name too long");
566 if (bytes.len > buf.len) @compileError("string too long; max 16 bytes");
357567 mem.copy(u8, buf[0..], bytes);
358568 return buf;
359569}
360570
571fn makeString(self: *MachO, bytes: []const u8) !u32 {
572 try self.string_table.ensureCapacity(self.base.allocator, self.string_table.items.len + bytes.len + 1);
573 const result = self.string_table.items.len;
574 self.string_table.appendSliceAssumeCapacity(bytes);
575 self.string_table.appendAssumeCapacity(0);
576 return @intCast(u32, result);
577}
578
361579fn alignSize(comptime Int: type, min_size: anytype, alignment: Int) Int {
362580 const size = @intCast(Int, min_size);
363581 if (size % alignment == 0) return size;
......@@ -370,7 +588,7 @@ fn commandSize(min_size: anytype) u32 {
370588 return alignSize(u32, min_size, @sizeOf(u64));
371589}
372590
373fn addPadding(self: *MachO, size: u32, file_offset: u64) !void {
591fn addPadding(self: *MachO, size: u64, file_offset: u64) !void {
374592 if (size == 0) return;
375593
376594 const buf = try self.base.allocator.alloc(u8, size);
......@@ -380,3 +598,151 @@ fn addPadding(self: *MachO, size: u32, file_offset: u64) !void {
380598
381599 try self.base.file.?.pwriteAll(buf, file_offset);
382600}
601
602fn detectAllocCollision(self: *MachO, start: u64, size: u64) ?u64 {
603 const hdr_size: u64 = @sizeOf(macho.mach_header_64);
604 if (start < hdr_size)
605 return hdr_size;
606
607 const end = start + satMul(size, alloc_num) / alloc_den;
608
609 {
610 const off = @sizeOf(macho.mach_header_64);
611 var tight_size: u64 = 0;
612 for (self.load_commands.items) |cmd| {
613 tight_size += cmd.cmdsize();
614 }
615 const increased_size = satMul(tight_size, alloc_num) / alloc_den;
616 const test_end = off + increased_size;
617 if (end > off and start < test_end) {
618 return test_end;
619 }
620 }
621
622 for (self.sections.items) |section| {
623 const increased_size = satMul(section.size, alloc_num) / alloc_den;
624 const test_end = section.offset + increased_size;
625 if (end > section.offset and start < test_end) {
626 return test_end;
627 }
628 }
629
630 if (self.symtab_cmd_index) |symtab_index| {
631 const symtab = self.load_commands.items[symtab_index].Symtab;
632 {
633 const tight_size = @sizeOf(macho.nlist_64) * symtab.nsyms;
634 const increased_size = satMul(tight_size, alloc_num) / alloc_den;
635 const test_end = symtab.symoff + increased_size;
636 if (end > symtab.symoff and start < test_end) {
637 return test_end;
638 }
639 }
640 {
641 const increased_size = satMul(symtab.strsize, alloc_num) / alloc_den;
642 const test_end = symtab.stroff + increased_size;
643 if (end > symtab.stroff and start < test_end) {
644 return test_end;
645 }
646 }
647 }
648
649 return null;
650}
651
652fn allocatedSize(self: *MachO, start: u64) u64 {
653 if (start == 0)
654 return 0;
655 var min_pos: u64 = std.math.maxInt(u64);
656 {
657 const off = @sizeOf(macho.mach_header_64);
658 if (off > start and off < min_pos) min_pos = off;
659 }
660 for (self.sections.items) |section| {
661 if (section.offset <= start) continue;
662 if (section.offset < min_pos) min_pos = section.offset;
663 }
664 if (self.symtab_cmd_index) |symtab_index| {
665 const symtab = self.load_commands.items[symtab_index].Symtab;
666 if (symtab.symoff > start and symtab.symoff < min_pos) min_pos = symtab.symoff;
667 if (symtab.stroff > start and symtab.stroff < min_pos) min_pos = symtab.stroff;
668 }
669 return min_pos - start;
670}
671
672fn findFreeSpace(self: *MachO, object_size: u64, min_alignment: u16) u64 {
673 var start: u64 = 0;
674 while (self.detectAllocCollision(start, object_size)) |item_end| {
675 start = mem.alignForwardGeneric(u64, item_end, min_alignment);
676 }
677 return start;
678}
679
680fn writeSymbol(self: *MachO, index: usize) !void {
681 const tracy = trace(@src());
682 defer tracy.end();
683
684 const symtab = &self.load_commands.items[self.symtab_cmd_index.?].Symtab;
685 var sym = [1]macho.nlist_64{self.symbol_table.items[index]};
686 const off = symtab.symoff + @sizeOf(macho.nlist_64) * index;
687 log.debug("writing symbol {} at 0x{x}\n", .{ sym[0], off });
688 try self.base.file.?.pwriteAll(mem.sliceAsBytes(sym[0..1]), off);
689}
690
691/// Writes Mach-O file header.
692/// Should be invoked last as it needs up-to-date values of ncmds and sizeof_cmds bookkeeping
693/// variables.
694fn writeMachOHeader(self: *MachO) !void {
695 var hdr: macho.mach_header_64 = undefined;
696 hdr.magic = macho.MH_MAGIC_64;
697
698 const CpuInfo = struct {
699 cpu_type: macho.cpu_type_t,
700 cpu_subtype: macho.cpu_subtype_t,
701 };
702
703 const cpu_info: CpuInfo = switch (self.base.options.target.cpu.arch) {
704 .aarch64 => .{
705 .cpu_type = macho.CPU_TYPE_ARM64,
706 .cpu_subtype = macho.CPU_SUBTYPE_ARM_ALL,
707 },
708 .x86_64 => .{
709 .cpu_type = macho.CPU_TYPE_X86_64,
710 .cpu_subtype = macho.CPU_SUBTYPE_X86_64_ALL,
711 },
712 else => return error.UnsupportedMachOArchitecture,
713 };
714 hdr.cputype = cpu_info.cpu_type;
715 hdr.cpusubtype = cpu_info.cpu_subtype;
716
717 const filetype: u32 = switch (self.base.options.output_mode) {
718 .Exe => macho.MH_EXECUTE,
719 .Obj => macho.MH_OBJECT,
720 .Lib => switch (self.base.options.link_mode) {
721 .Static => return error.TODOStaticLibMachOType,
722 .Dynamic => macho.MH_DYLIB,
723 },
724 };
725 hdr.filetype = filetype;
726 hdr.ncmds = @intCast(u32, self.load_commands.items.len);
727
728 var sizeofcmds: u32 = 0;
729 for (self.load_commands.items) |cmd| {
730 sizeofcmds += cmd.cmdsize();
731 }
732
733 hdr.sizeofcmds = sizeofcmds;
734
735 // TODO should these be set to something else?
736 hdr.flags = 0;
737 hdr.reserved = 0;
738
739 log.debug("writing Mach-O header {}\n", .{hdr});
740
741 try self.base.file.?.pwriteAll(@ptrCast([*]const u8, &hdr)[0..@sizeOf(macho.mach_header_64)], 0);
742}
743
744/// Saturating multiplication
745fn satMul(a: anytype, b: anytype) @TypeOf(a, b) {
746 const T = @TypeOf(a, b);
747 return std.math.mul(T, a, b) catch std.math.maxInt(T);
748}
src-self-hosted/liveness.zig+26-15
......@@ -15,7 +15,7 @@ pub fn analyze(
1515
1616 var table = std.AutoHashMap(*ir.Inst, void).init(gpa);
1717 defer table.deinit();
18 try table.ensureCapacity(body.instructions.len);
18 try table.ensureCapacity(@intCast(u32, body.instructions.len));
1919 try analyzeWithTable(arena, &table, null, body);
2020}
2121
......@@ -84,8 +84,11 @@ fn analyzeInst(
8484 try analyzeWithTable(arena, table, &then_table, inst.then_body);
8585
8686 // Reset the table back to its state from before the branch.
87 for (then_table.items()) |entry| {
88 table.removeAssertDiscard(entry.key);
87 {
88 var it = then_table.iterator();
89 while (it.next()) |entry| {
90 table.removeAssertDiscard(entry.key);
91 }
8992 }
9093
9194 var else_table = std.AutoHashMap(*ir.Inst, void).init(table.allocator);
......@@ -97,28 +100,36 @@ fn analyzeInst(
97100 var else_entry_deaths = std.ArrayList(*ir.Inst).init(table.allocator);
98101 defer else_entry_deaths.deinit();
99102
100 for (else_table.items()) |entry| {
101 const else_death = entry.key;
102 if (!then_table.contains(else_death)) {
103 try then_entry_deaths.append(else_death);
103 {
104 var it = else_table.iterator();
105 while (it.next()) |entry| {
106 const else_death = entry.key;
107 if (!then_table.contains(else_death)) {
108 try then_entry_deaths.append(else_death);
109 }
104110 }
105111 }
106112 // This loop is the same, except it's for the then branch, and it additionally
107113 // has to put its items back into the table to undo the reset.
108 for (then_table.items()) |entry| {
109 const then_death = entry.key;
110 if (!else_table.contains(then_death)) {
111 try else_entry_deaths.append(then_death);
114 {
115 var it = then_table.iterator();
116 while (it.next()) |entry| {
117 const then_death = entry.key;
118 if (!else_table.contains(then_death)) {
119 try else_entry_deaths.append(then_death);
120 }
121 _ = try table.put(then_death, {});
112122 }
113 _ = try table.put(then_death, {});
114123 }
115124 // Now we have to correctly populate new_set.
116125 if (new_set) |ns| {
117 try ns.ensureCapacity(ns.items().len + then_table.items().len + else_table.items().len);
118 for (then_table.items()) |entry| {
126 try ns.ensureCapacity(@intCast(u32, ns.count() + then_table.count() + else_table.count()));
127 var it = then_table.iterator();
128 while (it.next()) |entry| {
119129 _ = ns.putAssumeCapacity(entry.key, {});
120130 }
121 for (else_table.items()) |entry| {
131 it = else_table.iterator();
132 while (it.next()) |entry| {
122133 _ = ns.putAssumeCapacity(entry.key, {});
123134 }
124135 }
src-self-hosted/main.zig+2-1
......@@ -839,7 +839,8 @@ fn fmtPathFile(
839839 // As a heuristic, we make enough capacity for the same as the input source.
840840 try fmt.out_buffer.ensureCapacity(source_code.len);
841841 fmt.out_buffer.items.len = 0;
842 const anything_changed = try std.zig.render(fmt.gpa, fmt.out_buffer.writer(), tree);
842 const writer = fmt.out_buffer.writer();
843 const anything_changed = try std.zig.render(fmt.gpa, writer, tree);
843844 if (!anything_changed)
844845 return; // Good thing we didn't waste any file system access on this.
845846
src-self-hosted/test.zig+11-11
......@@ -474,15 +474,15 @@ pub const TestContext = struct {
474474 var all_errors = try module.getAllErrorsAlloc();
475475 defer all_errors.deinit(allocator);
476476 if (all_errors.list.len != 0) {
477 std.debug.warn("\nErrors occurred updating the module:\n================\n", .{});
477 std.debug.print("\nErrors occurred updating the module:\n================\n", .{});
478478 for (all_errors.list) |err| {
479 std.debug.warn(":{}:{}: error: {}\n================\n", .{ err.line + 1, err.column + 1, err.msg });
479 std.debug.print(":{}:{}: error: {}\n================\n", .{ err.line + 1, err.column + 1, err.msg });
480480 }
481481 if (case.cbe) {
482482 const C = module.bin_file.cast(link.File.C).?;
483 std.debug.warn("Generated C: \n===============\n{}\n\n===========\n\n", .{C.main.items});
483 std.debug.print("Generated C: \n===============\n{}\n\n===========\n\n", .{C.main.items});
484484 }
485 std.debug.warn("Test failed.\n", .{});
485 std.debug.print("Test failed.\n", .{});
486486 std.process.exit(1);
487487 }
488488 }
......@@ -497,12 +497,12 @@ pub const TestContext = struct {
497497 var out = file.reader().readAllAlloc(arena, 1024 * 1024) catch @panic("Unable to read C output!");
498498
499499 if (expected_output.len != out.len) {
500 std.debug.warn("\nTransformed C length differs:\n================\nExpected:\n================\n{}\n================\nFound:\n================\n{}\n================\nTest failed.\n", .{ expected_output, out });
500 std.debug.print("\nTransformed C length differs:\n================\nExpected:\n================\n{}\n================\nFound:\n================\n{}\n================\nTest failed.\n", .{ expected_output, out });
501501 std.process.exit(1);
502502 }
503503 for (expected_output) |e, i| {
504504 if (out[i] != e) {
505 std.debug.warn("\nTransformed C differs:\n================\nExpected:\n================\n{}\n================\nFound:\n================\n{}\n================\nTest failed.\n", .{ expected_output, out });
505 std.debug.print("\nTransformed C differs:\n================\nExpected:\n================\n{}\n================\nFound:\n================\n{}\n================\nTest failed.\n", .{ expected_output, out });
506506 std.process.exit(1);
507507 }
508508 }
......@@ -526,12 +526,12 @@ pub const TestContext = struct {
526526 defer test_node.end();
527527
528528 if (expected_output.len != out_zir.items.len) {
529 std.debug.warn("{}\nTransformed ZIR length differs:\n================\nExpected:\n================\n{}\n================\nFound:\n================\n{}\n================\nTest failed.\n", .{ case.name, expected_output, out_zir.items });
529 std.debug.print("{}\nTransformed ZIR length differs:\n================\nExpected:\n================\n{}\n================\nFound:\n================\n{}\n================\nTest failed.\n", .{ case.name, expected_output, out_zir.items });
530530 std.process.exit(1);
531531 }
532532 for (expected_output) |e, i| {
533533 if (out_zir.items[i] != e) {
534 std.debug.warn("{}\nTransformed ZIR differs:\n================\nExpected:\n================\n{}\n================\nFound:\n================\n{}\n================\nTest failed.\n", .{ case.name, expected_output, out_zir.items });
534 std.debug.print("{}\nTransformed ZIR differs:\n================\nExpected:\n================\n{}\n================\nFound:\n================\n{}\n================\nTest failed.\n", .{ case.name, expected_output, out_zir.items });
535535 std.process.exit(1);
536536 }
537537 }
......@@ -554,7 +554,7 @@ pub const TestContext = struct {
554554 break;
555555 }
556556 } else {
557 std.debug.warn("{}\nUnexpected error:\n================\n:{}:{}: error: {}\n================\nTest failed.\n", .{ case.name, a.line + 1, a.column + 1, a.msg });
557 std.debug.print("{}\nUnexpected error:\n================\n:{}:{}: error: {}\n================\nTest failed.\n", .{ case.name, a.line + 1, a.column + 1, a.msg });
558558 std.process.exit(1);
559559 }
560560 }
......@@ -562,7 +562,7 @@ pub const TestContext = struct {
562562 for (handled_errors) |h, i| {
563563 if (!h) {
564564 const er = e[i];
565 std.debug.warn("{}\nDid not receive error:\n================\n{}:{}: {}\n================\nTest failed.\n", .{ case.name, er.line, er.column, er.msg });
565 std.debug.print("{}\nDid not receive error:\n================\n{}:{}: {}\n================\nTest failed.\n", .{ case.name, er.line, er.column, er.msg });
566566 std.process.exit(1);
567567 }
568568 }
......@@ -643,7 +643,7 @@ pub const TestContext = struct {
643643 switch (exec_result.term) {
644644 .Exited => |code| {
645645 if (code != 0) {
646 std.debug.warn("elf file exited with code {}\n", .{code});
646 std.debug.print("elf file exited with code {}\n", .{code});
647647 return error.BinaryBadExitCode;
648648 }
649649 },
src-self-hosted/translate_c.zig+6-19
......@@ -19,23 +19,9 @@ pub const Error = error{OutOfMemory};
1919const TypeError = Error || error{UnsupportedType};
2020const TransError = TypeError || error{UnsupportedTranslation};
2121
22const DeclTable = std.HashMap(usize, []const u8, addrHash, addrEql, false);
22const DeclTable = std.AutoArrayHashMap(usize, []const u8);
2323
24fn addrHash(x: usize) u32 {
25 switch (@typeInfo(usize).Int.bits) {
26 32 => return x,
27 // pointers are usually aligned so we ignore the bits that are probably all 0 anyway
28 // usually the larger bits of addr space are unused so we just chop em off
29 64 => return @truncate(u32, x >> 4),
30 else => @compileError("unreachable"),
31 }
32}
33
34fn addrEql(a: usize, b: usize) bool {
35 return a == b;
36}
37
38const SymbolTable = std.StringHashMap(*ast.Node);
24const SymbolTable = std.StringArrayHashMap(*ast.Node);
3925const AliasList = std.ArrayList(struct {
4026 alias: []const u8,
4127 name: []const u8,
......@@ -285,7 +271,7 @@ pub const Context = struct {
285271 /// a list of names that we found by visiting all the top level decls without
286272 /// translating them. The other maps are updated as we translate; this one is updated
287273 /// up front in a pre-processing step.
288 global_names: std.StringHashMap(void),
274 global_names: std.StringArrayHashMap(void),
289275
290276 fn getMangle(c: *Context) u32 {
291277 c.mangle_count += 1;
......@@ -380,7 +366,7 @@ pub fn translate(
380366 .alias_list = AliasList.init(gpa),
381367 .global_scope = try arena.allocator.create(Scope.Root),
382368 .clang_context = ZigClangASTUnit_getASTContext(ast_unit).?,
383 .global_names = std.StringHashMap(void).init(gpa),
369 .global_names = std.StringArrayHashMap(void).init(gpa),
384370 .token_ids = .{},
385371 .token_locs = .{},
386372 .errors = .{},
......@@ -6424,7 +6410,8 @@ fn getFnProto(c: *Context, ref: *ast.Node) ?*ast.Node.FnProto {
64246410}
64256411
64266412fn addMacros(c: *Context) !void {
6427 for (c.global_scope.macro_table.items()) |kv| {
6413 var it = c.global_scope.macro_table.iterator();
6414 while (it.next()) |kv| {
64286415 if (getFnProto(c, kv.value)) |proto_node| {
64296416 // If a macro aliases a global variable which is a function pointer, we conclude that
64306417 // the macro is intended to represent a function that assumes the function pointer
src-self-hosted/type.zig+100-22
......@@ -163,7 +163,7 @@ pub const Type = extern union {
163163 // Hot path for common case:
164164 if (a.castPointer()) |a_payload| {
165165 if (b.castPointer()) |b_payload| {
166 return eql(a_payload.pointee_type, b_payload.pointee_type);
166 return a.tag() == b.tag() and eql(a_payload.pointee_type, b_payload.pointee_type);
167167 }
168168 }
169169 const is_slice_a = isSlice(a);
......@@ -189,10 +189,10 @@ pub const Type = extern union {
189189 .Array => {
190190 if (a.arrayLen() != b.arrayLen())
191191 return false;
192 if (a.elemType().eql(b.elemType()))
192 if (!a.elemType().eql(b.elemType()))
193193 return false;
194 const sentinel_a = a.arraySentinel();
195 const sentinel_b = b.arraySentinel();
194 const sentinel_a = a.sentinel();
195 const sentinel_b = b.sentinel();
196196 if (sentinel_a) |sa| {
197197 if (sentinel_b) |sb| {
198198 return sa.eql(sb);
......@@ -238,7 +238,7 @@ pub const Type = extern union {
238238 }
239239 }
240240
241 pub fn hash(self: Type) u32 {
241 pub fn hash(self: Type) u64 {
242242 var hasher = std.hash.Wyhash.init(0);
243243 const zig_type_tag = self.zigTypeTag();
244244 std.hash.autoHash(&hasher, zig_type_tag);
......@@ -303,7 +303,7 @@ pub const Type = extern union {
303303 // TODO implement more type hashing
304304 },
305305 }
306 return @truncate(u32, hasher.final());
306 return hasher.final();
307307 }
308308
309309 pub fn copy(self: Type, allocator: *Allocator) error{OutOfMemory}!Type {
......@@ -501,9 +501,9 @@ pub const Type = extern union {
501501 .noreturn,
502502 => return out_stream.writeAll(@tagName(t)),
503503
504 .enum_literal => return out_stream.writeAll("@TypeOf(.EnumLiteral)"),
505 .@"null" => return out_stream.writeAll("@TypeOf(null)"),
506 .@"undefined" => return out_stream.writeAll("@TypeOf(undefined)"),
504 .enum_literal => return out_stream.writeAll("@Type(.EnumLiteral)"),
505 .@"null" => return out_stream.writeAll("@Type(.Null)"),
506 .@"undefined" => return out_stream.writeAll("@Type(.Undefined)"),
507507
508508 .@"anyframe" => return out_stream.writeAll("anyframe"),
509509 .anyerror_void_error_union => return out_stream.writeAll("anyerror!void"),
......@@ -630,8 +630,8 @@ pub const Type = extern union {
630630 const payload = @fieldParentPtr(Payload.Pointer, "base", ty.ptr_otherwise);
631631 if (payload.sentinel) |some| switch (payload.size) {
632632 .One, .C => unreachable,
633 .Many => try out_stream.writeAll("[*:{}]"),
634 .Slice => try out_stream.writeAll("[:{}]"),
633 .Many => try out_stream.print("[*:{}]", .{some}),
634 .Slice => try out_stream.print("[:{}]", .{some}),
635635 } else switch (payload.size) {
636636 .One => try out_stream.writeAll("*"),
637637 .Many => try out_stream.writeAll("[*]"),
......@@ -1341,6 +1341,81 @@ pub const Type = extern union {
13411341 };
13421342 }
13431343
1344 pub fn isAllowzeroPtr(self: Type) bool {
1345 return switch (self.tag()) {
1346 .u8,
1347 .i8,
1348 .u16,
1349 .i16,
1350 .u32,
1351 .i32,
1352 .u64,
1353 .i64,
1354 .usize,
1355 .isize,
1356 .c_short,
1357 .c_ushort,
1358 .c_int,
1359 .c_uint,
1360 .c_long,
1361 .c_ulong,
1362 .c_longlong,
1363 .c_ulonglong,
1364 .c_longdouble,
1365 .f16,
1366 .f32,
1367 .f64,
1368 .f128,
1369 .c_void,
1370 .bool,
1371 .void,
1372 .type,
1373 .anyerror,
1374 .comptime_int,
1375 .comptime_float,
1376 .noreturn,
1377 .@"null",
1378 .@"undefined",
1379 .array,
1380 .array_sentinel,
1381 .array_u8,
1382 .array_u8_sentinel_0,
1383 .fn_noreturn_no_args,
1384 .fn_void_no_args,
1385 .fn_naked_noreturn_no_args,
1386 .fn_ccc_void_no_args,
1387 .function,
1388 .int_unsigned,
1389 .int_signed,
1390 .single_mut_pointer,
1391 .single_const_pointer,
1392 .many_const_pointer,
1393 .many_mut_pointer,
1394 .c_const_pointer,
1395 .c_mut_pointer,
1396 .const_slice,
1397 .mut_slice,
1398 .single_const_pointer_to_comptime_int,
1399 .const_slice_u8,
1400 .optional,
1401 .optional_single_mut_pointer,
1402 .optional_single_const_pointer,
1403 .enum_literal,
1404 .error_union,
1405 .@"anyframe",
1406 .anyframe_T,
1407 .anyerror_void_error_union,
1408 .error_set,
1409 .error_set_single,
1410 => false,
1411
1412 .pointer => {
1413 const payload = @fieldParentPtr(Payload.Pointer, "base", self.ptr_otherwise);
1414 return payload.@"allowzero";
1415 },
1416 };
1417 }
1418
13441419 /// Asserts that the type is an optional
13451420 pub fn isPtrLikeOptional(self: Type) bool {
13461421 switch (self.tag()) {
......@@ -1585,8 +1660,8 @@ pub const Type = extern union {
15851660 };
15861661 }
15871662
1588 /// Asserts the type is an array or vector.
1589 pub fn arraySentinel(self: Type) ?Value {
1663 /// Asserts the type is an array, pointer or vector.
1664 pub fn sentinel(self: Type) ?Value {
15901665 return switch (self.tag()) {
15911666 .u8,
15921667 .i8,
......@@ -1626,16 +1701,8 @@ pub const Type = extern union {
16261701 .fn_naked_noreturn_no_args,
16271702 .fn_ccc_void_no_args,
16281703 .function,
1629 .pointer,
1630 .single_const_pointer,
1631 .single_mut_pointer,
1632 .many_const_pointer,
1633 .many_mut_pointer,
1634 .c_const_pointer,
1635 .c_mut_pointer,
16361704 .const_slice,
16371705 .mut_slice,
1638 .single_const_pointer_to_comptime_int,
16391706 .const_slice_u8,
16401707 .int_unsigned,
16411708 .int_signed,
......@@ -1651,7 +1718,18 @@ pub const Type = extern union {
16511718 .error_set_single,
16521719 => unreachable,
16531720
1654 .array, .array_u8 => return null,
1721 .single_const_pointer,
1722 .single_mut_pointer,
1723 .many_const_pointer,
1724 .many_mut_pointer,
1725 .c_const_pointer,
1726 .c_mut_pointer,
1727 .single_const_pointer_to_comptime_int,
1728 .array,
1729 .array_u8,
1730 => return null,
1731
1732 .pointer => return self.cast(Payload.Pointer).?.sentinel,
16551733 .array_sentinel => return self.cast(Payload.ArraySentinel).?.sentinel,
16561734 .array_u8_sentinel_0 => return Value.initTag(.zero),
16571735 };
src-self-hosted/value.zig+5-4
......@@ -301,15 +301,15 @@ pub const Value = extern union {
301301 .comptime_int_type => return out_stream.writeAll("comptime_int"),
302302 .comptime_float_type => return out_stream.writeAll("comptime_float"),
303303 .noreturn_type => return out_stream.writeAll("noreturn"),
304 .null_type => return out_stream.writeAll("@TypeOf(null)"),
305 .undefined_type => return out_stream.writeAll("@TypeOf(undefined)"),
304 .null_type => return out_stream.writeAll("@Type(.Null)"),
305 .undefined_type => return out_stream.writeAll("@Type(.Undefined)"),
306306 .fn_noreturn_no_args_type => return out_stream.writeAll("fn() noreturn"),
307307 .fn_void_no_args_type => return out_stream.writeAll("fn() void"),
308308 .fn_naked_noreturn_no_args_type => return out_stream.writeAll("fn() callconv(.Naked) noreturn"),
309309 .fn_ccc_void_no_args_type => return out_stream.writeAll("fn() callconv(.C) void"),
310310 .single_const_pointer_to_comptime_int_type => return out_stream.writeAll("*const comptime_int"),
311311 .const_slice_u8_type => return out_stream.writeAll("[]const u8"),
312 .enum_literal_type => return out_stream.writeAll("@TypeOf(.EnumLiteral)"),
312 .enum_literal_type => return out_stream.writeAll("@Type(.EnumLiteral)"),
313313 .anyframe_type => return out_stream.writeAll("anyframe"),
314314
315315 .null_value => return out_stream.writeAll("null"),
......@@ -358,7 +358,8 @@ pub const Value = extern union {
358358 .error_set => {
359359 const error_set = val.cast(Payload.ErrorSet).?;
360360 try out_stream.writeAll("error{");
361 for (error_set.fields.items()) |entry| {
361 var it = error_set.fields.iterator();
362 while (it.next()) |entry| {
362363 try out_stream.print("{},", .{entry.value});
363364 }
364365 return out_stream.writeAll("}");
src-self-hosted/zir.zig+42-5
......@@ -78,6 +78,13 @@ pub const Inst = struct {
7878 bitor,
7979 /// A labeled block of code, which can return a value.
8080 block,
81 /// A block of code, which can return a value. There are no instructions that break out of
82 /// this block; it is implied that the final instruction is the result.
83 block_flat,
84 /// Same as `block` but additionally makes the inner instructions execute at comptime.
85 block_comptime,
86 /// Same as `block_flat` but additionally makes the inner instructions execute at comptime.
87 block_comptime_flat,
8188 /// Boolean NOT. See also `bitnot`.
8289 boolnot,
8390 /// Return a value from a `Block`.
......@@ -224,6 +231,10 @@ pub const Inst = struct {
224231 const_slice_type,
225232 /// Create a pointer type with attributes
226233 ptr_type,
234 /// Slice operation `array_ptr[start..end:sentinel]`
235 slice,
236 /// Slice operation with just start `lhs[rhs..]`
237 slice_start,
227238 /// Write a value to a pointer. For loading, see `deref`.
228239 store,
229240 /// String Literal. Makes an anonymous Decl and then takes a pointer to it.
......@@ -336,11 +347,17 @@ pub const Inst = struct {
336347 .xor,
337348 .error_union_type,
338349 .merge_error_sets,
350 .slice_start,
339351 => BinOp,
340352
353 .block,
354 .block_flat,
355 .block_comptime,
356 .block_comptime_flat,
357 => Block,
358
341359 .arg => Arg,
342360 .array_type_sentinel => ArrayTypeSentinel,
343 .block => Block,
344361 .@"break" => Break,
345362 .breakvoid => BreakVoid,
346363 .call => Call,
......@@ -368,6 +385,7 @@ pub const Inst = struct {
368385 .ptr_type => PtrType,
369386 .enum_literal => EnumLiteral,
370387 .error_set => ErrorSet,
388 .slice => Slice,
371389 };
372390 }
373391
......@@ -392,6 +410,9 @@ pub const Inst = struct {
392410 .bitcast_result_ptr,
393411 .bitor,
394412 .block,
413 .block_flat,
414 .block_comptime,
415 .block_comptime_flat,
395416 .boolnot,
396417 .breakpoint,
397418 .call,
......@@ -466,6 +487,8 @@ pub const Inst = struct {
466487 .error_union_type,
467488 .bitnot,
468489 .error_set,
490 .slice,
491 .slice_start,
469492 => false,
470493
471494 .@"break",
......@@ -946,6 +969,20 @@ pub const Inst = struct {
946969 },
947970 kw_args: struct {},
948971 };
972
973 pub const Slice = struct {
974 pub const base_tag = Tag.slice;
975 base: Inst,
976
977 positionals: struct {
978 array_ptr: *Inst,
979 start: *Inst,
980 },
981 kw_args: struct {
982 end: ?*Inst = null,
983 sentinel: ?*Inst = null,
984 },
985 };
949986};
950987
951988pub const ErrorMsg = struct {
......@@ -1034,7 +1071,7 @@ pub const Module = struct {
10341071 defer write.loop_table.deinit();
10351072
10361073 // First, build a map of *Inst to @ or % indexes
1037 try write.inst_table.ensureCapacity(self.decls.len);
1074 try write.inst_table.ensureCapacity(@intCast(u32, self.decls.len));
10381075
10391076 for (self.decls) |decl, decl_i| {
10401077 try write.inst_table.putNoClobber(decl.inst, .{ .inst = decl.inst, .index = null, .name = decl.name });
......@@ -1670,7 +1707,7 @@ pub fn emit(allocator: *Allocator, old_module: IrModule) !Module {
16701707 .arena = std.heap.ArenaAllocator.init(allocator),
16711708 .old_module = &old_module,
16721709 .next_auto_name = 0,
1673 .names = std.StringHashMap(void).init(allocator),
1710 .names = std.StringArrayHashMap(void).init(allocator),
16741711 .primitive_table = std.AutoHashMap(Inst.Primitive.Builtin, *Decl).init(allocator),
16751712 .indent = 0,
16761713 .block_table = std.AutoHashMap(*ir.Inst.Block, *Inst.Block).init(allocator),
......@@ -1743,7 +1780,7 @@ const EmitZIR = struct {
17431780 arena: std.heap.ArenaAllocator,
17441781 old_module: *const IrModule,
17451782 decls: std.ArrayListUnmanaged(*Decl),
1746 names: std.StringHashMap(void),
1783 names: std.StringArrayHashMap(void),
17471784 next_auto_name: usize,
17481785 primitive_table: std.AutoHashMap(Inst.Primitive.Builtin, *Decl),
17491786 indent: usize,
......@@ -2559,7 +2596,7 @@ const EmitZIR = struct {
25592596 var len_pl = Value.Payload.Int_u64{ .int = ty.arrayLen() };
25602597 const len = Value.initPayload(&len_pl.base);
25612598
2562 const inst = if (ty.arraySentinel()) |sentinel| blk: {
2599 const inst = if (ty.sentinel()) |sentinel| blk: {
25632600 const inst = try self.arena.allocator.create(Inst.ArrayTypeSentinel);
25642601 inst.* = .{
25652602 .base = .{
src-self-hosted/zir_sema.zig+95-17
......@@ -31,7 +31,10 @@ pub fn analyzeInst(mod: *Module, scope: *Scope, old_inst: *zir.Inst) InnerError!
3131 .arg => return analyzeInstArg(mod, scope, old_inst.castTag(.arg).?),
3232 .bitcast_ref => return analyzeInstBitCastRef(mod, scope, old_inst.castTag(.bitcast_ref).?),
3333 .bitcast_result_ptr => return analyzeInstBitCastResultPtr(mod, scope, old_inst.castTag(.bitcast_result_ptr).?),
34 .block => return analyzeInstBlock(mod, scope, old_inst.castTag(.block).?),
34 .block => return analyzeInstBlock(mod, scope, old_inst.castTag(.block).?, false),
35 .block_comptime => return analyzeInstBlock(mod, scope, old_inst.castTag(.block_comptime).?, true),
36 .block_flat => return analyzeInstBlockFlat(mod, scope, old_inst.castTag(.block_flat).?, false),
37 .block_comptime_flat => return analyzeInstBlockFlat(mod, scope, old_inst.castTag(.block_comptime_flat).?, true),
3538 .@"break" => return analyzeInstBreak(mod, scope, old_inst.castTag(.@"break").?),
3639 .breakpoint => return analyzeInstBreakpoint(mod, scope, old_inst.castTag(.breakpoint).?),
3740 .breakvoid => return analyzeInstBreakVoid(mod, scope, old_inst.castTag(.breakvoid).?),
......@@ -129,6 +132,8 @@ pub fn analyzeInst(mod: *Module, scope: *Scope, old_inst: *zir.Inst) InnerError!
129132 .error_union_type => return analyzeInstErrorUnionType(mod, scope, old_inst.castTag(.error_union_type).?),
130133 .anyframe_type => return analyzeInstAnyframeType(mod, scope, old_inst.castTag(.anyframe_type).?),
131134 .error_set => return analyzeInstErrorSet(mod, scope, old_inst.castTag(.error_set).?),
135 .slice => return analyzeInstSlice(mod, scope, old_inst.castTag(.slice).?),
136 .slice_start => return analyzeInstSliceStart(mod, scope, old_inst.castTag(.slice_start).?),
132137 }
133138}
134139
......@@ -147,17 +152,16 @@ pub fn analyzeBody(mod: *Module, scope: *Scope, body: zir.Module.Body) !void {
147152 }
148153}
149154
150pub fn analyzeBodyValueAsType(mod: *Module, block_scope: *Scope.Block, body: zir.Module.Body) !Type {
155pub fn analyzeBodyValueAsType(
156 mod: *Module,
157 block_scope: *Scope.Block,
158 zir_result_inst: *zir.Inst,
159 body: zir.Module.Body,
160) !Type {
151161 try analyzeBody(mod, &block_scope.base, body);
152 for (block_scope.instructions.items) |inst| {
153 if (inst.castTag(.ret)) |ret| {
154 const val = try mod.resolveConstValue(&block_scope.base, ret.operand);
155 return val.toType(block_scope.base.arena());
156 } else {
157 return mod.fail(&block_scope.base, inst.src, "unable to resolve comptime value", .{});
158 }
159 }
160 unreachable;
162 const result_inst = zir_result_inst.analyzed_inst.?;
163 const val = try mod.resolveConstValue(&block_scope.base, result_inst);
164 return val.toType(block_scope.base.arena());
161165}
162166
163167pub fn analyzeZirDecl(mod: *Module, decl: *Decl, src_decl: *zir.Decl) InnerError!bool {
......@@ -362,7 +366,7 @@ fn analyzeInstRef(mod: *Module, scope: *Scope, inst: *zir.Inst.UnOp) InnerError!
362366}
363367
364368fn analyzeInstRetType(mod: *Module, scope: *Scope, inst: *zir.Inst.NoOp) InnerError!*Inst {
365 const b = try mod.requireRuntimeBlock(scope, inst.base.src);
369 const b = try mod.requireFunctionBlock(scope, inst.base.src);
366370 const fn_ty = b.func.?.owner_decl.typed_value.most_recent.typed_value.ty;
367371 const ret_type = fn_ty.fnReturnType();
368372 return mod.constType(scope, inst.base.src, ret_type);
......@@ -517,6 +521,7 @@ fn analyzeInstLoop(mod: *Module, scope: *Scope, inst: *zir.Inst.Loop) InnerError
517521 .decl = parent_block.decl,
518522 .instructions = .{},
519523 .arena = parent_block.arena,
524 .is_comptime = parent_block.is_comptime,
520525 };
521526 defer child_block.instructions.deinit(mod.gpa);
522527
......@@ -529,7 +534,29 @@ fn analyzeInstLoop(mod: *Module, scope: *Scope, inst: *zir.Inst.Loop) InnerError
529534 return &loop_inst.base;
530535}
531536
532fn analyzeInstBlock(mod: *Module, scope: *Scope, inst: *zir.Inst.Block) InnerError!*Inst {
537fn analyzeInstBlockFlat(mod: *Module, scope: *Scope, inst: *zir.Inst.Block, is_comptime: bool) InnerError!*Inst {
538 const parent_block = scope.cast(Scope.Block).?;
539
540 var child_block: Scope.Block = .{
541 .parent = parent_block,
542 .func = parent_block.func,
543 .decl = parent_block.decl,
544 .instructions = .{},
545 .arena = parent_block.arena,
546 .label = null,
547 .is_comptime = parent_block.is_comptime or is_comptime,
548 };
549 defer child_block.instructions.deinit(mod.gpa);
550
551 try analyzeBody(mod, &child_block.base, inst.positionals.body);
552
553 const copied_instructions = try parent_block.arena.dupe(*Inst, child_block.instructions.items);
554 try parent_block.instructions.appendSlice(mod.gpa, copied_instructions);
555
556 return copied_instructions[copied_instructions.len - 1];
557}
558
559fn analyzeInstBlock(mod: *Module, scope: *Scope, inst: *zir.Inst.Block, is_comptime: bool) InnerError!*Inst {
533560 const parent_block = scope.cast(Scope.Block).?;
534561
535562 // Reserve space for a Block instruction so that generated Break instructions can
......@@ -557,6 +584,7 @@ fn analyzeInstBlock(mod: *Module, scope: *Scope, inst: *zir.Inst.Block) InnerErr
557584 .results = .{},
558585 .block_inst = block_inst,
559586 }),
587 .is_comptime = is_comptime or parent_block.is_comptime,
560588 };
561589 const label = &child_block.label.?;
562590
......@@ -569,6 +597,28 @@ fn analyzeInstBlock(mod: *Module, scope: *Scope, inst: *zir.Inst.Block) InnerErr
569597 assert(child_block.instructions.items.len != 0);
570598 assert(child_block.instructions.items[child_block.instructions.items.len - 1].ty.isNoReturn());
571599
600 if (label.results.items.len == 0) {
601 // No need for a block instruction. We can put the new instructions directly into the parent block.
602 const copied_instructions = try parent_block.arena.dupe(*Inst, child_block.instructions.items);
603 try parent_block.instructions.appendSlice(mod.gpa, copied_instructions);
604 return copied_instructions[copied_instructions.len - 1];
605 }
606 if (label.results.items.len == 1) {
607 const last_inst_index = child_block.instructions.items.len - 1;
608 const last_inst = child_block.instructions.items[last_inst_index];
609 if (last_inst.breakBlock()) |br_block| {
610 if (br_block == block_inst) {
611 // No need for a block instruction. We can put the new instructions directly into the parent block.
612 // Here we omit the break instruction.
613 const copied_instructions = try parent_block.arena.dupe(*Inst, child_block.instructions.items[0..last_inst_index]);
614 try parent_block.instructions.appendSlice(mod.gpa, copied_instructions);
615 return label.results.items[0];
616 }
617 }
618 }
619 // It should be impossible to have the number of results be > 1 in a comptime scope.
620 assert(!child_block.is_comptime); // We should have already got a compile error in the condbr condition.
621
572622 // Need to set the type and emit the Block instruction. This allows machine code generation
573623 // to emit a jump instruction to after the block when it encounters the break.
574624 try parent_block.instructions.append(mod.gpa, &block_inst.base);
......@@ -595,8 +645,12 @@ fn analyzeInstBreakVoid(mod: *Module, scope: *Scope, inst: *zir.Inst.BreakVoid)
595645}
596646
597647fn analyzeInstDbgStmt(mod: *Module, scope: *Scope, inst: *zir.Inst.NoOp) InnerError!*Inst {
598 const b = try mod.requireRuntimeBlock(scope, inst.base.src);
599 return mod.addNoOp(b, inst.base.src, Type.initTag(.void), .dbg_stmt);
648 if (scope.cast(Scope.Block)) |b| {
649 if (!b.is_comptime) {
650 return mod.addNoOp(b, inst.base.src, Type.initTag(.void), .dbg_stmt);
651 }
652 }
653 return mod.constVoid(scope, inst.base.src);
600654}
601655
602656fn analyzeInstDeclRefStr(mod: *Module, scope: *Scope, inst: *zir.Inst.DeclRefStr) InnerError!*Inst {
......@@ -764,7 +818,7 @@ fn analyzeInstErrorSet(mod: *Module, scope: *Scope, inst: *zir.Inst.ErrorSet) In
764818 .fields = .{},
765819 .decl = undefined, // populated below
766820 };
767 try payload.fields.ensureCapacity(&new_decl_arena.allocator, inst.positionals.fields.len);
821 try payload.fields.ensureCapacity(&new_decl_arena.allocator, @intCast(u32, inst.positionals.fields.len));
768822
769823 for (inst.positionals.fields) |field_name| {
770824 const entry = try mod.getErrorValue(field_name);
......@@ -1083,7 +1137,7 @@ fn analyzeInstElemPtr(mod: *Module, scope: *Scope, inst: *zir.Inst.ElemPtr) Inne
10831137 const array_ptr = try resolveInst(mod, scope, inst.positionals.array_ptr);
10841138 const uncasted_index = try resolveInst(mod, scope, inst.positionals.index);
10851139 const elem_index = try mod.coerce(scope, Type.initTag(.usize), uncasted_index);
1086
1140
10871141 const elem_ty = switch (array_ptr.ty.zigTypeTag()) {
10881142 .Pointer => array_ptr.ty.elemType(),
10891143 else => return mod.fail(scope, inst.positionals.array_ptr.src, "expected pointer, found '{}'", .{array_ptr.ty}),
......@@ -1120,6 +1174,22 @@ fn analyzeInstElemPtr(mod: *Module, scope: *Scope, inst: *zir.Inst.ElemPtr) Inne
11201174 return mod.fail(scope, inst.base.src, "TODO implement more analyze elemptr", .{});
11211175}
11221176
1177fn analyzeInstSlice(mod: *Module, scope: *Scope, inst: *zir.Inst.Slice) InnerError!*Inst {
1178 const array_ptr = try resolveInst(mod, scope, inst.positionals.array_ptr);
1179 const start = try resolveInst(mod, scope, inst.positionals.start);
1180 const end = if (inst.kw_args.end) |end| try resolveInst(mod, scope, end) else null;
1181 const sentinel = if (inst.kw_args.sentinel) |sentinel| try resolveInst(mod, scope, sentinel) else null;
1182
1183 return mod.analyzeSlice(scope, inst.base.src, array_ptr, start, end, sentinel);
1184}
1185
1186fn analyzeInstSliceStart(mod: *Module, scope: *Scope, inst: *zir.Inst.BinOp) InnerError!*Inst {
1187 const array_ptr = try resolveInst(mod, scope, inst.positionals.lhs);
1188 const start = try resolveInst(mod, scope, inst.positionals.rhs);
1189
1190 return mod.analyzeSlice(scope, inst.base.src, array_ptr, start, null, null);
1191}
1192
11231193fn analyzeInstShl(mod: *Module, scope: *Scope, inst: *zir.Inst.BinOp) InnerError!*Inst {
11241194 return mod.fail(scope, inst.base.src, "TODO implement analyzeInstShl", .{});
11251195}
......@@ -1187,6 +1257,12 @@ fn analyzeInstArithmetic(mod: *Module, scope: *Scope, inst: *zir.Inst.BinOp) Inn
11871257
11881258 if (casted_lhs.value()) |lhs_val| {
11891259 if (casted_rhs.value()) |rhs_val| {
1260 if (lhs_val.isUndef() or rhs_val.isUndef()) {
1261 return mod.constInst(scope, inst.base.src, .{
1262 .ty = resolved_type,
1263 .val = Value.initTag(.undef),
1264 });
1265 }
11901266 return analyzeInstComptimeOp(mod, scope, scalar_type, inst, lhs_val, rhs_val);
11911267 }
11921268 }
......@@ -1376,6 +1452,7 @@ fn analyzeInstCondBr(mod: *Module, scope: *Scope, inst: *zir.Inst.CondBr) InnerE
13761452 .decl = parent_block.decl,
13771453 .instructions = .{},
13781454 .arena = parent_block.arena,
1455 .is_comptime = parent_block.is_comptime,
13791456 };
13801457 defer true_block.instructions.deinit(mod.gpa);
13811458 try analyzeBody(mod, &true_block.base, inst.positionals.then_body);
......@@ -1386,6 +1463,7 @@ fn analyzeInstCondBr(mod: *Module, scope: *Scope, inst: *zir.Inst.CondBr) InnerE
13861463 .decl = parent_block.decl,
13871464 .instructions = .{},
13881465 .arena = parent_block.arena,
1466 .is_comptime = parent_block.is_comptime,
13891467 };
13901468 defer false_block.instructions.deinit(mod.gpa);
13911469 try analyzeBody(mod, &false_block.base, inst.positionals.else_body);
src/ir.cpp+12-1
......@@ -15342,9 +15342,14 @@ static IrInstGen *ir_analyze_cast(IrAnalyze *ira, IrInst *source_instr,
1534215342 ZigType *array_type = actual_type->data.pointer.child_type;
1534315343 bool const_ok = (slice_ptr_type->data.pointer.is_const || array_type->data.array.len == 0
1534415344 || !actual_type->data.pointer.is_const);
15345
1534515346 if (const_ok && types_match_const_cast_only(ira, slice_ptr_type->data.pointer.child_type,
1534615347 array_type->data.array.child_type, source_node,
15347 !slice_ptr_type->data.pointer.is_const).id == ConstCastResultIdOk)
15348 !slice_ptr_type->data.pointer.is_const).id == ConstCastResultIdOk &&
15349 (slice_ptr_type->data.pointer.sentinel == nullptr ||
15350 (array_type->data.array.sentinel != nullptr &&
15351 const_values_equal(ira->codegen, array_type->data.array.sentinel,
15352 slice_ptr_type->data.pointer.sentinel))))
1534815353 {
1534915354 // If the pointers both have ABI align, it works.
1535015355 // Or if the array length is 0, alignment doesn't matter.
......@@ -25684,6 +25689,10 @@ static Error ir_make_type_info_value(IrAnalyze *ira, IrInst* source_instr, ZigTy
2568425689 }
2568525690 set_optional_payload(inner_fields[2], struct_field->init_val);
2568625691
25692 inner_fields[3]->special = ConstValSpecialStatic;
25693 inner_fields[3]->type = ira->codegen->builtin_types.entry_bool;
25694 inner_fields[3]->data.x_bool = struct_field->is_comptime;
25695
2568725696 ZigValue *name = create_const_str_lit(ira->codegen, struct_field->name)->data.x_ptr.data.ref.pointee;
2568825697 init_const_slice(ira->codegen, inner_fields[0], name, 0, buf_len(struct_field->name), true);
2568925698
......@@ -26292,6 +26301,8 @@ static ZigType *type_info_to_type(IrAnalyze *ira, IrInst *source_instr, ZigTypeI
2629226301 buf_ptr(&field->type_entry->name), buf_ptr(&field->type_entry->name)));
2629326302 return ira->codegen->invalid_inst_gen->value->type;
2629426303 }
26304 if ((err = get_const_field_bool(ira, source_instr->source_node, field_value, "is_comptime", 3, &field->is_comptime)))
26305 return ira->codegen->invalid_inst_gen->value->type;
2629526306 }
2629626307
2629726308 return entry;
test/compile_errors.zig+8
......@@ -2,6 +2,14 @@ const tests = @import("tests.zig");
22const std = @import("std");
33
44pub fn addCases(cases: *tests.CompileErrorContext) void {
5 cases.add("slice sentinel mismatch",
6 \\export fn entry() void {
7 \\ const y: [:1]const u8 = &[_:2]u8{ 1, 2 };
8 \\}
9 , &[_][]const u8{
10 "tmp.zig:2:37: error: expected type '[:1]const u8', found '*const [2:2]u8'",
11 });
12
513 cases.add("@Type with undefined",
614 \\comptime {
715 \\ _ = @Type(.{ .Array = .{ .len = 0, .child = u8, .sentinel = undefined } });
test/stage1/behavior/type_info.zig+6
......@@ -418,3 +418,9 @@ test "Struct.is_tuple" {
418418 expect(@typeInfo(@TypeOf(.{0})).Struct.is_tuple);
419419 expect(!@typeInfo(@TypeOf(.{ .a = 0 })).Struct.is_tuple);
420420}
421
422test "StructField.is_comptime" {
423 const info = @typeInfo(struct { x: u8 = 3, comptime y: u32 = 5 }).Struct;
424 expect(!info.fields[0].is_comptime);
425 expect(info.fields[1].is_comptime);
426}
test/stage2/test.zig+16-7
......@@ -274,7 +274,7 @@ pub fn addCases(ctx: *TestContext) !void {
274274 }
275275
276276 {
277 var case = ctx.exe("substracting numbers at runtime", linux_x64);
277 var case = ctx.exe("subtracting numbers at runtime", linux_x64);
278278 case.addCompareOutput(
279279 \\export fn _start() noreturn {
280280 \\ sub(7, 4);
......@@ -967,10 +967,19 @@ pub fn addCases(ctx: *TestContext) !void {
967967 \\fn entry() void {}
968968 , &[_][]const u8{":2:4: error: redefinition of 'entry'"});
969969
970 ctx.compileError("extern variable has no type", linux_x64,
971 \\comptime {
972 \\ _ = foo;
973 \\}
974 \\extern var foo;
975 , &[_][]const u8{":4:1: error: unable to infer variable type"});
970 {
971 var case = ctx.obj("extern variable has no type", linux_x64);
972 case.addError(
973 \\comptime {
974 \\ _ = foo;
975 \\}
976 \\extern var foo: i32;
977 , &[_][]const u8{":2:9: error: unable to resolve comptime value"});
978 case.addError(
979 \\export fn entry() void {
980 \\ _ = foo;
981 \\}
982 \\extern var foo;
983 , &[_][]const u8{":4:1: error: unable to infer variable type"});
984 }
976985}