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)...@@ -25,6 +25,8 @@ if(ZIG_PREFER_CLANG_CPP_DYLIB)
25 clang-cpp25 clang-cpp
26 PATHS26 PATHS
27 ${CLANG_LIBDIRS}27 ${CLANG_LIBDIRS}
28 /usr/lib/llvm/11/lib
29 /usr/lib/llvm/11/lib64
28 /usr/lib/llvm-11/lib30 /usr/lib/llvm-11/lib
29 /usr/local/llvm110/lib31 /usr/local/llvm110/lib
30 /usr/local/llvm11/lib32 /usr/local/llvm11/lib
cmake/Findllvm.cmake+2
...@@ -26,6 +26,8 @@ if(ZIG_PREFER_CLANG_CPP_DYLIB)...@@ -26,6 +26,8 @@ if(ZIG_PREFER_CLANG_CPP_DYLIB)
26 LLVM26 LLVM
27 PATHS27 PATHS
28 ${LLVM_LIBDIRS}28 ${LLVM_LIBDIRS}
29 /usr/lib/llvm/11/lib
30 /usr/lib/llvm/11/lib64
29 /usr/lib/llvm-11/lib31 /usr/lib/llvm-11/lib
30 /usr/local/llvm11/lib32 /usr/local/llvm11/lib
31 /usr/local/llvm110/lib33 /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 {...@@ -20,7 +20,8 @@ pub const BufSet = struct {
20 }20 }
2121
22 pub fn deinit(self: *BufSet) void {22 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| {
24 self.free(entry.key);25 self.free(entry.key);
25 }26 }
26 self.hash_map.deinit();27 self.hash_map.deinit();
lib/std/builtin.zig+1
...@@ -261,6 +261,7 @@ pub const TypeInfo = union(enum) {...@@ -261,6 +261,7 @@ pub const TypeInfo = union(enum) {
261 name: []const u8,261 name: []const u8,
262 field_type: type,262 field_type: type,
263 default_value: anytype,263 default_value: anytype,
264 is_comptime: bool,
264 };265 };
265266
266 /// This data structure is used by the Zig language code generation and267 /// 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);...@@ -330,3 +330,8 @@ pub const FILE = @Type(.Opaque);
330pub extern "c" fn dlopen(path: [*:0]const u8, mode: c_int) ?*c_void;330pub extern "c" fn dlopen(path: [*:0]const u8, mode: c_int) ?*c_void;
331pub extern "c" fn dlclose(handle: *c_void) c_int;331pub extern "c" fn dlclose(handle: *c_void) c_int;
332pub extern "c" fn dlsym(handle: ?*c_void, symbol: [*:0]const u8) ?*c_void;332pub 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 {...@@ -44,10 +44,10 @@ pub const ChildProcess = struct {
44 stderr_behavior: StdIo,44 stderr_behavior: StdIo,
4545
46 /// Set to change the user id when spawning the child process.46 /// 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
49 /// Set to change the group id when spawning the child process.49 /// 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
52 /// Set to change the current working directory when spawning the child process.52 /// Set to change the current working directory when spawning the child process.
53 cwd: ?[]const u8,53 cwd: ?[]const u8,
lib/std/fmt.zig+17
...@@ -66,6 +66,7 @@ fn peekIsAlign(comptime fmt: []const u8) bool {...@@ -66,6 +66,7 @@ fn peekIsAlign(comptime fmt: []const u8) bool {
66/// - output numeric value in hexadecimal notation66/// - output numeric value in hexadecimal notation
67/// - `s`: print a pointer-to-many as a c-string, use zero-termination67/// - `s`: print a pointer-to-many as a c-string, use zero-termination
68/// - `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.68/// - `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
69/// - `e`: output floating point value in scientific notation70/// - `e`: output floating point value in scientific notation
70/// - `d`: output numeric value in decimal notation71/// - `d`: output numeric value in decimal notation
71/// - `b`: output integer value in binary notation72/// - `b`: output integer value in binary notation
...@@ -599,6 +600,16 @@ pub fn formatText(...@@ -599,6 +600,16 @@ pub fn formatText(
599 try formatInt(c, 16, fmt[0] == 'X', FormatOptions{ .width = 2, .fill = '0' }, writer);600 try formatInt(c, 16, fmt[0] == 'X', FormatOptions{ .width = 2, .fill = '0' }, writer);
600 }601 }
601 return;602 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;
602 } else {613 } else {
603 @compileError("Unknown format string: '" ++ fmt ++ "'");614 @compileError("Unknown format string: '" ++ fmt ++ "'");
604 }615 }
...@@ -1319,6 +1330,12 @@ test "slice" {...@@ -1319,6 +1330,12 @@ test "slice" {
1319 try testFmt("buf: Test\n Other text", "buf: {s}\n Other text", .{"Test"});1330 try testFmt("buf: Test\n Other text", "buf: {s}\n Other text", .{"Test"});
1320}1331}
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
1322test "pointer" {1339test "pointer" {
1323 {1340 {
1324 const value = @intToPtr(*align(1) i32, 0xdeadbeef);1341 const value = @intToPtr(*align(1) i32, 0xdeadbeef);
lib/std/fmt/parse_float.zig+4-1
...@@ -37,7 +37,9 @@...@@ -37,7 +37,9 @@
37const std = @import("../std.zig");37const std = @import("../std.zig");
38const ascii = std.ascii;38const 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
42const f64_plus_zero: u64 = 0x0000000000000000;44const f64_plus_zero: u64 = 0x0000000000000000;
43const f64_minus_zero: u64 = 0x8000000000000000;45const f64_minus_zero: u64 = 0x8000000000000000;
...@@ -409,6 +411,7 @@ test "fmt.parseFloat" {...@@ -409,6 +411,7 @@ test "fmt.parseFloat" {
409 expect(approxEq(T, try parseFloat(T, "123142.1"), 123142.1, epsilon));411 expect(approxEq(T, try parseFloat(T, "123142.1"), 123142.1, epsilon));
410 expect(approxEq(T, try parseFloat(T, "-123142.1124"), @as(T, -123142.1124), epsilon));412 expect(approxEq(T, try parseFloat(T, "-123142.1124"), @as(T, -123142.1124), epsilon));
411 expect(approxEq(T, try parseFloat(T, "0.7062146892655368"), @as(T, 0.7062146892655368), epsilon));413 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));
412 }415 }
413 }416 }
414}417}
lib/std/hash_map.zig+846-697
...@@ -4,91 +4,94 @@...@@ -4,91 +4,94 @@
4// The MIT license requires this copyright notice to be included in all copies4// The MIT license requires this copyright notice to be included in all copies
5// and substantial portions of the software.5// and substantial portions of the software.
6const std = @import("std.zig");6const std = @import("std.zig");
7const debug = std.debug;7const builtin = @import("builtin");
8const assert = debug.assert;8const assert = debug.assert;
9const testing = std.testing;9const autoHash = std.hash.autoHash;
10const debug = std.debug;
11const warn = debug.warn;
10const math = std.math;12const math = std.math;
11const mem = std.mem;13const mem = std.mem;
12const meta = std.meta;14const meta = std.meta;
13const trait = meta.trait;15const trait = meta.trait;
14const autoHash = std.hash.autoHash;
15const Wyhash = std.hash.Wyhash;
16const Allocator = mem.Allocator;16const Allocator = mem.Allocator;
17const builtin = @import("builtin");17const Wyhash = std.hash.Wyhash;
18const hash_map = @This();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
20pub fn AutoHashMap(comptime K: type, comptime V: type) type {41pub 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);
22}43}
2344
24pub fn AutoHashMapUnmanaged(comptime K: type, comptime V: type) type {45pub 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);
26}47}
2748
28/// Builtin hashmap for strings as keys.49/// Builtin hashmap for strings as keys.
29pub fn StringHashMap(comptime V: type) type {50pub fn StringHashMap(comptime V: type) type {
30 return HashMap([]const u8, V, hashString, eqlString, true);51 return HashMap([]const u8, V, hashString, eqlString, DefaultMaxLoadPercentage);
31}52}
3253
33pub fn StringHashMapUnmanaged(comptime V: type) type {54pub fn StringHashMapUnmanaged(comptime V: type) type {
34 return HashMapUnmanaged([]const u8, V, hashString, eqlString, true);55 return HashMapUnmanaged([]const u8, V, hashString, eqlString, DefaultMaxLoadPercentage);
35}56}
3657
37pub fn eqlString(a: []const u8, b: []const u8) bool {58pub fn eqlString(a: []const u8, b: []const u8) bool {
38 return mem.eql(u8, a, b);59 return mem.eql(u8, a, b);
39}60}
4061
41pub fn hashString(s: []const u8) u32 {62pub fn hashString(s: []const u8) u64 {
42 return @truncate(u32, std.hash.Wyhash.hash(0, s));63 return std.hash.Wyhash.hash(0, s);
43}64}
4465
45/// Insertion order is preserved.66pub const DefaultMaxLoadPercentage = 80;
46/// Deletions perform a "swap removal" on the entries list.67
47/// Modifying the hash map while iterating is allowed, however one must understand68/// General purpose hash table.
48/// the (well defined) behavior when mixing insertions and deletions with iteration.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.
49/// For a hash map that can be initialized directly that does not store an Allocator72/// For a hash map that can be initialized directly that does not store an Allocator
50/// field, see `HashMapUnmanaged`.73/// field, see `HashMapUnmanaged`.
51/// When `store_hash` is `false`, this data structure is biased towards cheap `eql`74/// If iterating over the table entries is a strong usecase and needs to be fast,
52/// functions. It does not store each item's hash in the table. Setting `store_hash`75/// prefer the alternative `std.ArrayHashMap`.
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.
55pub fn HashMap(76pub fn HashMap(
56 comptime K: type,77 comptime K: type,
57 comptime V: type,78 comptime V: type,
58 comptime hash: fn (key: K) u32,79 comptime hashFn: fn (key: K) u64,
59 comptime eql: fn (a: K, b: K) bool,80 comptime eqlFn: fn (a: K, b: K) bool,
60 comptime store_hash: bool,81 comptime MaxLoadPercentage: u64,
61) type {82) type {
62 return struct {83 return struct {
63 unmanaged: Unmanaged,84 unmanaged: Unmanaged,
64 allocator: *Allocator,85 allocator: *Allocator,
6586
66 pub const Unmanaged = HashMapUnmanaged(K, V, hash, eql, store_hash);87 pub const Unmanaged = HashMapUnmanaged(K, V, hashFn, eqlFn, MaxLoadPercentage);
67 pub const Entry = Unmanaged.Entry;88 pub const Entry = Unmanaged.Entry;
68 pub const Hash = Unmanaged.Hash;89 pub const Hash = Unmanaged.Hash;
90 pub const Iterator = Unmanaged.Iterator;
91 pub const Size = Unmanaged.Size;
69 pub const GetOrPutResult = Unmanaged.GetOrPutResult;92 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
90 const Self = @This();94 const Self = @This();
91 const Index = Unmanaged.Index;
9295
93 pub fn init(allocator: *Allocator) Self {96 pub fn init(allocator: *Allocator) Self {
94 return .{97 return .{
...@@ -110,17 +113,12 @@ pub fn HashMap(...@@ -110,17 +113,12 @@ pub fn HashMap(
110 return self.unmanaged.clearAndFree(self.allocator);113 return self.unmanaged.clearAndFree(self.allocator);
111 }114 }
112115
113 /// Deprecated. Use `items().len`.
114 pub fn count(self: Self) usize {116 pub fn count(self: Self) usize {
115 return self.items().len;117 return self.unmanaged.count();
116 }118 }
117119
118 /// Deprecated. Iterate using `items`.
119 pub fn iterator(self: *const Self) Iterator {120 pub fn iterator(self: *const Self) Iterator {
120 return Iterator{121 return self.unmanaged.iterator();
121 .hm = self,
122 .index = 0,
123 };
124 }122 }
125123
126 /// If key exists this function cannot fail.124 /// If key exists this function cannot fail.
...@@ -150,13 +148,13 @@ pub fn HashMap(...@@ -150,13 +148,13 @@ pub fn HashMap(
150148
151 /// Increases capacity, guaranteeing that insertions up until the149 /// Increases capacity, guaranteeing that insertions up until the
152 /// `expected_count` will not cause an allocation, and therefore cannot fail.150 /// `expected_count` will not cause an allocation, and therefore cannot fail.
153 pub fn ensureCapacity(self: *Self, new_capacity: usize) !void {151 pub fn ensureCapacity(self: *Self, expected_count: Size) !void {
154 return self.unmanaged.ensureCapacity(self.allocator, new_capacity);152 return self.unmanaged.ensureCapacity(self.allocator, expected_count);
155 }153 }
156154
157 /// Returns the number of total elements which may be present before it is155 /// Returns the number of total elements which may be present before it is
158 /// no longer guaranteed that no allocations will be performed.156 /// no longer guaranteed that no allocations will be performed.
159 pub fn capacity(self: *Self) usize {157 pub fn capacity(self: *Self) Size {
160 return self.unmanaged.capacity();158 return self.unmanaged.capacity();
161 }159 }
162160
...@@ -197,18 +195,14 @@ pub fn HashMap(...@@ -197,18 +195,14 @@ pub fn HashMap(
197 return self.unmanaged.fetchPutAssumeCapacity(key, value);195 return self.unmanaged.fetchPutAssumeCapacity(key, value);
198 }196 }
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
208 pub fn get(self: Self, key: K) ?V {198 pub fn get(self: Self, key: K) ?V {
209 return self.unmanaged.get(key);199 return self.unmanaged.get(key);
210 }200 }
211201
202 pub fn getEntry(self: Self, key: K) ?*Entry {
203 return self.unmanaged.getEntry(key);
204 }
205
212 pub fn contains(self: Self, key: K) bool {206 pub fn contains(self: Self, key: K) bool {
213 return self.unmanaged.contains(key);207 return self.unmanaged.contains(key);
214 }208 }
...@@ -225,10 +219,6 @@ pub fn HashMap(...@@ -225,10 +219,6 @@ pub fn HashMap(
225 return self.unmanaged.removeAssertDiscard(key);219 return self.unmanaged.removeAssertDiscard(key);
226 }220 }
227221
228 pub fn items(self: Self) []Entry {
229 return self.unmanaged.items();
230 }
231
232 pub fn clone(self: Self) !Self {222 pub fn clone(self: Self) !Self {
233 var other = try self.unmanaged.clone(self.allocator);223 var other = try self.unmanaged.clone(self.allocator);
234 return other.promote(self.allocator);224 return other.promote(self.allocator);
...@@ -236,63 +226,152 @@ pub fn HashMap(...@@ -236,63 +226,152 @@ pub fn HashMap(
236 };226 };
237}227}
238228
239/// General purpose hash table.229/// A HashMap based on open addressing and linear probing.
240/// Insertion order is preserved.230/// A lookup or modification typically occurs only 2 cache misses.
241/// Deletions perform a "swap removal" on the entries list.231/// No order is guaranteed and any modification invalidates live iterators.
242/// Modifying the hash map while iterating is allowed, however one must understand232/// It achieves good performance with quite high load factors (by default,
243/// the (well defined) behavior when mixing insertions and deletions with iteration.233/// grow is triggered at 80% full) and only one byte of overhead per element.
244/// This type does not store an Allocator field - the Allocator must be passed in234/// The struct itself is only 16 bytes for a small footprint. This comes at
245/// with each function call that requires it. See `HashMap` for a type that stores235/// the price of handling size with u32, which should be reasonnable enough
246/// an Allocator field for convenience.236/// for almost all uses.
247/// Can be initialized directly using the default field values.237/// Deletions are achieved with tombstones.
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.
256pub fn HashMapUnmanaged(238pub fn HashMapUnmanaged(
257 comptime K: type,239 comptime K: type,
258 comptime V: type,240 comptime V: type,
259 comptime hash: fn (key: K) u32,241 hashFn: fn (key: K) u64,
260 comptime eql: fn (a: K, b: K) bool,242 eqlFn: fn (a: K, b: K) bool,
261 comptime store_hash: bool,243 comptime MaxLoadPercentage: u64,
262) type {244) type {
245 comptime assert(MaxLoadPercentage > 0 and MaxLoadPercentage < 100);
246
263 return struct {247 return struct {
264 /// It is permitted to access this field directly.248 const Self = @This();
265 entries: std.ArrayListUnmanaged(Entry) = .{},249
266250 // This is actually a midway pointer to the single buffer containing
267 /// When entries length is less than `linear_scan_max`, this remains `null`.251 // a `Header` field, the `Metadata`s and `Entry`s.
268 /// Once entries length grows big enough, this field is allocated. There is252 // At `-@sizeOf(Header)` is the Header field.
269 /// an IndexHeader followed by an array of Index(I) structs, where I is defined253 // At `sizeOf(Metadata) * capacity + offset`, which is pointed to by
270 /// by how many total indexes there are.254 // self.header().entries, is the array of entries.
271 index_header: ?*IndexHeader = null,255 // This means that the hashmap only holds one live allocation, to
272256 // reduce memory fragmentation and struct size.
273 /// Modifying the key is illegal behavior.257 /// Pointer to the metadata.
274 /// Modifying the value is allowed.258 metadata: ?[*]Metadata = null,
275 /// Entry pointers become invalid whenever this HashMap is modified,259
276 /// unless `ensureCapacity` was previously used.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
277 pub const Entry = struct {280 pub const Entry = struct {
278 /// This field is `void` if `store_hash` is `false`.
279 hash: Hash,
280 key: K,281 key: K,
281 value: V,282 value: V,
282 };283 };
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
286 pub const GetOrPutResult = struct {369 pub const GetOrPutResult = struct {
287 entry: *Entry,370 entry: *Entry,
288 found_existing: bool,371 found_existing: bool,
289 };372 };
290373
291 pub const Managed = HashMap(K, V, hash, eql, store_hash);374 pub const Managed = HashMap(K, V, hashFn, eqlFn, MaxLoadPercentage);
292
293 const Self = @This();
294
295 const linear_scan_max = 8;
296375
297 pub fn promote(self: Self, allocator: *Allocator) Managed {376 pub fn promote(self: Self, allocator: *Allocator) Managed {
298 return .{377 return .{
...@@ -301,167 +380,156 @@ pub fn HashMapUnmanaged(...@@ -301,167 +380,156 @@ pub fn HashMapUnmanaged(
301 };380 };
302 }381 }
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
304 pub fn deinit(self: *Self, allocator: *Allocator) void {391 pub fn deinit(self: *Self, allocator: *Allocator) void {
305 self.entries.deinit(allocator);392 self.deallocate(allocator);
306 if (self.index_header) |header| {
307 header.free(allocator);
308 }
309 self.* = undefined;393 self.* = undefined;
310 }394 }
311395
312 pub fn clearRetainingCapacity(self: *Self) void {396 fn deallocate(self: *Self, allocator: *Allocator) void {
313 self.entries.items.len = 0;397 if (self.metadata == null) return;
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 }
324398
325 pub fn clearAndFree(self: *Self, allocator: *Allocator) void {399 const cap = self.capacity();
326 self.entries.shrink(allocator, 0);400 const meta_size = @sizeOf(Header) + cap * @sizeOf(Metadata);
327 if (self.index_header) |header| {401
328 header.free(allocator);402 const alignment = @alignOf(Entry) - 1;
329 self.index_header = null;403 const entries_size = @as(usize, cap) * @sizeOf(Entry) + alignment;
330 }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;
331 }414 }
332415
333 /// If key exists this function cannot fail.416 fn capacityForSize(size: Size) Size {
334 /// If there is an existing item with `key`, then the result417 var new_cap = @truncate(u32, (@as(u64, size) * 100) / MaxLoadPercentage + 1);
335 /// `Entry` pointer points to it, and found_existing is true.418 new_cap = math.ceilPowerOfTwo(u32, new_cap) catch unreachable;
336 /// Otherwise, puts a new item with undefined value, and419 return new_cap;
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);
348 }420 }
349421
350 /// If there is an existing item with `key`, then the result422 pub fn ensureCapacity(self: *Self, allocator: *Allocator, new_size: Size) !void {
351 /// `Entry` pointer points to it, and found_existing is true.423 if (new_size > self.size)
352 /// Otherwise, puts a new item with undefined value, and424 try self.growIfNeeded(allocator, new_size - self.size);
353 /// the `Entry` pointer points to it. Caller should then initialize425 }
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 };
380426
381 switch (header.capacityIndexType()) {427 pub fn clearRetainingCapacity(self: *Self) void {
382 .u8 => return self.getOrPutInternal(key, header, u8),428 if (self.metadata) |_| {
383 .u16 => return self.getOrPutInternal(key, header, u16),429 self.initMetadatas();
384 .u32 => return self.getOrPutInternal(key, header, u32),430 self.size = 0;
385 .usize => return self.getOrPutInternal(key, header, usize),431 self.available = 0;
386 }432 }
387 }433 }
388434
389 pub fn getOrPutValue(self: *Self, allocator: *Allocator, key: K, value: V) !*Entry {435 pub fn clearAndFree(self: *Self, allocator: *Allocator) void {
390 const res = try self.getOrPut(allocator, key);436 self.deallocate(allocator);
391 if (!res.found_existing)437 self.size = 0;
392 res.entry.value = value;438 self.available = 0;
439 }
393440
394 return res.entry;441 pub fn count(self: *const Self) Size {
442 return self.size;
395 }443 }
396444
397 /// Increases capacity, guaranteeing that insertions up until the445 fn header(self: *const Self) *Header {
398 /// `expected_count` will not cause an allocation, and therefore cannot fail.446 return @ptrCast(*Header, @ptrCast([*]Header, self.metadata.?) - 1);
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 }
424 }447 }
425448
426 /// Returns the number of total elements which may be present before it is449 fn entries(self: *const Self) [*]Entry {
427 /// no longer guaranteed that no allocations will be performed.450 return self.header().entries;
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);
433 }451 }
434452
435 /// Clobbers any existing data. To detect if a put would clobber453 pub fn capacity(self: *const Self) Size {
436 /// existing data, see `getOrPut`.454 if (self.metadata == null) return 0;
437 pub fn put(self: *Self, allocator: *Allocator, key: K, value: V) !void {455
438 const result = try self.getOrPut(allocator, key);456 return self.header().capacity;
439 result.entry.value = value;
440 }457 }
441458
442 /// Inserts a key-value pair into the hash map, asserting that no previous459 pub fn iterator(self: *const Self) Iterator {
443 /// entry with the same key is already present460 return .{ .hm = self };
461 }
462
463 /// Insert an entry in the map. Assumes it is not already present.
444 pub fn putNoClobber(self: *Self, allocator: *Allocator, key: K, value: V) !void {464 pub fn putNoClobber(self: *Self, allocator: *Allocator, key: K, value: V) !void {
445 const result = try self.getOrPut(allocator, key);465 assert(!self.contains(key));
446 assert(!result.found_existing);466 try self.growIfNeeded(allocator, 1);
447 result.entry.value = value;467
468 self.putAssumeCapacityNoClobber(key, value);
448 }469 }
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`.
453 pub fn putAssumeCapacity(self: *Self, key: K, value: V) void {471 pub fn putAssumeCapacity(self: *Self, key: K, value: V) void {
454 const result = self.getOrPutAssumeCapacity(key);472 const hash = hashFn(key);
455 result.entry.value = value;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;
456 }506 }
457507
458 /// Asserts there is enough capacity to store the new key-value pair.508 /// Insert an entry in the map. Assumes it is not already present,
459 /// Asserts that it does not clobber any existing data.509 /// and that no allocation is needed.
460 /// To detect if a put would clobber existing data, see `getOrPutAssumeCapacity`.
461 pub fn putAssumeCapacityNoClobber(self: *Self, key: K, value: V) void {510 pub fn putAssumeCapacityNoClobber(self: *Self, key: K, value: V) void {
462 const result = self.getOrPutAssumeCapacity(key);511 assert(!self.contains(key));
463 assert(!result.found_existing);512
464 result.entry.value = value;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;
465 }533 }
466534
467 /// Inserts a new `Entry` into the hash map, returning the previous one, if any.535 /// Inserts a new `Entry` into the hash map, returning the previous one, if any.
...@@ -488,400 +556,622 @@ pub fn HashMapUnmanaged(...@@ -488,400 +556,622 @@ pub fn HashMapUnmanaged(
488 }556 }
489557
490 pub fn getEntry(self: Self, key: K) ?*Entry {558 pub fn getEntry(self: Self, key: K) ?*Entry {
491 const index = self.getIndex(key) orelse return null;559 if (self.size == 0) {
492 return &self.entries.items[index];560 return null;
493 }561 }
494562
495 pub fn getIndex(self: Self, key: K) ?usize {563 const hash = hashFn(key);
496 const header = self.index_header orelse {564 const mask = self.capacity() - 1;
497 // Linear scan.565 const fingerprint = Metadata.takeFingerprint(hash);
498 const h = if (store_hash) hash(key) else {};566 var idx = @truncate(usize, hash & mask);
499 for (self.entries.items) |*item, i| {567
500 if (item.hash == h and eql(key, item.key)) {568 var metadata = self.metadata.? + idx;
501 return i;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;
502 }574 }
503 }575 }
504 return null;576 idx = (idx + 1) & mask;
505 };577 metadata = self.metadata.? + idx;
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),
511 }578 }
512 }
513579
514 pub fn get(self: Self, key: K) ?V {580 return null;
515 return if (self.getEntry(key)) |entry| entry.value else null;
516 }581 }
517582
518 pub fn contains(self: Self, key: K) bool {583 /// Insert an entry if the associated key is not already present, otherwise update preexisting value.
519 return self.getEntry(key) != null;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;
520 }588 }
521589
522 /// If there is an `Entry` with a matching key, it is deleted from590 /// Get an optional pointer to the value associated with key, if present.
523 /// the hash map, and then returned from this function.591 pub fn get(self: Self, key: K) ?V {
524 pub fn remove(self: *Self, key: K) ?Entry {592 if (self.size == 0) {
525 const header = self.index_header orelse {593 return null;
526 // Linear scan.594 }
527 const h = if (store_hash) hash(key) else {};595
528 for (self.entries.items) |item, i| {596 const hash = hashFn(key);
529 if (item.hash == h and eql(key, item.key)) {597 const mask = self.capacity() - 1;
530 return self.entries.swapRemove(i);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;
531 }607 }
532 }608 }
533 return null;609 idx = (idx + 1) & mask;
534 };610 metadata = self.metadata.? + idx;
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),
540 }611 }
541 }
542612
543 /// Asserts there is an `Entry` with matching key, deletes it from the hash map,613 return null;
544 /// and discards it.
545 pub fn removeAssertDiscard(self: *Self, key: K) void {
546 assert(self.remove(key) != null);
547 }614 }
548615
549 pub fn items(self: Self) []Entry {616 pub fn getOrPut(self: *Self, allocator: *Allocator, key: K) !GetOrPutResult {
550 return self.entries.items;617 try self.growIfNeeded(allocator, 1);
618
619 return self.getOrPutAssumeCapacity(key);
551 }620 }
552621
553 pub fn clone(self: Self, allocator: *Allocator) !Self {622 pub fn getOrPutAssumeCapacity(self: *Self, key: K) GetOrPutResult {
554 var other: Self = .{};623 const hash = hashFn(key);
555 try other.entries.appendSlice(allocator, self.entries.items);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| {640 idx = (idx + 1) & mask;
558 const new_header = try IndexHeader.alloc(allocator, header.indexes_len);641 metadata = self.metadata.? + idx;
559 other.insertAllEntriesIntoNewHeader(new_header);
560 other.index_header = new_header;
561 }642 }
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 };
563 }659 }
564660
565 fn removeInternal(self: *Self, key: K, header: *IndexHeader, comptime I: type) ?Entry {661 pub fn getOrPutValue(self: *Self, allocator: *Allocator, key: K, value: V) !*Entry {
566 const indexes = header.indexes(I);662 const res = try self.getOrPut(allocator, key);
567 const h = hash(key);663 if (!res.found_existing) res.entry.value = value;
568 const start_index = header.constrainIndex(h);664 return res.entry;
569 var roll_over: usize = 0;665 }
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 }
588666
589 // Now we have to shift over the following indexes.667 /// Return true if there is a value associated with key in the map.
590 roll_over += 1;668 pub fn contains(self: *const Self, key: K) bool {
591 while (roll_over < header.indexes_len) : (roll_over += 1) {669 return self.get(key) != null;
592 const next_index_index = header.constrainIndex(start_index + roll_over);670 }
593 const next_index = &indexes[next_index_index];671
594 if (next_index.isEmpty() or next_index.distance_from_start_index == 0) {672 /// If there is an `Entry` with a matching key, it is deleted from
595 index.setEmpty();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;
596 return removed_entry;691 return removed_entry;
597 }692 }
598 index.* = next_index.*;
599 index.distance_from_start_index -= 1;
600 index = next_index;
601 }693 }
602 unreachable;694 idx = (idx + 1) & mask;
695 metadata = self.metadata.? + idx;
603 }696 }
697
604 return null;698 return null;
605 }699 }
606700
607 fn updateEntryIndex(701 /// Asserts there is an `Entry` with matching key, deletes it from the hash map,
608 self: *Self,702 /// and discards it.
609 header: *IndexHeader,703 pub fn removeAssertDiscard(self: *Self, key: K) void {
610 old_entry_index: usize,704 assert(self.contains(key));
611 new_entry_index: usize,705
612 comptime I: type,706 const hash = hashFn(key);
613 indexes: []Index(I),707 const mask = self.capacity() - 1;
614 ) void {708 const fingerprint = Metadata.takeFingerprint(hash);
615 const h = if (store_hash) self.entries.items[new_entry_index].hash else hash(self.entries.items[new_entry_index].key);709 var idx = @truncate(usize, hash & mask);
616 const start_index = header.constrainIndex(h);710
617 var roll_over: usize = 0;711 var metadata = self.metadata.? + idx;
618 while (roll_over <= header.max_distance_from_start_index) : (roll_over += 1) {712 while (metadata[0].isUsed() or metadata[0].isTombstone()) {
619 const index_index = header.constrainIndex(start_index + roll_over);713 if (metadata[0].isUsed() and metadata[0].fingerprint == fingerprint) {
620 const index = &indexes[index_index];714 const entry = &self.entries()[idx];
621 if (index.entry_index == old_entry_index) {715 if (eqlFn(entry.key, key)) {
622 index.entry_index = @intCast(I, new_entry_index);716 metadata[0].remove();
623 return;717 entry.* = undefined;
718 self.size -= 1;
719 return;
720 }
624 }721 }
722 idx = (idx + 1) & mask;
723 metadata = self.metadata.? + idx;
625 }724 }
725
626 unreachable;726 unreachable;
627 }727 }
628728
629 /// Must ensureCapacity before calling this.729 fn initMetadatas(self: *Self) void {
630 fn getOrPutInternal(self: *Self, key: K, header: *IndexHeader, comptime I: type) GetOrPutResult {730 @memset(@ptrCast([*]u8, self.metadata.?), 0, @sizeOf(Metadata) * self.capacity());
631 const indexes = header.indexes(I);731 }
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 }
659732
660 // This pointer survives the following append because we call733 // This counts the number of occupied slots, used + tombstones, which is
661 // entries.ensureCapacity before getOrPutInternal.734 // what has to stay under the MaxLoadPercentage of capacity.
662 const entry = &self.entries.items[index.entry_index];735 fn load(self: *const Self) Size {
663 const hash_match = if (store_hash) h == entry.hash else true;736 const max_load = (self.capacity() * MaxLoadPercentage) / 100;
664 if (hash_match and eql(key, entry.key)) {737 assert(max_load >= self.available);
665 return .{738 return @truncate(Size, max_load - self.available);
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;
725 }739 }
726740
727 fn getInternal(self: Self, key: K, header: *IndexHeader, comptime I: type) ?usize {741 fn growIfNeeded(self: *Self, allocator: *Allocator, new_count: Size) !void {
728 const indexes = header.indexes(I);742 if (new_count > self.available) {
729 const h = hash(key);743 try self.grow(allocator, capacityForSize(self.load() + new_count));
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;
742 }744 }
743 return null;
744 }745 }
745746
746 fn insertAllEntriesIntoNewHeader(self: *Self, header: *IndexHeader) void {747 pub fn clone(self: Self, allocator: *Allocator) !Self {
747 switch (header.capacityIndexType()) {748 var other = Self{};
748 .u8 => return self.insertAllEntriesIntoNewHeaderGeneric(header, u8),749 if (self.size == 0)
749 .u16 => return self.insertAllEntriesIntoNewHeaderGeneric(header, u16),750 return other;
750 .u32 => return self.insertAllEntriesIntoNewHeaderGeneric(header, u32),751
751 .usize => return self.insertAllEntriesIntoNewHeaderGeneric(header, usize),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 }
752 }767 }
768
769 return other;
753 }770 }
754771
755 fn insertAllEntriesIntoNewHeaderGeneric(self: *Self, header: *IndexHeader, comptime I: type) void {772 fn grow(self: *Self, allocator: *Allocator, new_capacity: Size) !void {
756 const indexes = header.indexes(I);773 const new_cap = std.math.max(new_capacity, MinimalCapacity);
757 entry_loop: for (self.entries.items) |entry, i| {774 assert(new_cap > self.capacity());
758 const h = if (store_hash) entry.hash else hash(entry.key);775 assert(std.math.isPowerOfTwo(new_cap));
759 const start_index = header.constrainIndex(h);776
760 var entry_index = i;777 var map = Self{};
761 var roll_over: usize = 0;778 defer map.deinit(allocator);
762 var distance_from_start_index: usize = 0;779 try map.allocate(allocator, new_cap);
763 while (roll_over < header.indexes_len) : ({780 map.initMetadatas();
764 roll_over += 1;781 map.available = @truncate(u32, (new_cap * MaxLoadPercentage) / 100);
765 distance_from_start_index += 1;782
766 }) {783 if (self.size != 0) {
767 const index_index = header.constrainIndex(start_index + roll_over);784 const old_capacity = self.capacity();
768 const next_index = indexes[index_index];785 var i: Size = 0;
769 if (next_index.isEmpty()) {786 var metadata = self.metadata.?;
770 header.maybeBumpMax(distance_from_start_index);787 var entr = self.entries();
771 indexes[index_index] = .{788 while (i < old_capacity) : (i += 1) {
772 .distance_from_start_index = @intCast(I, distance_from_start_index),789 if (metadata[i].isUsed()) {
773 .entry_index = @intCast(I, entry_index),790 const entry = &entr[i];
774 };791 map.putAssumeCapacityNoClobber(entry.key, entry.value);
775 continue :entry_loop;792 if (map.size == self.size)
776 }793 break;
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;
785 }794 }
786 }795 }
787 unreachable;
788 }796 }
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);
789 }822 }
790 };823 };
791}824}
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 {862 try map.ensureCapacity(20);
796 if (indexes_len < math.maxInt(u8))863 const initial_capacity = map.capacity();
797 return .u8;864 testing.expect(initial_capacity >= 20);
798 if (indexes_len < math.maxInt(u16))865 var i: i32 = 0;
799 return .u16;866 while (i < 20) : (i += 1) {
800 if (indexes_len < math.maxInt(u32))867 testing.expect(map.fetchPutAssumeCapacity(i, i + 10) == null);
801 return .u32;868 }
802 return .usize;869 // shouldn't resize from putAssumeCapacity
870 testing.expect(initial_capacity == map.capacity());
803}871}
804872
805fn capacityIndexSize(indexes_len: usize) usize {873test "std.hash_map ensureCapacity with tombstones" {
806 switch (capacityIndexType(indexes_len)) {874 var map = AutoHashMap(i32, i32).init(std.testing.allocator);
807 .u8 => return @sizeOf(Index(u8)),875 defer map.deinit();
808 .u16 => return @sizeOf(Index(u16)),876
809 .u32 => return @sizeOf(Index(u32)),877 var i: i32 = 0;
810 .usize => return @sizeOf(Index(usize)),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);
811 }883 }
812}884}
813885
814fn Index(comptime I: type) type {886test "std.hash_map clearRetainingCapacity" {
815 return extern struct {887 var map = AutoHashMap(u32, u32).init(std.testing.allocator);
816 entry_index: I,888 defer map.deinit();
817 distance_from_start_index: I,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{896 const cap = map.capacity();
822 .entry_index = math.maxInt(I),897 expect(cap > 0);
823 .distance_from_start_index = undefined,898
824 };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 {910 const growTo = 12456;
827 return idx.entry_index == math.maxInt(I);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);
828 }988 }
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 {997 i = 0;
831 idx.entry_index = math.maxInt(I);998 while (i < 16) : (i += 1) {
999 if (i % 3 == 0) {
1000 expect(!map.contains(i));
1001 } else {
1002 expectEqual(map.get(i).?, i);
832 }1003 }
833 };1004 }
834}1005}
8351006
836/// This struct is trailed by an array of `Index(I)`, where `I`1007test "std.hash_map reverse removes" {
837/// and the array length are determined by `indexes_len`.1008 var map = AutoHashMap(u32, u32).init(std.testing.allocator);
838const IndexHeader = struct {1009 defer map.deinit();
839 max_distance_from_start_index: usize,
840 indexes_len: usize,
8411010
842 fn constrainIndex(header: IndexHeader, i: usize) usize {1011 var i: u32 = 0;
843 // This is an optimization for modulo of power of two integers;1012 while (i < 16) : (i += 1) {
844 // it requires `indexes_len` to always be a power of two.1013 try map.putNoClobber(i, i);
845 return i & (header.indexes_len - 1);
846 }1014 }
8471015
848 fn indexes(header: *IndexHeader, comptime I: type) []Index(I) {1016 i = 16;
849 const start = @ptrCast([*]Index(I), @ptrCast([*]u8, header) + @sizeOf(IndexHeader));1017 while (i > 0) : (i -= 1) {
850 return start[0..header.indexes_len];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 }
851 }1024 }
8521025
853 fn capacityIndexType(header: IndexHeader) CapacityIndexType {1026 expectEqual(map.count(), 0);
854 return hash_map.capacityIndexType(header.indexes_len);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);
855 }1036 }
8561037
857 fn maybeBumpMax(header: *IndexHeader, distance_from_start_index: usize) void {1038 _ = map.remove(7);
858 if (distance_from_start_index > header.max_distance_from_start_index) {1039 _ = map.remove(15);
859 header.max_distance_from_start_index = distance_from_start_index;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);
860 }1053 }
861 }1054 }
8621055
863 fn alloc(allocator: *Allocator, len: usize) !*IndexHeader {1056 try map.put(15, 15);
864 const index_size = hash_map.capacityIndexSize(len);1057 try map.put(13, 13);
865 const nbytes = @sizeOf(IndexHeader) + index_size * len;1058 try map.put(14, 14);
866 const bytes = try allocator.allocAdvanced(u8, @alignOf(IndexHeader), nbytes, .exact);1059 try map.put(7, 7);
867 @memset(bytes.ptr + @sizeOf(IndexHeader), 0xff, bytes.len - @sizeOf(IndexHeader));1060 i = 0;
868 const result = @ptrCast(*IndexHeader, bytes.ptr);1061 while (i < 16) : (i += 1) {
869 result.* = .{1062 expectEqual(map.get(i).?, i);
870 .max_distance_from_start_index = 0,1063 }
871 .indexes_len = len,1064}
872 };1065
873 return result;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);
874 }1158 }
8751159
876 fn free(header: *IndexHeader, allocator: *Allocator) void {1160 i = 0;
877 const index_size = hash_map.capacityIndexSize(header.indexes_len);1161 while (i < 20) : (i += 1) {
878 const ptr = @ptrCast([*]u8, header);1162 var n = try map.getOrPutValue(i, 1);
879 const slice = ptr[0 .. @sizeOf(IndexHeader) + header.indexes_len * index_size];
880 allocator.free(slice);
881 }1163 }
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" {
885 var map = AutoHashMap(i32, i32).init(std.testing.allocator);1175 var map = AutoHashMap(i32, i32).init(std.testing.allocator);
886 defer map.deinit();1176 defer map.deinit();
8871177
...@@ -925,85 +1215,10 @@ test "basic hash map usage" {...@@ -925,85 +1215,10 @@ test "basic hash map usage" {
925 map.removeAssertDiscard(3);1215 map.removeAssertDiscard(3);
926}1216}
9271217
928test "iterator hash map" {1218test "std.hash_map clone" {
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" {
1003 var original = AutoHashMap(i32, i32).init(std.testing.allocator);1219 var original = AutoHashMap(i32, i32).init(std.testing.allocator);
1004 defer original.deinit();1220 defer original.deinit();
10051221
1006 // put more than `linear_scan_max` so we can test that the index header is properly cloned
1007 var i: u8 = 0;1222 var i: u8 = 0;
1008 while (i < 10) : (i += 1) {1223 while (i < 10) : (i += 1) {
1009 try original.putNoClobber(i, i * 10);1224 try original.putNoClobber(i, i * 10);
...@@ -1017,69 +1232,3 @@ test "clone" {...@@ -1017,69 +1232,3 @@ test "clone" {
1017 testing.expect(copy.get(i).? == i * 10);1232 testing.expect(copy.get(i).? == i * 10);
1018 }1233 }
1019}1234}
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 {...@@ -325,7 +325,8 @@ pub fn GeneralPurposeAllocator(comptime config: Config) type {
325 break;325 break;
326 }326 }
327 }327 }
328 for (self.large_allocations.items()) |*large_alloc| {328 var it = self.large_allocations.iterator();
329 while (it.next()) |large_alloc| {
329 log.err("Memory leak detected: {}", .{large_alloc.value.getStackTrace()});330 log.err("Memory leak detected: {}", .{large_alloc.value.getStackTrace()});
330 leaks = true;331 leaks = true;
331 }332 }
...@@ -584,7 +585,7 @@ pub fn GeneralPurposeAllocator(comptime config: Config) type {...@@ -584,7 +585,7 @@ pub fn GeneralPurposeAllocator(comptime config: Config) type {
584 if (new_aligned_size > largest_bucket_object_size) {585 if (new_aligned_size > largest_bucket_object_size) {
585 try self.large_allocations.ensureCapacity(586 try self.large_allocations.ensureCapacity(
586 self.backing_allocator,587 self.backing_allocator,
587 self.large_allocations.entries.items.len + 1,588 self.large_allocations.count() + 1,
588 );589 );
589590
590 const slice = try self.backing_allocator.allocFn(self.backing_allocator, len, ptr_align, len_align, ret_addr);591 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 {...@@ -123,9 +123,9 @@ pub const Headers = struct {
123123
124 pub fn deinit(self: *Self) void {124 pub fn deinit(self: *Self) void {
125 {125 {
126 for (self.index.items()) |*entry| {126 var it = self.index.iterator();
127 const dex = &entry.value;127 while (it.next()) |entry| {
128 dex.deinit(self.allocator);128 entry.value.deinit(self.allocator);
129 self.allocator.free(entry.key);129 self.allocator.free(entry.key);
130 }130 }
131 self.index.deinit(self.allocator);131 self.index.deinit(self.allocator);
...@@ -333,7 +333,8 @@ pub const Headers = struct {...@@ -333,7 +333,8 @@ pub const Headers = struct {
333333
334 fn rebuildIndex(self: *Self) void {334 fn rebuildIndex(self: *Self) void {
335 // clear out the indexes335 // clear out the indexes
336 for (self.index.items()) |*entry| {336 var it = self.index.iterator();
337 while (it.next()) |entry| {
337 entry.value.shrinkRetainingCapacity(0);338 entry.value.shrinkRetainingCapacity(0);
338 }339 }
339 // fill up indexes again; we know capacity is fine from before340 // fill up indexes again; we know capacity is fine from before
lib/std/io.zig+9
...@@ -169,6 +169,15 @@ pub const BitOutStream = BitWriter;...@@ -169,6 +169,15 @@ pub const BitOutStream = BitWriter;
169/// Deprecated: use `bitWriter`169/// Deprecated: use `bitWriter`
170pub const bitOutStream = bitWriter;170pub 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
172pub const Packing = @import("io/serialization.zig").Packing;181pub const Packing = @import("io/serialization.zig").Packing;
173182
174pub const Serializer = @import("io/serialization.zig").Serializer;183pub 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 {...@@ -705,34 +705,34 @@ pub fn Vector(comptime len: u32, comptime child: type) type {
705pub fn cast(comptime DestType: type, target: anytype) DestType {705pub fn cast(comptime DestType: type, target: anytype) DestType {
706 const TargetType = @TypeOf(target);706 const TargetType = @TypeOf(target);
707 switch (@typeInfo(DestType)) {707 switch (@typeInfo(DestType)) {
708 .Pointer => {708 .Pointer => |dest_ptr| {
709 switch (@typeInfo(TargetType)) {709 switch (@typeInfo(TargetType)) {
710 .Int, .ComptimeInt => {710 .Int, .ComptimeInt => {
711 return @intToPtr(DestType, target);711 return @intToPtr(DestType, target);
712 },712 },
713 .Pointer => |ptr| {713 .Pointer => |ptr| {
714 return @ptrCast(DestType, @alignCast(ptr.alignment, target));714 return @ptrCast(DestType, @alignCast(dest_ptr.alignment, target));
715 },715 },
716 .Optional => |opt| {716 .Optional => |opt| {
717 if (@typeInfo(opt.child) == .Pointer) {717 if (@typeInfo(opt.child) == .Pointer) {
718 return @ptrCast(DestType, @alignCast(@alignOf(opt.child.Child), target));718 return @ptrCast(DestType, @alignCast(dest_ptr, target));
719 }719 }
720 },720 },
721 else => {},721 else => {},
722 }722 }
723 },723 },
724 .Optional => |opt| {724 .Optional => |dest_opt| {
725 if (@typeInfo(opt.child) == .Pointer) {725 if (@typeInfo(dest_opt.child) == .Pointer) {
726 switch (@typeInfo(TargetType)) {726 switch (@typeInfo(TargetType)) {
727 .Int, .ComptimeInt => {727 .Int, .ComptimeInt => {
728 return @intToPtr(DestType, target);728 return @intToPtr(DestType, target);
729 },729 },
730 .Pointer => |ptr| {730 .Pointer => {
731 return @ptrCast(DestType, @alignCast(ptr.alignment, target));731 return @ptrCast(DestType, @alignCast(@alignOf(dest_opt.child.Child), target));
732 },732 },
733 .Optional => |target_opt| {733 .Optional => |target_opt| {
734 if (@typeInfo(target_opt.child) == .Pointer) {734 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));
736 }736 }
737 },737 },
738 else => {},738 else => {},
lib/std/meta/trailer_flags.zig+1
...@@ -46,6 +46,7 @@ pub fn TrailerFlags(comptime Fields: type) type {...@@ -46,6 +46,7 @@ pub fn TrailerFlags(comptime Fields: type) type {
46 ??struct_field.field_type,46 ??struct_field.field_type,
47 @as(?struct_field.field_type, null),47 @as(?struct_field.field_type, null),
48 ),48 ),
49 .is_comptime = false,
49 };50 };
50 }51 }
51 break :blk @Type(.{52 break :blk @Type(.{
lib/std/net.zig+4-1
...@@ -1164,7 +1164,7 @@ fn linuxLookupNameFromDnsSearch(...@@ -1164,7 +1164,7 @@ fn linuxLookupNameFromDnsSearch(
1164 }1164 }
11651165
1166 const search = if (rc.search.isNull() or dots >= rc.ndots or mem.endsWith(u8, name, "."))1166 const search = if (rc.search.isNull() or dots >= rc.ndots or mem.endsWith(u8, name, "."))
1167 &[_]u8{}1167 ""
1168 else1168 else
1169 rc.search.span();1169 rc.search.span();
11701170
...@@ -1641,6 +1641,9 @@ pub const StreamServer = struct {...@@ -1641,6 +1641,9 @@ pub const StreamServer = struct {
1641 /// by the socket buffer limits, not by the system memory.1641 /// by the socket buffer limits, not by the system memory.
1642 SystemResources,1642 SystemResources,
16431643
1644 /// Socket is not listening for new connections.
1645 SocketNotListening,
1646
1644 ProtocolFailure,1647 ProtocolFailure,
16451648
1646 /// Firewall rules forbid connection.1649 /// 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...@@ -2512,13 +2512,14 @@ pub fn readlinkatZ(dirfd: fd_t, file_path: [*:0]const u8, out_buffer: []u8) Read
2512 }2512 }
2513}2513}
25142514
2515pub const SetIdError = error{2515pub const SetEidError = error{
2516 ResourceLimitReached,
2517 InvalidUserId,2516 InvalidUserId,
2518 PermissionDenied,2517 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 {
2522 switch (errno(system.setuid(uid))) {2523 switch (errno(system.setuid(uid))) {
2523 0 => return,2524 0 => return,
2524 EAGAIN => return error.ResourceLimitReached,2525 EAGAIN => return error.ResourceLimitReached,
...@@ -2528,7 +2529,16 @@ pub fn setuid(uid: u32) SetIdError!void {...@@ -2528,7 +2529,16 @@ pub fn setuid(uid: u32) SetIdError!void {
2528 }2529 }
2529}2530}
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 {
2532 switch (errno(system.setreuid(ruid, euid))) {2542 switch (errno(system.setreuid(ruid, euid))) {
2533 0 => return,2543 0 => return,
2534 EAGAIN => return error.ResourceLimitReached,2544 EAGAIN => return error.ResourceLimitReached,
...@@ -2538,7 +2548,7 @@ pub fn setreuid(ruid: u32, euid: u32) SetIdError!void {...@@ -2538,7 +2548,7 @@ pub fn setreuid(ruid: u32, euid: u32) SetIdError!void {
2538 }2548 }
2539}2549}
25402550
2541pub fn setgid(gid: u32) SetIdError!void {2551pub fn setgid(gid: gid_t) SetIdError!void {
2542 switch (errno(system.setgid(gid))) {2552 switch (errno(system.setgid(gid))) {
2543 0 => return,2553 0 => return,
2544 EAGAIN => return error.ResourceLimitReached,2554 EAGAIN => return error.ResourceLimitReached,
...@@ -2548,7 +2558,16 @@ pub fn setgid(gid: u32) SetIdError!void {...@@ -2548,7 +2558,16 @@ pub fn setgid(gid: u32) SetIdError!void {
2548 }2558 }
2549}2559}
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 {
2552 switch (errno(system.setregid(rgid, egid))) {2571 switch (errno(system.setregid(rgid, egid))) {
2553 0 => return,2572 0 => return,
2554 EAGAIN => return error.ResourceLimitReached,2573 EAGAIN => return error.ResourceLimitReached,
...@@ -2802,6 +2821,9 @@ pub const AcceptError = error{...@@ -2802,6 +2821,9 @@ pub const AcceptError = error{
2802 /// by the socket buffer limits, not by the system memory.2821 /// by the socket buffer limits, not by the system memory.
2803 SystemResources,2822 SystemResources,
28042823
2824 /// Socket is not listening for new connections.
2825 SocketNotListening,
2826
2805 ProtocolFailure,2827 ProtocolFailure,
28062828
2807 /// Firewall rules forbid connection.2829 /// Firewall rules forbid connection.
...@@ -2870,7 +2892,7 @@ pub fn accept(...@@ -2870,7 +2892,7 @@ pub fn accept(
2870 EBADF => unreachable, // always a race condition2892 EBADF => unreachable, // always a race condition
2871 ECONNABORTED => return error.ConnectionAborted,2893 ECONNABORTED => return error.ConnectionAborted,
2872 EFAULT => unreachable,2894 EFAULT => unreachable,
2873 EINVAL => unreachable,2895 EINVAL => return error.SocketNotListening,
2874 ENOTSOCK => unreachable,2896 ENOTSOCK => unreachable,
2875 EMFILE => return error.ProcessFdQuotaExceeded,2897 EMFILE => return error.ProcessFdQuotaExceeded,
2876 ENFILE => return error.SystemFdQuotaExceeded,2898 ENFILE => return error.SystemFdQuotaExceeded,
...@@ -5328,3 +5350,71 @@ pub fn signalfd(fd: fd_t, mask: *const sigset_t, flags: u32) !fd_t {...@@ -5328,3 +5350,71 @@ pub fn signalfd(fd: fd_t, mask: *const sigset_t, flags: u32) !fd_t {
5328 else => |err| return std.os.unexpectedErrno(err),5350 else => |err| return std.os.unexpectedErrno(err),
5329 }5351 }
5330}5352}
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");...@@ -7,9 +7,13 @@ const std = @import("../../std.zig");
7const assert = std.debug.assert;7const assert = std.debug.assert;
8const maxInt = std.math.maxInt;8const 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
10pub const fd_t = c_int;12pub const fd_t = c_int;
11pub const pid_t = c_int;13pub const pid_t = c_int;
12pub const mode_t = c_uint;14pub const mode_t = c_uint;
15pub const uid_t = u32;
16pub const gid_t = u32;
1317
14pub const in_port_t = u16;18pub const in_port_t = u16;
15pub const sa_family_t = u8;19pub const sa_family_t = u8;
...@@ -79,8 +83,8 @@ pub const Stat = extern struct {...@@ -79,8 +83,8 @@ pub const Stat = extern struct {
79 mode: u16,83 mode: u16,
80 nlink: u16,84 nlink: u16,
81 ino: ino_t,85 ino: ino_t,
82 uid: u32,86 uid: uid_t,
83 gid: u32,87 gid: gid_t,
84 rdev: i32,88 rdev: i32,
85 atimesec: isize,89 atimesec: isize,
86 atimensec: isize,90 atimensec: isize,
lib/std/os/bits/dragonfly.zig+10-3
...@@ -9,10 +9,17 @@ const maxInt = std.math.maxInt;...@@ -9,10 +9,17 @@ const maxInt = std.math.maxInt;
9pub fn S_ISCHR(m: u32) bool {9pub fn S_ISCHR(m: u32) bool {
10 return m & S_IFMT == S_IFCHR;10 return m & S_IFMT == S_IFCHR;
11}11}
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
12pub const fd_t = c_int;17pub const fd_t = c_int;
13pub const pid_t = c_int;18pub const pid_t = c_int;
14pub const off_t = c_long;19pub const off_t = c_long;
15pub const mode_t = c_uint;20pub const mode_t = c_uint;
21pub const uid_t = u32;
22pub const gid_t = u32;
1623
17pub const ENOTSUP = EOPNOTSUPP;24pub const ENOTSUP = EOPNOTSUPP;
18pub const EWOULDBLOCK = EAGAIN;25pub const EWOULDBLOCK = EAGAIN;
...@@ -151,8 +158,8 @@ pub const Stat = extern struct {...@@ -151,8 +158,8 @@ pub const Stat = extern struct {
151 dev: c_uint,158 dev: c_uint,
152 mode: c_ushort,159 mode: c_ushort,
153 padding1: u16,160 padding1: u16,
154 uid: c_uint,161 uid: uid_t,
155 gid: c_uint,162 gid: gid_t,
156 rdev: c_uint,163 rdev: c_uint,
157 atim: timespec,164 atim: timespec,
158 mtim: timespec,165 mtim: timespec,
...@@ -511,7 +518,7 @@ pub const siginfo_t = extern struct {...@@ -511,7 +518,7 @@ pub const siginfo_t = extern struct {
511 si_errno: c_int,518 si_errno: c_int,
512 si_code: c_int,519 si_code: c_int,
513 si_pid: c_int,520 si_pid: c_int,
514 si_uid: c_uint,521 si_uid: uid_t,
515 si_status: c_int,522 si_status: c_int,
516 si_addr: ?*c_void,523 si_addr: ?*c_void,
517 si_value: union_sigval,524 si_value: union_sigval,
lib/std/os/bits/freebsd.zig+6-2
...@@ -6,8 +6,12 @@...@@ -6,8 +6,12 @@
6const std = @import("../../std.zig");6const std = @import("../../std.zig");
7const maxInt = std.math.maxInt;7const 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
9pub const fd_t = c_int;11pub const fd_t = c_int;
10pub const pid_t = c_int;12pub const pid_t = c_int;
13pub const uid_t = u32;
14pub const gid_t = u32;
11pub const mode_t = c_uint;15pub const mode_t = c_uint;
1216
13pub const socklen_t = u32;17pub const socklen_t = u32;
...@@ -128,8 +132,8 @@ pub const Stat = extern struct {...@@ -128,8 +132,8 @@ pub const Stat = extern struct {
128132
129 mode: u16,133 mode: u16,
130 __pad0: u16,134 __pad0: u16,
131 uid: u32,135 uid: uid_t,
132 gid: u32,136 gid: gid_t,
133 __pad1: u32,137 __pad1: u32,
134 rdev: u64,138 rdev: u64,
135139
lib/std/os/bits/linux.zig+4-4
...@@ -29,7 +29,7 @@ const is_mips = builtin.arch.isMIPS();...@@ -29,7 +29,7 @@ const is_mips = builtin.arch.isMIPS();
2929
30pub const pid_t = i32;30pub const pid_t = i32;
31pub const fd_t = i32;31pub const fd_t = i32;
32pub const uid_t = i32;32pub const uid_t = u32;
33pub const gid_t = u32;33pub const gid_t = u32;
34pub const clock_t = isize;34pub const clock_t = isize;
3535
...@@ -853,7 +853,7 @@ pub const signalfd_siginfo = extern struct {...@@ -853,7 +853,7 @@ pub const signalfd_siginfo = extern struct {
853 errno: i32,853 errno: i32,
854 code: i32,854 code: i32,
855 pid: u32,855 pid: u32,
856 uid: u32,856 uid: uid_t,
857 fd: i32,857 fd: i32,
858 tid: u32,858 tid: u32,
859 band: u32,859 band: u32,
...@@ -1491,10 +1491,10 @@ pub const Statx = extern struct {...@@ -1491,10 +1491,10 @@ pub const Statx = extern struct {
1491 nlink: u32,1491 nlink: u32,
14921492
1493 /// User ID of owner1493 /// User ID of owner
1494 uid: u32,1494 uid: uid_t,
14951495
1496 /// Group ID of owner1496 /// Group ID of owner
1497 gid: u32,1497 gid: gid_t,
14981498
1499 /// File type and mode1499 /// File type and mode
1500 mode: u16,1500 mode: u16,
lib/std/os/bits/linux/x86_64.zig+3-2
...@@ -7,6 +7,7 @@...@@ -7,6 +7,7 @@
7const std = @import("../../../std.zig");7const std = @import("../../../std.zig");
8const pid_t = linux.pid_t;8const pid_t = linux.pid_t;
9const uid_t = linux.uid_t;9const uid_t = linux.uid_t;
10const gid_t = linux.gid_t;
10const clock_t = linux.clock_t;11const clock_t = linux.clock_t;
11const stack_t = linux.stack_t;12const stack_t = linux.stack_t;
12const sigset_t = linux.sigset_t;13const sigset_t = linux.sigset_t;
...@@ -523,8 +524,8 @@ pub const Stat = extern struct {...@@ -523,8 +524,8 @@ pub const Stat = extern struct {
523 nlink: usize,524 nlink: usize,
524525
525 mode: u32,526 mode: u32,
526 uid: u32,527 uid: uid_t,
527 gid: u32,528 gid: gid_t,
528 __pad0: u32,529 __pad0: u32,
529 rdev: u64,530 rdev: u64,
530 size: off_t,531 size: off_t,
lib/std/os/linux.zig+56-26
...@@ -655,7 +655,7 @@ pub fn nanosleep(req: *const timespec, rem: ?*timespec) usize {...@@ -655,7 +655,7 @@ pub fn nanosleep(req: *const timespec, rem: ?*timespec) usize {
655 return syscall2(.nanosleep, @ptrToInt(req), @ptrToInt(rem));655 return syscall2(.nanosleep, @ptrToInt(req), @ptrToInt(rem));
656}656}
657657
658pub fn setuid(uid: u32) usize {658pub fn setuid(uid: uid_t) usize {
659 if (@hasField(SYS, "setuid32")) {659 if (@hasField(SYS, "setuid32")) {
660 return syscall1(.setuid32, uid);660 return syscall1(.setuid32, uid);
661 } else {661 } else {
...@@ -663,7 +663,7 @@ pub fn setuid(uid: u32) usize {...@@ -663,7 +663,7 @@ pub fn setuid(uid: u32) usize {
663 }663 }
664}664}
665665
666pub fn setgid(gid: u32) usize {666pub fn setgid(gid: gid_t) usize {
667 if (@hasField(SYS, "setgid32")) {667 if (@hasField(SYS, "setgid32")) {
668 return syscall1(.setgid32, gid);668 return syscall1(.setgid32, gid);
669 } else {669 } else {
...@@ -671,7 +671,7 @@ pub fn setgid(gid: u32) usize {...@@ -671,7 +671,7 @@ pub fn setgid(gid: u32) usize {
671 }671 }
672}672}
673673
674pub fn setreuid(ruid: u32, euid: u32) usize {674pub fn setreuid(ruid: uid_t, euid: uid_t) usize {
675 if (@hasField(SYS, "setreuid32")) {675 if (@hasField(SYS, "setreuid32")) {
676 return syscall2(.setreuid32, ruid, euid);676 return syscall2(.setreuid32, ruid, euid);
677 } else {677 } else {
...@@ -679,7 +679,7 @@ pub fn setreuid(ruid: u32, euid: u32) usize {...@@ -679,7 +679,7 @@ pub fn setreuid(ruid: u32, euid: u32) usize {
679 }679 }
680}680}
681681
682pub fn setregid(rgid: u32, egid: u32) usize {682pub fn setregid(rgid: gid_t, egid: gid_t) usize {
683 if (@hasField(SYS, "setregid32")) {683 if (@hasField(SYS, "setregid32")) {
684 return syscall2(.setregid32, rgid, egid);684 return syscall2(.setregid32, rgid, egid);
685 } else {685 } else {
...@@ -687,47 +687,61 @@ pub fn setregid(rgid: u32, egid: u32) usize {...@@ -687,47 +687,61 @@ pub fn setregid(rgid: u32, egid: u32) usize {
687 }687 }
688}688}
689689
690pub fn getuid() u32 {690pub fn getuid() uid_t {
691 if (@hasField(SYS, "getuid32")) {691 if (@hasField(SYS, "getuid32")) {
692 return @as(u32, syscall0(.getuid32));692 return @as(uid_t, syscall0(.getuid32));
693 } else {693 } else {
694 return @as(u32, syscall0(.getuid));694 return @as(uid_t, syscall0(.getuid));
695 }695 }
696}696}
697697
698pub fn getgid() u32 {698pub fn getgid() gid_t {
699 if (@hasField(SYS, "getgid32")) {699 if (@hasField(SYS, "getgid32")) {
700 return @as(u32, syscall0(.getgid32));700 return @as(gid_t, syscall0(.getgid32));
701 } else {701 } else {
702 return @as(u32, syscall0(.getgid));702 return @as(gid_t, syscall0(.getgid));
703 }703 }
704}704}
705705
706pub fn geteuid() u32 {706pub fn geteuid() uid_t {
707 if (@hasField(SYS, "geteuid32")) {707 if (@hasField(SYS, "geteuid32")) {
708 return @as(u32, syscall0(.geteuid32));708 return @as(uid_t, syscall0(.geteuid32));
709 } else {709 } else {
710 return @as(u32, syscall0(.geteuid));710 return @as(uid_t, syscall0(.geteuid));
711 }711 }
712}712}
713713
714pub fn getegid() u32 {714pub fn getegid() gid_t {
715 if (@hasField(SYS, "getegid32")) {715 if (@hasField(SYS, "getegid32")) {
716 return @as(u32, syscall0(.getegid32));716 return @as(gid_t, syscall0(.getegid32));
717 } else {717 } else {
718 return @as(u32, syscall0(.getegid));718 return @as(gid_t, syscall0(.getegid));
719 }719 }
720}720}
721721
722pub fn seteuid(euid: u32) usize {722pub fn seteuid(euid: uid_t) usize {
723 return setreuid(std.math.maxInt(u32), euid);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));
724}731}
725732
726pub fn setegid(egid: u32) usize {733pub fn setegid(egid: gid_t) usize {
727 return setregid(std.math.maxInt(u32), egid);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));
728}742}
729743
730pub fn getresuid(ruid: *u32, euid: *u32, suid: *u32) usize {744pub fn getresuid(ruid: *uid_t, euid: *uid_t, suid: *uid_t) usize {
731 if (@hasField(SYS, "getresuid32")) {745 if (@hasField(SYS, "getresuid32")) {
732 return syscall3(.getresuid32, @ptrToInt(ruid), @ptrToInt(euid), @ptrToInt(suid));746 return syscall3(.getresuid32, @ptrToInt(ruid), @ptrToInt(euid), @ptrToInt(suid));
733 } else {747 } else {
...@@ -735,7 +749,7 @@ pub fn getresuid(ruid: *u32, euid: *u32, suid: *u32) usize {...@@ -735,7 +749,7 @@ pub fn getresuid(ruid: *u32, euid: *u32, suid: *u32) usize {
735 }749 }
736}750}
737751
738pub fn getresgid(rgid: *u32, egid: *u32, sgid: *u32) usize {752pub fn getresgid(rgid: *gid_t, egid: *gid_t, sgid: *gid_t) usize {
739 if (@hasField(SYS, "getresgid32")) {753 if (@hasField(SYS, "getresgid32")) {
740 return syscall3(.getresgid32, @ptrToInt(rgid), @ptrToInt(egid), @ptrToInt(sgid));754 return syscall3(.getresgid32, @ptrToInt(rgid), @ptrToInt(egid), @ptrToInt(sgid));
741 } else {755 } else {
...@@ -743,7 +757,7 @@ pub fn getresgid(rgid: *u32, egid: *u32, sgid: *u32) usize {...@@ -743,7 +757,7 @@ pub fn getresgid(rgid: *u32, egid: *u32, sgid: *u32) usize {
743 }757 }
744}758}
745759
746pub fn setresuid(ruid: u32, euid: u32, suid: u32) usize {760pub fn setresuid(ruid: uid_t, euid: uid_t, suid: uid_t) usize {
747 if (@hasField(SYS, "setresuid32")) {761 if (@hasField(SYS, "setresuid32")) {
748 return syscall3(.setresuid32, ruid, euid, suid);762 return syscall3(.setresuid32, ruid, euid, suid);
749 } else {763 } else {
...@@ -751,7 +765,7 @@ pub fn setresuid(ruid: u32, euid: u32, suid: u32) usize {...@@ -751,7 +765,7 @@ pub fn setresuid(ruid: u32, euid: u32, suid: u32) usize {
751 }765 }
752}766}
753767
754pub fn setresgid(rgid: u32, egid: u32, sgid: u32) usize {768pub fn setresgid(rgid: gid_t, egid: gid_t, sgid: gid_t) usize {
755 if (@hasField(SYS, "setresgid32")) {769 if (@hasField(SYS, "setresgid32")) {
756 return syscall3(.setresgid32, rgid, egid, sgid);770 return syscall3(.setresgid32, rgid, egid, sgid);
757 } else {771 } else {
...@@ -759,7 +773,7 @@ pub fn setresgid(rgid: u32, egid: u32, sgid: u32) usize {...@@ -759,7 +773,7 @@ pub fn setresgid(rgid: u32, egid: u32, sgid: u32) usize {
759 }773 }
760}774}
761775
762pub fn getgroups(size: usize, list: *u32) usize {776pub fn getgroups(size: usize, list: *gid_t) usize {
763 if (@hasField(SYS, "getgroups32")) {777 if (@hasField(SYS, "getgroups32")) {
764 return syscall2(.getgroups32, size, @ptrToInt(list));778 return syscall2(.getgroups32, size, @ptrToInt(list));
765 } else {779 } else {
...@@ -767,7 +781,7 @@ pub fn getgroups(size: usize, list: *u32) usize {...@@ -767,7 +781,7 @@ pub fn getgroups(size: usize, list: *u32) usize {
767 }781 }
768}782}
769783
770pub fn setgroups(size: usize, list: *const u32) usize {784pub fn setgroups(size: usize, list: *const gid_t) usize {
771 if (@hasField(SYS, "setgroups32")) {785 if (@hasField(SYS, "setgroups32")) {
772 return syscall2(.setgroups32, size, @ptrToInt(list));786 return syscall2(.setgroups32, size, @ptrToInt(list));
773 } else {787 } else {
...@@ -1226,6 +1240,22 @@ pub fn bpf(cmd: BPF.Cmd, attr: *BPF.Attr, size: u32) usize {...@@ -1226,6 +1240,22 @@ pub fn bpf(cmd: BPF.Cmd, attr: *BPF.Attr, size: u32) usize {
1226 return syscall3(.bpf, @enumToInt(cmd), @ptrToInt(attr), size);1240 return syscall3(.bpf, @enumToInt(cmd), @ptrToInt(attr), size);
1227}1241}
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
1229test "" {1259test "" {
1230 if (builtin.os.tag == .linux) {1260 if (builtin.os.tag == .linux) {
1231 _ = @import("linux/test.zig");1261 _ = @import("linux/test.zig");
lib/std/os/test.zig+36
...@@ -555,3 +555,39 @@ test "signalfd" {...@@ -555,3 +555,39 @@ test "signalfd" {
555 return error.SkipZigTest;555 return error.SkipZigTest;
556 _ = std.os.signalfd;556 _ = std.os.signalfd;
557}557}
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...@@ -287,3 +287,5 @@ pub extern "kernel32" fn K32GetWsChangesEx(hProcess: HANDLE, lpWatchInfoEx: PPSA
287pub extern "kernel32" fn K32InitializeProcessForWsWatch(hProcess: HANDLE) callconv(.Stdcall) BOOL;287pub extern "kernel32" fn K32InitializeProcessForWsWatch(hProcess: HANDLE) callconv(.Stdcall) BOOL;
288pub extern "kernel32" fn K32QueryWorkingSet(hProcess: HANDLE, pv: PVOID, cb: DWORD) callconv(.Stdcall) BOOL;288pub extern "kernel32" fn K32QueryWorkingSet(hProcess: HANDLE, pv: PVOID, cb: DWORD) callconv(.Stdcall) BOOL;
289pub extern "kernel32" fn K32QueryWorkingSetEx(hProcess: HANDLE, pv: PVOID, cb: DWORD) callconv(.Stdcall) BOOL;289pub 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...@@ -578,8 +578,8 @@ fn testWindowsCmdLine(input_cmd_line: [*]const u8, expected_args: []const []cons
578}578}
579579
580pub const UserInfo = struct {580pub const UserInfo = struct {
581 uid: u32,581 uid: os.uid_t,
582 gid: u32,582 gid: os.gid_t,
583};583};
584584
585/// POSIX function which gets a uid from username.585/// POSIX function which gets a uid from username.
...@@ -607,8 +607,8 @@ pub fn posixGetUserInfo(name: []const u8) !UserInfo {...@@ -607,8 +607,8 @@ pub fn posixGetUserInfo(name: []const u8) !UserInfo {
607 var buf: [std.mem.page_size]u8 = undefined;607 var buf: [std.mem.page_size]u8 = undefined;
608 var name_index: usize = 0;608 var name_index: usize = 0;
609 var state = State.Start;609 var state = State.Start;
610 var uid: u32 = 0;610 var uid: os.uid_t = 0;
611 var gid: u32 = 0;611 var gid: os.gid_t = 0;
612612
613 while (true) {613 while (true) {
614 const amt_read = try reader.read(buf[0..]);614 const amt_read = try reader.read(buf[0..]);
lib/std/progress.zig+3-3
...@@ -197,7 +197,7 @@ pub const Progress = struct {...@@ -197,7 +197,7 @@ pub const Progress = struct {
197 var maybe_node: ?*Node = &self.root;197 var maybe_node: ?*Node = &self.root;
198 while (maybe_node) |node| {198 while (maybe_node) |node| {
199 if (need_ellipse) {199 if (need_ellipse) {
200 self.bufWrite(&end, "...", .{});200 self.bufWrite(&end, "... ", .{});
201 }201 }
202 need_ellipse = false;202 need_ellipse = false;
203 if (node.name.len != 0 or node.estimated_total_items != null) {203 if (node.name.len != 0 or node.estimated_total_items != null) {
...@@ -218,7 +218,7 @@ pub const Progress = struct {...@@ -218,7 +218,7 @@ pub const Progress = struct {
218 maybe_node = node.recently_updated_child;218 maybe_node = node.recently_updated_child;
219 }219 }
220 if (need_ellipse) {220 if (need_ellipse) {
221 self.bufWrite(&end, "...", .{});221 self.bufWrite(&end, "... ", .{});
222 }222 }
223 }223 }
224224
...@@ -253,7 +253,7 @@ pub const Progress = struct {...@@ -253,7 +253,7 @@ pub const Progress = struct {
253 const bytes_needed_for_esc_codes_at_end = if (std.builtin.os.tag == .windows) 0 else 11;253 const bytes_needed_for_esc_codes_at_end = if (std.builtin.os.tag == .windows) 0 else 11;
254 const max_end = self.output_buffer.len - bytes_needed_for_esc_codes_at_end;254 const max_end = self.output_buffer.len - bytes_needed_for_esc_codes_at_end;
255 if (end.* > max_end) {255 if (end.* > max_end) {
256 const suffix = "...";256 const suffix = "... ";
257 self.columns_written = self.columns_written - (end.* - max_end) + suffix.len;257 self.columns_written = self.columns_written - (end.* - max_end) + suffix.len;
258 std.mem.copy(u8, self.output_buffer[max_end..], suffix);258 std.mem.copy(u8, self.output_buffer[max_end..], suffix);
259 end.* = max_end + suffix.len;259 end.* = max_end + suffix.len;
lib/std/special/test_runner.zig+1-1
...@@ -40,7 +40,7 @@ pub fn main() anyerror!void {...@@ -40,7 +40,7 @@ pub fn main() anyerror!void {
40 test_node.activate();40 test_node.activate();
41 progress.refresh();41 progress.refresh();
42 if (progress.terminal == null) {42 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 });
44 }44 }
45 const result = if (test_fn.async_frame_size) |size| switch (io_mode) {45 const result = if (test_fn.async_frame_size) |size| switch (io_mode) {
46 .evented => blk: {46 .evented => blk: {
lib/std/std.zig+7
...@@ -3,11 +3,15 @@...@@ -3,11 +3,15 @@
3// This file is part of [zig](https://ziglang.org/), which is MIT licensed.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 copies4// The MIT license requires this copyright notice to be included in all copies
5// and substantial portions of the software.5// and substantial portions of the software.
6pub const ArrayHashMap = array_hash_map.ArrayHashMap;
7pub const ArrayHashMapUnmanaged = array_hash_map.ArrayHashMapUnmanaged;
6pub const ArrayList = @import("array_list.zig").ArrayList;8pub const ArrayList = @import("array_list.zig").ArrayList;
7pub const ArrayListAligned = @import("array_list.zig").ArrayListAligned;9pub const ArrayListAligned = @import("array_list.zig").ArrayListAligned;
8pub const ArrayListAlignedUnmanaged = @import("array_list.zig").ArrayListAlignedUnmanaged;10pub const ArrayListAlignedUnmanaged = @import("array_list.zig").ArrayListAlignedUnmanaged;
9pub const ArrayListSentineled = @import("array_list_sentineled.zig").ArrayListSentineled;11pub const ArrayListSentineled = @import("array_list_sentineled.zig").ArrayListSentineled;
10pub const ArrayListUnmanaged = @import("array_list.zig").ArrayListUnmanaged;12pub const ArrayListUnmanaged = @import("array_list.zig").ArrayListUnmanaged;
13pub const AutoArrayHashMap = array_hash_map.AutoArrayHashMap;
14pub const AutoArrayHashMapUnmanaged = array_hash_map.AutoArrayHashMapUnmanaged;
11pub const AutoHashMap = hash_map.AutoHashMap;15pub const AutoHashMap = hash_map.AutoHashMap;
12pub const AutoHashMapUnmanaged = hash_map.AutoHashMapUnmanaged;16pub const AutoHashMapUnmanaged = hash_map.AutoHashMapUnmanaged;
13pub const BloomFilter = @import("bloom_filter.zig").BloomFilter;17pub const BloomFilter = @import("bloom_filter.zig").BloomFilter;
...@@ -32,10 +36,13 @@ pub const SinglyLinkedList = @import("linked_list.zig").SinglyLinkedList;...@@ -32,10 +36,13 @@ pub const SinglyLinkedList = @import("linked_list.zig").SinglyLinkedList;
32pub const SpinLock = @import("spinlock.zig").SpinLock;36pub const SpinLock = @import("spinlock.zig").SpinLock;
33pub const StringHashMap = hash_map.StringHashMap;37pub const StringHashMap = hash_map.StringHashMap;
34pub const StringHashMapUnmanaged = hash_map.StringHashMapUnmanaged;38pub const StringHashMapUnmanaged = hash_map.StringHashMapUnmanaged;
39pub const StringArrayHashMap = array_hash_map.StringArrayHashMap;
40pub const StringArrayHashMapUnmanaged = array_hash_map.StringArrayHashMapUnmanaged;
35pub const TailQueue = @import("linked_list.zig").TailQueue;41pub const TailQueue = @import("linked_list.zig").TailQueue;
36pub const Target = @import("target.zig").Target;42pub const Target = @import("target.zig").Target;
37pub const Thread = @import("thread.zig").Thread;43pub const Thread = @import("thread.zig").Thread;
3844
45pub const array_hash_map = @import("array_hash_map.zig");
39pub const atomic = @import("atomic.zig");46pub const atomic = @import("atomic.zig");
40pub const base64 = @import("base64.zig");47pub const base64 = @import("base64.zig");
41pub const build = @import("build.zig");48pub const build = @import("build.zig");
lib/std/target.zig+1-1
...@@ -101,7 +101,7 @@ pub const Target = struct {...@@ -101,7 +101,7 @@ pub const Target = struct {
101101
102 /// Latest Windows version that the Zig Standard Library is aware of102 /// Latest Windows version that the Zig Standard Library is aware of
103 pub const latest = WindowsVersion.win10_20h1;103 pub const latest = WindowsVersion.win10_20h1;
104 104
105 pub const Range = struct {105 pub const Range = struct {
106 min: WindowsVersion,106 min: WindowsVersion,
107 max: WindowsVersion,107 max: WindowsVersion,
lib/std/zig/parser_test.zig+114-6
...@@ -615,6 +615,17 @@ test "zig fmt: infix operator and then multiline string literal" {...@@ -615,6 +615,17 @@ test "zig fmt: infix operator and then multiline string literal" {
615 );615 );
616}616}
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
618test "zig fmt: C pointers" {629test "zig fmt: C pointers" {
619 try testCanonical(630 try testCanonical(
620 \\const Ptr = [*c]i32;631 \\const Ptr = [*c]i32;
...@@ -885,6 +896,28 @@ test "zig fmt: 2nd arg multiline string" {...@@ -885,6 +896,28 @@ test "zig fmt: 2nd arg multiline string" {
885 );896 );
886}897}
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
888test "zig fmt: if condition wraps" {921test "zig fmt: if condition wraps" {
889 try testTransform(922 try testTransform(
890 \\comptime {923 \\comptime {
...@@ -915,6 +948,11 @@ test "zig fmt: if condition wraps" {...@@ -915,6 +948,11 @@ test "zig fmt: if condition wraps" {
915 \\ var a = if (a) |*f| x: {948 \\ var a = if (a) |*f| x: {
916 \\ break :x &a.b;949 \\ break :x &a.b;
917 \\ } else |err| err;950 \\ } else |err| err;
951 \\ var a = if (cond and
952 \\ cond) |*f|
953 \\ x: {
954 \\ break :x &a.b;
955 \\ } else |err| err;
918 \\}956 \\}
919 ,957 ,
920 \\comptime {958 \\comptime {
...@@ -951,6 +989,35 @@ test "zig fmt: if condition wraps" {...@@ -951,6 +989,35 @@ test "zig fmt: if condition wraps" {
951 \\ var a = if (a) |*f| x: {989 \\ var a = if (a) |*f| x: {
952 \\ break :x &a.b;990 \\ break :x &a.b;
953 \\ } else |err| err;991 \\ } 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 \\ }
954 \\}1021 \\}
955 \\1022 \\
956 );1023 );
...@@ -977,6 +1044,18 @@ test "zig fmt: if condition has line break but must not wrap" {...@@ -977,6 +1044,18 @@ test "zig fmt: if condition has line break but must not wrap" {
977 );1044 );
978}1045}
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
980test "zig fmt: same-line doc comment on variable declaration" {1059test "zig fmt: same-line doc comment on variable declaration" {
981 try testTransform(1060 try testTransform(
982 \\pub const MAP_ANONYMOUS = 0x1000; /// allocated from memory, swap space1061 \\pub const MAP_ANONYMOUS = 0x1000; /// allocated from memory, swap space
...@@ -1228,7 +1307,7 @@ test "zig fmt: array literal with hint" {...@@ -1228,7 +1307,7 @@ test "zig fmt: array literal with hint" {
1228 \\const a = []u8{1307 \\const a = []u8{
1229 \\ 1, 2,1308 \\ 1, 2,
1230 \\ 3, //1309 \\ 3, //
1231 \\ 4,1310 \\ 4,
1232 \\ 5, 6,1311 \\ 5, 6,
1233 \\ 7,1312 \\ 7,
1234 \\};1313 \\};
...@@ -1293,7 +1372,7 @@ test "zig fmt: multiline string parameter in fn call with trailing comma" {...@@ -1293,7 +1372,7 @@ test "zig fmt: multiline string parameter in fn call with trailing comma" {
1293 \\ \\ZIG_C_HEADER_FILES {}1372 \\ \\ZIG_C_HEADER_FILES {}
1294 \\ \\ZIG_DIA_GUIDS_LIB {}1373 \\ \\ZIG_DIA_GUIDS_LIB {}
1295 \\ \\1374 \\ \\
1296 \\ ,1375 \\ ,
1297 \\ std.cstr.toSliceConst(c.ZIG_CMAKE_BINARY_DIR),1376 \\ std.cstr.toSliceConst(c.ZIG_CMAKE_BINARY_DIR),
1298 \\ std.cstr.toSliceConst(c.ZIG_CXX_COMPILER),1377 \\ std.cstr.toSliceConst(c.ZIG_CXX_COMPILER),
1299 \\ std.cstr.toSliceConst(c.ZIG_DIA_GUIDS_LIB),1378 \\ std.cstr.toSliceConst(c.ZIG_DIA_GUIDS_LIB),
...@@ -2885,20 +2964,20 @@ test "zig fmt: multiline string in array" {...@@ -2885,20 +2964,20 @@ test "zig fmt: multiline string in array" {
2885 try testCanonical(2964 try testCanonical(
2886 \\const Foo = [][]const u8{2965 \\const Foo = [][]const u8{
2887 \\ \\aaa2966 \\ \\aaa
2888 \\,2967 \\ ,
2889 \\ \\bbb2968 \\ \\bbb
2890 \\};2969 \\};
2891 \\2970 \\
2892 \\fn bar() void {2971 \\fn bar() void {
2893 \\ const Foo = [][]const u8{2972 \\ const Foo = [][]const u8{
2894 \\ \\aaa2973 \\ \\aaa
2895 \\ ,2974 \\ ,
2896 \\ \\bbb2975 \\ \\bbb
2897 \\ };2976 \\ };
2898 \\ const Bar = [][]const u8{ // comment here2977 \\ const Bar = [][]const u8{ // comment here
2899 \\ \\aaa2978 \\ \\aaa
2900 \\ \\2979 \\ \\
2901 \\ , // and another comment can go here2980 \\ , // and another comment can go here
2902 \\ \\bbb2981 \\ \\bbb
2903 \\ };2982 \\ };
2904 \\}2983 \\}
...@@ -3214,6 +3293,34 @@ test "zig fmt: C var args" {...@@ -3214,6 +3293,34 @@ test "zig fmt: C var args" {
3214 );3293 );
3215}3294}
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
3217const std = @import("std");3324const std = @import("std");
3218const mem = std.mem;3325const mem = std.mem;
3219const warn = std.debug.warn;3326const warn = std.debug.warn;
...@@ -3256,7 +3363,8 @@ fn testParse(source: []const u8, allocator: *mem.Allocator, anything_changed: *b...@@ -3256,7 +3363,8 @@ fn testParse(source: []const u8, allocator: *mem.Allocator, anything_changed: *b
3256 var buffer = std.ArrayList(u8).init(allocator);3363 var buffer = std.ArrayList(u8).init(allocator);
3257 errdefer buffer.deinit();3364 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);
3260 return buffer.toOwnedSlice();3368 return buffer.toOwnedSlice();
3261}3369}
3262fn testTransform(source: []const u8, expected_source: []const u8) !void {3370fn testTransform(source: []const u8, expected_source: []const u8) !void {
lib/std/zig/render.zig+763-912
...@@ -6,10 +6,12 @@...@@ -6,10 +6,12 @@
6const std = @import("../std.zig");6const std = @import("../std.zig");
7const assert = std.debug.assert;7const assert = std.debug.assert;
8const mem = std.mem;8const mem = std.mem;
9const meta = std.meta;
9const ast = std.zig.ast;10const ast = std.zig.ast;
10const Token = std.zig.Token;11const Token = std.zig.Token;
1112
12const indent_delta = 4;13const indent_delta = 4;
14const asm_indent_delta = 2;
1315
14pub const Error = error{16pub const Error = error{
15 /// Ran out of memory allocating call stack frames to complete rendering.17 /// 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...@@ -21,70 +23,32 @@ pub fn render(allocator: *mem.Allocator, stream: anytype, tree: *ast.Tree) (@Typ
21 // cannot render an invalid tree23 // cannot render an invalid tree
22 std.debug.assert(tree.errors.len == 0);24 std.debug.assert(tree.errors.len == 0);
2325
24 // make a passthrough stream that checks whether something changed26 var change_detection_stream = std.io.changeDetectionStream(tree.source, stream);
25 const MyStream = struct {27 var auto_indenting_stream = std.io.autoIndentingStream(indent_delta, change_detection_stream.writer());
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 };
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) {31 return change_detection_stream.changeDetected();
64 my_stream.anything_changed = true;
65 }
66
67 return my_stream.anything_changed;
68}32}
6933
70fn renderRoot(34fn renderRoot(
71 allocator: *mem.Allocator,35 allocator: *mem.Allocator,
72 stream: anytype,36 ais: anytype,
73 tree: *ast.Tree,37 tree: *ast.Tree,
74) (@TypeOf(stream).Error || Error)!void {38) (@TypeOf(ais.*).Error || Error)!void {
39
75 // render all the line comments at the beginning of the file40 // render all the line comments at the beginning of the file
76 for (tree.token_ids) |token_id, i| {41 for (tree.token_ids) |token_id, i| {
77 if (token_id != .LineComment) break;42 if (token_id != .LineComment) break;
78 const token_loc = tree.token_locs[i];43 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), " ")});
80 const next_token = tree.token_locs[i + 1];45 const next_token = tree.token_locs[i + 1];
81 const loc = tree.tokenLocationLoc(token_loc.end, next_token);46 const loc = tree.tokenLocationLoc(token_loc.end, next_token);
82 if (loc.line >= 2) {47 if (loc.line >= 2) {
83 try stream.writeByte('\n');48 try ais.insertNewline();
84 }49 }
85 }50 }
8651
87 var start_col: usize = 0;
88 var decl_i: ast.NodeIndex = 0;52 var decl_i: ast.NodeIndex = 0;
89 const root_decls = tree.root_node.decls();53 const root_decls = tree.root_node.decls();
9054
...@@ -145,7 +109,7 @@ fn renderRoot(...@@ -145,7 +109,7 @@ fn renderRoot(
145 // If there's no next reformatted `decl`, just copy the109 // If there's no next reformatted `decl`, just copy the
146 // remaining input tokens and bail out.110 // remaining input tokens and bail out.
147 const start = tree.token_locs[copy_start_token_index].start;111 const start = tree.token_locs[copy_start_token_index].start;
148 try copyFixingWhitespace(stream, tree.source[start..]);112 try copyFixingWhitespace(ais, tree.source[start..]);
149 return;113 return;
150 }114 }
151 decl = root_decls[decl_i];115 decl = root_decls[decl_i];
...@@ -186,26 +150,25 @@ fn renderRoot(...@@ -186,26 +150,25 @@ fn renderRoot(
186150
187 const start = tree.token_locs[copy_start_token_index].start;151 const start = tree.token_locs[copy_start_token_index].start;
188 const end = tree.token_locs[copy_end_token_index].start;152 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]);
190 }154 }
191155
192 try renderTopLevelDecl(allocator, stream, tree, 0, &start_col, decl);156 try renderTopLevelDecl(allocator, ais, tree, decl);
193 decl_i += 1;157 decl_i += 1;
194 if (decl_i >= root_decls.len) return;158 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]);
196 }160 }
197}161}
198162
199fn renderExtraNewline(tree: *ast.Tree, stream: anytype, start_col: *usize, node: *ast.Node) @TypeOf(stream).Error!void {163fn renderExtraNewline(tree: *ast.Tree, ais: anytype, node: *ast.Node) @TypeOf(ais.*).Error!void {
200 return renderExtraNewlineToken(tree, stream, start_col, node.firstToken());164 return renderExtraNewlineToken(tree, ais, node.firstToken());
201}165}
202166
203fn renderExtraNewlineToken(167fn renderExtraNewlineToken(
204 tree: *ast.Tree,168 tree: *ast.Tree,
205 stream: anytype,169 ais: anytype,
206 start_col: *usize,
207 first_token: ast.TokenIndex,170 first_token: ast.TokenIndex,
208) @TypeOf(stream).Error!void {171) @TypeOf(ais.*).Error!void {
209 var prev_token = first_token;172 var prev_token = first_token;
210 if (prev_token == 0) return;173 if (prev_token == 0) return;
211 var newline_threshold: usize = 2;174 var newline_threshold: usize = 2;
...@@ -218,28 +181,27 @@ fn renderExtraNewlineToken(...@@ -218,28 +181,27 @@ fn renderExtraNewlineToken(
218 const prev_token_end = tree.token_locs[prev_token - 1].end;181 const prev_token_end = tree.token_locs[prev_token - 1].end;
219 const loc = tree.tokenLocation(prev_token_end, first_token);182 const loc = tree.tokenLocation(prev_token_end, first_token);
220 if (loc.line >= newline_threshold) {183 if (loc.line >= newline_threshold) {
221 try stream.writeByte('\n');184 try ais.insertNewline();
222 start_col.* = 0;
223 }185 }
224}186}
225187
226fn renderTopLevelDecl(allocator: *mem.Allocator, stream: anytype, tree: *ast.Tree, indent: usize, start_col: *usize, decl: *ast.Node) (@TypeOf(stream).Error || Error)!void {188fn renderTopLevelDecl(allocator: *mem.Allocator, ais: anytype, tree: *ast.Tree, decl: *ast.Node) (@TypeOf(ais.*).Error || Error)!void {
227 try renderContainerDecl(allocator, stream, tree, indent, start_col, decl, .Newline);189 try renderContainerDecl(allocator, ais, tree, decl, .Newline);
228}190}
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 {
231 switch (decl.tag) {193 switch (decl.tag) {
232 .FnProto => {194 .FnProto => {
233 const fn_proto = @fieldParentPtr(ast.Node.FnProto, "base", decl);195 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
237 if (fn_proto.getBodyNode()) |body_node| {199 if (fn_proto.getBodyNode()) |body_node| {
238 try renderExpression(allocator, stream, tree, indent, start_col, decl, .Space);200 try renderExpression(allocator, ais, tree, decl, .Space);
239 try renderExpression(allocator, stream, tree, indent, start_col, body_node, space);201 try renderExpression(allocator, ais, tree, body_node, space);
240 } else {202 } else {
241 try renderExpression(allocator, stream, tree, indent, start_col, decl, .None);203 try renderExpression(allocator, ais, tree, decl, .None);
242 try renderToken(tree, stream, tree.nextToken(decl.lastToken()), indent, start_col, space);204 try renderToken(tree, ais, tree.nextToken(decl.lastToken()), space);
243 }205 }
244 },206 },
245207
...@@ -247,35 +209,35 @@ fn renderContainerDecl(allocator: *mem.Allocator, stream: anytype, tree: *ast.Tr...@@ -247,35 +209,35 @@ fn renderContainerDecl(allocator: *mem.Allocator, stream: anytype, tree: *ast.Tr
247 const use_decl = @fieldParentPtr(ast.Node.Use, "base", decl);209 const use_decl = @fieldParentPtr(ast.Node.Use, "base", decl);
248210
249 if (use_decl.visib_token) |visib_token| {211 if (use_decl.visib_token) |visib_token| {
250 try renderToken(tree, stream, visib_token, indent, start_col, .Space); // pub212 try renderToken(tree, ais, visib_token, .Space); // pub
251 }213 }
252 try renderToken(tree, stream, use_decl.use_token, indent, start_col, .Space); // usingnamespace214 try renderToken(tree, ais, use_decl.use_token, .Space); // usingnamespace
253 try renderExpression(allocator, stream, tree, indent, start_col, use_decl.expr, .None);215 try renderExpression(allocator, ais, tree, use_decl.expr, .None);
254 try renderToken(tree, stream, use_decl.semicolon_token, indent, start_col, space); // ;216 try renderToken(tree, ais, use_decl.semicolon_token, space); // ;
255 },217 },
256218
257 .VarDecl => {219 .VarDecl => {
258 const var_decl = @fieldParentPtr(ast.Node.VarDecl, "base", decl);220 const var_decl = @fieldParentPtr(ast.Node.VarDecl, "base", decl);
259221
260 try renderDocComments(tree, stream, var_decl, var_decl.getDocComments(), indent, start_col);222 try renderDocComments(tree, ais, var_decl, var_decl.getDocComments());
261 try renderVarDecl(allocator, stream, tree, indent, start_col, var_decl);223 try renderVarDecl(allocator, ais, tree, var_decl);
262 },224 },
263225
264 .TestDecl => {226 .TestDecl => {
265 const test_decl = @fieldParentPtr(ast.Node.TestDecl, "base", decl);227 const test_decl = @fieldParentPtr(ast.Node.TestDecl, "base", decl);
266228
267 try renderDocComments(tree, stream, test_decl, test_decl.doc_comments, indent, start_col);229 try renderDocComments(tree, ais, test_decl, test_decl.doc_comments);
268 try renderToken(tree, stream, test_decl.test_token, indent, start_col, .Space);230 try renderToken(tree, ais, test_decl.test_token, .Space);
269 try renderExpression(allocator, stream, tree, indent, start_col, test_decl.name, .Space);231 try renderExpression(allocator, ais, tree, test_decl.name, .Space);
270 try renderExpression(allocator, stream, tree, indent, start_col, test_decl.body_node, space);232 try renderExpression(allocator, ais, tree, test_decl.body_node, space);
271 },233 },
272234
273 .ContainerField => {235 .ContainerField => {
274 const field = @fieldParentPtr(ast.Node.ContainerField, "base", decl);236 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);
277 if (field.comptime_token) |t| {239 if (field.comptime_token) |t| {
278 try renderToken(tree, stream, t, indent, start_col, .Space); // comptime240 try renderToken(tree, ais, t, .Space); // comptime
279 }241 }
280242
281 const src_has_trailing_comma = blk: {243 const src_has_trailing_comma = blk: {
...@@ -288,68 +250,67 @@ fn renderContainerDecl(allocator: *mem.Allocator, stream: anytype, tree: *ast.Tr...@@ -288,68 +250,67 @@ fn renderContainerDecl(allocator: *mem.Allocator, stream: anytype, tree: *ast.Tr
288 const last_token_space: Space = if (src_has_trailing_comma) .None else space;250 const last_token_space: Space = if (src_has_trailing_comma) .None else space;
289251
290 if (field.type_expr == null and field.value_expr == null) {252 if (field.type_expr == null and field.value_expr == null) {
291 try renderToken(tree, stream, field.name_token, indent, start_col, last_token_space); // name253 try renderToken(tree, ais, field.name_token, last_token_space); // name
292 } else if (field.type_expr != null and field.value_expr == null) {254 } else if (field.type_expr != null and field.value_expr == null) {
293 try renderToken(tree, stream, field.name_token, indent, start_col, .None); // name255 try renderToken(tree, ais, field.name_token, .None); // name
294 try renderToken(tree, stream, tree.nextToken(field.name_token), indent, start_col, .Space); // :256 try renderToken(tree, ais, tree.nextToken(field.name_token), .Space); // :
295257
296 if (field.align_expr) |align_value_expr| {258 if (field.align_expr) |align_value_expr| {
297 try renderExpression(allocator, stream, tree, indent, start_col, field.type_expr.?, .Space); // type259 try renderExpression(allocator, ais, tree, field.type_expr.?, .Space); // type
298 const lparen_token = tree.prevToken(align_value_expr.firstToken());260 const lparen_token = tree.prevToken(align_value_expr.firstToken());
299 const align_kw = tree.prevToken(lparen_token);261 const align_kw = tree.prevToken(lparen_token);
300 const rparen_token = tree.nextToken(align_value_expr.lastToken());262 const rparen_token = tree.nextToken(align_value_expr.lastToken());
301 try renderToken(tree, stream, align_kw, indent, start_col, .None); // align263 try renderToken(tree, ais, align_kw, .None); // align
302 try renderToken(tree, stream, lparen_token, indent, start_col, .None); // (264 try renderToken(tree, ais, lparen_token, .None); // (
303 try renderExpression(allocator, stream, tree, indent, start_col, align_value_expr, .None); // alignment265 try renderExpression(allocator, ais, tree, align_value_expr, .None); // alignment
304 try renderToken(tree, stream, rparen_token, indent, start_col, last_token_space); // )266 try renderToken(tree, ais, rparen_token, last_token_space); // )
305 } else {267 } else {
306 try renderExpression(allocator, stream, tree, indent, start_col, field.type_expr.?, last_token_space); // type268 try renderExpression(allocator, ais, tree, field.type_expr.?, last_token_space); // type
307 }269 }
308 } else if (field.type_expr == null and field.value_expr != null) {270 } else if (field.type_expr == null and field.value_expr != null) {
309 try renderToken(tree, stream, field.name_token, indent, start_col, .Space); // name271 try renderToken(tree, ais, field.name_token, .Space); // name
310 try renderToken(tree, stream, tree.nextToken(field.name_token), indent, start_col, .Space); // =272 try renderToken(tree, ais, tree.nextToken(field.name_token), .Space); // =
311 try renderExpression(allocator, stream, tree, indent, start_col, field.value_expr.?, last_token_space); // value273 try renderExpression(allocator, ais, tree, field.value_expr.?, last_token_space); // value
312 } else {274 } else {
313 try renderToken(tree, stream, field.name_token, indent, start_col, .None); // name275 try renderToken(tree, ais, field.name_token, .None); // name
314 try renderToken(tree, stream, tree.nextToken(field.name_token), indent, start_col, .Space); // :276 try renderToken(tree, ais, tree.nextToken(field.name_token), .Space); // :
315277
316 if (field.align_expr) |align_value_expr| {278 if (field.align_expr) |align_value_expr| {
317 try renderExpression(allocator, stream, tree, indent, start_col, field.type_expr.?, .Space); // type279 try renderExpression(allocator, ais, tree, field.type_expr.?, .Space); // type
318 const lparen_token = tree.prevToken(align_value_expr.firstToken());280 const lparen_token = tree.prevToken(align_value_expr.firstToken());
319 const align_kw = tree.prevToken(lparen_token);281 const align_kw = tree.prevToken(lparen_token);
320 const rparen_token = tree.nextToken(align_value_expr.lastToken());282 const rparen_token = tree.nextToken(align_value_expr.lastToken());
321 try renderToken(tree, stream, align_kw, indent, start_col, .None); // align283 try renderToken(tree, ais, align_kw, .None); // align
322 try renderToken(tree, stream, lparen_token, indent, start_col, .None); // (284 try renderToken(tree, ais, lparen_token, .None); // (
323 try renderExpression(allocator, stream, tree, indent, start_col, align_value_expr, .None); // alignment285 try renderExpression(allocator, ais, tree, align_value_expr, .None); // alignment
324 try renderToken(tree, stream, rparen_token, indent, start_col, .Space); // )286 try renderToken(tree, ais, rparen_token, .Space); // )
325 } else {287 } else {
326 try renderExpression(allocator, stream, tree, indent, start_col, field.type_expr.?, .Space); // type288 try renderExpression(allocator, ais, tree, field.type_expr.?, .Space); // type
327 }289 }
328 try renderToken(tree, stream, tree.prevToken(field.value_expr.?.firstToken()), indent, start_col, .Space); // =290 try renderToken(tree, ais, tree.prevToken(field.value_expr.?.firstToken()), .Space); // =
329 try renderExpression(allocator, stream, tree, indent, start_col, field.value_expr.?, last_token_space); // value291 try renderExpression(allocator, ais, tree, field.value_expr.?, last_token_space); // value
330 }292 }
331293
332 if (src_has_trailing_comma) {294 if (src_has_trailing_comma) {
333 const comma = tree.nextToken(field.lastToken());295 const comma = tree.nextToken(field.lastToken());
334 try renderToken(tree, stream, comma, indent, start_col, space);296 try renderToken(tree, ais, comma, space);
335 }297 }
336 },298 },
337299
338 .Comptime => {300 .Comptime => {
339 assert(!decl.requireSemiColon());301 assert(!decl.requireSemiColon());
340 try renderExpression(allocator, stream, tree, indent, start_col, decl, space);302 try renderExpression(allocator, ais, tree, decl, space);
341 },303 },
342304
343 .DocComment => {305 .DocComment => {
344 const comment = @fieldParentPtr(ast.Node.DocComment, "base", decl);306 const comment = @fieldParentPtr(ast.Node.DocComment, "base", decl);
345 const kind = tree.token_ids[comment.first_line];307 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);
347 var tok_i = comment.first_line + 1;309 var tok_i = comment.first_line + 1;
348 while (true) : (tok_i += 1) {310 while (true) : (tok_i += 1) {
349 const tok_id = tree.token_ids[tok_i];311 const tok_id = tree.token_ids[tok_i];
350 if (tok_id == kind) {312 if (tok_id == kind) {
351 try stream.writeByteNTimes(' ', indent);313 try renderToken(tree, ais, tok_i, .Newline);
352 try renderToken(tree, stream, tok_i, indent, start_col, .Newline);
353 } else if (tok_id == .LineComment) {314 } else if (tok_id == .LineComment) {
354 continue;315 continue;
355 } else {316 } else {
...@@ -363,13 +324,11 @@ fn renderContainerDecl(allocator: *mem.Allocator, stream: anytype, tree: *ast.Tr...@@ -363,13 +324,11 @@ fn renderContainerDecl(allocator: *mem.Allocator, stream: anytype, tree: *ast.Tr
363324
364fn renderExpression(325fn renderExpression(
365 allocator: *mem.Allocator,326 allocator: *mem.Allocator,
366 stream: anytype,327 ais: anytype,
367 tree: *ast.Tree,328 tree: *ast.Tree,
368 indent: usize,
369 start_col: *usize,
370 base: *ast.Node,329 base: *ast.Node,
371 space: Space,330 space: Space,
372) (@TypeOf(stream).Error || Error)!void {331) (@TypeOf(ais.*).Error || Error)!void {
373 switch (base.tag) {332 switch (base.tag) {
374 .Identifier,333 .Identifier,
375 .IntegerLiteral,334 .IntegerLiteral,
...@@ -383,18 +342,18 @@ fn renderExpression(...@@ -383,18 +342,18 @@ fn renderExpression(
383 .UndefinedLiteral,342 .UndefinedLiteral,
384 => {343 => {
385 const casted_node = base.cast(ast.Node.OneToken).?;344 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);
387 },346 },
388347
389 .AnyType => {348 .AnyType => {
390 const any_type = base.castTag(.AnyType).?;349 const any_type = base.castTag(.AnyType).?;
391 if (mem.eql(u8, tree.tokenSlice(any_type.token), "var")) {350 if (mem.eql(u8, tree.tokenSlice(any_type.token), "var")) {
392 // TODO remove in next release cycle351 // TODO remove in next release cycle
393 try stream.writeAll("anytype");352 try ais.writer().writeAll("anytype");
394 if (space == .Comma) try stream.writeAll(",\n");353 if (space == .Comma) try ais.writer().writeAll(",\n");
395 return;354 return;
396 }355 }
397 return renderToken(tree, stream, any_type.token, indent, start_col, space);356 return renderToken(tree, ais, any_type.token, space);
398 },357 },
399358
400 .Block, .LabeledBlock => {359 .Block, .LabeledBlock => {
...@@ -424,65 +383,65 @@ fn renderExpression(...@@ -424,65 +383,65 @@ fn renderExpression(
424 };383 };
425384
426 if (block.label) |label| {385 if (block.label) |label| {
427 try renderToken(tree, stream, label, indent, start_col, Space.None);386 try renderToken(tree, ais, label, Space.None);
428 try renderToken(tree, stream, tree.nextToken(label), indent, start_col, Space.Space);387 try renderToken(tree, ais, tree.nextToken(label), Space.Space);
429 }388 }
430389
431 if (block.statements.len == 0) {390 if (block.statements.len == 0) {
432 try renderToken(tree, stream, block.lbrace, indent + indent_delta, start_col, Space.None);391 ais.pushIndentNextLine();
433 return renderToken(tree, stream, block.rbrace, indent, start_col, space);392 defer ais.popIndent();
393 try renderToken(tree, ais, block.lbrace, Space.None);
434 } else {394 } else {
435 const block_indent = indent + indent_delta;395 ais.pushIndentNextLine();
436 try renderToken(tree, stream, block.lbrace, block_indent, start_col, Space.Newline);396 defer ais.popIndent();
397
398 try renderToken(tree, ais, block.lbrace, Space.Newline);
437399
438 for (block.statements) |statement, i| {400 for (block.statements) |statement, i| {
439 try stream.writeByteNTimes(' ', block_indent);401 try renderStatement(allocator, ais, tree, statement);
440 try renderStatement(allocator, stream, tree, block_indent, start_col, statement);
441402
442 if (i + 1 < block.statements.len) {403 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]);
444 }405 }
445 }406 }
446
447 try stream.writeByteNTimes(' ', indent);
448 return renderToken(tree, stream, block.rbrace, indent, start_col, space);
449 }407 }
408 return renderToken(tree, ais, block.rbrace, space);
450 },409 },
451410
452 .Defer => {411 .Defer => {
453 const defer_node = @fieldParentPtr(ast.Node.Defer, "base", base);412 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);
456 if (defer_node.payload) |payload| {415 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);
458 }417 }
459 return renderExpression(allocator, stream, tree, indent, start_col, defer_node.expr, space);418 return renderExpression(allocator, ais, tree, defer_node.expr, space);
460 },419 },
461 .Comptime => {420 .Comptime => {
462 const comptime_node = @fieldParentPtr(ast.Node.Comptime, "base", base);421 const comptime_node = @fieldParentPtr(ast.Node.Comptime, "base", base);
463422
464 try renderToken(tree, stream, comptime_node.comptime_token, indent, start_col, Space.Space);423 try renderToken(tree, ais, comptime_node.comptime_token, Space.Space);
465 return renderExpression(allocator, stream, tree, indent, start_col, comptime_node.expr, space);424 return renderExpression(allocator, ais, tree, comptime_node.expr, space);
466 },425 },
467 .Nosuspend => {426 .Nosuspend => {
468 const nosuspend_node = @fieldParentPtr(ast.Node.Nosuspend, "base", base);427 const nosuspend_node = @fieldParentPtr(ast.Node.Nosuspend, "base", base);
469 if (mem.eql(u8, tree.tokenSlice(nosuspend_node.nosuspend_token), "noasync")) {428 if (mem.eql(u8, tree.tokenSlice(nosuspend_node.nosuspend_token), "noasync")) {
470 // TODO: remove this429 // TODO: remove this
471 try stream.writeAll("nosuspend ");430 try ais.writer().writeAll("nosuspend ");
472 } else {431 } 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);
474 }433 }
475 return renderExpression(allocator, stream, tree, indent, start_col, nosuspend_node.expr, space);434 return renderExpression(allocator, ais, tree, nosuspend_node.expr, space);
476 },435 },
477436
478 .Suspend => {437 .Suspend => {
479 const suspend_node = @fieldParentPtr(ast.Node.Suspend, "base", base);438 const suspend_node = @fieldParentPtr(ast.Node.Suspend, "base", base);
480439
481 if (suspend_node.body) |body| {440 if (suspend_node.body) |body| {
482 try renderToken(tree, stream, suspend_node.suspend_token, indent, start_col, Space.Space);441 try renderToken(tree, ais, suspend_node.suspend_token, Space.Space);
483 return renderExpression(allocator, stream, tree, indent, start_col, body, space);442 return renderExpression(allocator, ais, tree, body, space);
484 } else {443 } else {
485 return renderToken(tree, stream, suspend_node.suspend_token, indent, start_col, space);444 return renderToken(tree, ais, suspend_node.suspend_token, space);
486 }445 }
487 },446 },
488447
...@@ -490,26 +449,21 @@ fn renderExpression(...@@ -490,26 +449,21 @@ fn renderExpression(
490 const infix_op_node = @fieldParentPtr(ast.Node.Catch, "base", base);449 const infix_op_node = @fieldParentPtr(ast.Node.Catch, "base", base);
491450
492 const op_space = Space.Space;451 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
495 const after_op_space = blk: {454 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));455 const same_line = tree.tokensOnSameLine(infix_op_node.op_token, tree.nextToken(infix_op_node.op_token));
497 break :blk if (loc.line == 0) op_space else Space.Newline;456 break :blk if (same_line) op_space else Space.Newline;
498 };457 };
499458
500 try renderToken(tree, stream, infix_op_node.op_token, indent, start_col, after_op_space);459 try renderToken(tree, ais, infix_op_node.op_token, 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 }
507460
508 if (infix_op_node.payload) |payload| {461 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);
510 }463 }
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);
513 },467 },
514468
515 .Add,469 .Add,
...@@ -561,22 +515,16 @@ fn renderExpression(...@@ -561,22 +515,16 @@ fn renderExpression(
561 .Period, .ErrorUnion, .Range => Space.None,515 .Period, .ErrorUnion, .Range => Space.None,
562 else => Space.Space,516 else => Space.Space,
563 };517 };
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
566 const after_op_space = blk: {520 const after_op_space = blk: {
567 const loc = tree.tokenLocation(tree.token_locs[infix_op_node.op_token].end, tree.nextToken(infix_op_node.op_token));521 const loc = tree.tokenLocation(tree.token_locs[infix_op_node.op_token].end, tree.nextToken(infix_op_node.op_token));
568 break :blk if (loc.line == 0) op_space else Space.Newline;522 break :blk if (loc.line == 0) op_space else Space.Newline;
569 };523 };
570524
571 try renderToken(tree, stream, infix_op_node.op_token, indent, start_col, after_op_space);525 try renderToken(tree, ais, infix_op_node.op_token, after_op_space);
572 if (after_op_space == Space.Newline and526 ais.pushIndentOneShot();
573 tree.token_ids[tree.nextToken(infix_op_node.op_token)] != .MultilineStringLiteralLine)527 return renderExpression(allocator, ais, tree, infix_op_node.rhs, space);
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);
580 },528 },
581529
582 .BitNot,530 .BitNot,
...@@ -587,8 +535,8 @@ fn renderExpression(...@@ -587,8 +535,8 @@ fn renderExpression(
587 .AddressOf,535 .AddressOf,
588 => {536 => {
589 const casted_node = @fieldParentPtr(ast.Node.SimplePrefixOp, "base", base);537 const casted_node = @fieldParentPtr(ast.Node.SimplePrefixOp, "base", base);
590 try renderToken(tree, stream, casted_node.op_token, indent, start_col, Space.None);538 try renderToken(tree, ais, casted_node.op_token, Space.None);
591 return renderExpression(allocator, stream, tree, indent, start_col, casted_node.rhs, space);539 return renderExpression(allocator, ais, tree, casted_node.rhs, space);
592 },540 },
593541
594 .Try,542 .Try,
...@@ -596,18 +544,16 @@ fn renderExpression(...@@ -596,18 +544,16 @@ fn renderExpression(
596 .Await,544 .Await,
597 => {545 => {
598 const casted_node = @fieldParentPtr(ast.Node.SimplePrefixOp, "base", base);546 const casted_node = @fieldParentPtr(ast.Node.SimplePrefixOp, "base", base);
599 try renderToken(tree, stream, casted_node.op_token, indent, start_col, Space.Space);547 try renderToken(tree, ais, casted_node.op_token, Space.Space);
600 return renderExpression(allocator, stream, tree, indent, start_col, casted_node.rhs, space);548 return renderExpression(allocator, ais, tree, casted_node.rhs, space);
601 },549 },
602550
603 .ArrayType => {551 .ArrayType => {
604 const array_type = @fieldParentPtr(ast.Node.ArrayType, "base", base);552 const array_type = @fieldParentPtr(ast.Node.ArrayType, "base", base);
605 return renderArrayType(553 return renderArrayType(
606 allocator,554 allocator,
607 stream,555 ais,
608 tree,556 tree,
609 indent,
610 start_col,
611 array_type.op_token,557 array_type.op_token,
612 array_type.rhs,558 array_type.rhs,
613 array_type.len_expr,559 array_type.len_expr,
...@@ -619,10 +565,8 @@ fn renderExpression(...@@ -619,10 +565,8 @@ fn renderExpression(
619 const array_type = @fieldParentPtr(ast.Node.ArrayTypeSentinel, "base", base);565 const array_type = @fieldParentPtr(ast.Node.ArrayTypeSentinel, "base", base);
620 return renderArrayType(566 return renderArrayType(
621 allocator,567 allocator,
622 stream,568 ais,
623 tree,569 tree,
624 indent,
625 start_col,
626 array_type.op_token,570 array_type.op_token,
627 array_type.rhs,571 array_type.rhs,
628 array_type.len_expr,572 array_type.len_expr,
...@@ -635,111 +579,111 @@ fn renderExpression(...@@ -635,111 +579,111 @@ fn renderExpression(
635 const ptr_type = @fieldParentPtr(ast.Node.PtrType, "base", base);579 const ptr_type = @fieldParentPtr(ast.Node.PtrType, "base", base);
636 const op_tok_id = tree.token_ids[ptr_type.op_token];580 const op_tok_id = tree.token_ids[ptr_type.op_token];
637 switch (op_tok_id) {581 switch (op_tok_id) {
638 .Asterisk, .AsteriskAsterisk => try stream.writeByte('*'),582 .Asterisk, .AsteriskAsterisk => try ais.writer().writeByte('*'),
639 .LBracket => if (tree.token_ids[ptr_type.op_token + 2] == .Identifier)583 .LBracket => if (tree.token_ids[ptr_type.op_token + 2] == .Identifier)
640 try stream.writeAll("[*c")584 try ais.writer().writeAll("[*c")
641 else585 else
642 try stream.writeAll("[*"),586 try ais.writer().writeAll("[*"),
643 else => unreachable,587 else => unreachable,
644 }588 }
645 if (ptr_type.ptr_info.sentinel) |sentinel| {589 if (ptr_type.ptr_info.sentinel) |sentinel| {
646 const colon_token = tree.prevToken(sentinel.firstToken());590 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); // :
648 const sentinel_space = switch (op_tok_id) {592 const sentinel_space = switch (op_tok_id) {
649 .LBracket => Space.None,593 .LBracket => Space.None,
650 else => Space.Space,594 else => Space.Space,
651 };595 };
652 try renderExpression(allocator, stream, tree, indent, start_col, sentinel, sentinel_space);596 try renderExpression(allocator, ais, tree, sentinel, sentinel_space);
653 }597 }
654 switch (op_tok_id) {598 switch (op_tok_id) {
655 .Asterisk, .AsteriskAsterisk => {},599 .Asterisk, .AsteriskAsterisk => {},
656 .LBracket => try stream.writeByte(']'),600 .LBracket => try ais.writer().writeByte(']'),
657 else => unreachable,601 else => unreachable,
658 }602 }
659 if (ptr_type.ptr_info.allowzero_token) |allowzero_token| {603 if (ptr_type.ptr_info.allowzero_token) |allowzero_token| {
660 try renderToken(tree, stream, allowzero_token, indent, start_col, Space.Space); // allowzero604 try renderToken(tree, ais, allowzero_token, Space.Space); // allowzero
661 }605 }
662 if (ptr_type.ptr_info.align_info) |align_info| {606 if (ptr_type.ptr_info.align_info) |align_info| {
663 const lparen_token = tree.prevToken(align_info.node.firstToken());607 const lparen_token = tree.prevToken(align_info.node.firstToken());
664 const align_token = tree.prevToken(lparen_token);608 const align_token = tree.prevToken(lparen_token);
665609
666 try renderToken(tree, stream, align_token, indent, start_col, Space.None); // align610 try renderToken(tree, ais, align_token, Space.None); // align
667 try renderToken(tree, stream, lparen_token, indent, start_col, Space.None); // (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
671 if (align_info.bit_range) |bit_range| {615 if (align_info.bit_range) |bit_range| {
672 const colon1 = tree.prevToken(bit_range.start.firstToken());616 const colon1 = tree.prevToken(bit_range.start.firstToken());
673 const colon2 = tree.prevToken(bit_range.end.firstToken());617 const colon2 = tree.prevToken(bit_range.end.firstToken());
674618
675 try renderToken(tree, stream, colon1, indent, start_col, Space.None); // :619 try renderToken(tree, ais, colon1, Space.None); // :
676 try renderExpression(allocator, stream, tree, indent, start_col, bit_range.start, Space.None);620 try renderExpression(allocator, ais, tree, bit_range.start, Space.None);
677 try renderToken(tree, stream, colon2, indent, start_col, Space.None); // :621 try renderToken(tree, ais, colon2, Space.None); // :
678 try renderExpression(allocator, stream, tree, indent, start_col, bit_range.end, Space.None);622 try renderExpression(allocator, ais, tree, bit_range.end, Space.None);
679623
680 const rparen_token = tree.nextToken(bit_range.end.lastToken());624 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); // )
682 } else {626 } else {
683 const rparen_token = tree.nextToken(align_info.node.lastToken());627 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); // )
685 }629 }
686 }630 }
687 if (ptr_type.ptr_info.const_token) |const_token| {631 if (ptr_type.ptr_info.const_token) |const_token| {
688 try renderToken(tree, stream, const_token, indent, start_col, Space.Space); // const632 try renderToken(tree, ais, const_token, Space.Space); // const
689 }633 }
690 if (ptr_type.ptr_info.volatile_token) |volatile_token| {634 if (ptr_type.ptr_info.volatile_token) |volatile_token| {
691 try renderToken(tree, stream, volatile_token, indent, start_col, Space.Space); // volatile635 try renderToken(tree, ais, volatile_token, Space.Space); // volatile
692 }636 }
693 return renderExpression(allocator, stream, tree, indent, start_col, ptr_type.rhs, space);637 return renderExpression(allocator, ais, tree, ptr_type.rhs, space);
694 },638 },
695639
696 .SliceType => {640 .SliceType => {
697 const slice_type = @fieldParentPtr(ast.Node.SliceType, "base", base);641 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); // [
699 if (slice_type.ptr_info.sentinel) |sentinel| {643 if (slice_type.ptr_info.sentinel) |sentinel| {
700 const colon_token = tree.prevToken(sentinel.firstToken());644 const colon_token = tree.prevToken(sentinel.firstToken());
701 try renderToken(tree, stream, colon_token, indent, start_col, Space.None); // :645 try renderToken(tree, ais, colon_token, Space.None); // :
702 try renderExpression(allocator, stream, tree, indent, start_col, sentinel, Space.None);646 try renderExpression(allocator, ais, tree, sentinel, Space.None);
703 try renderToken(tree, stream, tree.nextToken(sentinel.lastToken()), indent, start_col, Space.None); // ]647 try renderToken(tree, ais, tree.nextToken(sentinel.lastToken()), Space.None); // ]
704 } else {648 } 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); // ]
706 }650 }
707651
708 if (slice_type.ptr_info.allowzero_token) |allowzero_token| {652 if (slice_type.ptr_info.allowzero_token) |allowzero_token| {
709 try renderToken(tree, stream, allowzero_token, indent, start_col, Space.Space); // allowzero653 try renderToken(tree, ais, allowzero_token, Space.Space); // allowzero
710 }654 }
711 if (slice_type.ptr_info.align_info) |align_info| {655 if (slice_type.ptr_info.align_info) |align_info| {
712 const lparen_token = tree.prevToken(align_info.node.firstToken());656 const lparen_token = tree.prevToken(align_info.node.firstToken());
713 const align_token = tree.prevToken(lparen_token);657 const align_token = tree.prevToken(lparen_token);
714658
715 try renderToken(tree, stream, align_token, indent, start_col, Space.None); // align659 try renderToken(tree, ais, align_token, Space.None); // align
716 try renderToken(tree, stream, lparen_token, indent, start_col, Space.None); // (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
720 if (align_info.bit_range) |bit_range| {664 if (align_info.bit_range) |bit_range| {
721 const colon1 = tree.prevToken(bit_range.start.firstToken());665 const colon1 = tree.prevToken(bit_range.start.firstToken());
722 const colon2 = tree.prevToken(bit_range.end.firstToken());666 const colon2 = tree.prevToken(bit_range.end.firstToken());
723667
724 try renderToken(tree, stream, colon1, indent, start_col, Space.None); // :668 try renderToken(tree, ais, colon1, Space.None); // :
725 try renderExpression(allocator, stream, tree, indent, start_col, bit_range.start, Space.None);669 try renderExpression(allocator, ais, tree, bit_range.start, Space.None);
726 try renderToken(tree, stream, colon2, indent, start_col, Space.None); // :670 try renderToken(tree, ais, colon2, Space.None); // :
727 try renderExpression(allocator, stream, tree, indent, start_col, bit_range.end, Space.None);671 try renderExpression(allocator, ais, tree, bit_range.end, Space.None);
728672
729 const rparen_token = tree.nextToken(bit_range.end.lastToken());673 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); // )
731 } else {675 } else {
732 const rparen_token = tree.nextToken(align_info.node.lastToken());676 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); // )
734 }678 }
735 }679 }
736 if (slice_type.ptr_info.const_token) |const_token| {680 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);
738 }682 }
739 if (slice_type.ptr_info.volatile_token) |volatile_token| {683 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);
741 }685 }
742 return renderExpression(allocator, stream, tree, indent, start_col, slice_type.rhs, space);686 return renderExpression(allocator, ais, tree, slice_type.rhs, space);
743 },687 },
744688
745 .ArrayInitializer, .ArrayInitializerDot => {689 .ArrayInitializer, .ArrayInitializerDot => {
...@@ -768,27 +712,33 @@ fn renderExpression(...@@ -768,27 +712,33 @@ fn renderExpression(
768712
769 if (exprs.len == 0) {713 if (exprs.len == 0) {
770 switch (lhs) {714 switch (lhs) {
771 .dot => |dot| try renderToken(tree, stream, dot, indent, start_col, Space.None),715 .dot => |dot| try renderToken(tree, ais, dot, Space.None),
772 .node => |node| try renderExpression(allocator, stream, tree, indent, start_col, node, 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);
773 }723 }
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) {
779 const expr = exprs[0];728 const expr = exprs[0];
729
780 switch (lhs) {730 switch (lhs) {
781 .dot => |dot| try renderToken(tree, stream, dot, indent, start_col, Space.None),731 .dot => |dot| try renderToken(tree, ais, dot, Space.None),
782 .node => |node| try renderExpression(allocator, stream, tree, indent, start_col, node, Space.None),732 .node => |node| try renderExpression(allocator, ais, tree, node, Space.None),
783 }733 }
784 try renderToken(tree, stream, lbrace, indent, start_col, Space.None);734 try renderToken(tree, ais, lbrace, Space.None);
785 try renderExpression(allocator, stream, tree, indent, start_col, expr, Space.None);735 try renderExpression(allocator, ais, tree, expr, Space.None);
786 return renderToken(tree, stream, rtoken, indent, start_col, space);736 return renderToken(tree, ais, rtoken, space);
787 }737 }
788738
789 switch (lhs) {739 switch (lhs) {
790 .dot => |dot| try renderToken(tree, stream, dot, indent, start_col, Space.None),740 .dot => |dot| try renderToken(tree, ais, dot, Space.None),
791 .node => |node| try renderExpression(allocator, stream, tree, indent, start_col, node, Space.None),741 .node => |node| try renderExpression(allocator, ais, tree, node, Space.None),
792 }742 }
793743
794 // scan to find row size744 // scan to find row size
...@@ -830,79 +780,70 @@ fn renderExpression(...@@ -830,79 +780,70 @@ fn renderExpression(
830 var expr_widths = widths[0 .. widths.len - row_size];780 var expr_widths = widths[0 .. widths.len - row_size];
831 var column_widths = widths[widths.len - row_size ..];781 var column_widths = widths[widths.len - row_size ..];
832782
833 // Null stream for counting the printed length of each expression783 // Null ais for counting the printed length of each expression
834 var counting_stream = std.io.countingOutStream(std.io.null_out_stream);784 var counting_stream = std.io.countingOutStream(std.io.null_out_stream);
785 var auto_indenting_stream = std.io.autoIndentingStream(indent_delta, counting_stream.writer());
835786
836 for (exprs) |expr, i| {787 for (exprs) |expr, i| {
837 counting_stream.bytes_written = 0;788 counting_stream.bytes_written = 0;
838 var dummy_col: usize = 0;789 try renderExpression(allocator, &auto_indenting_stream, tree, expr, Space.None);
839 try renderExpression(allocator, counting_stream.outStream(), tree, indent, &dummy_col, expr, Space.None);
840 const width = @intCast(usize, counting_stream.bytes_written);790 const width = @intCast(usize, counting_stream.bytes_written);
841 const col = i % row_size;791 const col = i % row_size;
842 column_widths[col] = std.math.max(column_widths[col], width);792 column_widths[col] = std.math.max(column_widths[col], width);
843 expr_widths[i] = width;793 expr_widths[i] = width;
844 }794 }
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) {801 var col: usize = 1;
849 try renderToken(tree, stream, lbrace, new_indent, start_col, Space.Newline);802 for (exprs) |expr, i| {
850 try stream.writeByteNTimes(' ', new_indent);803 if (i + 1 < exprs.len) {
851 } else {804 const next_expr = exprs[i + 1];
852 new_indent -= indent_delta;805 try renderExpression(allocator, ais, tree, expr, Space.None);
853 try renderToken(tree, stream, lbrace, new_indent, start_col, Space.None);
854 }
855806
856 var col: usize = 1;807 const comma = tree.nextToken(expr.*.lastToken());
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);
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) {812 const padding = column_widths[i % row_size] - expr_widths[i];
865 try renderToken(tree, stream, comma, new_indent, start_col, Space.Space); // ,813 try ais.writer().writeByteNTimes(' ', padding);
866814
867 const padding = column_widths[i % row_size] - expr_widths[i];815 col += 1;
868 try stream.writeByteNTimes(' ', padding);816 continue;
817 }
818 col = 1;
869819
870 col += 1;820 if (tree.token_ids[tree.nextToken(comma)] != .MultilineStringLiteralLine) {
871 continue;821 try renderToken(tree, ais, comma, Space.Newline); // ,
872 }822 } else {
873 col = 1;823 try renderToken(tree, ais, comma, Space.None); // ,
824 }
874825
875 if (tree.token_ids[tree.nextToken(comma)] != .MultilineStringLiteralLine) {826 try renderExtraNewline(tree, ais, next_expr);
876 try renderToken(tree, stream, comma, new_indent, start_col, Space.Newline); // ,
877 } else {827 } else {
878 try renderToken(tree, stream, comma, new_indent, start_col, Space.None); // ,828 try renderExpression(allocator, ais, tree, expr, Space.Comma); // ,
879 }
880
881 try renderExtraNewline(tree, stream, start_col, next_expr);
882 if (next_expr.tag != .MultilineStringLiteral) {
883 try stream.writeByteNTimes(' ', new_indent);
884 }829 }
885 } else {
886 try renderExpression(allocator, stream, tree, new_indent, start_col, expr, Space.Comma); // ,
887 }830 }
888 }831 }
889 if (exprs[exprs.len - 1].tag != .MultilineStringLiteral) {832 return renderToken(tree, ais, rtoken, space);
890 try stream.writeByteNTimes(' ', indent);
891 }
892 return renderToken(tree, stream, rtoken, indent, start_col, space);
893 } else {833 } else {
894 try renderToken(tree, stream, lbrace, indent, start_col, Space.Space);834 try renderToken(tree, ais, lbrace, Space.Space);
895 for (exprs) |expr, i| {835 for (exprs) |expr, i| {
896 if (i + 1 < exprs.len) {836 if (i + 1 < exprs.len) {
897 try renderExpression(allocator, stream, tree, indent, start_col, expr, Space.None);837 const next_expr = exprs[i + 1];
898 const comma = tree.nextToken(expr.lastToken());838 try renderExpression(allocator, ais, tree, expr, Space.None);
899 try renderToken(tree, stream, comma, indent, start_col, Space.Space); // ,839 const comma = tree.nextToken(expr.*.lastToken());
840 try renderToken(tree, ais, comma, Space.Space); // ,
900 } else {841 } else {
901 try renderExpression(allocator, stream, tree, indent, start_col, expr, Space.Space);842 try renderExpression(allocator, ais, tree, expr, Space.Space);
902 }843 }
903 }844 }
904845
905 return renderToken(tree, stream, rtoken, indent, start_col, space);846 return renderToken(tree, ais, rtoken, space);
906 }847 }
907 },848 },
908849
...@@ -932,11 +873,17 @@ fn renderExpression(...@@ -932,11 +873,17 @@ fn renderExpression(
932873
933 if (field_inits.len == 0) {874 if (field_inits.len == 0) {
934 switch (lhs) {875 switch (lhs) {
935 .dot => |dot| try renderToken(tree, stream, dot, indent, start_col, Space.None),876 .dot => |dot| try renderToken(tree, ais, dot, Space.None),
936 .node => |node| try renderExpression(allocator, stream, tree, indent, start_col, node, Space.None),877 .node => |node| try renderExpression(allocator, ais, tree, node, Space.None),
937 }878 }
938 try renderToken(tree, stream, lbrace, indent + indent_delta, start_col, Space.None);879
939 return renderToken(tree, stream, rtoken, indent, start_col, space);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);
940 }887 }
941888
942 const src_has_trailing_comma = blk: {889 const src_has_trailing_comma = blk: {
...@@ -952,9 +899,10 @@ fn renderExpression(...@@ -952,9 +899,10 @@ fn renderExpression(
952 const expr_outputs_one_line = blk: {899 const expr_outputs_one_line = blk: {
953 // render field expressions until a LF is found900 // render field expressions until a LF is found
954 for (field_inits) |field_init| {901 for (field_inits) |field_init| {
955 var find_stream = FindByteOutStream.init('\n');902 var find_stream = std.io.findByteOutStream('\n', std.io.null_out_stream);
956 var dummy_col: usize = 0;903 var auto_indenting_stream = std.io.autoIndentingStream(indent_delta, find_stream.writer());
957 try renderExpression(allocator, find_stream.outStream(), tree, 0, &dummy_col, field_init, Space.None);904
905 try renderExpression(allocator, &auto_indenting_stream, tree, field_init, Space.None);
958 if (find_stream.byte_found) break :blk false;906 if (find_stream.byte_found) break :blk false;
959 }907 }
960 break :blk true;908 break :blk true;
...@@ -967,7 +915,6 @@ fn renderExpression(...@@ -967,7 +915,6 @@ fn renderExpression(
967 .StructInitializer,915 .StructInitializer,
968 .StructInitializerDot,916 .StructInitializerDot,
969 => break :blk,917 => break :blk,
970
971 else => {},918 else => {},
972 }919 }
973920
...@@ -977,76 +924,78 @@ fn renderExpression(...@@ -977,76 +924,78 @@ fn renderExpression(
977 }924 }
978925
979 switch (lhs) {926 switch (lhs) {
980 .dot => |dot| try renderToken(tree, stream, dot, indent, start_col, Space.None),927 .dot => |dot| try renderToken(tree, ais, dot, Space.None),
981 .node => |node| try renderExpression(allocator, stream, tree, indent, start_col, node, Space.None),928 .node => |node| try renderExpression(allocator, ais, tree, node, Space.None),
982 }929 }
983 try renderToken(tree, stream, lbrace, indent, start_col, Space.Space);930 try renderToken(tree, ais, lbrace, Space.Space);
984 try renderExpression(allocator, stream, tree, indent, start_col, &field_init.base, Space.Space);931 try renderExpression(allocator, ais, tree, &field_init.base, Space.Space);
985 return renderToken(tree, stream, rtoken, indent, start_col, space);932 return renderToken(tree, ais, rtoken, space);
986 }933 }
987934
988 if (!src_has_trailing_comma and src_same_line and expr_outputs_one_line) {935 if (!src_has_trailing_comma and src_same_line and expr_outputs_one_line) {
989 // render all on one line, no trailing comma936 // render all on one line, no trailing comma
990 switch (lhs) {937 switch (lhs) {
991 .dot => |dot| try renderToken(tree, stream, dot, indent, start_col, Space.None),938 .dot => |dot| try renderToken(tree, ais, dot, Space.None),
992 .node => |node| try renderExpression(allocator, stream, tree, indent, start_col, node, Space.None),939 .node => |node| try renderExpression(allocator, ais, tree, node, Space.None),
993 }940 }
994 try renderToken(tree, stream, lbrace, indent, start_col, Space.Space);941 try renderToken(tree, ais, lbrace, Space.Space);
995942
996 for (field_inits) |field_init, i| {943 for (field_inits) |field_init, i| {
997 if (i + 1 < field_inits.len) {944 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
1000 const comma = tree.nextToken(field_init.lastToken());947 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);
1002 } else {949 } else {
1003 try renderExpression(allocator, stream, tree, indent, start_col, field_init, Space.Space);950 try renderExpression(allocator, ais, tree, field_init, Space.Space);
1004 }951 }
1005 }952 }
1006953
1007 return renderToken(tree, stream, rtoken, indent, start_col, space);954 return renderToken(tree, ais, rtoken, space);
1008 }955 }
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) {963 ais.pushIndentNextLine();
1013 .dot => |dot| try renderToken(tree, stream, dot, new_indent, start_col, Space.None),964 defer ais.popIndent();
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);
1017965
1018 for (field_inits) |field_init, i| {966 try renderToken(tree, ais, lbrace, Space.Newline);
1019 try stream.writeByteNTimes(' ', new_indent);
1020967
1021 if (i + 1 < field_inits.len) {968 for (field_inits) |field_init, i| {
1022 try renderExpression(allocator, stream, tree, new_indent, start_col, field_init, Space.None);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());973 const comma = tree.nextToken(field_init.lastToken());
1025 try renderToken(tree, stream, comma, new_indent, start_col, Space.Newline);974 try renderToken(tree, ais, comma, Space.Newline);
1026975
1027 try renderExtraNewline(tree, stream, start_col, field_inits[i + 1]);976 try renderExtraNewline(tree, ais, next_field_init);
1028 } else {977 } else {
1029 try renderExpression(allocator, stream, tree, new_indent, start_col, field_init, Space.Comma);978 try renderExpression(allocator, ais, tree, field_init, Space.Comma);
979 }
1030 }980 }
1031 }981 }
1032982
1033 try stream.writeByteNTimes(' ', indent);983 return renderToken(tree, ais, rtoken, space);
1034 return renderToken(tree, stream, rtoken, indent, start_col, space);
1035 },984 },
1036985
1037 .Call => {986 .Call => {
1038 const call = @fieldParentPtr(ast.Node.Call, "base", base);987 const call = @fieldParentPtr(ast.Node.Call, "base", base);
1039 if (call.async_token) |async_token| {988 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);
1041 }990 }
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
1045 const lparen = tree.nextToken(call.lhs.lastToken());994 const lparen = tree.nextToken(call.lhs.lastToken());
1046995
1047 if (call.params_len == 0) {996 if (call.params_len == 0) {
1048 try renderToken(tree, stream, lparen, indent, start_col, Space.None);997 try renderToken(tree, ais, lparen, Space.None);
1049 return renderToken(tree, stream, call.rtoken, indent, start_col, space);998 return renderToken(tree, ais, call.rtoken, space);
1050 }999 }
10511000
1052 const src_has_trailing_comma = blk: {1001 const src_has_trailing_comma = blk: {
...@@ -1055,43 +1004,41 @@ fn renderExpression(...@@ -1055,43 +1004,41 @@ fn renderExpression(
1055 };1004 };
10561005
1057 if (src_has_trailing_comma) {1006 if (src_has_trailing_comma) {
1058 const new_indent = indent + indent_delta;1007 try renderToken(tree, ais, lparen, Space.Newline);
1059 try renderToken(tree, stream, lparen, new_indent, start_col, Space.Newline);
10601008
1061 const params = call.params();1009 const params = call.params();
1062 for (params) |param_node, i| {1010 for (params) |param_node, i| {
1063 const param_node_new_indent = if (param_node.tag == .MultilineStringLiteral) blk: {1011 ais.pushIndent();
1064 break :blk indent;1012 defer ais.popIndent();
1065 } else blk: {
1066 try stream.writeByteNTimes(' ', new_indent);
1067 break :blk new_indent;
1068 };
10691013
1070 if (i + 1 < params.len) {1014 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);
1072 const comma = tree.nextToken(param_node.lastToken());1017 const comma = tree.nextToken(param_node.lastToken());
1073 try renderToken(tree, stream, comma, new_indent, start_col, Space.Newline); // ,1018 try renderToken(tree, ais, comma, Space.Newline); // ,
1074 try renderExtraNewline(tree, stream, start_col, params[i + 1]);1019 try renderExtraNewline(tree, ais, next_node);
1075 } else {1020 } else {
1076 try renderExpression(allocator, stream, tree, param_node_new_indent, start_col, param_node, Space.Comma);1021 try renderExpression(allocator, ais, tree, param_node, Space.Comma);
1077 try stream.writeByteNTimes(' ', indent);
1078 return renderToken(tree, stream, call.rtoken, indent, start_col, space);
1079 }1022 }
1080 }1023 }
1024 return renderToken(tree, ais, call.rtoken, space);
1081 }1025 }
10821026
1083 try renderToken(tree, stream, lparen, indent, start_col, Space.None); // (1027 try renderToken(tree, ais, lparen, Space.None); // (
10841028
1085 const params = call.params();1029 const params = call.params();
1086 for (params) |param_node, i| {1030 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
1089 if (i + 1 < params.len) {1035 if (i + 1 < params.len) {
1036 const next_param = params[i + 1];
1090 const comma = tree.nextToken(param_node.lastToken());1037 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);
1092 }1039 }
1093 }1040 }
1094 return renderToken(tree, stream, call.rtoken, indent, start_col, space);1041 return renderToken(tree, ais, call.rtoken, space);
1095 },1042 },
10961043
1097 .ArrayAccess => {1044 .ArrayAccess => {
...@@ -1100,26 +1047,25 @@ fn renderExpression(...@@ -1100,26 +1047,25 @@ fn renderExpression(
1100 const lbracket = tree.nextToken(suffix_op.lhs.lastToken());1047 const lbracket = tree.nextToken(suffix_op.lhs.lastToken());
1101 const rbracket = tree.nextToken(suffix_op.index_expr.lastToken());1048 const rbracket = tree.nextToken(suffix_op.index_expr.lastToken());
11021049
1103 try renderExpression(allocator, stream, tree, indent, start_col, suffix_op.lhs, Space.None);1050 try renderExpression(allocator, ais, tree, suffix_op.lhs, Space.None);
1104 try renderToken(tree, stream, lbracket, indent, start_col, Space.None); // [1051 try renderToken(tree, ais, lbracket, Space.None); // [
11051052
1106 const starts_with_comment = tree.token_ids[lbracket + 1] == .LineComment;1053 const starts_with_comment = tree.token_ids[lbracket + 1] == .LineComment;
1107 const ends_with_comment = tree.token_ids[rbracket - 1] == .LineComment;1054 const ends_with_comment = tree.token_ids[rbracket - 1] == .LineComment;
1108 const new_indent = if (ends_with_comment) indent + indent_delta else indent;1055 {
1109 const new_space = if (ends_with_comment) Space.Newline else Space.None;1056 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);1057
1111 if (starts_with_comment) {1058 ais.pushIndent();
1112 try stream.writeByte('\n');1059 defer ais.popIndent();
1113 }1060 try renderExpression(allocator, ais, tree, suffix_op.index_expr, new_space);
1114 if (ends_with_comment or starts_with_comment) {
1115 try stream.writeByteNTimes(' ', indent);
1116 }1061 }
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); // ]
1118 },1064 },
1065
1119 .Slice => {1066 .Slice => {
1120 const suffix_op = base.castTag(.Slice).?;1067 const suffix_op = base.castTag(.Slice).?;
11211068 try renderExpression(allocator, ais, tree, suffix_op.lhs, Space.None);
1122 try renderExpression(allocator, stream, tree, indent, start_col, suffix_op.lhs, Space.None);
11231069
1124 const lbracket = tree.prevToken(suffix_op.start.firstToken());1070 const lbracket = tree.prevToken(suffix_op.start.firstToken());
1125 const dotdot = tree.nextToken(suffix_op.start.lastToken());1071 const dotdot = tree.nextToken(suffix_op.start.lastToken());
...@@ -1129,32 +1075,33 @@ fn renderExpression(...@@ -1129,32 +1075,33 @@ fn renderExpression(
1129 const after_start_space = if (after_start_space_bool) Space.Space else Space.None;1075 const after_start_space = if (after_start_space_bool) Space.Space else Space.None;
1130 const after_op_space = if (suffix_op.end != null) after_start_space else Space.None;1076 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); // [1078 try renderToken(tree, ais, lbracket, Space.None); // [
1133 try renderExpression(allocator, stream, tree, indent, start_col, suffix_op.start, after_start_space);1079 try renderExpression(allocator, ais, tree, suffix_op.start, after_start_space);
1134 try renderToken(tree, stream, dotdot, indent, start_col, after_op_space); // ..1080 try renderToken(tree, ais, dotdot, after_op_space); // ..
1135 if (suffix_op.end) |end| {1081 if (suffix_op.end) |end| {
1136 const after_end_space = if (suffix_op.sentinel != null) Space.Space else Space.None;1082 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);
1138 }1084 }
1139 if (suffix_op.sentinel) |sentinel| {1085 if (suffix_op.sentinel) |sentinel| {
1140 const colon = tree.prevToken(sentinel.firstToken());1086 const colon = tree.prevToken(sentinel.firstToken());
1141 try renderToken(tree, stream, colon, indent, start_col, Space.None); // :1087 try renderToken(tree, ais, colon, Space.None); // :
1142 try renderExpression(allocator, stream, tree, indent, start_col, sentinel, Space.None);1088 try renderExpression(allocator, ais, tree, sentinel, Space.None);
1143 }1089 }
1144 return renderToken(tree, stream, suffix_op.rtoken, indent, start_col, space); // ]1090 return renderToken(tree, ais, suffix_op.rtoken, space); // ]
1145 },1091 },
1092
1146 .Deref => {1093 .Deref => {
1147 const suffix_op = base.castTag(.Deref).?;1094 const suffix_op = base.castTag(.Deref).?;
11481095
1149 try renderExpression(allocator, stream, tree, indent, start_col, suffix_op.lhs, Space.None);1096 try renderExpression(allocator, ais, tree, suffix_op.lhs, Space.None);
1150 return renderToken(tree, stream, suffix_op.rtoken, indent, start_col, space); // .*1097 return renderToken(tree, ais, suffix_op.rtoken, space); // .*
1151 },1098 },
1152 .UnwrapOptional => {1099 .UnwrapOptional => {
1153 const suffix_op = base.castTag(.UnwrapOptional).?;1100 const suffix_op = base.castTag(.UnwrapOptional).?;
11541101
1155 try renderExpression(allocator, stream, tree, indent, start_col, suffix_op.lhs, Space.None);1102 try renderExpression(allocator, ais, tree, suffix_op.lhs, Space.None);
1156 try renderToken(tree, stream, tree.prevToken(suffix_op.rtoken), indent, start_col, Space.None); // .1103 try renderToken(tree, ais, tree.prevToken(suffix_op.rtoken), Space.None); // .
1157 return renderToken(tree, stream, suffix_op.rtoken, indent, start_col, space); // ?1104 return renderToken(tree, ais, suffix_op.rtoken, space); // ?
1158 },1105 },
11591106
1160 .Break => {1107 .Break => {
...@@ -1163,145 +1110,152 @@ fn renderExpression(...@@ -1163,145 +1110,152 @@ fn renderExpression(
1163 const maybe_label = flow_expr.getLabel();1110 const maybe_label = flow_expr.getLabel();
11641111
1165 if (maybe_label == null and maybe_rhs == null) {1112 if (maybe_label == null and maybe_rhs == null) {
1166 return renderToken(tree, stream, flow_expr.ltoken, indent, start_col, space); // break1113 return renderToken(tree, ais, flow_expr.ltoken, space); // break
1167 }1114 }
11681115
1169 try renderToken(tree, stream, flow_expr.ltoken, indent, start_col, Space.Space); // break1116 try renderToken(tree, ais, flow_expr.ltoken, Space.Space); // break
1170 if (maybe_label) |label| {1117 if (maybe_label) |label| {
1171 const colon = tree.nextToken(flow_expr.ltoken);1118 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
1174 if (maybe_rhs == null) {1121 if (maybe_rhs == null) {
1175 return renderToken(tree, stream, label, indent, start_col, space); // label1122 return renderToken(tree, ais, label, space); // label
1176 }1123 }
1177 try renderToken(tree, stream, label, indent, start_col, Space.Space); // label1124 try renderToken(tree, ais, label, Space.Space); // label
1178 }1125 }
1179 return renderExpression(allocator, stream, tree, indent, start_col, maybe_rhs.?, space);1126 return renderExpression(allocator, ais, tree, maybe_rhs.?, space);
1180 },1127 },
11811128
1182 .Continue => {1129 .Continue => {
1183 const flow_expr = base.castTag(.Continue).?;1130 const flow_expr = base.castTag(.Continue).?;
1184 if (flow_expr.getLabel()) |label| {1131 if (flow_expr.getLabel()) |label| {
1185 try renderToken(tree, stream, flow_expr.ltoken, indent, start_col, Space.Space); // continue1132 try renderToken(tree, ais, flow_expr.ltoken, Space.Space); // continue
1186 const colon = tree.nextToken(flow_expr.ltoken);1133 const colon = tree.nextToken(flow_expr.ltoken);
1187 try renderToken(tree, stream, colon, indent, start_col, Space.None); // :1134 try renderToken(tree, ais, colon, Space.None); // :
1188 return renderToken(tree, stream, label, indent, start_col, space); // label1135 return renderToken(tree, ais, label, space); // label
1189 } else {1136 } else {
1190 return renderToken(tree, stream, flow_expr.ltoken, indent, start_col, space); // continue1137 return renderToken(tree, ais, flow_expr.ltoken, space); // continue
1191 }1138 }
1192 },1139 },
11931140
1194 .Return => {1141 .Return => {
1195 const flow_expr = base.castTag(.Return).?;1142 const flow_expr = base.castTag(.Return).?;
1196 if (flow_expr.getRHS()) |rhs| {1143 if (flow_expr.getRHS()) |rhs| {
1197 try renderToken(tree, stream, flow_expr.ltoken, indent, start_col, Space.Space);1144 try renderToken(tree, ais, flow_expr.ltoken, Space.Space);
1198 return renderExpression(allocator, stream, tree, indent, start_col, rhs, space);1145 return renderExpression(allocator, ais, tree, rhs, space);
1199 } else {1146 } else {
1200 return renderToken(tree, stream, flow_expr.ltoken, indent, start_col, space);1147 return renderToken(tree, ais, flow_expr.ltoken, space);
1201 }1148 }
1202 },1149 },
12031150
1204 .Payload => {1151 .Payload => {
1205 const payload = @fieldParentPtr(ast.Node.Payload, "base", base);1152 const payload = @fieldParentPtr(ast.Node.Payload, "base", base);
12061153
1207 try renderToken(tree, stream, payload.lpipe, indent, start_col, Space.None);1154 try renderToken(tree, ais, payload.lpipe, Space.None);
1208 try renderExpression(allocator, stream, tree, indent, start_col, payload.error_symbol, Space.None);1155 try renderExpression(allocator, ais, tree, payload.error_symbol, Space.None);
1209 return renderToken(tree, stream, payload.rpipe, indent, start_col, space);1156 return renderToken(tree, ais, payload.rpipe, space);
1210 },1157 },
12111158
1212 .PointerPayload => {1159 .PointerPayload => {
1213 const payload = @fieldParentPtr(ast.Node.PointerPayload, "base", base);1160 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);
1216 if (payload.ptr_token) |ptr_token| {1163 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);
1218 }1165 }
1219 try renderExpression(allocator, stream, tree, indent, start_col, payload.value_symbol, Space.None);1166 try renderExpression(allocator, ais, tree, payload.value_symbol, Space.None);
1220 return renderToken(tree, stream, payload.rpipe, indent, start_col, space);1167 return renderToken(tree, ais, payload.rpipe, space);
1221 },1168 },
12221169
1223 .PointerIndexPayload => {1170 .PointerIndexPayload => {
1224 const payload = @fieldParentPtr(ast.Node.PointerIndexPayload, "base", base);1171 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);
1227 if (payload.ptr_token) |ptr_token| {1174 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);
1229 }1176 }
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
1232 if (payload.index_symbol) |index_symbol| {1179 if (payload.index_symbol) |index_symbol| {
1233 const comma = tree.nextToken(payload.value_symbol.lastToken());1180 const comma = tree.nextToken(payload.value_symbol.lastToken());
12341181
1235 try renderToken(tree, stream, comma, indent, start_col, Space.Space);1182 try renderToken(tree, ais, comma, Space.Space);
1236 try renderExpression(allocator, stream, tree, indent, start_col, index_symbol, Space.None);1183 try renderExpression(allocator, ais, tree, index_symbol, Space.None);
1237 }1184 }
12381185
1239 return renderToken(tree, stream, payload.rpipe, indent, start_col, space);1186 return renderToken(tree, ais, payload.rpipe, space);
1240 },1187 },
12411188
1242 .GroupedExpression => {1189 .GroupedExpression => {
1243 const grouped_expr = @fieldParentPtr(ast.Node.GroupedExpression, "base", base);1190 const grouped_expr = @fieldParentPtr(ast.Node.GroupedExpression, "base", base);
12441191
1245 try renderToken(tree, stream, grouped_expr.lparen, indent, start_col, Space.None);1192 try renderToken(tree, ais, grouped_expr.lparen, Space.None);
1246 try renderExpression(allocator, stream, tree, indent, start_col, grouped_expr.expr, Space.None);1193 {
1247 return renderToken(tree, stream, grouped_expr.rparen, indent, start_col, space);1194 ais.pushIndentOneShot();
1195 try renderExpression(allocator, ais, tree, grouped_expr.expr, Space.None);
1196 }
1197 return renderToken(tree, ais, grouped_expr.rparen, space);
1248 },1198 },
12491199
1250 .FieldInitializer => {1200 .FieldInitializer => {
1251 const field_init = @fieldParentPtr(ast.Node.FieldInitializer, "base", base);1201 const field_init = @fieldParentPtr(ast.Node.FieldInitializer, "base", base);
12521202
1253 try renderToken(tree, stream, field_init.period_token, indent, start_col, Space.None); // .1203 try renderToken(tree, ais, field_init.period_token, Space.None); // .
1254 try renderToken(tree, stream, field_init.name_token, indent, start_col, Space.Space); // name1204 try renderToken(tree, ais, field_init.name_token, Space.Space); // name
1255 try renderToken(tree, stream, tree.nextToken(field_init.name_token), indent, start_col, Space.Space); // =1205 try renderToken(tree, ais, tree.nextToken(field_init.name_token), Space.Space); // =
1256 return renderExpression(allocator, stream, tree, indent, start_col, field_init.expr, space);1206 return renderExpression(allocator, ais, tree, field_init.expr, space);
1257 },1207 },
12581208
1259 .ContainerDecl => {1209 .ContainerDecl => {
1260 const container_decl = @fieldParentPtr(ast.Node.ContainerDecl, "base", base);1210 const container_decl = @fieldParentPtr(ast.Node.ContainerDecl, "base", base);
12611211
1262 if (container_decl.layout_token) |layout_token| {1212 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);
1264 }1214 }
12651215
1266 switch (container_decl.init_arg_expr) {1216 switch (container_decl.init_arg_expr) {
1267 .None => {1217 .None => {
1268 try renderToken(tree, stream, container_decl.kind_token, indent, start_col, Space.Space); // union1218 try renderToken(tree, ais, container_decl.kind_token, Space.Space); // union
1269 },1219 },
1270 .Enum => |enum_tag_type| {1220 .Enum => |enum_tag_type| {
1271 try renderToken(tree, stream, container_decl.kind_token, indent, start_col, Space.None); // union1221 try renderToken(tree, ais, container_decl.kind_token, Space.None); // union
12721222
1273 const lparen = tree.nextToken(container_decl.kind_token);1223 const lparen = tree.nextToken(container_decl.kind_token);
1274 const enum_token = tree.nextToken(lparen);1224 const enum_token = tree.nextToken(lparen);
12751225
1276 try renderToken(tree, stream, lparen, indent, start_col, Space.None); // (1226 try renderToken(tree, ais, lparen, Space.None); // (
1277 try renderToken(tree, stream, enum_token, indent, start_col, Space.None); // enum1227 try renderToken(tree, ais, enum_token, Space.None); // enum
12781228
1279 if (enum_tag_type) |expr| {1229 if (enum_tag_type) |expr| {
1280 try renderToken(tree, stream, tree.nextToken(enum_token), indent, start_col, Space.None); // (1230 try renderToken(tree, ais, tree.nextToken(enum_token), Space.None); // (
1281 try renderExpression(allocator, stream, tree, indent, start_col, expr, Space.None);1231 try renderExpression(allocator, ais, tree, expr, Space.None);
12821232
1283 const rparen = tree.nextToken(expr.lastToken());1233 const rparen = tree.nextToken(expr.lastToken());
1284 try renderToken(tree, stream, rparen, indent, start_col, Space.None); // )1234 try renderToken(tree, ais, rparen, Space.None); // )
1285 try renderToken(tree, stream, tree.nextToken(rparen), indent, start_col, Space.Space); // )1235 try renderToken(tree, ais, tree.nextToken(rparen), Space.Space); // )
1286 } else {1236 } 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); // )
1288 }1238 }
1289 },1239 },
1290 .Type => |type_expr| {1240 .Type => |type_expr| {
1291 try renderToken(tree, stream, container_decl.kind_token, indent, start_col, Space.None); // union1241 try renderToken(tree, ais, container_decl.kind_token, Space.None); // union
12921242
1293 const lparen = tree.nextToken(container_decl.kind_token);1243 const lparen = tree.nextToken(container_decl.kind_token);
1294 const rparen = tree.nextToken(type_expr.lastToken());1244 const rparen = tree.nextToken(type_expr.lastToken());
12951245
1296 try renderToken(tree, stream, lparen, indent, start_col, Space.None); // (1246 try renderToken(tree, ais, lparen, Space.None); // (
1297 try renderExpression(allocator, stream, tree, indent, start_col, type_expr, Space.None);1247 try renderExpression(allocator, ais, tree, type_expr, Space.None);
1298 try renderToken(tree, stream, rparen, indent, start_col, Space.Space); // )1248 try renderToken(tree, ais, rparen, Space.Space); // )
1299 },1249 },
1300 }1250 }
13011251
1302 if (container_decl.fields_and_decls_len == 0) {1252 if (container_decl.fields_and_decls_len == 0) {
1303 try renderToken(tree, stream, container_decl.lbrace_token, indent + indent_delta, start_col, Space.None); // {1253 {
1304 return renderToken(tree, stream, container_decl.rbrace_token, indent, start_col, space); // }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); // }
1305 }1259 }
13061260
1307 const src_has_trailing_comma = blk: {1261 const src_has_trailing_comma = blk: {
...@@ -1332,43 +1286,39 @@ fn renderExpression(...@@ -1332,43 +1286,39 @@ fn renderExpression(
13321286
1333 if (src_has_trailing_comma or !src_has_only_fields) {1287 if (src_has_trailing_comma or !src_has_only_fields) {
1334 // One declaration per line1288 // One declaration per line
1335 const new_indent = indent + indent_delta;1289 ais.pushIndentNextLine();
1336 try renderToken(tree, stream, container_decl.lbrace_token, new_indent, start_col, .Newline); // {1290 defer ais.popIndent();
1291 try renderToken(tree, ais, container_decl.lbrace_token, .Newline); // {
13371292
1338 for (fields_and_decls) |decl, i| {1293 for (fields_and_decls) |decl, i| {
1339 try stream.writeByteNTimes(' ', new_indent);1294 try renderContainerDecl(allocator, ais, tree, decl, .Newline);
1340 try renderContainerDecl(allocator, stream, tree, new_indent, start_col, decl, .Newline);
13411295
1342 if (i + 1 < fields_and_decls.len) {1296 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]);
1344 }1298 }
1345 }1299 }
1346
1347 try stream.writeByteNTimes(' ', indent);
1348 } else if (src_has_newline) {1300 } else if (src_has_newline) {
1349 // All the declarations on the same line, but place the items on1301 // All the declarations on the same line, but place the items on
1350 // their own line1302 // 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;1305 ais.pushIndent();
1354 try stream.writeByteNTimes(' ', new_indent);1306 defer ais.popIndent();
13551307
1356 for (fields_and_decls) |decl, i| {1308 for (fields_and_decls) |decl, i| {
1357 const space_after_decl: Space = if (i + 1 >= fields_and_decls.len) .Newline else .Space;1309 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);
1359 }1311 }
1360
1361 try stream.writeByteNTimes(' ', indent);
1362 } else {1312 } else {
1363 // All the declarations on the same line1313 // 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
1366 for (fields_and_decls) |decl| {1316 for (fields_and_decls) |decl| {
1367 try renderContainerDecl(allocator, stream, tree, indent, start_col, decl, .Space);1317 try renderContainerDecl(allocator, ais, tree, decl, .Space);
1368 }1318 }
1369 }1319 }
13701320
1371 return renderToken(tree, stream, container_decl.rbrace_token, indent, start_col, space); // }1321 return renderToken(tree, ais, container_decl.rbrace_token, space); // }
1372 },1322 },
13731323
1374 .ErrorSetDecl => {1324 .ErrorSetDecl => {
...@@ -1377,9 +1327,9 @@ fn renderExpression(...@@ -1377,9 +1327,9 @@ fn renderExpression(
1377 const lbrace = tree.nextToken(err_set_decl.error_token);1327 const lbrace = tree.nextToken(err_set_decl.error_token);
13781328
1379 if (err_set_decl.decls_len == 0) {1329 if (err_set_decl.decls_len == 0) {
1380 try renderToken(tree, stream, err_set_decl.error_token, indent, start_col, Space.None);1330 try renderToken(tree, ais, err_set_decl.error_token, Space.None);
1381 try renderToken(tree, stream, lbrace, indent, start_col, Space.None);1331 try renderToken(tree, ais, lbrace, Space.None);
1382 return renderToken(tree, stream, err_set_decl.rbrace_token, indent, start_col, space);1332 return renderToken(tree, ais, err_set_decl.rbrace_token, space);
1383 }1333 }
13841334
1385 if (err_set_decl.decls_len == 1) blk: {1335 if (err_set_decl.decls_len == 1) blk: {
...@@ -1393,13 +1343,13 @@ fn renderExpression(...@@ -1393,13 +1343,13 @@ fn renderExpression(
1393 break :blk;1343 break :blk;
1394 }1344 }
13951345
1396 try renderToken(tree, stream, err_set_decl.error_token, indent, start_col, Space.None); // error1346 try renderToken(tree, ais, err_set_decl.error_token, Space.None); // error
1397 try renderToken(tree, stream, lbrace, indent, start_col, Space.None); // {1347 try renderToken(tree, ais, lbrace, Space.None); // {
1398 try renderExpression(allocator, stream, tree, indent, start_col, node, Space.None);1348 try renderExpression(allocator, ais, tree, node, Space.None);
1399 return renderToken(tree, stream, err_set_decl.rbrace_token, indent, start_col, space); // }1349 return renderToken(tree, ais, err_set_decl.rbrace_token, space); // }
1400 }1350 }
14011351
1402 try renderToken(tree, stream, err_set_decl.error_token, indent, start_col, Space.None); // error1352 try renderToken(tree, ais, err_set_decl.error_token, Space.None); // error
14031353
1404 const src_has_trailing_comma = blk: {1354 const src_has_trailing_comma = blk: {
1405 const maybe_comma = tree.prevToken(err_set_decl.rbrace_token);1355 const maybe_comma = tree.prevToken(err_set_decl.rbrace_token);
...@@ -1407,72 +1357,66 @@ fn renderExpression(...@@ -1407,72 +1357,66 @@ fn renderExpression(
1407 };1357 };
14081358
1409 if (src_has_trailing_comma) {1359 if (src_has_trailing_comma) {
1410 try renderToken(tree, stream, lbrace, indent, start_col, Space.Newline); // {1360 {
1411 const new_indent = indent + indent_delta;1361 ais.pushIndent();
14121362 defer ais.popIndent();
1413 const decls = err_set_decl.decls();1363
1414 for (decls) |node, i| {1364 try renderToken(tree, ais, lbrace, Space.Newline); // {
1415 try stream.writeByteNTimes(' ', new_indent);1365 const decls = err_set_decl.decls();
14161366 for (decls) |node, i| {
1417 if (i + 1 < decls.len) {1367 if (i + 1 < decls.len) {
1418 try renderExpression(allocator, stream, tree, new_indent, start_col, node, Space.None);1368 try renderExpression(allocator, ais, tree, node, Space.None);
1419 try renderToken(tree, stream, tree.nextToken(node.lastToken()), new_indent, start_col, Space.Newline); // ,1369 try renderToken(tree, ais, tree.nextToken(node.lastToken()), Space.Newline); // ,
14201370
1421 try renderExtraNewline(tree, stream, start_col, decls[i + 1]);1371 try renderExtraNewline(tree, ais, decls[i + 1]);
1422 } else {1372 } else {
1423 try renderExpression(allocator, stream, tree, new_indent, start_col, node, Space.Comma);1373 try renderExpression(allocator, ais, tree, node, Space.Comma);
1374 }
1424 }1375 }
1425 }1376 }
14261377
1427 try stream.writeByteNTimes(' ', indent);1378 return renderToken(tree, ais, err_set_decl.rbrace_token, space); // }
1428 return renderToken(tree, stream, err_set_decl.rbrace_token, indent, start_col, space); // }
1429 } else {1379 } else {
1430 try renderToken(tree, stream, lbrace, indent, start_col, Space.Space); // {1380 try renderToken(tree, ais, lbrace, Space.Space); // {
14311381
1432 const decls = err_set_decl.decls();1382 const decls = err_set_decl.decls();
1433 for (decls) |node, i| {1383 for (decls) |node, i| {
1434 if (i + 1 < decls.len) {1384 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
1437 const comma_token = tree.nextToken(node.lastToken());1387 const comma_token = tree.nextToken(node.lastToken());
1438 assert(tree.token_ids[comma_token] == .Comma);1388 assert(tree.token_ids[comma_token] == .Comma);
1439 try renderToken(tree, stream, comma_token, indent, start_col, Space.Space); // ,1389 try renderToken(tree, ais, comma_token, Space.Space); // ,
1440 try renderExtraNewline(tree, stream, start_col, decls[i + 1]);1390 try renderExtraNewline(tree, ais, decls[i + 1]);
1441 } else {1391 } else {
1442 try renderExpression(allocator, stream, tree, indent, start_col, node, Space.Space);1392 try renderExpression(allocator, ais, tree, node, Space.Space);
1443 }1393 }
1444 }1394 }
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); // }
1447 }1397 }
1448 },1398 },
14491399
1450 .ErrorTag => {1400 .ErrorTag => {
1451 const tag = @fieldParentPtr(ast.Node.ErrorTag, "base", base);1401 const tag = @fieldParentPtr(ast.Node.ErrorTag, "base", base);
14521402
1453 try renderDocComments(tree, stream, tag, tag.doc_comments, indent, start_col);1403 try renderDocComments(tree, ais, tag, tag.doc_comments);
1454 return renderToken(tree, stream, tag.name_token, indent, start_col, space); // name1404 return renderToken(tree, ais, tag.name_token, space); // name
1455 },1405 },
14561406
1457 .MultilineStringLiteral => {1407 .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
1460 const multiline_str_literal = @fieldParentPtr(ast.Node.MultilineStringLiteral, "base", base);1408 const multiline_str_literal = @fieldParentPtr(ast.Node.MultilineStringLiteral, "base", base);
14611409
1462 var skip_first_indent = true;1410 {
1463 if (tree.token_ids[multiline_str_literal.firstToken() - 1] != .LineComment) {1411 const locked_indents = ais.lockOneShotIndent();
1464 try stream.print("\n", .{});1412 defer {
1465 skip_first_indent = false;1413 var i: u8 = 0;
1466 }1414 while (i < locked_indents) : (i += 1) ais.popIndent();
1467
1468 for (multiline_str_literal.lines()) |t| {
1469 if (!skip_first_indent) {
1470 try stream.writeByteNTimes(' ', indent + indent_delta);
1471 }1415 }
1472 try renderToken(tree, stream, t, indent, start_col, Space.None);1416 try ais.maybeInsertNewline();
1473 skip_first_indent = false;1417
1418 for (multiline_str_literal.lines()) |t| try renderToken(tree, ais, t, Space.None);
1474 }1419 }
1475 try stream.writeByteNTimes(' ', indent);
1476 },1420 },
14771421
1478 .BuiltinCall => {1422 .BuiltinCall => {
...@@ -1480,9 +1424,9 @@ fn renderExpression(...@@ -1480,9 +1424,9 @@ fn renderExpression(
14801424
1481 // TODO remove after 0.7.0 release1425 // TODO remove after 0.7.0 release
1482 if (mem.eql(u8, tree.tokenSlice(builtin_call.builtin_token), "@OpaqueType"))1426 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); // @name1429 try renderToken(tree, ais, builtin_call.builtin_token, Space.None); // @name
14861430
1487 const src_params_trailing_comma = blk: {1431 const src_params_trailing_comma = blk: {
1488 if (builtin_call.params_len < 2) break :blk false;1432 if (builtin_call.params_len < 2) break :blk false;
...@@ -1494,31 +1438,30 @@ fn renderExpression(...@@ -1494,31 +1438,30 @@ fn renderExpression(
1494 const lparen = tree.nextToken(builtin_call.builtin_token);1438 const lparen = tree.nextToken(builtin_call.builtin_token);
14951439
1496 if (!src_params_trailing_comma) {1440 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
1499 // render all on one line, no trailing comma1443 // render all on one line, no trailing comma
1500 const params = builtin_call.params();1444 const params = builtin_call.params();
1501 for (params) |param_node, i| {1445 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
1504 if (i + 1 < params.len) {1448 if (i + 1 < params.len) {
1505 const comma_token = tree.nextToken(param_node.lastToken());1449 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); // ,
1507 }1451 }
1508 }1452 }
1509 } else {1453 } else {
1510 // one param per line1454 // one param per line
1511 const new_indent = indent + indent_delta;1455 ais.pushIndent();
1512 try renderToken(tree, stream, lparen, new_indent, start_col, Space.Newline); // (1456 defer ais.popIndent();
1457 try renderToken(tree, ais, lparen, Space.Newline); // (
15131458
1514 for (builtin_call.params()) |param_node| {1459 for (builtin_call.params()) |param_node| {
1515 try stream.writeByteNTimes(' ', new_indent);1460 try renderExpression(allocator, ais, tree, param_node, Space.Comma);
1516 try renderExpression(allocator, stream, tree, indent, start_col, param_node, Space.Comma);
1517 }1461 }
1518 try stream.writeByteNTimes(' ', indent);
1519 }1462 }
15201463
1521 return renderToken(tree, stream, builtin_call.rparen_token, indent, start_col, space); // )1464 return renderToken(tree, ais, builtin_call.rparen_token, space); // )
1522 },1465 },
15231466
1524 .FnProto => {1467 .FnProto => {
...@@ -1528,24 +1471,24 @@ fn renderExpression(...@@ -1528,24 +1471,24 @@ fn renderExpression(
1528 const visib_token = tree.token_ids[visib_token_index];1471 const visib_token = tree.token_ids[visib_token_index];
1529 assert(visib_token == .Keyword_pub or visib_token == .Keyword_export);1472 assert(visib_token == .Keyword_pub or visib_token == .Keyword_export);
15301473
1531 try renderToken(tree, stream, visib_token_index, indent, start_col, Space.Space); // pub1474 try renderToken(tree, ais, visib_token_index, Space.Space); // pub
1532 }1475 }
15331476
1534 if (fn_proto.getExternExportInlineToken()) |extern_export_inline_token| {1477 if (fn_proto.getExternExportInlineToken()) |extern_export_inline_token| {
1535 if (fn_proto.getIsExternPrototype() == null)1478 if (fn_proto.getIsExternPrototype() == null)
1536 try renderToken(tree, stream, extern_export_inline_token, indent, start_col, Space.Space); // extern/export/inline1479 try renderToken(tree, ais, extern_export_inline_token, Space.Space); // extern/export/inline
1537 }1480 }
15381481
1539 if (fn_proto.getLibName()) |lib_name| {1482 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);
1541 }1484 }
15421485
1543 const lparen = if (fn_proto.getNameToken()) |name_token| blk: {1486 const lparen = if (fn_proto.getNameToken()) |name_token| blk: {
1544 try renderToken(tree, stream, fn_proto.fn_token, indent, start_col, Space.Space); // fn1487 try renderToken(tree, ais, fn_proto.fn_token, Space.Space); // fn
1545 try renderToken(tree, stream, name_token, indent, start_col, Space.None); // name1488 try renderToken(tree, ais, name_token, Space.None); // name
1546 break :blk tree.nextToken(name_token);1489 break :blk tree.nextToken(name_token);
1547 } else blk: {1490 } else blk: {
1548 try renderToken(tree, stream, fn_proto.fn_token, indent, start_col, Space.Space); // fn1491 try renderToken(tree, ais, fn_proto.fn_token, Space.Space); // fn
1549 break :blk tree.nextToken(fn_proto.fn_token);1492 break :blk tree.nextToken(fn_proto.fn_token);
1550 };1493 };
1551 assert(tree.token_ids[lparen] == .LParen);1494 assert(tree.token_ids[lparen] == .LParen);
...@@ -1572,47 +1515,45 @@ fn renderExpression(...@@ -1572,47 +1515,45 @@ fn renderExpression(
1572 };1515 };
15731516
1574 if (!src_params_trailing_comma) {1517 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
1577 // render all on one line, no trailing comma1520 // render all on one line, no trailing comma
1578 for (fn_proto.params()) |param_decl, i| {1521 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
1581 if (i + 1 < fn_proto.params_len or fn_proto.getVarArgsToken() != null) {1524 if (i + 1 < fn_proto.params_len or fn_proto.getVarArgsToken() != null) {
1582 const comma = tree.nextToken(param_decl.lastToken());1525 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); // ,
1584 }1527 }
1585 }1528 }
1586 if (fn_proto.getVarArgsToken()) |var_args_token| {1529 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);
1588 }1531 }
1589 } else {1532 } else {
1590 // one param per line1533 // one param per line
1591 const new_indent = indent + indent_delta;1534 ais.pushIndent();
1592 try renderToken(tree, stream, lparen, new_indent, start_col, Space.Newline); // (1535 defer ais.popIndent();
1536 try renderToken(tree, ais, lparen, Space.Newline); // (
15931537
1594 for (fn_proto.params()) |param_decl| {1538 for (fn_proto.params()) |param_decl| {
1595 try stream.writeByteNTimes(' ', new_indent);1539 try renderParamDecl(allocator, ais, tree, param_decl, Space.Comma);
1596 try renderParamDecl(allocator, stream, tree, new_indent, start_col, param_decl, Space.Comma);
1597 }1540 }
1598 if (fn_proto.getVarArgsToken()) |var_args_token| {1541 if (fn_proto.getVarArgsToken()) |var_args_token| {
1599 try stream.writeByteNTimes(' ', new_indent);1542 try renderToken(tree, ais, var_args_token, Space.Comma);
1600 try renderToken(tree, stream, var_args_token, new_indent, start_col, Space.Comma);
1601 }1543 }
1602 try stream.writeByteNTimes(' ', indent);
1603 }1544 }
16041545
1605 try renderToken(tree, stream, rparen, indent, start_col, Space.Space); // )1546 try renderToken(tree, ais, rparen, Space.Space); // )
16061547
1607 if (fn_proto.getAlignExpr()) |align_expr| {1548 if (fn_proto.getAlignExpr()) |align_expr| {
1608 const align_rparen = tree.nextToken(align_expr.lastToken());1549 const align_rparen = tree.nextToken(align_expr.lastToken());
1609 const align_lparen = tree.prevToken(align_expr.firstToken());1550 const align_lparen = tree.prevToken(align_expr.firstToken());
1610 const align_kw = tree.prevToken(align_lparen);1551 const align_kw = tree.prevToken(align_lparen);
16111552
1612 try renderToken(tree, stream, align_kw, indent, start_col, Space.None); // align1553 try renderToken(tree, ais, align_kw, Space.None); // align
1613 try renderToken(tree, stream, align_lparen, indent, start_col, Space.None); // (1554 try renderToken(tree, ais, align_lparen, Space.None); // (
1614 try renderExpression(allocator, stream, tree, indent, start_col, align_expr, Space.None);1555 try renderExpression(allocator, ais, tree, align_expr, Space.None);
1615 try renderToken(tree, stream, align_rparen, indent, start_col, Space.Space); // )1556 try renderToken(tree, ais, align_rparen, Space.Space); // )
1616 }1557 }
16171558
1618 if (fn_proto.getSectionExpr()) |section_expr| {1559 if (fn_proto.getSectionExpr()) |section_expr| {
...@@ -1620,10 +1561,10 @@ fn renderExpression(...@@ -1620,10 +1561,10 @@ fn renderExpression(
1620 const section_lparen = tree.prevToken(section_expr.firstToken());1561 const section_lparen = tree.prevToken(section_expr.firstToken());
1621 const section_kw = tree.prevToken(section_lparen);1562 const section_kw = tree.prevToken(section_lparen);
16221563
1623 try renderToken(tree, stream, section_kw, indent, start_col, Space.None); // section1564 try renderToken(tree, ais, section_kw, Space.None); // section
1624 try renderToken(tree, stream, section_lparen, indent, start_col, Space.None); // (1565 try renderToken(tree, ais, section_lparen, Space.None); // (
1625 try renderExpression(allocator, stream, tree, indent, start_col, section_expr, Space.None);1566 try renderExpression(allocator, ais, tree, section_expr, Space.None);
1626 try renderToken(tree, stream, section_rparen, indent, start_col, Space.Space); // )1567 try renderToken(tree, ais, section_rparen, Space.Space); // )
1627 }1568 }
16281569
1629 if (fn_proto.getCallconvExpr()) |callconv_expr| {1570 if (fn_proto.getCallconvExpr()) |callconv_expr| {
...@@ -1631,23 +1572,23 @@ fn renderExpression(...@@ -1631,23 +1572,23 @@ fn renderExpression(
1631 const callconv_lparen = tree.prevToken(callconv_expr.firstToken());1572 const callconv_lparen = tree.prevToken(callconv_expr.firstToken());
1632 const callconv_kw = tree.prevToken(callconv_lparen);1573 const callconv_kw = tree.prevToken(callconv_lparen);
16331574
1634 try renderToken(tree, stream, callconv_kw, indent, start_col, Space.None); // callconv1575 try renderToken(tree, ais, callconv_kw, Space.None); // callconv
1635 try renderToken(tree, stream, callconv_lparen, indent, start_col, Space.None); // (1576 try renderToken(tree, ais, callconv_lparen, Space.None); // (
1636 try renderExpression(allocator, stream, tree, indent, start_col, callconv_expr, Space.None);1577 try renderExpression(allocator, ais, tree, callconv_expr, Space.None);
1637 try renderToken(tree, stream, callconv_rparen, indent, start_col, Space.Space); // )1578 try renderToken(tree, ais, callconv_rparen, Space.Space); // )
1638 } else if (fn_proto.getIsExternPrototype() != null) {1579 } else if (fn_proto.getIsExternPrototype() != null) {
1639 try stream.writeAll("callconv(.C) ");1580 try ais.writer().writeAll("callconv(.C) ");
1640 } else if (fn_proto.getIsAsync() != null) {1581 } else if (fn_proto.getIsAsync() != null) {
1641 try stream.writeAll("callconv(.Async) ");1582 try ais.writer().writeAll("callconv(.Async) ");
1642 }1583 }
16431584
1644 switch (fn_proto.return_type) {1585 switch (fn_proto.return_type) {
1645 .Explicit => |node| {1586 .Explicit => |node| {
1646 return renderExpression(allocator, stream, tree, indent, start_col, node, space);1587 return renderExpression(allocator, ais, tree, node, space);
1647 },1588 },
1648 .InferErrorSet => |node| {1589 .InferErrorSet => |node| {
1649 try renderToken(tree, stream, tree.prevToken(node.firstToken()), indent, start_col, Space.None); // !1590 try renderToken(tree, ais, tree.prevToken(node.firstToken()), Space.None); // !
1650 return renderExpression(allocator, stream, tree, indent, start_col, node, space);1591 return renderExpression(allocator, ais, tree, node, space);
1651 },1592 },
1652 .Invalid => unreachable,1593 .Invalid => unreachable,
1653 }1594 }
...@@ -1657,11 +1598,11 @@ fn renderExpression(...@@ -1657,11 +1598,11 @@ fn renderExpression(
1657 const anyframe_type = @fieldParentPtr(ast.Node.AnyFrameType, "base", base);1598 const anyframe_type = @fieldParentPtr(ast.Node.AnyFrameType, "base", base);
16581599
1659 if (anyframe_type.result) |result| {1600 if (anyframe_type.result) |result| {
1660 try renderToken(tree, stream, anyframe_type.anyframe_token, indent, start_col, Space.None); // anyframe1601 try renderToken(tree, ais, anyframe_type.anyframe_token, Space.None); // anyframe
1661 try renderToken(tree, stream, result.arrow_token, indent, start_col, Space.None); // ->1602 try renderToken(tree, ais, result.arrow_token, Space.None); // ->
1662 return renderExpression(allocator, stream, tree, indent, start_col, result.return_type, space);1603 return renderExpression(allocator, ais, tree, result.return_type, space);
1663 } else {1604 } else {
1664 return renderToken(tree, stream, anyframe_type.anyframe_token, indent, start_col, space); // anyframe1605 return renderToken(tree, ais, anyframe_type.anyframe_token, space); // anyframe
1665 }1606 }
1666 },1607 },
16671608
...@@ -1670,38 +1611,38 @@ fn renderExpression(...@@ -1670,38 +1611,38 @@ fn renderExpression(
1670 .Switch => {1611 .Switch => {
1671 const switch_node = @fieldParentPtr(ast.Node.Switch, "base", base);1612 const switch_node = @fieldParentPtr(ast.Node.Switch, "base", base);
16721613
1673 try renderToken(tree, stream, switch_node.switch_token, indent, start_col, Space.Space); // switch1614 try renderToken(tree, ais, switch_node.switch_token, Space.Space); // switch
1674 try renderToken(tree, stream, tree.nextToken(switch_node.switch_token), indent, start_col, Space.None); // (1615 try renderToken(tree, ais, tree.nextToken(switch_node.switch_token), Space.None); // (
16751616
1676 const rparen = tree.nextToken(switch_node.expr.lastToken());1617 const rparen = tree.nextToken(switch_node.expr.lastToken());
1677 const lbrace = tree.nextToken(rparen);1618 const lbrace = tree.nextToken(rparen);
16781619
1679 if (switch_node.cases_len == 0) {1620 if (switch_node.cases_len == 0) {
1680 try renderExpression(allocator, stream, tree, indent, start_col, switch_node.expr, Space.None);1621 try renderExpression(allocator, ais, tree, switch_node.expr, Space.None);
1681 try renderToken(tree, stream, rparen, indent, start_col, Space.Space); // )1622 try renderToken(tree, ais, rparen, Space.Space); // )
1682 try renderToken(tree, stream, lbrace, indent, start_col, Space.None); // {1623 try renderToken(tree, ais, lbrace, Space.None); // {
1683 return renderToken(tree, stream, switch_node.rbrace, indent, start_col, space); // }1624 return renderToken(tree, ais, switch_node.rbrace, space); // }
1684 }1625 }
16851626
1686 try renderExpression(allocator, stream, tree, indent, start_col, switch_node.expr, Space.None);1627 try renderExpression(allocator, ais, tree, switch_node.expr, Space.None);
16871628 try renderToken(tree, ais, rparen, Space.Space); // )
1688 const new_indent = indent + indent_delta;
16891629
1690 try renderToken(tree, stream, rparen, indent, start_col, Space.Space); // )1630 {
1691 try renderToken(tree, stream, lbrace, new_indent, start_col, Space.Newline); // {1631 ais.pushIndentNextLine();
1632 defer ais.popIndent();
1633 try renderToken(tree, ais, lbrace, Space.Newline); // {
16921634
1693 const cases = switch_node.cases();1635 const cases = switch_node.cases();
1694 for (cases) |node, i| {1636 for (cases) |node, i| {
1695 try stream.writeByteNTimes(' ', new_indent);1637 try renderExpression(allocator, ais, tree, node, Space.Comma);
1696 try renderExpression(allocator, stream, tree, new_indent, start_col, node, Space.Comma);
16971638
1698 if (i + 1 < cases.len) {1639 if (i + 1 < cases.len) {
1699 try renderExtraNewline(tree, stream, start_col, cases[i + 1]);1640 try renderExtraNewline(tree, ais, cases[i + 1]);
1641 }
1700 }1642 }
1701 }1643 }
17021644
1703 try stream.writeByteNTimes(' ', indent);1645 return renderToken(tree, ais, switch_node.rbrace, space); // }
1704 return renderToken(tree, stream, switch_node.rbrace, indent, start_col, space); // }
1705 },1646 },
17061647
1707 .SwitchCase => {1648 .SwitchCase => {
...@@ -1718,43 +1659,41 @@ fn renderExpression(...@@ -1718,43 +1659,41 @@ fn renderExpression(
1718 const items = switch_case.items();1659 const items = switch_case.items();
1719 for (items) |node, i| {1660 for (items) |node, i| {
1720 if (i + 1 < items.len) {1661 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
1723 const comma_token = tree.nextToken(node.lastToken());1664 const comma_token = tree.nextToken(node.lastToken());
1724 try renderToken(tree, stream, comma_token, indent, start_col, Space.Space); // ,1665 try renderToken(tree, ais, comma_token, Space.Space); // ,
1725 try renderExtraNewline(tree, stream, start_col, items[i + 1]);1666 try renderExtraNewline(tree, ais, items[i + 1]);
1726 } else {1667 } else {
1727 try renderExpression(allocator, stream, tree, indent, start_col, node, Space.Space);1668 try renderExpression(allocator, ais, tree, node, Space.Space);
1728 }1669 }
1729 }1670 }
1730 } else {1671 } else {
1731 const items = switch_case.items();1672 const items = switch_case.items();
1732 for (items) |node, i| {1673 for (items) |node, i| {
1733 if (i + 1 < items.len) {1674 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
1736 const comma_token = tree.nextToken(node.lastToken());1677 const comma_token = tree.nextToken(node.lastToken());
1737 try renderToken(tree, stream, comma_token, indent, start_col, Space.Newline); // ,1678 try renderToken(tree, ais, comma_token, Space.Newline); // ,
1738 try renderExtraNewline(tree, stream, start_col, items[i + 1]);1679 try renderExtraNewline(tree, ais, items[i + 1]);
1739 try stream.writeByteNTimes(' ', indent);
1740 } else {1680 } else {
1741 try renderExpression(allocator, stream, tree, indent, start_col, node, Space.Comma);1681 try renderExpression(allocator, ais, tree, node, Space.Comma);
1742 try stream.writeByteNTimes(' ', indent);
1743 }1682 }
1744 }1683 }
1745 }1684 }
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
1749 if (switch_case.payload) |payload| {1688 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);
1751 }1690 }
17521691
1753 return renderExpression(allocator, stream, tree, indent, start_col, switch_case.expr, space);1692 return renderExpression(allocator, ais, tree, switch_case.expr, space);
1754 },1693 },
1755 .SwitchElse => {1694 .SwitchElse => {
1756 const switch_else = @fieldParentPtr(ast.Node.SwitchElse, "base", base);1695 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);
1758 },1697 },
1759 .Else => {1698 .Else => {
1760 const else_node = @fieldParentPtr(ast.Node.Else, "base", base);1699 const else_node = @fieldParentPtr(ast.Node.Else, "base", base);
...@@ -1763,37 +1702,37 @@ fn renderExpression(...@@ -1763,37 +1702,37 @@ fn renderExpression(
1763 const same_line = body_is_block or tree.tokensOnSameLine(else_node.else_token, else_node.body.lastToken());1702 const same_line = body_is_block or tree.tokensOnSameLine(else_node.else_token, else_node.body.lastToken());
17641703
1765 const after_else_space = if (same_line or else_node.payload != null) Space.Space else Space.Newline;1704 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
1768 if (else_node.payload) |payload| {1707 if (else_node.payload) |payload| {
1769 const payload_space = if (same_line) Space.Space else Space.Newline;1708 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);
1771 }1710 }
17721711
1773 if (same_line) {1712 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);
1775 }1718 }
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);
1780 },1719 },
17811720
1782 .While => {1721 .While => {
1783 const while_node = @fieldParentPtr(ast.Node.While, "base", base);1722 const while_node = @fieldParentPtr(ast.Node.While, "base", base);
17841723
1785 if (while_node.label) |label| {1724 if (while_node.label) |label| {
1786 try renderToken(tree, stream, label, indent, start_col, Space.None); // label1725 try renderToken(tree, ais, label, Space.None); // label
1787 try renderToken(tree, stream, tree.nextToken(label), indent, start_col, Space.Space); // :1726 try renderToken(tree, ais, tree.nextToken(label), Space.Space); // :
1788 }1727 }
17891728
1790 if (while_node.inline_token) |inline_token| {1729 if (while_node.inline_token) |inline_token| {
1791 try renderToken(tree, stream, inline_token, indent, start_col, Space.Space); // inline1730 try renderToken(tree, ais, inline_token, Space.Space); // inline
1792 }1731 }
17931732
1794 try renderToken(tree, stream, while_node.while_token, indent, start_col, Space.Space); // while1733 try renderToken(tree, ais, while_node.while_token, Space.Space); // while
1795 try renderToken(tree, stream, tree.nextToken(while_node.while_token), indent, start_col, Space.None); // (1734 try renderToken(tree, ais, tree.nextToken(while_node.while_token), Space.None); // (
1796 try renderExpression(allocator, stream, tree, indent, start_col, while_node.condition, Space.None);1735 try renderExpression(allocator, ais, tree, while_node.condition, Space.None);
17971736
1798 const cond_rparen = tree.nextToken(while_node.condition.lastToken());1737 const cond_rparen = tree.nextToken(while_node.condition.lastToken());
17991738
...@@ -1815,12 +1754,12 @@ fn renderExpression(...@@ -1815,12 +1754,12 @@ fn renderExpression(
18151754
1816 {1755 {
1817 const rparen_space = if (while_node.payload != null or while_node.continue_expr != null) Space.Space else block_start_space;1756 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); // )
1819 }1758 }
18201759
1821 if (while_node.payload) |payload| {1760 if (while_node.payload) |payload| {
1822 const payload_space = if (while_node.continue_expr != null) Space.Space else block_start_space;1761 const payload_space = Space.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);1762 try renderExpression(allocator, ais, tree, payload, payload_space);
1824 }1763 }
18251764
1826 if (while_node.continue_expr) |continue_expr| {1765 if (while_node.continue_expr) |continue_expr| {
...@@ -1828,29 +1767,22 @@ fn renderExpression(...@@ -1828,29 +1767,22 @@ fn renderExpression(
1828 const lparen = tree.prevToken(continue_expr.firstToken());1767 const lparen = tree.prevToken(continue_expr.firstToken());
1829 const colon = tree.prevToken(lparen);1768 const colon = tree.prevToken(lparen);
18301769
1831 try renderToken(tree, stream, colon, indent, start_col, Space.Space); // :1770 try renderToken(tree, ais, colon, Space.Space); // :
1832 try renderToken(tree, stream, lparen, indent, start_col, Space.None); // (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); // )
1837 }1776 }
18381777
1839 var new_indent = indent;1778 {
1840 if (block_start_space == Space.Newline) {1779 if (!body_is_block) ais.pushIndent();
1841 new_indent += indent_delta;1780 defer if (!body_is_block) ais.popIndent();
1842 try stream.writeByteNTimes(' ', new_indent);1781 try renderExpression(allocator, ais, tree, while_node.body, after_body_space);
1843 start_col.* = new_indent;
1844 }1782 }
18451783
1846 try renderExpression(allocator, stream, tree, indent, start_col, while_node.body, after_body_space);
1847
1848 if (while_node.@"else") |@"else"| {1784 if (while_node.@"else") |@"else"| {
1849 if (after_body_space == Space.Newline) {1785 return renderExpression(allocator, ais, tree, &@"else".base, space);
1850 try stream.writeByteNTimes(' ', indent);
1851 start_col.* = indent;
1852 }
1853 return renderExpression(allocator, stream, tree, indent, start_col, &@"else".base, space);
1854 }1786 }
1855 },1787 },
18561788
...@@ -1858,17 +1790,17 @@ fn renderExpression(...@@ -1858,17 +1790,17 @@ fn renderExpression(
1858 const for_node = @fieldParentPtr(ast.Node.For, "base", base);1790 const for_node = @fieldParentPtr(ast.Node.For, "base", base);
18591791
1860 if (for_node.label) |label| {1792 if (for_node.label) |label| {
1861 try renderToken(tree, stream, label, indent, start_col, Space.None); // label1793 try renderToken(tree, ais, label, Space.None); // label
1862 try renderToken(tree, stream, tree.nextToken(label), indent, start_col, Space.Space); // :1794 try renderToken(tree, ais, tree.nextToken(label), Space.Space); // :
1863 }1795 }
18641796
1865 if (for_node.inline_token) |inline_token| {1797 if (for_node.inline_token) |inline_token| {
1866 try renderToken(tree, stream, inline_token, indent, start_col, Space.Space); // inline1798 try renderToken(tree, ais, inline_token, Space.Space); // inline
1867 }1799 }
18681800
1869 try renderToken(tree, stream, for_node.for_token, indent, start_col, Space.Space); // for1801 try renderToken(tree, ais, for_node.for_token, Space.Space); // for
1870 try renderToken(tree, stream, tree.nextToken(for_node.for_token), indent, start_col, Space.None); // (1802 try renderToken(tree, ais, tree.nextToken(for_node.for_token), Space.None); // (
1871 try renderExpression(allocator, stream, tree, indent, start_col, for_node.array_expr, Space.None);1803 try renderExpression(allocator, ais, tree, for_node.array_expr, Space.None);
18721804
1873 const rparen = tree.nextToken(for_node.array_expr.lastToken());1805 const rparen = tree.nextToken(for_node.array_expr.lastToken());
18741806
...@@ -1876,10 +1808,10 @@ fn renderExpression(...@@ -1876,10 +1808,10 @@ fn renderExpression(
1876 const src_one_line_to_body = !body_is_block and tree.tokensOnSameLine(rparen, for_node.body.firstToken());1808 const src_one_line_to_body = !body_is_block and tree.tokensOnSameLine(rparen, for_node.body.firstToken());
1877 const body_on_same_line = body_is_block or src_one_line_to_body;1809 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
1881 const space_after_payload = if (body_on_same_line) Space.Space else Space.Newline;1813 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
1884 const space_after_body = blk: {1816 const space_after_body = blk: {
1885 if (for_node.@"else") |@"else"| {1817 if (for_node.@"else") |@"else"| {
...@@ -1894,13 +1826,14 @@ fn renderExpression(...@@ -1894,13 +1826,14 @@ fn renderExpression(
1894 }1826 }
1895 };1827 };
18961828
1897 const body_indent = if (body_on_same_line) indent else indent + indent_delta;1829 {
1898 if (!body_on_same_line) try stream.writeByteNTimes(' ', body_indent);1830 if (!body_on_same_line) ais.pushIndent();
1899 try renderExpression(allocator, stream, tree, body_indent, start_col, for_node.body, space_after_body); // { body }1831 defer if (!body_on_same_line) ais.popIndent();
1832 try renderExpression(allocator, ais, tree, for_node.body, space_after_body); // { body }
1833 }
19001834
1901 if (for_node.@"else") |@"else"| {1835 if (for_node.@"else") |@"else"| {
1902 if (space_after_body == Space.Newline) try stream.writeByteNTimes(' ', indent);1836 return renderExpression(allocator, ais, tree, &@"else".base, space); // else
1903 return renderExpression(allocator, stream, tree, indent, start_col, &@"else".base, space); // else
1904 }1837 }
1905 },1838 },
19061839
...@@ -1910,29 +1843,29 @@ fn renderExpression(...@@ -1910,29 +1843,29 @@ fn renderExpression(
1910 const lparen = tree.nextToken(if_node.if_token);1843 const lparen = tree.nextToken(if_node.if_token);
1911 const rparen = tree.nextToken(if_node.condition.lastToken());1844 const rparen = tree.nextToken(if_node.condition.lastToken());
19121845
1913 try renderToken(tree, stream, if_node.if_token, indent, start_col, Space.Space); // if1846 try renderToken(tree, ais, if_node.if_token, Space.Space); // if
1914 try renderToken(tree, stream, lparen, indent, start_col, Space.None); // (1847 try renderToken(tree, ais, lparen, Space.None); // (
19151848
1916 try renderExpression(allocator, stream, tree, indent, start_col, if_node.condition, Space.None); // condition1849 try renderExpression(allocator, ais, tree, if_node.condition, Space.None); // condition
19171850
1918 const body_is_if_block = if_node.body.tag == .If;1851 const body_is_if_block = if_node.body.tag == .If;
1919 const body_is_block = nodeIsBlock(if_node.body);1852 const body_is_block = nodeIsBlock(if_node.body);
19201853
1921 if (body_is_if_block) {1854 if (body_is_if_block) {
1922 try renderExtraNewline(tree, stream, start_col, if_node.body);1855 try renderExtraNewline(tree, ais, if_node.body);
1923 } else if (body_is_block) {1856 } else if (body_is_block) {
1924 const after_rparen_space = if (if_node.payload == null) Space.BlockStart else Space.Space;1857 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
1927 if (if_node.payload) |payload| {1860 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|
1929 }1862 }
19301863
1931 if (if_node.@"else") |@"else"| {1864 if (if_node.@"else") |@"else"| {
1932 try renderExpression(allocator, stream, tree, indent, start_col, if_node.body, Space.SpaceOrOutdent);1865 try renderExpression(allocator, ais, tree, if_node.body, Space.SpaceOrOutdent);
1933 return renderExpression(allocator, stream, tree, indent, start_col, &@"else".base, space);1866 return renderExpression(allocator, ais, tree, &@"else".base, space);
1934 } else {1867 } else {
1935 return renderExpression(allocator, stream, tree, indent, start_col, if_node.body, space);1868 return renderExpression(allocator, ais, tree, if_node.body, space);
1936 }1869 }
1937 }1870 }
19381871
...@@ -1940,186 +1873,184 @@ fn renderExpression(...@@ -1940,186 +1873,184 @@ fn renderExpression(
19401873
1941 if (src_has_newline) {1874 if (src_has_newline) {
1942 const after_rparen_space = if (if_node.payload == null) Space.Newline else Space.Space;1875 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
1945 if (if_node.payload) |payload| {1878 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);
1947 }1880 }
19481881
1949 const new_indent = indent + indent_delta;
1950 try stream.writeByteNTimes(' ', new_indent);
1951
1952 if (if_node.@"else") |@"else"| {1882 if (if_node.@"else") |@"else"| {
1953 const else_is_block = nodeIsBlock(@"else".body);1883 const else_is_block = nodeIsBlock(@"else".body);
1954 try renderExpression(allocator, stream, tree, new_indent, start_col, if_node.body, Space.Newline);1884
1955 try stream.writeByteNTimes(' ', indent);1885 {
1886 ais.pushIndent();
1887 defer ais.popIndent();
1888 try renderExpression(allocator, ais, tree, if_node.body, Space.Newline);
1889 }
19561890
1957 if (else_is_block) {1891 if (else_is_block) {
1958 try renderToken(tree, stream, @"else".else_token, indent, start_col, Space.Space); // else1892 try renderToken(tree, ais, @"else".else_token, Space.Space); // else
19591893
1960 if (@"else".payload) |payload| {1894 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);
1962 }1896 }
19631897
1964 return renderExpression(allocator, stream, tree, indent, start_col, @"else".body, space);1898 return renderExpression(allocator, ais, tree, @"else".body, space);
1965 } else {1899 } else {
1966 const after_else_space = if (@"else".payload == null) Space.Newline else Space.Space;1900 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); // else1901 try renderToken(tree, ais, @"else".else_token, after_else_space); // else
19681902
1969 if (@"else".payload) |payload| {1903 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);
1971 }1905 }
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);
1975 }1910 }
1976 } else {1911 } 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);
1978 }1915 }
1979 }1916 }
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
1983 if (if_node.payload) |payload| {1922 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);
1985 }1924 }
19861925
1987 if (if_node.@"else") |@"else"| {1926 if (if_node.@"else") |@"else"| {
1988 try renderExpression(allocator, stream, tree, indent, start_col, if_node.body, Space.Space);1927 try renderExpression(allocator, ais, tree, if_node.body, Space.Space);
1989 try renderToken(tree, stream, @"else".else_token, indent, start_col, Space.Space);1928 try renderToken(tree, ais, @"else".else_token, Space.Space);
19901929
1991 if (@"else".payload) |payload| {1930 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);
1993 }1932 }
19941933
1995 return renderExpression(allocator, stream, tree, indent, start_col, @"else".body, space);1934 return renderExpression(allocator, ais, tree, @"else".body, space);
1996 } else {1935 } else {
1997 return renderExpression(allocator, stream, tree, indent, start_col, if_node.body, space);1936 return renderExpression(allocator, ais, tree, if_node.body, space);
1998 }1937 }
1999 },1938 },
20001939
2001 .Asm => {1940 .Asm => {
2002 const asm_node = @fieldParentPtr(ast.Node.Asm, "base", base);1941 const asm_node = @fieldParentPtr(ast.Node.Asm, "base", base);
20031942
2004 try renderToken(tree, stream, asm_node.asm_token, indent, start_col, Space.Space); // asm1943 try renderToken(tree, ais, asm_node.asm_token, Space.Space); // asm
20051944
2006 if (asm_node.volatile_token) |volatile_token| {1945 if (asm_node.volatile_token) |volatile_token| {
2007 try renderToken(tree, stream, volatile_token, indent, start_col, Space.Space); // volatile1946 try renderToken(tree, ais, volatile_token, Space.Space); // volatile
2008 try renderToken(tree, stream, tree.nextToken(volatile_token), indent, start_col, Space.None); // (1947 try renderToken(tree, ais, tree.nextToken(volatile_token), Space.None); // (
2009 } else {1948 } 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); // (
2011 }1950 }
20121951
2013 if (asm_node.outputs.len == 0 and asm_node.inputs.len == 0 and asm_node.clobbers.len == 0) {1952 asmblk: {
2014 try renderExpression(allocator, stream, tree, indent, start_col, asm_node.template, Space.None);1953 ais.pushIndent();
2015 return renderToken(tree, stream, asm_node.rparen, indent, start_col, space);1954 defer ais.popIndent();
2016 }
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) {1963 ais.setIndentDelta(asm_indent_delta);
2023 // After rendering a multiline string literal the cursor is1964 defer ais.setIndentDelta(indent_delta);
2024 // already offset by indent
2025 try stream.writeByteNTimes(' ', indent_delta);
2026 } else {
2027 try stream.writeByteNTimes(' ', indent_once);
2028 }
20291965
2030 const colon1 = tree.nextToken(asm_node.template.lastToken());1966 const colon1 = tree.nextToken(asm_node.template.lastToken());
2031 const indent_extra = indent_once + 2;
20321967
2033 const colon2 = if (asm_node.outputs.len == 0) blk: {1968 const colon2 = if (asm_node.outputs.len == 0) blk: {
2034 try renderToken(tree, stream, colon1, indent, start_col, Space.Newline); // :1969 try renderToken(tree, ais, colon1, Space.Newline); // :
2035 try stream.writeByteNTimes(' ', indent_once);
20361970
2037 break :blk tree.nextToken(colon1);1971 break :blk tree.nextToken(colon1);
2038 } else blk: {1972 } else blk: {
2039 try renderToken(tree, stream, colon1, indent, start_col, Space.Space); // :1973 try renderToken(tree, ais, colon1, 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 };
20671974
2068 const colon3 = if (asm_node.inputs.len == 0) blk: {1975 ais.pushIndent();
2069 try renderToken(tree, stream, colon2, indent, start_col, Space.Newline); // :1976 defer ais.popIndent();
2070 try stream.writeByteNTimes(' ', indent_once);
20711977
2072 break :blk tree.nextToken(colon2);1978 for (asm_node.outputs) |*asm_output, i| {
2073 } else blk: {1979 if (i + 1 < asm_node.outputs.len) {
2074 try renderToken(tree, stream, colon2, indent, start_col, Space.Space); // :1980 const next_asm_output = asm_node.outputs[i + 1];
20751981 try renderAsmOutput(allocator, ais, tree, asm_output, Space.None);
2076 for (asm_node.inputs) |*asm_input, i| {1982
2077 if (i + 1 < asm_node.inputs.len) {1983 const comma = tree.prevToken(next_asm_output.firstToken());
2078 const next_asm_input = &asm_node.inputs[i + 1];1984 try renderToken(tree, ais, comma, Space.Newline); // ,
2079 try renderAsmInput(allocator, stream, tree, indent_extra, start_col, asm_input, Space.None);1985 try renderExtraNewlineToken(tree, ais, next_asm_output.firstToken());
20801986 } else if (asm_node.inputs.len == 0 and asm_node.clobbers.len == 0) {
2081 const comma = tree.prevToken(next_asm_input.firstToken());1987 try renderAsmOutput(allocator, ais, tree, asm_output, Space.Newline);
2082 try renderToken(tree, stream, comma, indent_extra, start_col, Space.Newline); // ,1988 break :asmblk;
2083 try renderExtraNewlineToken(tree, stream, start_col, next_asm_input.firstToken());1989 } else {
20841990 try renderAsmOutput(allocator, ais, tree, asm_output, Space.Newline);
2085 try stream.writeByteNTimes(' ', indent_extra);1991 const comma_or_colon = tree.nextToken(asm_output.lastToken());
2086 } else if (asm_node.clobbers.len == 0) {1992 break :blk switch (tree.token_ids[comma_or_colon]) {
2087 try renderAsmInput(allocator, stream, tree, indent_extra, start_col, asm_input, Space.Newline);1993 .Comma => tree.nextToken(comma_or_colon),
2088 try stream.writeByteNTimes(' ', indent);1994 else => comma_or_colon,
2089 return renderToken(tree, stream, asm_node.rparen, indent, start_col, space); // )1995 };
2090 } else {1996 }
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 };
2098 }1997 }
2099 }1998 unreachable;
2100 unreachable;1999 };
2101 };
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| {2031 try renderToken(tree, ais, colon3, Space.Space); // :
2106 if (i + 1 >= asm_node.clobbers.len) {2032 ais.pushIndent();
2107 try renderExpression(allocator, stream, tree, indent_extra, start_col, clobber_node, Space.Newline);2033 defer ais.popIndent();
2108 try stream.writeByteNTimes(' ', indent);2034 for (asm_node.clobbers) |clobber_node, i| {
2109 return renderToken(tree, stream, asm_node.rparen, indent, start_col, space);2035 if (i + 1 >= asm_node.clobbers.len) {
2110 } else {2036 try renderExpression(allocator, ais, tree, clobber_node, Space.Newline);
2111 try renderExpression(allocator, stream, tree, indent_extra, start_col, clobber_node, Space.None);2037 break :asmblk;
2112 const comma = tree.nextToken(clobber_node.lastToken());2038 } else {
2113 try renderToken(tree, stream, comma, indent_once, start_col, Space.Space); // ,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 }
2114 }2043 }
2115 }2044 }
2045
2046 return renderToken(tree, ais, asm_node.rparen, space);
2116 },2047 },
21172048
2118 .EnumLiteral => {2049 .EnumLiteral => {
2119 const enum_literal = @fieldParentPtr(ast.Node.EnumLiteral, "base", base);2050 const enum_literal = @fieldParentPtr(ast.Node.EnumLiteral, "base", base);
21202051
2121 try renderToken(tree, stream, enum_literal.dot, indent, start_col, Space.None); // .2052 try renderToken(tree, ais, enum_literal.dot, Space.None); // .
2122 return renderToken(tree, stream, enum_literal.name, indent, start_col, space); // name2053 return renderToken(tree, ais, enum_literal.name, space); // name
2123 },2054 },
21242055
2125 .ContainerField,2056 .ContainerField,
...@@ -2133,118 +2064,115 @@ fn renderExpression(...@@ -2133,118 +2064,115 @@ fn renderExpression(
21332064
2134fn renderArrayType(2065fn renderArrayType(
2135 allocator: *mem.Allocator,2066 allocator: *mem.Allocator,
2136 stream: anytype,2067 ais: anytype,
2137 tree: *ast.Tree,2068 tree: *ast.Tree,
2138 indent: usize,
2139 start_col: *usize,
2140 lbracket: ast.TokenIndex,2069 lbracket: ast.TokenIndex,
2141 rhs: *ast.Node,2070 rhs: *ast.Node,
2142 len_expr: *ast.Node,2071 len_expr: *ast.Node,
2143 opt_sentinel: ?*ast.Node,2072 opt_sentinel: ?*ast.Node,
2144 space: Space,2073 space: Space,
2145) (@TypeOf(stream).Error || Error)!void {2074) (@TypeOf(ais.*).Error || Error)!void {
2146 const rbracket = tree.nextToken(if (opt_sentinel) |sentinel|2075 const rbracket = tree.nextToken(if (opt_sentinel) |sentinel|
2147 sentinel.lastToken()2076 sentinel.lastToken()
2148 else2077 else
2149 len_expr.lastToken());2078 len_expr.lastToken());
21502079
2151 try renderToken(tree, stream, lbracket, indent, start_col, Space.None); // [
2152
2153 const starts_with_comment = tree.token_ids[lbracket + 1] == .LineComment;2080 const starts_with_comment = tree.token_ids[lbracket + 1] == .LineComment;
2154 const ends_with_comment = tree.token_ids[rbracket - 1] == .LineComment;2081 const ends_with_comment = tree.token_ids[rbracket - 1] == .LineComment;
2155 const new_indent = if (ends_with_comment) indent + indent_delta else indent;
2156 const new_space = if (ends_with_comment) Space.Newline else Space.None;2082 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);2083 {
2158 if (starts_with_comment) {2084 const do_indent = (starts_with_comment or ends_with_comment);
2159 try stream.writeByte('\n');2085 if (do_indent) ais.pushIndent();
2160 }2086 defer if (do_indent) ais.popIndent();
2161 if (ends_with_comment or starts_with_comment) {2087
2162 try stream.writeByteNTimes(' ', indent);2088 try renderToken(tree, ais, lbracket, Space.None); // [
2163 }2089 try renderExpression(allocator, ais, tree, len_expr, new_space);
2164 if (opt_sentinel) |sentinel| {2090
2165 const colon_token = tree.prevToken(sentinel.firstToken());2091 if (starts_with_comment) {
2166 try renderToken(tree, stream, colon_token, indent, start_col, Space.None); // :2092 try ais.maybeInsertNewline();
2167 try renderExpression(allocator, stream, tree, indent, start_col, sentinel, Space.None);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 }
2168 }2102 }
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);
2172}2106}
21732107
2174fn renderAsmOutput(2108fn renderAsmOutput(
2175 allocator: *mem.Allocator,2109 allocator: *mem.Allocator,
2176 stream: anytype,2110 ais: anytype,
2177 tree: *ast.Tree,2111 tree: *ast.Tree,
2178 indent: usize,
2179 start_col: *usize,
2180 asm_output: *const ast.Node.Asm.Output,2112 asm_output: *const ast.Node.Asm.Output,
2181 space: Space,2113 space: Space,
2182) (@TypeOf(stream).Error || Error)!void {2114) (@TypeOf(ais.*).Error || Error)!void {
2183 try stream.writeAll("[");2115 try ais.writer().writeAll("[");
2184 try renderExpression(allocator, stream, tree, indent, start_col, asm_output.symbolic_name, Space.None);2116 try renderExpression(allocator, ais, tree, asm_output.symbolic_name, Space.None);
2185 try stream.writeAll("] ");2117 try ais.writer().writeAll("] ");
2186 try renderExpression(allocator, stream, tree, indent, start_col, asm_output.constraint, Space.None);2118 try renderExpression(allocator, ais, tree, asm_output.constraint, Space.None);
2187 try stream.writeAll(" (");2119 try ais.writer().writeAll(" (");
21882120
2189 switch (asm_output.kind) {2121 switch (asm_output.kind) {
2190 ast.Node.Asm.Output.Kind.Variable => |variable_name| {2122 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);
2192 },2124 },
2193 ast.Node.Asm.Output.Kind.Return => |return_type| {2125 ast.Node.Asm.Output.Kind.Return => |return_type| {
2194 try stream.writeAll("-> ");2126 try ais.writer().writeAll("-> ");
2195 try renderExpression(allocator, stream, tree, indent, start_col, return_type, Space.None);2127 try renderExpression(allocator, ais, tree, return_type, Space.None);
2196 },2128 },
2197 }2129 }
21982130
2199 return renderToken(tree, stream, asm_output.lastToken(), indent, start_col, space); // )2131 return renderToken(tree, ais, asm_output.lastToken(), space); // )
2200}2132}
22012133
2202fn renderAsmInput(2134fn renderAsmInput(
2203 allocator: *mem.Allocator,2135 allocator: *mem.Allocator,
2204 stream: anytype,2136 ais: anytype,
2205 tree: *ast.Tree,2137 tree: *ast.Tree,
2206 indent: usize,
2207 start_col: *usize,
2208 asm_input: *const ast.Node.Asm.Input,2138 asm_input: *const ast.Node.Asm.Input,
2209 space: Space,2139 space: Space,
2210) (@TypeOf(stream).Error || Error)!void {2140) (@TypeOf(ais.*).Error || Error)!void {
2211 try stream.writeAll("[");2141 try ais.writer().writeAll("[");
2212 try renderExpression(allocator, stream, tree, indent, start_col, asm_input.symbolic_name, Space.None);2142 try renderExpression(allocator, ais, tree, asm_input.symbolic_name, Space.None);
2213 try stream.writeAll("] ");2143 try ais.writer().writeAll("] ");
2214 try renderExpression(allocator, stream, tree, indent, start_col, asm_input.constraint, Space.None);2144 try renderExpression(allocator, ais, tree, asm_input.constraint, Space.None);
2215 try stream.writeAll(" (");2145 try ais.writer().writeAll(" (");
2216 try renderExpression(allocator, stream, tree, indent, start_col, asm_input.expr, Space.None);2146 try renderExpression(allocator, ais, tree, asm_input.expr, Space.None);
2217 return renderToken(tree, stream, asm_input.lastToken(), indent, start_col, space); // )2147 return renderToken(tree, ais, asm_input.lastToken(), space); // )
2218}2148}
22192149
2220fn renderVarDecl(2150fn renderVarDecl(
2221 allocator: *mem.Allocator,2151 allocator: *mem.Allocator,
2222 stream: anytype,2152 ais: anytype,
2223 tree: *ast.Tree,2153 tree: *ast.Tree,
2224 indent: usize,
2225 start_col: *usize,
2226 var_decl: *ast.Node.VarDecl,2154 var_decl: *ast.Node.VarDecl,
2227) (@TypeOf(stream).Error || Error)!void {2155) (@TypeOf(ais.*).Error || Error)!void {
2228 if (var_decl.getVisibToken()) |visib_token| {2156 if (var_decl.getVisibToken()) |visib_token| {
2229 try renderToken(tree, stream, visib_token, indent, start_col, Space.Space); // pub2157 try renderToken(tree, ais, visib_token, Space.Space); // pub
2230 }2158 }
22312159
2232 if (var_decl.getExternExportToken()) |extern_export_token| {2160 if (var_decl.getExternExportToken()) |extern_export_token| {
2233 try renderToken(tree, stream, extern_export_token, indent, start_col, Space.Space); // extern2161 try renderToken(tree, ais, extern_export_token, Space.Space); // extern
22342162
2235 if (var_decl.getLibName()) |lib_name| {2163 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"
2237 }2165 }
2238 }2166 }
22392167
2240 if (var_decl.getComptimeToken()) |comptime_token| {2168 if (var_decl.getComptimeToken()) |comptime_token| {
2241 try renderToken(tree, stream, comptime_token, indent, start_col, Space.Space); // comptime2169 try renderToken(tree, ais, comptime_token, Space.Space); // comptime
2242 }2170 }
22432171
2244 if (var_decl.getThreadLocalToken()) |thread_local_token| {2172 if (var_decl.getThreadLocalToken()) |thread_local_token| {
2245 try renderToken(tree, stream, thread_local_token, indent, start_col, Space.Space); // threadlocal2173 try renderToken(tree, ais, thread_local_token, Space.Space); // threadlocal
2246 }2174 }
2247 try renderToken(tree, stream, var_decl.mut_token, indent, start_col, Space.Space); // var2175 try renderToken(tree, ais, var_decl.mut_token, Space.Space); // var
22482176
2249 const name_space = if (var_decl.getTypeNode() == null and2177 const name_space = if (var_decl.getTypeNode() == null and
2250 (var_decl.getAlignNode() != null or2178 (var_decl.getAlignNode() != null or
...@@ -2253,95 +2181,92 @@ fn renderVarDecl(...@@ -2253,95 +2181,92 @@ fn renderVarDecl(
2253 Space.Space2181 Space.Space
2254 else2182 else
2255 Space.None;2183 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
2258 if (var_decl.getTypeNode()) |type_node| {2186 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);
2260 const s = if (var_decl.getAlignNode() != null or2188 const s = if (var_decl.getAlignNode() != null or
2261 var_decl.getSectionNode() != null or2189 var_decl.getSectionNode() != null or
2262 var_decl.getInitNode() != null) Space.Space else Space.None;2190 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);
2264 }2192 }
22652193
2266 if (var_decl.getAlignNode()) |align_node| {2194 if (var_decl.getAlignNode()) |align_node| {
2267 const lparen = tree.prevToken(align_node.firstToken());2195 const lparen = tree.prevToken(align_node.firstToken());
2268 const align_kw = tree.prevToken(lparen);2196 const align_kw = tree.prevToken(lparen);
2269 const rparen = tree.nextToken(align_node.lastToken());2197 const rparen = tree.nextToken(align_node.lastToken());
2270 try renderToken(tree, stream, align_kw, indent, start_col, Space.None); // align2198 try renderToken(tree, ais, align_kw, Space.None); // align
2271 try renderToken(tree, stream, lparen, indent, start_col, Space.None); // (2199 try renderToken(tree, ais, lparen, Space.None); // (
2272 try renderExpression(allocator, stream, tree, indent, start_col, align_node, Space.None);2200 try renderExpression(allocator, ais, tree, align_node, Space.None);
2273 const s = if (var_decl.getSectionNode() != null or var_decl.getInitNode() != null) Space.Space else Space.None;2201 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); // )
2275 }2203 }
22762204
2277 if (var_decl.getSectionNode()) |section_node| {2205 if (var_decl.getSectionNode()) |section_node| {
2278 const lparen = tree.prevToken(section_node.firstToken());2206 const lparen = tree.prevToken(section_node.firstToken());
2279 const section_kw = tree.prevToken(lparen);2207 const section_kw = tree.prevToken(lparen);
2280 const rparen = tree.nextToken(section_node.lastToken());2208 const rparen = tree.nextToken(section_node.lastToken());
2281 try renderToken(tree, stream, section_kw, indent, start_col, Space.None); // linksection2209 try renderToken(tree, ais, section_kw, Space.None); // linksection
2282 try renderToken(tree, stream, lparen, indent, start_col, Space.None); // (2210 try renderToken(tree, ais, lparen, Space.None); // (
2283 try renderExpression(allocator, stream, tree, indent, start_col, section_node, Space.None);2211 try renderExpression(allocator, ais, tree, section_node, Space.None);
2284 const s = if (var_decl.getInitNode() != null) Space.Space else Space.None;2212 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); // )
2286 }2214 }
22872215
2288 if (var_decl.getInitNode()) |init_node| {2216 if (var_decl.getInitNode()) |init_node| {
2289 const s = if (init_node.tag == .MultilineStringLiteral) Space.None else Space.Space;2217 const s = if (init_node.tag == .MultilineStringLiteral) Space.None else Space.Space;
2290 try renderToken(tree, stream, var_decl.getEqToken().?, indent, start_col, s); // =2218 try renderToken(tree, ais, var_decl.getEqToken().?, s); // =
2291 try renderExpression(allocator, stream, tree, indent, start_col, init_node, Space.None);2219 ais.pushIndentOneShot();
2220 try renderExpression(allocator, ais, tree, init_node, Space.None);
2292 }2221 }
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);
2295}2224}
22962225
2297fn renderParamDecl(2226fn renderParamDecl(
2298 allocator: *mem.Allocator,2227 allocator: *mem.Allocator,
2299 stream: anytype,2228 ais: anytype,
2300 tree: *ast.Tree,2229 tree: *ast.Tree,
2301 indent: usize,
2302 start_col: *usize,
2303 param_decl: ast.Node.FnProto.ParamDecl,2230 param_decl: ast.Node.FnProto.ParamDecl,
2304 space: Space,2231 space: Space,
2305) (@TypeOf(stream).Error || Error)!void {2232) (@TypeOf(ais.*).Error || Error)!void {
2306 try renderDocComments(tree, stream, param_decl, param_decl.doc_comments, indent, start_col);2233 try renderDocComments(tree, ais, param_decl, param_decl.doc_comments);
23072234
2308 if (param_decl.comptime_token) |comptime_token| {2235 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);
2310 }2237 }
2311 if (param_decl.noalias_token) |noalias_token| {2238 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);
2313 }2240 }
2314 if (param_decl.name_token) |name_token| {2241 if (param_decl.name_token) |name_token| {
2315 try renderToken(tree, stream, name_token, indent, start_col, Space.None);2242 try renderToken(tree, ais, name_token, Space.None);
2316 try renderToken(tree, stream, tree.nextToken(name_token), indent, start_col, Space.Space); // :2243 try renderToken(tree, ais, tree.nextToken(name_token), Space.Space); // :
2317 }2244 }
2318 switch (param_decl.param_type) {2245 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),
2320 }2247 }
2321}2248}
23222249
2323fn renderStatement(2250fn renderStatement(
2324 allocator: *mem.Allocator,2251 allocator: *mem.Allocator,
2325 stream: anytype,2252 ais: anytype,
2326 tree: *ast.Tree,2253 tree: *ast.Tree,
2327 indent: usize,
2328 start_col: *usize,
2329 base: *ast.Node,2254 base: *ast.Node,
2330) (@TypeOf(stream).Error || Error)!void {2255) (@TypeOf(ais.*).Error || Error)!void {
2331 switch (base.tag) {2256 switch (base.tag) {
2332 .VarDecl => {2257 .VarDecl => {
2333 const var_decl = @fieldParentPtr(ast.Node.VarDecl, "base", base);2258 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);
2335 },2260 },
2336 else => {2261 else => {
2337 if (base.requireSemiColon()) {2262 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
2340 const semicolon_index = tree.nextToken(base.lastToken());2265 const semicolon_index = tree.nextToken(base.lastToken());
2341 assert(tree.token_ids[semicolon_index] == .Semicolon);2266 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);
2343 } else {2268 } else {
2344 try renderExpression(allocator, stream, tree, indent, start_col, base, Space.Newline);2269 try renderExpression(allocator, ais, tree, base, Space.Newline);
2345 }2270 }
2346 },2271 },
2347 }2272 }
...@@ -2360,24 +2285,19 @@ const Space = enum {...@@ -2360,24 +2285,19 @@ const Space = enum {
23602285
2361fn renderTokenOffset(2286fn renderTokenOffset(
2362 tree: *ast.Tree,2287 tree: *ast.Tree,
2363 stream: anytype,2288 ais: anytype,
2364 token_index: ast.TokenIndex,2289 token_index: ast.TokenIndex,
2365 indent: usize,
2366 start_col: *usize,
2367 space: Space,2290 space: Space,
2368 token_skip_bytes: usize,2291 token_skip_bytes: usize,
2369) (@TypeOf(stream).Error || Error)!void {2292) (@TypeOf(ais.*).Error || Error)!void {
2370 if (space == Space.BlockStart) {2293 if (space == Space.BlockStart) {
2371 if (start_col.* < indent + indent_delta)2294 // If placing the lbrace on the current line would cause an uggly gap then put the lbrace on the next line
2372 return renderToken(tree, stream, token_index, indent, start_col, Space.Space);2295 const new_space = if (ais.isLineOverIndented()) Space.Newline else Space.Space;
2373 try renderToken(tree, stream, token_index, indent, start_col, Space.Newline);2296 return renderToken(tree, ais, token_index, new_space);
2374 try stream.writeByteNTimes(' ', indent);
2375 start_col.* = indent;
2376 return;
2377 }2297 }
23782298
2379 var token_loc = tree.token_locs[token_index];2299 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
2382 if (space == Space.NoComment)2302 if (space == Space.NoComment)
2383 return;2303 return;
...@@ -2386,20 +2306,20 @@ fn renderTokenOffset(...@@ -2386,20 +2306,20 @@ fn renderTokenOffset(
2386 var next_token_loc = tree.token_locs[token_index + 1];2306 var next_token_loc = tree.token_locs[token_index + 1];
23872307
2388 if (space == Space.Comma) switch (next_token_id) {2308 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),
2390 .LineComment => {2310 .LineComment => {
2391 try stream.writeAll(", ");2311 try ais.writer().writeAll(", ");
2392 return renderToken(tree, stream, token_index + 1, indent, start_col, Space.Newline);2312 return renderToken(tree, ais, token_index + 1, Space.Newline);
2393 },2313 },
2394 else => {2314 else => {
2395 if (token_index + 2 < tree.token_ids.len and2315 if (token_index + 2 < tree.token_ids.len and
2396 tree.token_ids[token_index + 2] == .MultilineStringLiteralLine)2316 tree.token_ids[token_index + 2] == .MultilineStringLiteralLine)
2397 {2317 {
2398 try stream.writeAll(",");2318 try ais.writer().writeAll(",");
2399 return;2319 return;
2400 } else {2320 } else {
2401 try stream.writeAll(",\n");2321 try ais.writer().writeAll(",");
2402 start_col.* = 0;2322 try ais.insertNewline();
2403 return;2323 return;
2404 }2324 }
2405 },2325 },
...@@ -2423,15 +2343,14 @@ fn renderTokenOffset(...@@ -2423,15 +2343,14 @@ fn renderTokenOffset(
2423 if (next_token_id == .MultilineStringLiteralLine) {2343 if (next_token_id == .MultilineStringLiteralLine) {
2424 return;2344 return;
2425 } else {2345 } else {
2426 try stream.writeAll("\n");2346 try ais.insertNewline();
2427 start_col.* = 0;
2428 return;2347 return;
2429 }2348 }
2430 },2349 },
2431 Space.Space, Space.SpaceOrOutdent => {2350 Space.Space, Space.SpaceOrOutdent => {
2432 if (next_token_id == .MultilineStringLiteralLine)2351 if (next_token_id == .MultilineStringLiteralLine)
2433 return;2352 return;
2434 try stream.writeByte(' ');2353 try ais.writer().writeByte(' ');
2435 return;2354 return;
2436 },2355 },
2437 Space.NoComment, Space.Comma, Space.BlockStart => unreachable,2356 Space.NoComment, Space.Comma, Space.BlockStart => unreachable,
...@@ -2448,8 +2367,7 @@ fn renderTokenOffset(...@@ -2448,8 +2367,7 @@ fn renderTokenOffset(
2448 next_token_id = tree.token_ids[token_index + offset];2367 next_token_id = tree.token_ids[token_index + offset];
2449 next_token_loc = tree.token_locs[token_index + offset];2368 next_token_loc = tree.token_locs[token_index + offset];
2450 if (next_token_id != .LineComment) {2369 if (next_token_id != .LineComment) {
2451 try stream.writeByte('\n');2370 try ais.insertNewline();
2452 start_col.* = 0;
2453 return;2371 return;
2454 }2372 }
2455 },2373 },
...@@ -2462,7 +2380,7 @@ fn renderTokenOffset(...@@ -2462,7 +2380,7 @@ fn renderTokenOffset(
24622380
2463 var loc = tree.tokenLocationLoc(token_loc.end, next_token_loc);2381 var loc = tree.tokenLocationLoc(token_loc.end, next_token_loc);
2464 if (loc.line == 0) {2382 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), " ")});
2466 offset = 2;2384 offset = 2;
2467 token_loc = next_token_loc;2385 token_loc = next_token_loc;
2468 next_token_loc = tree.token_locs[token_index + offset];2386 next_token_loc = tree.token_locs[token_index + offset];
...@@ -2470,26 +2388,16 @@ fn renderTokenOffset(...@@ -2470,26 +2388,16 @@ fn renderTokenOffset(
2470 if (next_token_id != .LineComment) {2388 if (next_token_id != .LineComment) {
2471 switch (space) {2389 switch (space) {
2472 Space.None, Space.Space => {2390 Space.None, Space.Space => {
2473 try stream.writeByte('\n');2391 try ais.insertNewline();
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;
2481 },2392 },
2482 Space.SpaceOrOutdent => {2393 Space.SpaceOrOutdent => {
2483 try stream.writeByte('\n');2394 try ais.insertNewline();
2484 try stream.writeByteNTimes(' ', indent);
2485 start_col.* = indent;
2486 },2395 },
2487 Space.Newline => {2396 Space.Newline => {
2488 if (next_token_id == .MultilineStringLiteralLine) {2397 if (next_token_id == .MultilineStringLiteralLine) {
2489 return;2398 return;
2490 } else {2399 } else {
2491 try stream.writeAll("\n");2400 try ais.insertNewline();
2492 start_col.* = 0;
2493 return;2401 return;
2494 }2402 }
2495 },2403 },
...@@ -2505,10 +2413,9 @@ fn renderTokenOffset(...@@ -2505,10 +2413,9 @@ fn renderTokenOffset(
2505 // translate-c doesn't generate correct newlines2413 // translate-c doesn't generate correct newlines
2506 // in generated code (loc.line == 0) so treat that case2414 // in generated code (loc.line == 0) so treat that case
2507 // as though there was meant to be a newline between the tokens2415 // 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);2416 var newline_count = if (loc.line <= 1) @as(u8, 1) else @as(u8, 2);
2509 try stream.writeByteNTimes('\n', newline_count);2417 while (newline_count > 0) : (newline_count -= 1) try ais.insertNewline();
2510 try stream.writeByteNTimes(' ', indent);2418 try ais.writer().writeAll(mem.trimRight(u8, tree.tokenSliceLoc(next_token_loc), " "));
2511 try stream.writeAll(mem.trimRight(u8, tree.tokenSliceLoc(next_token_loc), " "));
25122419
2513 offset += 1;2420 offset += 1;
2514 token_loc = next_token_loc;2421 token_loc = next_token_loc;
...@@ -2520,32 +2427,15 @@ fn renderTokenOffset(...@@ -2520,32 +2427,15 @@ fn renderTokenOffset(
2520 if (next_token_id == .MultilineStringLiteralLine) {2427 if (next_token_id == .MultilineStringLiteralLine) {
2521 return;2428 return;
2522 } else {2429 } else {
2523 try stream.writeAll("\n");2430 try ais.insertNewline();
2524 start_col.* = 0;
2525 return;2431 return;
2526 }2432 }
2527 },2433 },
2528 Space.None, Space.Space => {2434 Space.None, Space.Space => {
2529 try stream.writeByte('\n');2435 try ais.insertNewline();
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;
2544 },2436 },
2545 Space.SpaceOrOutdent => {2437 Space.SpaceOrOutdent => {
2546 try stream.writeByte('\n');2438 try ais.insertNewline();
2547 try stream.writeByteNTimes(' ', indent);
2548 start_col.* = indent;
2549 },2439 },
2550 Space.NoNewline => {},2440 Space.NoNewline => {},
2551 Space.NoComment, Space.Comma, Space.BlockStart => unreachable,2441 Space.NoComment, Space.Comma, Space.BlockStart => unreachable,
...@@ -2558,46 +2448,38 @@ fn renderTokenOffset(...@@ -2558,46 +2448,38 @@ fn renderTokenOffset(
25582448
2559fn renderToken(2449fn renderToken(
2560 tree: *ast.Tree,2450 tree: *ast.Tree,
2561 stream: anytype,2451 ais: anytype,
2562 token_index: ast.TokenIndex,2452 token_index: ast.TokenIndex,
2563 indent: usize,
2564 start_col: *usize,
2565 space: Space,2453 space: Space,
2566) (@TypeOf(stream).Error || Error)!void {2454) (@TypeOf(ais.*).Error || Error)!void {
2567 return renderTokenOffset(tree, stream, token_index, indent, start_col, space, 0);2455 return renderTokenOffset(tree, ais, token_index, space, 0);
2568}2456}
25692457
2570fn renderDocComments(2458fn renderDocComments(
2571 tree: *ast.Tree,2459 tree: *ast.Tree,
2572 stream: anytype,2460 ais: anytype,
2573 node: anytype,2461 node: anytype,
2574 doc_comments: ?*ast.Node.DocComment,2462 doc_comments: ?*ast.Node.DocComment,
2575 indent: usize,2463) (@TypeOf(ais.*).Error || Error)!void {
2576 start_col: *usize,
2577) (@TypeOf(stream).Error || Error)!void {
2578 const comment = doc_comments orelse return;2464 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());
2580}2466}
25812467
2582fn renderDocCommentsToken(2468fn renderDocCommentsToken(
2583 tree: *ast.Tree,2469 tree: *ast.Tree,
2584 stream: anytype,2470 ais: anytype,
2585 comment: *ast.Node.DocComment,2471 comment: *ast.Node.DocComment,
2586 first_token: ast.TokenIndex,2472 first_token: ast.TokenIndex,
2587 indent: usize,2473) (@TypeOf(ais.*).Error || Error)!void {
2588 start_col: *usize,
2589) (@TypeOf(stream).Error || Error)!void {
2590 var tok_i = comment.first_line;2474 var tok_i = comment.first_line;
2591 while (true) : (tok_i += 1) {2475 while (true) : (tok_i += 1) {
2592 switch (tree.token_ids[tok_i]) {2476 switch (tree.token_ids[tok_i]) {
2593 .DocComment, .ContainerDocComment => {2477 .DocComment, .ContainerDocComment => {
2594 if (comment.first_line < first_token) {2478 if (comment.first_line < first_token) {
2595 try renderToken(tree, stream, tok_i, indent, start_col, Space.Newline);2479 try renderToken(tree, ais, tok_i, Space.Newline);
2596 try stream.writeByteNTimes(' ', indent);
2597 } else {2480 } else {
2598 try renderToken(tree, stream, tok_i, indent, start_col, Space.NoComment);2481 try renderToken(tree, ais, tok_i, Space.NoComment);
2599 try stream.writeAll("\n");2482 try ais.insertNewline();
2600 try stream.writeByteNTimes(' ', indent);
2601 }2483 }
2602 },2484 },
2603 .LineComment => continue,2485 .LineComment => continue,
...@@ -2669,41 +2551,10 @@ fn nodeCausesSliceOpSpace(base: *ast.Node) bool {...@@ -2669,41 +2551,10 @@ fn nodeCausesSliceOpSpace(base: *ast.Node) bool {
2669 };2551 };
2670}2552}
26712553
2672/// A `std.io.OutStream` that returns whether the given character has been written to it.2554fn copyFixingWhitespace(ais: anytype, slice: []const u8) @TypeOf(ais.*).Error!void {
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 {
2704 for (slice) |byte| switch (byte) {2555 for (slice) |byte| switch (byte) {
2705 '\t' => try stream.writeAll(" "),2556 '\t' => try ais.writer().writeAll(" "),
2706 '\r' => {},2557 '\r' => {},
2707 else => try stream.writeByte(byte),2558 else => try ais.writer().writeByte(byte),
2708 };2559 };
2709}2560}
lib/std/zig/tokenizer.zig+2-1
...@@ -1175,6 +1175,7 @@ pub const Tokenizer = struct {...@@ -1175,6 +1175,7 @@ pub const Tokenizer = struct {
1175 },1175 },
1176 .num_dot_dec => switch (c) {1176 .num_dot_dec => switch (c) {
1177 '.' => {1177 '.' => {
1178 result.id = .IntegerLiteral;
1178 self.index -= 1;1179 self.index -= 1;
1179 state = .start;1180 state = .start;
1180 break;1181 break;
...@@ -1183,7 +1184,6 @@ pub const Tokenizer = struct {...@@ -1183,7 +1184,6 @@ pub const Tokenizer = struct {
1183 state = .float_exponent_unsigned;1184 state = .float_exponent_unsigned;
1184 },1185 },
1185 '0'...'9' => {1186 '0'...'9' => {
1186 result.id = .FloatLiteral;
1187 state = .float_fraction_dec;1187 state = .float_fraction_dec;
1188 },1188 },
1189 else => {1189 else => {
...@@ -1769,6 +1769,7 @@ test "tokenizer - number literals decimal" {...@@ -1769,6 +1769,7 @@ test "tokenizer - number literals decimal" {
1769 testTokenize("7", &[_]Token.Id{.IntegerLiteral});1769 testTokenize("7", &[_]Token.Id{.IntegerLiteral});
1770 testTokenize("8", &[_]Token.Id{.IntegerLiteral});1770 testTokenize("8", &[_]Token.Id{.IntegerLiteral});
1771 testTokenize("9", &[_]Token.Id{.IntegerLiteral});1771 testTokenize("9", &[_]Token.Id{.IntegerLiteral});
1772 testTokenize("1..", &[_]Token.Id{ .IntegerLiteral, .Ellipsis2 });
1772 testTokenize("0a", &[_]Token.Id{ .Invalid, .Identifier });1773 testTokenize("0a", &[_]Token.Id{ .Invalid, .Identifier });
1773 testTokenize("9b", &[_]Token.Id{ .Invalid, .Identifier });1774 testTokenize("9b", &[_]Token.Id{ .Invalid, .Identifier });
1774 testTokenize("1z", &[_]Token.Id{ .Invalid, .Identifier });1775 testTokenize("1z", &[_]Token.Id{ .Invalid, .Identifier });
src-self-hosted/Module.zig+242-130
...@@ -36,17 +36,17 @@ bin_file_path: []const u8,...@@ -36,17 +36,17 @@ bin_file_path: []const u8,
36/// It's rare for a decl to be exported, so we save memory by having a sparse map of36/// It's rare for a decl to be exported, so we save memory by having a sparse map of
37/// Decl pointers to details about them being exported.37/// Decl pointers to details about them being exported.
38/// The Export memory is owned by the `export_owners` table; the slice itself is owned by this table.38/// 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) = .{},
40/// We track which export is associated with the given symbol name for quick40/// We track which export is associated with the given symbol name for quick
41/// detection of symbol collisions.41/// detection of symbol collisions.
42symbol_exports: std.StringHashMapUnmanaged(*Export) = .{},42symbol_exports: std.StringArrayHashMapUnmanaged(*Export) = .{},
43/// This models the Decls that perform exports, so that `decl_exports` can be updated when a Decl43/// This models the Decls that perform exports, so that `decl_exports` can be updated when a Decl
44/// is modified. Note that the key of this table is not the Decl being exported, but the Decl that44/// is modified. Note that the key of this table is not the Decl being exported, but the Decl that
45/// is performing the export of another Decl.45/// is performing the export of another Decl.
46/// This table owns the Export memory.46/// This table owns the Export memory.
47export_owners: std.AutoHashMapUnmanaged(*Decl, []*Export) = .{},47export_owners: std.AutoArrayHashMapUnmanaged(*Decl, []*Export) = .{},
48/// Maps fully qualified namespaced names to the Decl struct for them.48/// 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
51link_error_flags: link.File.ErrorFlags = .{},51link_error_flags: link.File.ErrorFlags = .{},
5252
...@@ -57,13 +57,13 @@ work_queue: std.fifo.LinearFifo(WorkItem, .Dynamic),...@@ -57,13 +57,13 @@ work_queue: std.fifo.LinearFifo(WorkItem, .Dynamic),
57/// The ErrorMsg memory is owned by the decl, using Module's allocator.57/// The ErrorMsg memory is owned by the decl, using Module's allocator.
58/// Note that a Decl can succeed but the Fn it represents can fail. In this case,58/// Note that a Decl can succeed but the Fn it represents can fail. In this case,
59/// a Decl can have a failed_decls entry but have analysis status of success.59/// 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) = .{},
61/// Using a map here for consistency with the other fields here.61/// Using a map here for consistency with the other fields here.
62/// The ErrorMsg memory is owned by the `Scope`, using Module's allocator.62/// The ErrorMsg memory is owned by the `Scope`, using Module's allocator.
63failed_files: std.AutoHashMapUnmanaged(*Scope, *ErrorMsg) = .{},63failed_files: std.AutoArrayHashMapUnmanaged(*Scope, *ErrorMsg) = .{},
64/// Using a map here for consistency with the other fields here.64/// Using a map here for consistency with the other fields here.
65/// The ErrorMsg memory is owned by the `Export`, using Module's allocator.65/// 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
68/// Incrementing integer used to compare against the corresponding Decl68/// Incrementing integer used to compare against the corresponding Decl
69/// field to determine whether a Decl's status applies to an ongoing update, or a69/// field to determine whether a Decl's status applies to an ongoing update, or a
...@@ -125,7 +125,7 @@ pub const Decl = struct {...@@ -125,7 +125,7 @@ pub const Decl = struct {
125 /// mapping them to an address in the output file.125 /// mapping them to an address in the output file.
126 /// Memory owned by this decl, using Module's allocator.126 /// Memory owned by this decl, using Module's allocator.
127 name: [*:0]const u8,127 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`.
129 /// Reference to externally owned memory.129 /// Reference to externally owned memory.
130 scope: *Scope,130 scope: *Scope,
131 /// The AST Node decl index or ZIR Inst index that contains this declaration.131 /// The AST Node decl index or ZIR Inst index that contains this declaration.
...@@ -201,9 +201,9 @@ pub const Decl = struct {...@@ -201,9 +201,9 @@ pub const Decl = struct {
201 /// typed_value may need to be regenerated.201 /// typed_value may need to be regenerated.
202 dependencies: DepsTable = .{},202 dependencies: DepsTable = .{},
203203
204 /// The reason this is not `std.AutoHashMapUnmanaged` is a workaround for204 /// The reason this is not `std.AutoArrayHashMapUnmanaged` is a workaround for
205 /// stage1 compiler giving me: `error: struct 'Module.Decl' depends on itself`205 /// 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
208 pub fn destroy(self: *Decl, gpa: *Allocator) void {208 pub fn destroy(self: *Decl, gpa: *Allocator) void {
209 gpa.free(mem.spanZ(self.name));209 gpa.free(mem.spanZ(self.name));
...@@ -217,9 +217,10 @@ pub const Decl = struct {...@@ -217,9 +217,10 @@ pub const Decl = struct {
217217
218 pub fn src(self: Decl) usize {218 pub fn src(self: Decl) usize {
219 switch (self.scope.tag) {219 switch (self.scope.tag) {
220 .file => {220 .container => {
221 const file = @fieldParentPtr(Scope.File, "base", self.scope);221 const container = @fieldParentPtr(Scope.Container, "base", self.scope);
222 const tree = file.contents.tree;222 const tree = container.file_scope.contents.tree;
223 // TODO Container should have it's own decls()
223 const decl_node = tree.root_node.decls()[self.src_index];224 const decl_node = tree.root_node.decls()[self.src_index];
224 return tree.token_locs[decl_node.firstToken()].start;225 return tree.token_locs[decl_node.firstToken()].start;
225 },226 },
...@@ -229,7 +230,7 @@ pub const Decl = struct {...@@ -229,7 +230,7 @@ pub const Decl = struct {
229 const src_decl = module.decls[self.src_index];230 const src_decl = module.decls[self.src_index];
230 return src_decl.inst.src;231 return src_decl.inst.src;
231 },232 },
232 .block => unreachable,233 .file, .block => unreachable,
233 .gen_zir => unreachable,234 .gen_zir => unreachable,
234 .local_val => unreachable,235 .local_val => unreachable,
235 .local_ptr => unreachable,236 .local_ptr => unreachable,
...@@ -359,6 +360,7 @@ pub const Scope = struct {...@@ -359,6 +360,7 @@ pub const Scope = struct {
359 .local_ptr => return self.cast(LocalPtr).?.gen_zir.arena,360 .local_ptr => return self.cast(LocalPtr).?.gen_zir.arena,
360 .zir_module => return &self.cast(ZIRModule).?.contents.module.arena.allocator,361 .zir_module => return &self.cast(ZIRModule).?.contents.module.arena.allocator,
361 .file => unreachable,362 .file => unreachable,
363 .container => unreachable,
362 }364 }
363 }365 }
364366
...@@ -368,15 +370,16 @@ pub const Scope = struct {...@@ -368,15 +370,16 @@ pub const Scope = struct {
368 return switch (self.tag) {370 return switch (self.tag) {
369 .block => self.cast(Block).?.decl,371 .block => self.cast(Block).?.decl,
370 .gen_zir => self.cast(GenZIR).?.decl,372 .gen_zir => self.cast(GenZIR).?.decl,
371 .local_val => return self.cast(LocalVal).?.gen_zir.decl,373 .local_val => self.cast(LocalVal).?.gen_zir.decl,
372 .local_ptr => return self.cast(LocalPtr).?.gen_zir.decl,374 .local_ptr => self.cast(LocalPtr).?.gen_zir.decl,
373 .decl => self.cast(DeclAnalysis).?.decl,375 .decl => self.cast(DeclAnalysis).?.decl,
374 .zir_module => null,376 .zir_module => null,
375 .file => null,377 .file => null,
378 .container => null,
376 };379 };
377 }380 }
378381
379 /// Asserts the scope has a parent which is a ZIRModule or File and382 /// Asserts the scope has a parent which is a ZIRModule or Container and
380 /// returns it.383 /// returns it.
381 pub fn namespace(self: *Scope) *Scope {384 pub fn namespace(self: *Scope) *Scope {
382 switch (self.tag) {385 switch (self.tag) {
...@@ -385,7 +388,8 @@ pub const Scope = struct {...@@ -385,7 +388,8 @@ pub const Scope = struct {
385 .local_val => return self.cast(LocalVal).?.gen_zir.decl.scope,388 .local_val => return self.cast(LocalVal).?.gen_zir.decl.scope,
386 .local_ptr => return self.cast(LocalPtr).?.gen_zir.decl.scope,389 .local_ptr => return self.cast(LocalPtr).?.gen_zir.decl.scope,
387 .decl => return self.cast(DeclAnalysis).?.decl.scope,390 .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,
389 }393 }
390 }394 }
391395
...@@ -399,8 +403,9 @@ pub const Scope = struct {...@@ -399,8 +403,9 @@ pub const Scope = struct {
399 .local_val => unreachable,403 .local_val => unreachable,
400 .local_ptr => unreachable,404 .local_ptr => unreachable,
401 .decl => unreachable,405 .decl => unreachable,
406 .file => unreachable,
402 .zir_module => return self.cast(ZIRModule).?.fullyQualifiedNameHash(name),407 .zir_module => return self.cast(ZIRModule).?.fullyQualifiedNameHash(name),
403 .file => return self.cast(File).?.fullyQualifiedNameHash(name),408 .container => return self.cast(Container).?.fullyQualifiedNameHash(name),
404 }409 }
405 }410 }
406411
...@@ -409,11 +414,12 @@ pub const Scope = struct {...@@ -409,11 +414,12 @@ pub const Scope = struct {
409 switch (self.tag) {414 switch (self.tag) {
410 .file => return self.cast(File).?.contents.tree,415 .file => return self.cast(File).?.contents.tree,
411 .zir_module => unreachable,416 .zir_module => unreachable,
412 .decl => return self.cast(DeclAnalysis).?.decl.scope.cast(File).?.contents.tree,417 .decl => return self.cast(DeclAnalysis).?.decl.scope.cast(Container).?.file_scope.contents.tree,
413 .block => return self.cast(Block).?.decl.scope.cast(File).?.contents.tree,418 .block => return self.cast(Block).?.decl.scope.cast(Container).?.file_scope.contents.tree,
414 .gen_zir => return self.cast(GenZIR).?.decl.scope.cast(File).?.contents.tree,419 .gen_zir => return self.cast(GenZIR).?.decl.scope.cast(Container).?.file_scope.contents.tree,
415 .local_val => return self.cast(LocalVal).?.gen_zir.decl.scope.cast(File).?.contents.tree,420 .local_val => return self.cast(LocalVal).?.gen_zir.decl.scope.cast(Container).?.file_scope.contents.tree,
416 .local_ptr => return self.cast(LocalPtr).?.gen_zir.decl.scope.cast(File).?.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,
417 }423 }
418 }424 }
419425
...@@ -427,13 +433,15 @@ pub const Scope = struct {...@@ -427,13 +433,15 @@ pub const Scope = struct {
427 .decl => unreachable,433 .decl => unreachable,
428 .zir_module => unreachable,434 .zir_module => unreachable,
429 .file => unreachable,435 .file => unreachable,
436 .container => unreachable,
430 };437 };
431 }438 }
432439
433 /// Asserts the scope has a parent which is a ZIRModule or File and440 /// Asserts the scope has a parent which is a ZIRModule, Contaienr or File and
434 /// returns the sub_file_path field.441 /// returns the sub_file_path field.
435 pub fn subFilePath(base: *Scope) []const u8 {442 pub fn subFilePath(base: *Scope) []const u8 {
436 switch (base.tag) {443 switch (base.tag) {
444 .container => return @fieldParentPtr(Container, "base", base).file_scope.sub_file_path,
437 .file => return @fieldParentPtr(File, "base", base).sub_file_path,445 .file => return @fieldParentPtr(File, "base", base).sub_file_path,
438 .zir_module => return @fieldParentPtr(ZIRModule, "base", base).sub_file_path,446 .zir_module => return @fieldParentPtr(ZIRModule, "base", base).sub_file_path,
439 .block => unreachable,447 .block => unreachable,
...@@ -453,11 +461,13 @@ pub const Scope = struct {...@@ -453,11 +461,13 @@ pub const Scope = struct {
453 .local_val => unreachable,461 .local_val => unreachable,
454 .local_ptr => unreachable,462 .local_ptr => unreachable,
455 .decl => unreachable,463 .decl => unreachable,
464 .container => unreachable,
456 }465 }
457 }466 }
458467
459 pub fn getSource(base: *Scope, module: *Module) ![:0]const u8 {468 pub fn getSource(base: *Scope, module: *Module) ![:0]const u8 {
460 switch (base.tag) {469 switch (base.tag) {
470 .container => return @fieldParentPtr(Container, "base", base).file_scope.getSource(module),
461 .file => return @fieldParentPtr(File, "base", base).getSource(module),471 .file => return @fieldParentPtr(File, "base", base).getSource(module),
462 .zir_module => return @fieldParentPtr(ZIRModule, "base", base).getSource(module),472 .zir_module => return @fieldParentPtr(ZIRModule, "base", base).getSource(module),
463 .gen_zir => unreachable,473 .gen_zir => unreachable,
...@@ -471,8 +481,9 @@ pub const Scope = struct {...@@ -471,8 +481,9 @@ pub const Scope = struct {
471 /// Asserts the scope is a namespace Scope and removes the Decl from the namespace.481 /// Asserts the scope is a namespace Scope and removes the Decl from the namespace.
472 pub fn removeDecl(base: *Scope, child: *Decl) void {482 pub fn removeDecl(base: *Scope, child: *Decl) void {
473 switch (base.tag) {483 switch (base.tag) {
474 .file => return @fieldParentPtr(File, "base", base).removeDecl(child),484 .container => return @fieldParentPtr(Container, "base", base).removeDecl(child),
475 .zir_module => return @fieldParentPtr(ZIRModule, "base", base).removeDecl(child),485 .zir_module => return @fieldParentPtr(ZIRModule, "base", base).removeDecl(child),
486 .file => unreachable,
476 .block => unreachable,487 .block => unreachable,
477 .gen_zir => unreachable,488 .gen_zir => unreachable,
478 .local_val => unreachable,489 .local_val => unreachable,
...@@ -499,6 +510,7 @@ pub const Scope = struct {...@@ -499,6 +510,7 @@ pub const Scope = struct {
499 .local_val => unreachable,510 .local_val => unreachable,
500 .local_ptr => unreachable,511 .local_ptr => unreachable,
501 .decl => unreachable,512 .decl => unreachable,
513 .container => unreachable,
502 }514 }
503 }515 }
504516
...@@ -515,6 +527,8 @@ pub const Scope = struct {...@@ -515,6 +527,8 @@ pub const Scope = struct {
515 zir_module,527 zir_module,
516 /// .zig source code.528 /// .zig source code.
517 file,529 file,
530 /// struct, enum or union, every .file contains one of these.
531 container,
518 block,532 block,
519 decl,533 decl,
520 gen_zir,534 gen_zir,
...@@ -522,6 +536,33 @@ pub const Scope = struct {...@@ -522,6 +536,33 @@ pub const Scope = struct {
522 local_ptr,536 local_ptr,
523 };537 };
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
525 pub const File = struct {566 pub const File = struct {
526 pub const base_tag: Tag = .file;567 pub const base_tag: Tag = .file;
527 base: Scope = Scope{ .tag = base_tag },568 base: Scope = Scope{ .tag = base_tag },
...@@ -544,8 +585,7 @@ pub const Scope = struct {...@@ -544,8 +585,7 @@ pub const Scope = struct {
544 loaded_success,585 loaded_success,
545 },586 },
546587
547 /// Direct children of the file.588 root_container: Container,
548 decls: ArrayListUnmanaged(*Decl),
549589
550 pub fn unload(self: *File, gpa: *Allocator) void {590 pub fn unload(self: *File, gpa: *Allocator) void {
551 switch (self.status) {591 switch (self.status) {
...@@ -569,20 +609,11 @@ pub const Scope = struct {...@@ -569,20 +609,11 @@ pub const Scope = struct {
569 }609 }
570610
571 pub fn deinit(self: *File, gpa: *Allocator) void {611 pub fn deinit(self: *File, gpa: *Allocator) void {
572 self.decls.deinit(gpa);612 self.root_container.deinit(gpa);
573 self.unload(gpa);613 self.unload(gpa);
574 self.* = undefined;614 self.* = undefined;
575 }615 }
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
586 pub fn dumpSrc(self: *File, src: usize) void {617 pub fn dumpSrc(self: *File, src: usize) void {
587 const loc = std.zig.findLineColumn(self.source.bytes, src);618 const loc = std.zig.findLineColumn(self.source.bytes, src);
588 std.debug.print("{}:{}:{}\n", .{ self.sub_file_path, loc.line + 1, loc.column + 1 });619 std.debug.print("{}:{}:{}\n", .{ self.sub_file_path, loc.line + 1, loc.column + 1 });
...@@ -604,11 +635,6 @@ pub const Scope = struct {...@@ -604,11 +635,6 @@ pub const Scope = struct {
604 .bytes => |bytes| return bytes,635 .bytes => |bytes| return bytes,
605 }636 }
606 }637 }
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 }
612 };638 };
613639
614 pub const ZIRModule = struct {640 pub const ZIRModule = struct {
...@@ -725,6 +751,7 @@ pub const Scope = struct {...@@ -725,6 +751,7 @@ pub const Scope = struct {
725 /// Points to the arena allocator of DeclAnalysis751 /// Points to the arena allocator of DeclAnalysis
726 arena: *Allocator,752 arena: *Allocator,
727 label: ?Label = null,753 label: ?Label = null,
754 is_comptime: bool,
728755
729 pub const Label = struct {756 pub const Label = struct {
730 zir_block: *zir.Inst.Block,757 zir_block: *zir.Inst.Block,
...@@ -860,7 +887,10 @@ pub fn init(gpa: *Allocator, options: InitOptions) !Module {...@@ -860,7 +887,10 @@ pub fn init(gpa: *Allocator, options: InitOptions) !Module {
860 .source = .{ .unloaded = {} },887 .source = .{ .unloaded = {} },
861 .contents = .{ .not_available = {} },888 .contents = .{ .not_available = {} },
862 .status = .never_loaded,889 .status = .never_loaded,
863 .decls = .{},890 .root_container = .{
891 .file_scope = root_scope,
892 .decls = .{},
893 },
864 };894 };
865 break :blk &root_scope.base;895 break :blk &root_scope.base;
866 } else if (mem.endsWith(u8, options.root_pkg.root_src_path, ".zir")) {896 } else if (mem.endsWith(u8, options.root_pkg.root_src_path, ".zir")) {
...@@ -932,7 +962,8 @@ pub fn deinit(self: *Module) void {...@@ -932,7 +962,8 @@ pub fn deinit(self: *Module) void {
932 self.symbol_exports.deinit(gpa);962 self.symbol_exports.deinit(gpa);
933 self.root_scope.destroy(gpa);963 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| {
936 gpa.free(entry.key);967 gpa.free(entry.key);
937 }968 }
938 self.global_error_set.deinit(gpa);969 self.global_error_set.deinit(gpa);
...@@ -967,7 +998,7 @@ pub fn update(self: *Module) !void {...@@ -967,7 +998,7 @@ pub fn update(self: *Module) !void {
967 // to force a refresh we unload now.998 // to force a refresh we unload now.
968 if (self.root_scope.cast(Scope.File)) |zig_file| {999 if (self.root_scope.cast(Scope.File)) |zig_file| {
969 zig_file.unload(self.gpa);1000 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) {
971 error.AnalysisFail => {1002 error.AnalysisFail => {
972 assert(self.totalErrorCount() != 0);1003 assert(self.totalErrorCount() != 0);
973 },1004 },
...@@ -1235,8 +1266,8 @@ fn astGenAndAnalyzeDecl(self: *Module, decl: *Decl) !bool {...@@ -1235,8 +1266,8 @@ fn astGenAndAnalyzeDecl(self: *Module, decl: *Decl) !bool {
1235 const tracy = trace(@src());1266 const tracy = trace(@src());
1236 defer tracy.end();1267 defer tracy.end();
12371268
1238 const file_scope = decl.scope.cast(Scope.File).?;1269 const container_scope = decl.scope.cast(Scope.Container).?;
1239 const tree = try self.getAstTree(file_scope);1270 const tree = try self.getAstTree(container_scope);
1240 const ast_node = tree.root_node.decls()[decl.src_index];1271 const ast_node = tree.root_node.decls()[decl.src_index];
1241 switch (ast_node.tag) {1272 switch (ast_node.tag) {
1242 .FnProto => {1273 .FnProto => {
...@@ -1307,7 +1338,6 @@ fn astGenAndAnalyzeDecl(self: *Module, decl: *Decl) !bool {...@@ -1307,7 +1338,6 @@ fn astGenAndAnalyzeDecl(self: *Module, decl: *Decl) !bool {
1307 .return_type = return_type_inst,1338 .return_type = return_type_inst,
1308 .param_types = param_types,1339 .param_types = param_types,
1309 }, .{});1340 }, .{});
1310 _ = try astgen.addZIRUnOp(self, &fn_type_scope.base, fn_src, .@"return", fn_type_inst);
13111341
1312 // We need the memory for the Type to go into the arena for the Decl1342 // We need the memory for the Type to go into the arena for the Decl
1313 var decl_arena = std.heap.ArenaAllocator.init(self.gpa);1343 var decl_arena = std.heap.ArenaAllocator.init(self.gpa);
...@@ -1320,10 +1350,11 @@ fn astGenAndAnalyzeDecl(self: *Module, decl: *Decl) !bool {...@@ -1320,10 +1350,11 @@ fn astGenAndAnalyzeDecl(self: *Module, decl: *Decl) !bool {
1320 .decl = decl,1350 .decl = decl,
1321 .instructions = .{},1351 .instructions = .{},
1322 .arena = &decl_arena.allocator,1352 .arena = &decl_arena.allocator,
1353 .is_comptime = false,
1323 };1354 };
1324 defer block_scope.instructions.deinit(self.gpa);1355 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, .{
1327 .instructions = fn_type_scope.instructions.items,1358 .instructions = fn_type_scope.instructions.items,
1328 });1359 });
1329 const new_func = try decl_arena.allocator.create(Fn);1360 const new_func = try decl_arena.allocator.create(Fn);
...@@ -1457,6 +1488,7 @@ fn astGenAndAnalyzeDecl(self: *Module, decl: *Decl) !bool {...@@ -1457,6 +1488,7 @@ fn astGenAndAnalyzeDecl(self: *Module, decl: *Decl) !bool {
1457 .decl = decl,1488 .decl = decl,
1458 .instructions = .{},1489 .instructions = .{},
1459 .arena = &decl_arena.allocator,1490 .arena = &decl_arena.allocator,
1491 .is_comptime = true,
1460 };1492 };
1461 defer block_scope.instructions.deinit(self.gpa);1493 defer block_scope.instructions.deinit(self.gpa);
14621494
...@@ -1489,35 +1521,7 @@ fn astGenAndAnalyzeDecl(self: *Module, decl: *Decl) !bool {...@@ -1489,35 +1521,7 @@ fn astGenAndAnalyzeDecl(self: *Module, decl: *Decl) !bool {
1489 return self.failNode(&block_scope.base, sect_expr, "TODO implement function section expression", .{});1521 return self.failNode(&block_scope.base, sect_expr, "TODO implement function section expression", .{});
1490 }1522 }
14911523
1492 const explicit_type = blk: {1524 const var_info: struct { ty: Type, val: ?Value } = if (var_decl.getInitNode()) |init_node| vi: {
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: {
1521 var gen_scope_arena = std.heap.ArenaAllocator.init(self.gpa);1525 var gen_scope_arena = std.heap.ArenaAllocator.init(self.gpa);
1522 defer gen_scope_arena.deinit();1526 defer gen_scope_arena.deinit();
1523 var gen_scope: Scope.GenZIR = .{1527 var gen_scope: Scope.GenZIR = .{
...@@ -1526,11 +1530,19 @@ fn astGenAndAnalyzeDecl(self: *Module, decl: *Decl) !bool {...@@ -1526,11 +1530,19 @@ fn astGenAndAnalyzeDecl(self: *Module, decl: *Decl) !bool {
1526 .parent = decl.scope,1530 .parent = decl.scope,
1527 };1531 };
1528 defer gen_scope.instructions.deinit(self.gpa);1532 defer gen_scope.instructions.deinit(self.gpa);
1529 const src = tree.token_locs[init_node.firstToken()].start;
15301533
1531 // TODO comptime scope here1534 const init_result_loc: astgen.ResultLoc = if (var_decl.getTypeNode()) |type_node| rl: {
1532 const init_inst = try astgen.expr(self, &gen_scope.base, .none, init_node);1535 const src = tree.token_locs[type_node.firstToken()].start;
1533 _ = try astgen.addZIRUnOp(self, &gen_scope.base, src, .@"return", init_inst);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
1535 var inner_block: Scope.Block = .{1547 var inner_block: Scope.Block = .{
1536 .parent = null,1548 .parent = null,
...@@ -1538,42 +1550,58 @@ fn astGenAndAnalyzeDecl(self: *Module, decl: *Decl) !bool {...@@ -1538,42 +1550,58 @@ fn astGenAndAnalyzeDecl(self: *Module, decl: *Decl) !bool {
1538 .decl = decl,1550 .decl = decl,
1539 .instructions = .{},1551 .instructions = .{},
1540 .arena = &gen_scope_arena.allocator,1552 .arena = &gen_scope_arena.allocator,
1553 .is_comptime = true,
1541 };1554 };
1542 defer inner_block.instructions.deinit(self.gpa);1555 defer inner_block.instructions.deinit(self.gpa);
1543 try zir_sema.analyzeBody(self, &inner_block.base, .{ .instructions = gen_scope.instructions.items });1556 try zir_sema.analyzeBody(self, &inner_block.base, .{ .instructions = gen_scope.instructions.items });
15441557
1545 for (inner_block.instructions.items) |inst| {1558 // The result location guarantees the type coercion.
1546 if (inst.castTag(.ret)) |ret| {1559 const analyzed_init_inst = init_inst.analyzed_inst.?;
1547 const coerced = if (explicit_type) |some|1560 // The is_comptime in the Scope.Block guarantees the result is comptime-known.
1548 try self.coerce(&inner_block.base, some, ret.operand)1561 const val = analyzed_init_inst.value().?;
1549 else1562
1550 ret.operand;1563 const ty = try analyzed_init_inst.ty.copy(block_scope.arena);
1551 const val = coerced.value() orelse1564 break :vi .{
1552 return self.fail(&block_scope.base, inst.src, "unable to resolve comptime value", .{});1565 .ty = ty,
15531566 .val = try val.copy(block_scope.arena),
1554 var_type = explicit_type orelse try ret.operand.ty.copy(block_scope.arena);1567 };
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;
1561 } else if (!is_extern) {1568 } else if (!is_extern) {
1562 return self.failTok(&block_scope.base, var_decl.firstToken(), "variables must be initialized", .{});1569 return self.failTok(&block_scope.base, var_decl.firstToken(), "variables must be initialized", .{});
1563 } else if (explicit_type) |some| blk: {1570 } else if (var_decl.getTypeNode()) |type_node| vi: {
1564 var_type = some;1571 // Temporary arena for the zir instructions.
1565 break :blk null;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 };
1566 } else {1594 } else {
1567 return self.failTok(&block_scope.base, var_decl.firstToken(), "unable to infer variable type", .{});1595 return self.failTok(&block_scope.base, var_decl.firstToken(), "unable to infer variable type", .{});
1568 };1596 };
15691597
1570 if (is_mutable and !var_type.isValidVarType(is_extern)) {1598 if (is_mutable and !var_info.ty.isValidVarType(is_extern)) {
1571 return self.failTok(&block_scope.base, var_decl.firstToken(), "variable of type '{}' must be const", .{var_type});1599 return self.failTok(&block_scope.base, var_decl.firstToken(), "variable of type '{}' must be const", .{var_info.ty});
1572 }1600 }
15731601
1574 var type_changed = true;1602 var type_changed = true;
1575 if (decl.typedValueManaged()) |tvm| {1603 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
1578 tvm.deinit(self.gpa);1606 tvm.deinit(self.gpa);
1579 }1607 }
...@@ -1582,7 +1610,7 @@ fn astGenAndAnalyzeDecl(self: *Module, decl: *Decl) !bool {...@@ -1582,7 +1610,7 @@ fn astGenAndAnalyzeDecl(self: *Module, decl: *Decl) !bool {
1582 const var_payload = try decl_arena.allocator.create(Value.Payload.Variable);1610 const var_payload = try decl_arena.allocator.create(Value.Payload.Variable);
1583 new_variable.* = .{1611 new_variable.* = .{
1584 .owner_decl = decl,1612 .owner_decl = decl,
1585 .init = value orelse undefined,1613 .init = var_info.val orelse undefined,
1586 .is_extern = is_extern,1614 .is_extern = is_extern,
1587 .is_mutable = is_mutable,1615 .is_mutable = is_mutable,
1588 .is_threadlocal = is_threadlocal,1616 .is_threadlocal = is_threadlocal,
...@@ -1593,7 +1621,7 @@ fn astGenAndAnalyzeDecl(self: *Module, decl: *Decl) !bool {...@@ -1593,7 +1621,7 @@ fn astGenAndAnalyzeDecl(self: *Module, decl: *Decl) !bool {
1593 decl.typed_value = .{1621 decl.typed_value = .{
1594 .most_recent = .{1622 .most_recent = .{
1595 .typed_value = .{1623 .typed_value = .{
1596 .ty = var_type,1624 .ty = var_info.ty,
1597 .val = Value.initPayload(&var_payload.base),1625 .val = Value.initPayload(&var_payload.base),
1598 },1626 },
1599 .arena = decl_arena_state,1627 .arena = decl_arena_state,
...@@ -1628,8 +1656,7 @@ fn astGenAndAnalyzeDecl(self: *Module, decl: *Decl) !bool {...@@ -1628,8 +1656,7 @@ fn astGenAndAnalyzeDecl(self: *Module, decl: *Decl) !bool {
1628 };1656 };
1629 defer gen_scope.instructions.deinit(self.gpa);1657 defer gen_scope.instructions.deinit(self.gpa);
16301658
1631 // TODO comptime scope here1659 _ = try astgen.comptimeExpr(self, &gen_scope.base, .none, comptime_decl.expr);
1632 _ = try astgen.expr(self, &gen_scope.base, .none, comptime_decl.expr);
16331660
1634 var block_scope: Scope.Block = .{1661 var block_scope: Scope.Block = .{
1635 .parent = null,1662 .parent = null,
...@@ -1637,6 +1664,7 @@ fn astGenAndAnalyzeDecl(self: *Module, decl: *Decl) !bool {...@@ -1637,6 +1664,7 @@ fn astGenAndAnalyzeDecl(self: *Module, decl: *Decl) !bool {
1637 .decl = decl,1664 .decl = decl,
1638 .instructions = .{},1665 .instructions = .{},
1639 .arena = &analysis_arena.allocator,1666 .arena = &analysis_arena.allocator,
1667 .is_comptime = true,
1640 };1668 };
1641 defer block_scope.instructions.deinit(self.gpa);1669 defer block_scope.instructions.deinit(self.gpa);
16421670
...@@ -1699,10 +1727,12 @@ fn getSrcModule(self: *Module, root_scope: *Scope.ZIRModule) !*zir.Module {...@@ -1699,10 +1727,12 @@ fn getSrcModule(self: *Module, root_scope: *Scope.ZIRModule) !*zir.Module {
1699 }1727 }
1700}1728}
17011729
1702fn getAstTree(self: *Module, root_scope: *Scope.File) !*ast.Tree {1730fn getAstTree(self: *Module, container_scope: *Scope.Container) !*ast.Tree {
1703 const tracy = trace(@src());1731 const tracy = trace(@src());
1704 defer tracy.end();1732 defer tracy.end();
17051733
1734 const root_scope = container_scope.file_scope;
1735
1706 switch (root_scope.status) {1736 switch (root_scope.status) {
1707 .never_loaded, .unloaded_success => {1737 .never_loaded, .unloaded_success => {
1708 try self.failed_files.ensureCapacity(self.gpa, self.failed_files.items().len + 1);1738 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 {...@@ -1744,25 +1774,25 @@ fn getAstTree(self: *Module, root_scope: *Scope.File) !*ast.Tree {
1744 }1774 }
1745}1775}
17461776
1747fn analyzeRootSrcFile(self: *Module, root_scope: *Scope.File) !void {1777fn analyzeContainer(self: *Module, container_scope: *Scope.Container) !void {
1748 const tracy = trace(@src());1778 const tracy = trace(@src());
1749 defer tracy.end();1779 defer tracy.end();
17501780
1751 // We may be analyzing it for the first time, or this may be1781 // We may be analyzing it for the first time, or this may be
1752 // an incremental update. This code handles both cases.1782 // an incremental update. This code handles both cases.
1753 const tree = try self.getAstTree(root_scope);1783 const tree = try self.getAstTree(container_scope);
1754 const decls = tree.root_node.decls();1784 const decls = tree.root_node.decls();
17551785
1756 try self.work_queue.ensureUnusedCapacity(decls.len);1786 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
1759 // Keep track of the decls that we expect to see in this file so that1789 // Keep track of the decls that we expect to see in this file so that
1760 // we know which ones have been deleted.1790 // 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);
1762 defer deleted_decls.deinit();1792 defer deleted_decls.deinit();
1763 try deleted_decls.ensureCapacity(root_scope.decls.items.len);1793 try deleted_decls.ensureCapacity(container_scope.decls.items().len);
1764 for (root_scope.decls.items) |file_decl| {1794 for (container_scope.decls.items()) |entry| {
1765 deleted_decls.putAssumeCapacityNoClobber(file_decl, {});1795 deleted_decls.putAssumeCapacityNoClobber(entry.key, {});
1766 }1796 }
17671797
1768 for (decls) |src_decl, decl_i| {1798 for (decls) |src_decl, decl_i| {
...@@ -1774,7 +1804,7 @@ fn analyzeRootSrcFile(self: *Module, root_scope: *Scope.File) !void {...@@ -1774,7 +1804,7 @@ fn analyzeRootSrcFile(self: *Module, root_scope: *Scope.File) !void {
17741804
1775 const name_loc = tree.token_locs[name_tok];1805 const name_loc = tree.token_locs[name_tok];
1776 const name = tree.tokenSliceLoc(name_loc);1806 const name = tree.tokenSliceLoc(name_loc);
1777 const name_hash = root_scope.fullyQualifiedNameHash(name);1807 const name_hash = container_scope.fullyQualifiedNameHash(name);
1778 const contents_hash = std.zig.hashSrc(tree.getNodeSource(src_decl));1808 const contents_hash = std.zig.hashSrc(tree.getNodeSource(src_decl));
1779 if (self.decl_table.get(name_hash)) |decl| {1809 if (self.decl_table.get(name_hash)) |decl| {
1780 // Update the AST Node index of the decl, even if its contents are unchanged, it may1810 // 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 {...@@ -1802,8 +1832,8 @@ fn analyzeRootSrcFile(self: *Module, root_scope: *Scope.File) !void {
1802 }1832 }
1803 }1833 }
1804 } else {1834 } else {
1805 const new_decl = try self.createNewDecl(&root_scope.base, name, decl_i, name_hash, contents_hash);1835 const new_decl = try self.createNewDecl(&container_scope.base, name, decl_i, name_hash, contents_hash);
1806 root_scope.decls.appendAssumeCapacity(new_decl);1836 container_scope.decls.putAssumeCapacity(new_decl, {});
1807 if (fn_proto.getExternExportInlineToken()) |maybe_export_token| {1837 if (fn_proto.getExternExportInlineToken()) |maybe_export_token| {
1808 if (tree.token_ids[maybe_export_token] == .Keyword_export) {1838 if (tree.token_ids[maybe_export_token] == .Keyword_export) {
1809 self.work_queue.writeItemAssumeCapacity(.{ .analyze_decl = new_decl });1839 self.work_queue.writeItemAssumeCapacity(.{ .analyze_decl = new_decl });
...@@ -1813,7 +1843,7 @@ fn analyzeRootSrcFile(self: *Module, root_scope: *Scope.File) !void {...@@ -1813,7 +1843,7 @@ fn analyzeRootSrcFile(self: *Module, root_scope: *Scope.File) !void {
1813 } else if (src_decl.castTag(.VarDecl)) |var_decl| {1843 } else if (src_decl.castTag(.VarDecl)) |var_decl| {
1814 const name_loc = tree.token_locs[var_decl.name_token];1844 const name_loc = tree.token_locs[var_decl.name_token];
1815 const name = tree.tokenSliceLoc(name_loc);1845 const name = tree.tokenSliceLoc(name_loc);
1816 const name_hash = root_scope.fullyQualifiedNameHash(name);1846 const name_hash = container_scope.fullyQualifiedNameHash(name);
1817 const contents_hash = std.zig.hashSrc(tree.getNodeSource(src_decl));1847 const contents_hash = std.zig.hashSrc(tree.getNodeSource(src_decl));
1818 if (self.decl_table.get(name_hash)) |decl| {1848 if (self.decl_table.get(name_hash)) |decl| {
1819 // Update the AST Node index of the decl, even if its contents are unchanged, it may1849 // 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 {...@@ -1829,8 +1859,8 @@ fn analyzeRootSrcFile(self: *Module, root_scope: *Scope.File) !void {
1829 decl.contents_hash = contents_hash;1859 decl.contents_hash = contents_hash;
1830 }1860 }
1831 } else {1861 } else {
1832 const new_decl = try self.createNewDecl(&root_scope.base, name, decl_i, name_hash, contents_hash);1862 const new_decl = try self.createNewDecl(&container_scope.base, name, decl_i, name_hash, contents_hash);
1833 root_scope.decls.appendAssumeCapacity(new_decl);1863 container_scope.decls.putAssumeCapacity(new_decl, {});
1834 if (var_decl.getExternExportToken()) |maybe_export_token| {1864 if (var_decl.getExternExportToken()) |maybe_export_token| {
1835 if (tree.token_ids[maybe_export_token] == .Keyword_export) {1865 if (tree.token_ids[maybe_export_token] == .Keyword_export) {
1836 self.work_queue.writeItemAssumeCapacity(.{ .analyze_decl = new_decl });1866 self.work_queue.writeItemAssumeCapacity(.{ .analyze_decl = new_decl });
...@@ -1842,11 +1872,11 @@ fn analyzeRootSrcFile(self: *Module, root_scope: *Scope.File) !void {...@@ -1842,11 +1872,11 @@ fn analyzeRootSrcFile(self: *Module, root_scope: *Scope.File) !void {
1842 const name = try std.fmt.allocPrint(self.gpa, "__comptime_{}", .{name_index});1872 const name = try std.fmt.allocPrint(self.gpa, "__comptime_{}", .{name_index});
1843 defer self.gpa.free(name);1873 defer self.gpa.free(name);
18441874
1845 const name_hash = root_scope.fullyQualifiedNameHash(name);1875 const name_hash = container_scope.fullyQualifiedNameHash(name);
1846 const contents_hash = std.zig.hashSrc(tree.getNodeSource(src_decl));1876 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);1878 const new_decl = try self.createNewDecl(&container_scope.base, name, decl_i, name_hash, contents_hash);
1849 root_scope.decls.appendAssumeCapacity(new_decl);1879 container_scope.decls.putAssumeCapacity(new_decl, {});
1850 self.work_queue.writeItemAssumeCapacity(.{ .analyze_decl = new_decl });1880 self.work_queue.writeItemAssumeCapacity(.{ .analyze_decl = new_decl });
1851 } else if (src_decl.castTag(.ContainerField)) |container_field| {1881 } else if (src_decl.castTag(.ContainerField)) |container_field| {
1852 log.err("TODO: analyze container field", .{});1882 log.err("TODO: analyze container field", .{});
...@@ -1879,7 +1909,7 @@ fn analyzeRootZIRModule(self: *Module, root_scope: *Scope.ZIRModule) !void {...@@ -1879,7 +1909,7 @@ fn analyzeRootZIRModule(self: *Module, root_scope: *Scope.ZIRModule) !void {
18791909
1880 // Keep track of the decls that we expect to see in this file so that1910 // Keep track of the decls that we expect to see in this file so that
1881 // we know which ones have been deleted.1911 // 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);
1883 defer deleted_decls.deinit();1913 defer deleted_decls.deinit();
1884 try deleted_decls.ensureCapacity(self.decl_table.items().len);1914 try deleted_decls.ensureCapacity(self.decl_table.items().len);
1885 for (self.decl_table.items()) |entry| {1915 for (self.decl_table.items()) |entry| {
...@@ -2007,6 +2037,7 @@ fn analyzeFnBody(self: *Module, decl: *Decl, func: *Fn) !void {...@@ -2007,6 +2037,7 @@ fn analyzeFnBody(self: *Module, decl: *Decl, func: *Fn) !void {
2007 .decl = decl,2037 .decl = decl,
2008 .instructions = .{},2038 .instructions = .{},
2009 .arena = &arena.allocator,2039 .arena = &arena.allocator,
2040 .is_comptime = false,
2010 };2041 };
2011 defer inner_block.instructions.deinit(self.gpa);2042 defer inner_block.instructions.deinit(self.gpa);
20122043
...@@ -2088,16 +2119,23 @@ pub fn getErrorValue(self: *Module, name: []const u8) !std.StringHashMapUnmanage...@@ -2088,16 +2119,23 @@ pub fn getErrorValue(self: *Module, name: []const u8) !std.StringHashMapUnmanage
2088 errdefer self.global_error_set.removeAssertDiscard(name);2119 errdefer self.global_error_set.removeAssertDiscard(name);
20892120
2090 gop.entry.key = try self.gpa.dupe(u8, name);2121 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);
2092 return gop.entry.*;2123 return gop.entry.*;
2093}2124}
20942125
2095/// TODO split this into `requireRuntimeBlock` and `requireFunctionBlock` and audit callsites.2126pub fn requireFunctionBlock(self: *Module, scope: *Scope, src: usize) !*Scope.Block {
2096pub fn requireRuntimeBlock(self: *Module, scope: *Scope, src: usize) !*Scope.Block {
2097 return scope.cast(Scope.Block) orelse2127 return scope.cast(Scope.Block) orelse
2098 return self.fail(scope, src, "instruction illegal outside function body", .{});2128 return self.fail(scope, src, "instruction illegal outside function body", .{});
2099}2129}
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
2101pub fn resolveConstValue(self: *Module, scope: *Scope, base: *Inst) !Value {2139pub fn resolveConstValue(self: *Module, scope: *Scope, base: *Inst) !Value {
2102 return (try self.resolveDefinedValue(scope, base)) orelse2140 return (try self.resolveDefinedValue(scope, base)) orelse
2103 return self.fail(scope, base.src, "unable to resolve comptime value", .{});2141 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...@@ -2584,6 +2622,72 @@ pub fn analyzeIsErr(self: *Module, scope: *Scope, src: usize, operand: *Inst) In
2584 return self.fail(scope, src, "TODO implement analysis of iserr", .{});2622 return self.fail(scope, src, "TODO implement analysis of iserr", .{});
2585}2623}
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
2587/// Asserts that lhs and rhs types are both numeric.2691/// Asserts that lhs and rhs types are both numeric.
2588pub fn cmpNumeric(2692pub fn cmpNumeric(
2589 self: *Module,2693 self: *Module,
...@@ -2794,6 +2898,12 @@ pub fn resolvePeerTypes(self: *Module, scope: *Scope, instructions: []*Inst) !Ty...@@ -2794,6 +2898,12 @@ pub fn resolvePeerTypes(self: *Module, scope: *Scope, instructions: []*Inst) !Ty
2794 prev_inst = next_inst;2898 prev_inst = next_inst;
2795 continue;2899 continue;
2796 }2900 }
2901 if (next_inst.ty.zigTypeTag() == .Undefined)
2902 continue;
2903 if (prev_inst.ty.zigTypeTag() == .Undefined) {
2904 prev_inst = next_inst;
2905 continue;
2906 }
2797 if (prev_inst.ty.isInt() and2907 if (prev_inst.ty.isInt() and
2798 next_inst.ty.isInt() and2908 next_inst.ty.isInt() and
2799 prev_inst.ty.isSignedInt() == next_inst.ty.isSignedInt())2909 prev_inst.ty.isSignedInt() == next_inst.ty.isSignedInt())
...@@ -3045,6 +3155,7 @@ fn failWithOwnedErrorMsg(self: *Module, scope: *Scope, src: usize, err_msg: *Err...@@ -3045,6 +3155,7 @@ fn failWithOwnedErrorMsg(self: *Module, scope: *Scope, src: usize, err_msg: *Err
3045 self.failed_files.putAssumeCapacityNoClobber(scope, err_msg);3155 self.failed_files.putAssumeCapacityNoClobber(scope, err_msg);
3046 },3156 },
3047 .file => unreachable,3157 .file => unreachable,
3158 .container => unreachable,
3048 }3159 }
3049 return error.AnalysisFail;3160 return error.AnalysisFail;
3050}3161}
...@@ -3432,6 +3543,7 @@ pub fn addSafetyCheck(mod: *Module, parent_block: *Scope.Block, ok: *Inst, panic...@@ -3432,6 +3543,7 @@ pub fn addSafetyCheck(mod: *Module, parent_block: *Scope.Block, ok: *Inst, panic
3432 .decl = parent_block.decl,3543 .decl = parent_block.decl,
3433 .instructions = .{},3544 .instructions = .{},
3434 .arena = parent_block.arena,3545 .arena = parent_block.arena,
3546 .is_comptime = parent_block.is_comptime,
3435 };3547 };
3436 defer fail_block.instructions.deinit(mod.gpa);3548 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...@@ -258,7 +258,7 @@ pub fn expr(mod: *Module, scope: *Scope, rl: ResultLoc, node: *ast.Node) InnerEr
258 .OptionalType => return rlWrap(mod, scope, rl, try optionalType(mod, scope, node.castTag(.OptionalType).?)),258 .OptionalType => return rlWrap(mod, scope, rl, try optionalType(mod, scope, node.castTag(.OptionalType).?)),
259 .UnwrapOptional => return unwrapOptional(mod, scope, rl, node.castTag(.UnwrapOptional).?),259 .UnwrapOptional => return unwrapOptional(mod, scope, rl, node.castTag(.UnwrapOptional).?),
260 .Block => return rlWrapVoid(mod, scope, rl, node, try blockExpr(mod, scope, node.castTag(.Block).?)),260 .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),
262 .Break => return rlWrap(mod, scope, rl, try breakExpr(mod, scope, node.castTag(.Break).?)),262 .Break => return rlWrap(mod, scope, rl, try breakExpr(mod, scope, node.castTag(.Break).?)),
263 .PtrType => return rlWrap(mod, scope, rl, try ptrType(mod, scope, node.castTag(.PtrType).?)),263 .PtrType => return rlWrap(mod, scope, rl, try ptrType(mod, scope, node.castTag(.PtrType).?)),
264 .GroupedExpression => return expr(mod, scope, rl, node.castTag(.GroupedExpression).?.expr),264 .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...@@ -275,15 +275,16 @@ pub fn expr(mod: *Module, scope: *Scope, rl: ResultLoc, node: *ast.Node) InnerEr
275 .ErrorType => return rlWrap(mod, scope, rl, try errorType(mod, scope, node.castTag(.ErrorType).?)),275 .ErrorType => return rlWrap(mod, scope, rl, try errorType(mod, scope, node.castTag(.ErrorType).?)),
276 .For => return forExpr(mod, scope, rl, node.castTag(.For).?),276 .For => return forExpr(mod, scope, rl, node.castTag(.For).?),
277 .ArrayAccess => return arrayAccess(mod, scope, rl, node.castTag(.ArrayAccess).?),277 .ArrayAccess => return arrayAccess(mod, scope, rl, node.castTag(.ArrayAccess).?),
278 .Slice => return rlWrap(mod, scope, rl, try sliceExpr(mod, scope, node.castTag(.Slice).?)),
278 .Catch => return catchExpr(mod, scope, rl, node.castTag(.Catch).?),279 .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
280 .Defer => return mod.failNode(scope, node, "TODO implement astgen.expr for .Defer", .{}),283 .Defer => return mod.failNode(scope, node, "TODO implement astgen.expr for .Defer", .{}),
281 .Range => return mod.failNode(scope, node, "TODO implement astgen.expr for .Range", .{}),284 .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", .{}),
283 .Await => return mod.failNode(scope, node, "TODO implement astgen.expr for .Await", .{}),285 .Await => return mod.failNode(scope, node, "TODO implement astgen.expr for .Await", .{}),
284 .Resume => return mod.failNode(scope, node, "TODO implement astgen.expr for .Resume", .{}),286 .Resume => return mod.failNode(scope, node, "TODO implement astgen.expr for .Resume", .{}),
285 .Try => return mod.failNode(scope, node, "TODO implement astgen.expr for .Try", .{}),287 .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", .{}),
287 .ArrayInitializer => return mod.failNode(scope, node, "TODO implement astgen.expr for .ArrayInitializer", .{}),288 .ArrayInitializer => return mod.failNode(scope, node, "TODO implement astgen.expr for .ArrayInitializer", .{}),
288 .ArrayInitializerDot => return mod.failNode(scope, node, "TODO implement astgen.expr for .ArrayInitializerDot", .{}),289 .ArrayInitializerDot => return mod.failNode(scope, node, "TODO implement astgen.expr for .ArrayInitializerDot", .{}),
289 .StructInitializer => return mod.failNode(scope, node, "TODO implement astgen.expr for .StructInitializer", .{}),290 .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...@@ -294,11 +295,46 @@ pub fn expr(mod: *Module, scope: *Scope, rl: ResultLoc, node: *ast.Node) InnerEr
294 .AnyType => return mod.failNode(scope, node, "TODO implement astgen.expr for .AnyType", .{}),295 .AnyType => return mod.failNode(scope, node, "TODO implement astgen.expr for .AnyType", .{}),
295 .FnProto => return mod.failNode(scope, node, "TODO implement astgen.expr for .FnProto", .{}),296 .FnProto => return mod.failNode(scope, node, "TODO implement astgen.expr for .FnProto", .{}),
296 .ContainerDecl => return mod.failNode(scope, node, "TODO implement astgen.expr for .ContainerDecl", .{}),297 .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", .{}),
298 .Nosuspend => return mod.failNode(scope, node, "TODO implement astgen.expr for .Nosuspend", .{}),298 .Nosuspend => return mod.failNode(scope, node, "TODO implement astgen.expr for .Nosuspend", .{}),
299 }299 }
300}300}
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
302fn breakExpr(mod: *Module, parent_scope: *Scope, node: *ast.Node.ControlFlowExpression) InnerError!*zir.Inst {338fn breakExpr(mod: *Module, parent_scope: *Scope, node: *ast.Node.ControlFlowExpression) InnerError!*zir.Inst {
303 const tree = parent_scope.tree();339 const tree = parent_scope.tree();
304 const src = tree.token_locs[node.ltoken].start;340 const src = tree.token_locs[node.ltoken].start;
...@@ -360,10 +396,13 @@ fn labeledBlockExpr(...@@ -360,10 +396,13 @@ fn labeledBlockExpr(
360 parent_scope: *Scope,396 parent_scope: *Scope,
361 rl: ResultLoc,397 rl: ResultLoc,
362 block_node: *ast.Node.LabeledBlock,398 block_node: *ast.Node.LabeledBlock,
399 zir_tag: zir.Inst.Tag,
363) InnerError!*zir.Inst {400) InnerError!*zir.Inst {
364 const tracy = trace(@src());401 const tracy = trace(@src());
365 defer tracy.end();402 defer tracy.end();
366403
404 assert(zir_tag == .block or zir_tag == .block_comptime);
405
367 const tree = parent_scope.tree();406 const tree = parent_scope.tree();
368 const src = tree.token_locs[block_node.lbrace].start;407 const src = tree.token_locs[block_node.lbrace].start;
369408
...@@ -373,7 +412,7 @@ fn labeledBlockExpr(...@@ -373,7 +412,7 @@ fn labeledBlockExpr(
373 const block_inst = try gen_zir.arena.create(zir.Inst.Block);412 const block_inst = try gen_zir.arena.create(zir.Inst.Block);
374 block_inst.* = .{413 block_inst.* = .{
375 .base = .{414 .base = .{
376 .tag = .block,415 .tag = zir_tag,
377 .src = src,416 .src = src,
378 },417 },
379 .positionals = .{418 .positionals = .{
...@@ -751,13 +790,31 @@ fn errorType(mod: *Module, scope: *Scope, node: *ast.Node.OneToken) InnerError!*...@@ -751,13 +790,31 @@ fn errorType(mod: *Module, scope: *Scope, node: *ast.Node.OneToken) InnerError!*
751}790}
752791
753fn catchExpr(mod: *Module, scope: *Scope, rl: ResultLoc, node: *ast.Node.Catch) InnerError!*zir.Inst {792fn 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 {
754 const tree = scope.tree();811 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);814 const operand_ptr = try expr(mod, scope, .ref, lhs);
758 // TODO we could avoid an unnecessary copy if .iserr took a pointer815 // TODO we could avoid an unnecessary copy if .iserr, .isnull took a pointer
759 const err_union = try addZIRUnOp(mod, scope, src, .deref, err_union_ptr);816 const err_union = try addZIRUnOp(mod, scope, src, .deref, operand_ptr);
760 const cond = try addZIRUnOp(mod, scope, src, .iserr, err_union);817 const cond = try addZIRUnOp(mod, scope, src, cond_op, err_union);
761818
762 var block_scope: Scope.GenZIR = .{819 var block_scope: Scope.GenZIR = .{
763 .parent = scope,820 .parent = scope,
...@@ -773,7 +830,7 @@ fn catchExpr(mod: *Module, scope: *Scope, rl: ResultLoc, node: *ast.Node.Catch)...@@ -773,7 +830,7 @@ fn catchExpr(mod: *Module, scope: *Scope, rl: ResultLoc, node: *ast.Node.Catch)
773 .else_body = undefined, // populated below830 .else_body = undefined, // populated below
774 }, .{});831 }, .{});
775832
776 const block = try addZIRInstBlock(mod, scope, src, .{833 const block = try addZIRInstBlock(mod, scope, src, .block, .{
777 .instructions = try block_scope.arena.dupe(*zir.Inst, block_scope.instructions.items),834 .instructions = try block_scope.arena.dupe(*zir.Inst, block_scope.instructions.items),
778 });835 });
779836
...@@ -786,55 +843,55 @@ fn catchExpr(mod: *Module, scope: *Scope, rl: ResultLoc, node: *ast.Node.Catch)...@@ -786,55 +843,55 @@ fn catchExpr(mod: *Module, scope: *Scope, rl: ResultLoc, node: *ast.Node.Catch)
786 .inferred_ptr, .bitcasted_ptr, .block_ptr => .{ .block_ptr = block },843 .inferred_ptr, .bitcasted_ptr, .block_ptr => .{ .block_ptr = block },
787 };844 };
788845
789 var err_scope: Scope.GenZIR = .{846 var then_scope: Scope.GenZIR = .{
790 .parent = scope,847 .parent = scope,
791 .decl = block_scope.decl,848 .decl = block_scope.decl,
792 .arena = block_scope.arena,849 .arena = block_scope.arena,
793 .instructions = .{},850 .instructions = .{},
794 };851 };
795 defer err_scope.instructions.deinit(mod.gpa);852 defer then_scope.instructions.deinit(mod.gpa);
796853
797 var err_val_scope: Scope.LocalVal = undefined;854 var err_val_scope: Scope.LocalVal = undefined;
798 const err_sub_scope = blk: {855 const then_sub_scope = blk: {
799 const payload = node.payload orelse856 const payload = payload_node orelse
800 break :blk &err_scope.base;857 break :blk &then_scope.base;
801858
802 const err_name = tree.tokenSlice(payload.castTag(.Payload).?.error_symbol.firstToken());859 const err_name = tree.tokenSlice(payload.castTag(.Payload).?.error_symbol.firstToken());
803 if (mem.eql(u8, err_name, "_"))860 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);
807 err_val_scope = .{864 err_val_scope = .{
808 .parent = &err_scope.base,865 .parent = &then_scope.base,
809 .gen_zir = &err_scope,866 .gen_zir = &then_scope,
810 .name = err_name,867 .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),
812 };869 };
813 break :blk &err_val_scope.base;870 break :blk &err_val_scope.base;
814 };871 };
815872
816 _ = try addZIRInst(mod, &err_scope.base, src, zir.Inst.Break, .{873 _ = try addZIRInst(mod, &then_scope.base, src, zir.Inst.Break, .{
817 .block = block,874 .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),
819 }, .{});876 }, .{});
820877
821 var not_err_scope: Scope.GenZIR = .{878 var else_scope: Scope.GenZIR = .{
822 .parent = scope,879 .parent = scope,
823 .decl = block_scope.decl,880 .decl = block_scope.decl,
824 .arena = block_scope.arena,881 .arena = block_scope.arena,
825 .instructions = .{},882 .instructions = .{},
826 };883 };
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);886 const unwrapped_payload = try addZIRUnOp(mod, &else_scope.base, src, unwrap_op, operand_ptr);
830 _ = try addZIRInst(mod, &not_err_scope.base, src, zir.Inst.Break, .{887 _ = try addZIRInst(mod, &else_scope.base, src, zir.Inst.Break, .{
831 .block = block,888 .block = block,
832 .operand = unwrapped_payload,889 .operand = unwrapped_payload,
833 }, .{});890 }, .{});
834891
835 condbr.positionals.then_body = .{ .instructions = try err_scope.arena.dupe(*zir.Inst, err_scope.instructions.items) };892 condbr.positionals.then_body = .{ .instructions = try then_scope.arena.dupe(*zir.Inst, then_scope.instructions.items) };
836 condbr.positionals.else_body = .{ .instructions = try not_err_scope.arena.dupe(*zir.Inst, not_err_scope.instructions.items) };893 condbr.positionals.else_body = .{ .instructions = try else_scope.arena.dupe(*zir.Inst, else_scope.instructions.items) };
837 return rlWrap(mod, scope, rl, &block.base);894 return rlWrapPtr(mod, scope, rl, &block.base);
838}895}
839896
840/// Return whether the identifier names of two tokens are equal. Resolves @"" tokens without allocating.897/// 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...@@ -894,6 +951,36 @@ fn arrayAccess(mod: *Module, scope: *Scope, rl: ResultLoc, node: *ast.Node.Array
894 return rlWrapPtr(mod, scope, rl, try addZIRInst(mod, scope, src, zir.Inst.ElemPtr, .{ .array_ptr = array_ptr, .index = index }, .{}));951 return rlWrapPtr(mod, scope, rl, try addZIRInst(mod, scope, src, zir.Inst.ElemPtr, .{ .array_ptr = array_ptr, .index = index }, .{}));
895}952}
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
897fn deref(mod: *Module, scope: *Scope, node: *ast.Node.SimpleSuffixOp) InnerError!*zir.Inst {984fn deref(mod: *Module, scope: *Scope, node: *ast.Node.SimpleSuffixOp) InnerError!*zir.Inst {
898 const tree = scope.tree();985 const tree = scope.tree();
899 const src = tree.token_locs[node.rtoken].start;986 const src = tree.token_locs[node.rtoken].start;
...@@ -946,7 +1033,7 @@ fn boolBinOp(...@@ -946,7 +1033,7 @@ fn boolBinOp(
946 .else_body = undefined, // populated below1033 .else_body = undefined, // populated below
947 }, .{});1034 }, .{});
9481035
949 const block = try addZIRInstBlock(mod, scope, src, .{1036 const block = try addZIRInstBlock(mod, scope, src, .block, .{
950 .instructions = try block_scope.arena.dupe(*zir.Inst, block_scope.instructions.items),1037 .instructions = try block_scope.arena.dupe(*zir.Inst, block_scope.instructions.items),
951 });1038 });
9521039
...@@ -1095,7 +1182,7 @@ fn ifExpr(mod: *Module, scope: *Scope, rl: ResultLoc, if_node: *ast.Node.If) Inn...@@ -1095,7 +1182,7 @@ fn ifExpr(mod: *Module, scope: *Scope, rl: ResultLoc, if_node: *ast.Node.If) Inn
1095 .else_body = undefined, // populated below1182 .else_body = undefined, // populated below
1096 }, .{});1183 }, .{});
10971184
1098 const block = try addZIRInstBlock(mod, scope, if_src, .{1185 const block = try addZIRInstBlock(mod, scope, if_src, .block, .{
1099 .instructions = try block_scope.arena.dupe(*zir.Inst, block_scope.instructions.items),1186 .instructions = try block_scope.arena.dupe(*zir.Inst, block_scope.instructions.items),
1100 });1187 });
11011188
...@@ -1218,7 +1305,7 @@ fn whileExpr(mod: *Module, scope: *Scope, rl: ResultLoc, while_node: *ast.Node.W...@@ -1218,7 +1305,7 @@ fn whileExpr(mod: *Module, scope: *Scope, rl: ResultLoc, while_node: *ast.Node.W
1218 .then_body = undefined, // populated below1305 .then_body = undefined, // populated below
1219 .else_body = undefined, // populated below1306 .else_body = undefined, // populated below
1220 }, .{});1307 }, .{});
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, .{
1222 .instructions = try loop_scope.arena.dupe(*zir.Inst, continue_scope.instructions.items),1309 .instructions = try loop_scope.arena.dupe(*zir.Inst, continue_scope.instructions.items),
1223 });1310 });
1224 // TODO avoid emitting the continue expr when there1311 // 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...@@ -1231,7 +1318,7 @@ fn whileExpr(mod: *Module, scope: *Scope, rl: ResultLoc, while_node: *ast.Node.W
1231 const loop = try addZIRInstLoop(mod, &expr_scope.base, while_src, .{1318 const loop = try addZIRInstLoop(mod, &expr_scope.base, while_src, .{
1232 .instructions = try expr_scope.arena.dupe(*zir.Inst, loop_scope.instructions.items),1319 .instructions = try expr_scope.arena.dupe(*zir.Inst, loop_scope.instructions.items),
1233 });1320 });
1234 const while_block = try addZIRInstBlock(mod, scope, while_src, .{1321 const while_block = try addZIRInstBlock(mod, scope, while_src, .block, .{
1235 .instructions = try expr_scope.arena.dupe(*zir.Inst, expr_scope.instructions.items),1322 .instructions = try expr_scope.arena.dupe(*zir.Inst, expr_scope.instructions.items),
1236 });1323 });
12371324
...@@ -1365,7 +1452,7 @@ fn forExpr(mod: *Module, scope: *Scope, rl: ResultLoc, for_node: *ast.Node.For)...@@ -1365,7 +1452,7 @@ fn forExpr(mod: *Module, scope: *Scope, rl: ResultLoc, for_node: *ast.Node.For)
1365 .then_body = undefined, // populated below1452 .then_body = undefined, // populated below
1366 .else_body = undefined, // populated below1453 .else_body = undefined, // populated below
1367 }, .{});1454 }, .{});
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, .{
1369 .instructions = try loop_scope.arena.dupe(*zir.Inst, cond_scope.instructions.items),1456 .instructions = try loop_scope.arena.dupe(*zir.Inst, cond_scope.instructions.items),
1370 });1457 });
13711458
...@@ -1382,7 +1469,7 @@ fn forExpr(mod: *Module, scope: *Scope, rl: ResultLoc, for_node: *ast.Node.For)...@@ -1382,7 +1469,7 @@ fn forExpr(mod: *Module, scope: *Scope, rl: ResultLoc, for_node: *ast.Node.For)
1382 const loop = try addZIRInstLoop(mod, &for_scope.base, for_src, .{1469 const loop = try addZIRInstLoop(mod, &for_scope.base, for_src, .{
1383 .instructions = try for_scope.arena.dupe(*zir.Inst, loop_scope.instructions.items),1470 .instructions = try for_scope.arena.dupe(*zir.Inst, loop_scope.instructions.items),
1384 });1471 });
1385 const for_block = try addZIRInstBlock(mod, scope, for_src, .{1472 const for_block = try addZIRInstBlock(mod, scope, for_src, .block, .{
1386 .instructions = try for_scope.arena.dupe(*zir.Inst, for_scope.instructions.items),1473 .instructions = try for_scope.arena.dupe(*zir.Inst, for_scope.instructions.items),
1387 });1474 });
13881475
...@@ -2260,6 +2347,30 @@ pub fn addZIRBinOp(...@@ -2260,6 +2347,30 @@ pub fn addZIRBinOp(
2260 return &inst.base;2347 return &inst.base;
2261}2348}
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
2263pub fn addZIRInst(2374pub fn addZIRInst(
2264 mod: *Module,2375 mod: *Module,
2265 scope: *Scope,2376 scope: *Scope,
...@@ -2278,12 +2389,6 @@ pub fn addZIRInstConst(mod: *Module, scope: *Scope, src: usize, typed_value: Typ...@@ -2278,12 +2389,6 @@ pub fn addZIRInstConst(mod: *Module, scope: *Scope, src: usize, typed_value: Typ
2278 return addZIRInst(mod, scope, src, zir.Inst.Const, P{ .typed_value = typed_value }, .{});2389 return addZIRInst(mod, scope, src, zir.Inst.Const, P{ .typed_value = typed_value }, .{});
2279}2390}
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
2287/// TODO The existence of this function is a workaround for a bug in stage1.2392/// TODO The existence of this function is a workaround for a bug in stage1.
2288pub fn addZIRInstLoop(mod: *Module, scope: *Scope, src: usize, body: zir.Module.Body) !*zir.Inst.Loop {2393pub fn addZIRInstLoop(mod: *Module, scope: *Scope, src: usize, body: zir.Module.Body) !*zir.Inst.Loop {
2289 const P = std.meta.fieldInfo(zir.Inst.Loop, "positionals").field_type;2394 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(...@@ -132,7 +132,7 @@ pub fn generateSymbol(
132 .Array => {132 .Array => {
133 // TODO populate .debug_info for the array133 // TODO populate .debug_info for the array
134 if (typed_value.val.cast(Value.Payload.Bytes)) |payload| {134 if (typed_value.val.cast(Value.Payload.Bytes)) |payload| {
135 if (typed_value.ty.arraySentinel()) |sentinel| {135 if (typed_value.ty.sentinel()) |sentinel| {
136 try code.ensureCapacity(code.items.len + payload.data.len + 1);136 try code.ensureCapacity(code.items.len + payload.data.len + 1);
137 code.appendSliceAssumeCapacity(payload.data);137 code.appendSliceAssumeCapacity(payload.data);
138 const prev_len = code.items.len;138 const prev_len = code.items.len;
...@@ -359,7 +359,7 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {...@@ -359,7 +359,7 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {
359 };359 };
360360
361 const Branch = struct {361 const Branch = struct {
362 inst_table: std.AutoHashMapUnmanaged(*ir.Inst, MCValue) = .{},362 inst_table: std.AutoArrayHashMapUnmanaged(*ir.Inst, MCValue) = .{},
363363
364 fn deinit(self: *Branch, gpa: *Allocator) void {364 fn deinit(self: *Branch, gpa: *Allocator) void {
365 self.inst_table.deinit(gpa);365 self.inst_table.deinit(gpa);
...@@ -436,8 +436,8 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {...@@ -436,8 +436,8 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {
436 try branch_stack.append(.{});436 try branch_stack.append(.{});
437437
438 const src_data: struct {lbrace_src: usize, rbrace_src: usize, source: []const u8} = blk: {438 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| {439 if (module_fn.owner_decl.scope.cast(Module.Scope.Container)) |container_scope| {
440 const tree = scope_file.contents.tree;440 const tree = container_scope.file_scope.contents.tree;
441 const fn_proto = tree.root_node.decls()[module_fn.owner_decl.src_index].castTag(.FnProto).?;441 const fn_proto = tree.root_node.decls()[module_fn.owner_decl.src_index].castTag(.FnProto).?;
442 const block = fn_proto.getBodyNode().?.castTag(.Block).?;442 const block = fn_proto.getBodyNode().?.castTag(.Block).?;
443 const lbrace_src = tree.token_locs[block.lbrace].start;443 const lbrace_src = tree.token_locs[block.lbrace].start;
...@@ -750,7 +750,7 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {...@@ -750,7 +750,7 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {
750 const ptr_bits = arch.ptrBitWidth();750 const ptr_bits = arch.ptrBitWidth();
751 const ptr_bytes: u64 = @divExact(ptr_bits, 8);751 const ptr_bytes: u64 = @divExact(ptr_bits, 8);
752 if (abi_size <= ptr_bytes) {752 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);
754 if (self.allocReg(inst)) |reg| {754 if (self.allocReg(inst)) |reg| {
755 return MCValue{ .register = registerAlias(reg, abi_size) };755 return MCValue{ .register = registerAlias(reg, abi_size) };
756 }756 }
...@@ -788,7 +788,7 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {...@@ -788,7 +788,7 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {
788 /// `reg_owner` is the instruction that gets associated with the register in the register table.788 /// `reg_owner` is the instruction that gets associated with the register in the register table.
789 /// This can have a side effect of spilling instructions to the stack to free up a register.789 /// This can have a side effect of spilling instructions to the stack to free up a register.
790 fn copyToNewRegister(self: *Self, reg_owner: *ir.Inst, mcv: MCValue) !MCValue {790 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
793 const reg = self.allocReg(reg_owner) orelse b: {793 const reg = self.allocReg(reg_owner) orelse b: {
794 // We'll take over the first register. Move the instruction that was previously794 // 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 {...@@ -1247,7 +1247,7 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {
1247 if (inst.base.isUnused())1247 if (inst.base.isUnused())
1248 return MCValue.dead;1248 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
1252 const result = self.args[self.arg_index];1252 const result = self.args[self.arg_index];
1253 self.arg_index += 1;1253 self.arg_index += 1;
...@@ -1443,7 +1443,57 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {...@@ -1443,7 +1443,57 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {
1443 }1443 }
1444 } else if (self.bin_file.cast(link.File.MachO)) |macho_file| {1444 } else if (self.bin_file.cast(link.File.MachO)) |macho_file| {
1445 switch (arch) {1445 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 },
1447 .aarch64 => return self.fail(inst.base.src, "TODO implement codegen for call when linking with MachO for aarch64 arch", .{}),1497 .aarch64 => return self.fail(inst.base.src, "TODO implement codegen for call when linking with MachO for aarch64 arch", .{}),
1448 else => unreachable,1498 else => unreachable,
1449 }1499 }
...@@ -2486,6 +2536,11 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {...@@ -2486,6 +2536,11 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {
2486 const got = &elf_file.program_headers.items[elf_file.phdr_got_index.?];2536 const got = &elf_file.program_headers.items[elf_file.phdr_got_index.?];
2487 const got_addr = got.p_vaddr + decl.link.elf.offset_table_index * ptr_bytes;2537 const got_addr = got.p_vaddr + decl.link.elf.offset_table_index * ptr_bytes;
2488 return MCValue{ .memory = got_addr };2538 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 };
2489 } else {2544 } else {
2490 return self.fail(src, "TODO codegen non-ELF const Decl pointer", .{});2545 return self.fail(src, "TODO codegen non-ELF const Decl pointer", .{});
2491 }2546 }
src-self-hosted/codegen/c.zig+3-2
...@@ -85,7 +85,7 @@ fn genArray(file: *C, decl: *Decl) !void {...@@ -85,7 +85,7 @@ fn genArray(file: *C, decl: *Decl) !void {
85 const name = try map(file.base.allocator, mem.span(decl.name));85 const name = try map(file.base.allocator, mem.span(decl.name));
86 defer file.base.allocator.free(name);86 defer file.base.allocator.free(name);
87 if (tv.val.cast(Value.Payload.Bytes)) |payload|87 if (tv.val.cast(Value.Payload.Bytes)) |payload|
88 if (tv.ty.arraySentinel()) |sentinel|88 if (tv.ty.sentinel()) |sentinel|
89 if (sentinel.toUnsignedInt() == 0)89 if (sentinel.toUnsignedInt() == 0)
90 try file.constants.writer().print("const char *const {} = \"{}\";\n", .{ name, payload.data })90 try file.constants.writer().print("const char *const {} = \"{}\";\n", .{ name, payload.data })
91 else91 else
...@@ -110,7 +110,8 @@ const Context = struct {...@@ -110,7 +110,8 @@ const Context = struct {
110 }110 }
111111
112 fn deinit(self: *Context) void {112 fn deinit(self: *Context) void {
113 for (self.inst_map.items()) |kv| {113 var it = self.inst_map.iterator();
114 while (it.next()) |kv| {
114 self.file.base.allocator.free(kv.value);115 self.file.base.allocator.free(kv.value);
115 }116 }
116 self.inst_map.deinit();117 self.inst_map.deinit();
src-self-hosted/ir.zig+9-1
...@@ -189,7 +189,7 @@ pub const Inst = struct {...@@ -189,7 +189,7 @@ pub const Inst = struct {
189 }189 }
190190
191 pub fn cmpOperator(base: *Inst) ?std.math.CompareOperator {191 pub fn cmpOperator(base: *Inst) ?std.math.CompareOperator {
192 return switch (self.base.tag) {192 return switch (base.tag) {
193 .cmp_lt => .lt,193 .cmp_lt => .lt,
194 .cmp_lte => .lte,194 .cmp_lte => .lte,
195 .cmp_eq => .eq,195 .cmp_eq => .eq,
...@@ -220,6 +220,14 @@ pub const Inst = struct {...@@ -220,6 +220,14 @@ pub const Inst = struct {
220 unreachable;220 unreachable;
221 }221 }
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
223 pub const NoOp = struct {231 pub const NoOp = struct {
224 base: Inst,232 base: Inst,
225233
src-self-hosted/link.zig+1-1
...@@ -47,7 +47,7 @@ pub const File = struct {...@@ -47,7 +47,7 @@ pub const File = struct {
47 };47 };
4848
49 /// For DWARF .debug_info.49 /// 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
52 /// For DWARF .debug_info.52 /// For DWARF .debug_info.
53 pub const DbgInfoTypeReloc = struct {53 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 {...@@ -1629,7 +1629,8 @@ pub fn updateDecl(self: *Elf, module: *Module, decl: *Module.Decl) !void {
16291629
1630 var dbg_info_type_relocs: File.DbgInfoTypeRelocsTable = .{};1630 var dbg_info_type_relocs: File.DbgInfoTypeRelocsTable = .{};
1631 defer {1631 defer {
1632 for (dbg_info_type_relocs.items()) |*entry| {1632 var it = dbg_info_type_relocs.iterator();
1633 while (it.next()) |entry| {
1633 entry.value.relocs.deinit(self.base.allocator);1634 entry.value.relocs.deinit(self.base.allocator);
1634 }1635 }
1635 dbg_info_type_relocs.deinit(self.base.allocator);1636 dbg_info_type_relocs.deinit(self.base.allocator);
...@@ -1655,8 +1656,8 @@ pub fn updateDecl(self: *Elf, module: *Module, decl: *Module.Decl) !void {...@@ -1655,8 +1656,8 @@ pub fn updateDecl(self: *Elf, module: *Module, decl: *Module.Decl) !void {
1655 try dbg_line_buffer.ensureCapacity(26);1656 try dbg_line_buffer.ensureCapacity(26);
16561657
1657 const line_off: u28 = blk: {1658 const line_off: u28 = blk: {
1658 if (decl.scope.cast(Module.Scope.File)) |scope_file| {1659 if (decl.scope.cast(Module.Scope.Container)) |container_scope| {
1659 const tree = scope_file.contents.tree;1660 const tree = container_scope.file_scope.contents.tree;
1660 const file_ast_decls = tree.root_node.decls();1661 const file_ast_decls = tree.root_node.decls();
1661 // TODO Look into improving the performance here by adding a token-index-to-line1662 // TODO Look into improving the performance here by adding a token-index-to-line
1662 // lookup table. Currently this involves scanning over the source code for newlines.1663 // 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 {...@@ -1917,7 +1918,8 @@ pub fn updateDecl(self: *Elf, module: *Module, decl: *Module.Decl) !void {
1917 // Now we emit the .debug_info types of the Decl. These will count towards the size of1918 // Now we emit the .debug_info types of the Decl. These will count towards the size of
1918 // the buffer, so we have to do it before computing the offset, and we can't perform the actual1919 // the buffer, so we have to do it before computing the offset, and we can't perform the actual
1919 // relocations yet.1920 // relocations yet.
1920 for (dbg_info_type_relocs.items()) |*entry| {1921 var it = dbg_info_type_relocs.iterator();
1922 while (it.next()) |entry| {
1921 entry.value.off = @intCast(u32, dbg_info_buffer.items.len);1923 entry.value.off = @intCast(u32, dbg_info_buffer.items.len);
1922 try self.addDbgInfoType(entry.key, &dbg_info_buffer);1924 try self.addDbgInfoType(entry.key, &dbg_info_buffer);
1923 }1925 }
...@@ -1925,7 +1927,8 @@ pub fn updateDecl(self: *Elf, module: *Module, decl: *Module.Decl) !void {...@@ -1925,7 +1927,8 @@ pub fn updateDecl(self: *Elf, module: *Module, decl: *Module.Decl) !void {
1925 try self.updateDeclDebugInfoAllocation(text_block, @intCast(u32, dbg_info_buffer.items.len));1927 try self.updateDeclDebugInfoAllocation(text_block, @intCast(u32, dbg_info_buffer.items.len));
19261928
1927 // Now that we have the offset assigned we can finally perform type relocations.1929 // 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| {
1929 for (entry.value.relocs.items) |off| {1932 for (entry.value.relocs.items) |off| {
1930 mem.writeInt(1933 mem.writeInt(
1931 u32,1934 u32,
...@@ -2154,8 +2157,8 @@ pub fn updateDeclLineNumber(self: *Elf, module: *Module, decl: *const Module.Dec...@@ -2154,8 +2157,8 @@ pub fn updateDeclLineNumber(self: *Elf, module: *Module, decl: *const Module.Dec
2154 const tracy = trace(@src());2157 const tracy = trace(@src());
2155 defer tracy.end();2158 defer tracy.end();
21562159
2157 const scope_file = decl.scope.cast(Module.Scope.File).?;2160 const container_scope = decl.scope.cast(Module.Scope.Container).?;
2158 const tree = scope_file.contents.tree;2161 const tree = container_scope.file_scope.contents.tree;
2159 const file_ast_decls = tree.root_node.decls();2162 const file_ast_decls = tree.root_node.decls();
2160 // TODO Look into improving the performance here by adding a token-index-to-line2163 // TODO Look into improving the performance here by adding a token-index-to-line
2161 // lookup table. Currently this involves scanning over the source code for newlines.2164 // 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;...@@ -18,36 +18,66 @@ const File = link.File;
1818
19pub const base_tag: File.Tag = File.Tag.macho;19pub 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
21base: File,37base: File,
2238
23/// List of all load command headers that are in the file.39/// Table of all load commands
24/// We use it to track number and size of all commands needed by the header.40load_commands: std.ArrayListUnmanaged(LoadCommand) = .{},
25commands: std.ArrayListUnmanaged(macho.load_command) = std.ArrayListUnmanaged(macho.load_command){},41segment_cmd_index: ?u16 = null,
26command_file_offset: ?u64 = 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.46/// Table of all sections
29/// Same order as in the file.47sections: std.ArrayListUnmanaged(macho.section_64) = .{},
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){},
3348
34/// Offset (index) into __TEXT segment load command.49/// __TEXT segment sections
35text_segment_offset: ?u64 = null,50text_section_index: ?u16 = null,
36/// Offset (index) into __LINKEDIT segment load command.51cstring_section_index: ?u16 = null,
37linkedit_segment_offset: ?u664 = 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,
41entry_addr: ?u64 = null,60entry_addr: ?u64 = null,
4261
43/// The first 4GB of process' memory is reserved for the null (__PAGEZERO) segment.62/// Table of all symbols used.
44/// This is also the start address for our binary.63/// Internally references string table for names (which are optional).
45vm_start_address: u64 = 0x100000000,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
49error_flags: File.ErrorFlags = File.ErrorFlags{},74error_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
51/// `alloc_num / alloc_den` is the factor of padding when allocating.81/// `alloc_num / alloc_den` is the factor of padding when allocating.
52const alloc_num = 4;82const alloc_num = 4;
53const alloc_den = 3;83const alloc_den = 3;
...@@ -67,7 +97,23 @@ const LIB_SYSTEM_NAME: [*:0]const u8 = "System";...@@ -67,7 +97,23 @@ const LIB_SYSTEM_NAME: [*:0]const u8 = "System";
67const LIB_SYSTEM_PATH: [*:0]const u8 = DEFAULT_LIB_SEARCH_PATH ++ "/libSystem.B.dylib";97const LIB_SYSTEM_PATH: [*:0]const u8 = DEFAULT_LIB_SEARCH_PATH ++ "/libSystem.B.dylib";
6898
69pub const TextBlock = struct {99pub 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 };
71};117};
72118
73pub const SrcFn = struct {119pub const SrcFn = struct {
...@@ -117,6 +163,12 @@ fn openFile(allocator: *Allocator, file: fs.File, options: link.Options) !MachO...@@ -117,6 +163,12 @@ fn openFile(allocator: *Allocator, file: fs.File, options: link.Options) !MachO
117/// Truncates the existing file contents and overwrites the contents.163/// Truncates the existing file contents and overwrites the contents.
118/// Returns an error if `file` is not already open with +read +write +seek abilities.164/// Returns an error if `file` is not already open with +read +write +seek abilities.
119fn createFile(allocator: *Allocator, file: fs.File, options: link.Options) !MachO {165fn 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
120 var self: MachO = .{172 var self: MachO = .{
121 .base = .{173 .base = .{
122 .file = file,174 .file = file,
...@@ -127,104 +179,15 @@ fn createFile(allocator: *Allocator, file: fs.File, options: link.Options) !Mach...@@ -127,104 +179,15 @@ fn createFile(allocator: *Allocator, file: fs.File, options: link.Options) !Mach
127 };179 };
128 errdefer self.deinit();180 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
156 try self.populateMissingMetadata();182 try self.populateMissingMetadata();
157183
158 return self;184 return self;
159}185}
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
210pub fn flush(self: *MachO, module: *Module) !void {187pub 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
226 switch (self.base.options.output_mode) {188 switch (self.base.options.output_mode) {
227 .Exe => {189 .Exe => {
190 var last_cmd_offset: usize = @sizeOf(macho.mach_header_64);
228 {191 {
229 // Specify path to dynamic linker dyld192 // Specify path to dynamic linker dyld
230 const cmdsize = commandSize(@sizeOf(macho.dylinker_command) + mem.lenZ(DEFAULT_DYLD_PATH));193 const cmdsize = commandSize(@sizeOf(macho.dylinker_command) + mem.lenZ(DEFAULT_DYLD_PATH));
...@@ -235,18 +198,14 @@ pub fn flush(self: *MachO, module: *Module) !void {...@@ -235,18 +198,14 @@ pub fn flush(self: *MachO, module: *Module) !void {
235 .name = @sizeOf(macho.dylinker_command),198 .name = @sizeOf(macho.dylinker_command),
236 },199 },
237 };200 };
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);
246 try self.addPadding(cmdsize - @sizeOf(macho.dylinker_command), file_offset);205 try self.addPadding(cmdsize - @sizeOf(macho.dylinker_command), file_offset);
247206
248 try self.base.file.?.pwriteAll(mem.spanZ(DEFAULT_DYLD_PATH), file_offset);207 try self.base.file.?.pwriteAll(mem.spanZ(DEFAULT_DYLD_PATH), file_offset);
249 self.command_file_offset.? += cmdsize;208 last_cmd_offset += cmdsize;
250 }209 }
251210
252 {211 {
...@@ -268,21 +227,44 @@ pub fn flush(self: *MachO, module: *Module) !void {...@@ -268,21 +227,44 @@ pub fn flush(self: *MachO, module: *Module) !void {
268 .dylib = dylib,227 .dylib = dylib,
269 },228 },
270 };229 };
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);
279 try self.addPadding(cmdsize - @sizeOf(macho.dylib_command), file_offset);234 try self.addPadding(cmdsize - @sizeOf(macho.dylib_command), file_offset);
280235
281 try self.base.file.?.pwriteAll(mem.spanZ(LIB_SYSTEM_PATH), file_offset);236 try self.base.file.?.pwriteAll(mem.spanZ(LIB_SYSTEM_PATH), file_offset);
282 self.command_file_offset.? += cmdsize;237 last_cmd_offset += cmdsize;
283 }238 }
284 },239 },
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 },
286 .Lib => return error.TODOImplementWritingLibFiles,268 .Lib => return error.TODOImplementWritingLibFiles,
287 }269 }
288270
...@@ -297,14 +279,110 @@ pub fn flush(self: *MachO, module: *Module) !void {...@@ -297,14 +279,110 @@ pub fn flush(self: *MachO, module: *Module) !void {
297}279}
298280
299pub fn deinit(self: *MachO) void {281pub fn deinit(self: *MachO) void {
300 self.commands.deinit(self.base.allocator);282 self.offset_table.deinit(self.base.allocator);
301 self.segments.deinit(self.base.allocator);283 self.string_table.deinit(self.base.allocator);
284 self.symbol_table.deinit(self.base.allocator);
302 self.sections.deinit(self.base.allocator);285 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;
303}310}
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
309pub fn updateDeclLineNumber(self: *MachO, module: *Module, decl: *const Module.Decl) !void {}387pub fn updateDeclLineNumber(self: *MachO, module: *Module, decl: *const Module.Decl) !void {}
310388
...@@ -313,51 +391,191 @@ pub fn updateDeclExports(...@@ -313,51 +391,191 @@ pub fn updateDeclExports(
313 module: *Module,391 module: *Module,
314 decl: *const Module.Decl,392 decl: *const Module.Decl,
315 exports: []const *Module.Export,393 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
318pub fn freeDecl(self: *MachO, decl: *Module.Decl) void {}410pub fn freeDecl(self: *MachO, decl: *Module.Decl) void {}
319411
320pub fn getDeclVAddr(self: *MachO, decl: *const Module.Decl) u64 {412pub 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;
322}414}
323415
324pub fn populateMissingMetadata(self: *MachO) !void {416pub fn populateMissingMetadata(self: *MachO) !void {
325 if (self.text_segment_offset == null) {417 if (self.segment_cmd_index == null) {
326 self.text_segment_offset = @intCast(u64, self.segments.items.len);418 self.segment_cmd_index = @intCast(u16, self.load_commands.items.len);
327 const file_size = alignSize(u64, self.base.options.program_code_size_hint, 0x1000);419 try self.load_commands.append(self.base.allocator, .{
328 log.debug("vmsize/filesize = {}", .{file_size});420 .Segment = .{
329 const file_offset = 0;421 .cmd = macho.LC_SEGMENT_64,
330 const vm_address = self.vm_start_address; // the end of __PAGEZERO segment in VM422 .cmdsize = @sizeOf(macho.segment_command_64),
331 const protection = macho.VM_PROT_READ | macho.VM_PROT_EXECUTE;423 .segname = makeStaticString(""),
332 const cmdsize = commandSize(@sizeOf(macho.segment_command_64));424 .vmaddr = 0,
333 const text_segment = .{425 .vmsize = 0,
334 .cmd = macho.LC_SEGMENT_64,426 .fileoff = 0,
335 .cmdsize = cmdsize,427 .filesize = 0,
336 .segname = makeString("__TEXT"),428 .maxprot = 0,
337 .vmaddr = vm_address,429 .initprot = 0,
338 .vmsize = file_size,430 .nsects = 0,
339 .fileoff = 0, // __TEXT segment *always* starts at 0 file offset431 .flags = 0,
340 .filesize = 0, //file_size,432 },
341 .maxprot = protection,433 });
342 .initprot = protection,434 self.cmd_table_dirty = true;
343 .nsects = 0,435 }
344 .flags = 0,436 if (self.symtab_cmd_index == null) {
345 };437 self.symtab_cmd_index = @intCast(u16, self.load_commands.items.len);
346 try self.commands.append(self.base.allocator, .{438 try self.load_commands.append(self.base.allocator, .{
347 .cmd = macho.LC_SEGMENT_64,439 .Symtab = .{
348 .cmdsize = cmdsize,440 .cmd = macho.LC_SYMTAB,
441 .cmdsize = @sizeOf(macho.symtab_command),
442 .symoff = 0,
443 .nsyms = 0,
444 .stroff = 0,
445 .strsize = 0,
446 },
349 });447 });
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 }
351 }502 }
352}503}
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 {
355 var buf = [_]u8{0} ** 16;565 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");
357 mem.copy(u8, buf[0..], bytes);567 mem.copy(u8, buf[0..], bytes);
358 return buf;568 return buf;
359}569}
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
361fn alignSize(comptime Int: type, min_size: anytype, alignment: Int) Int {579fn alignSize(comptime Int: type, min_size: anytype, alignment: Int) Int {
362 const size = @intCast(Int, min_size);580 const size = @intCast(Int, min_size);
363 if (size % alignment == 0) return size;581 if (size % alignment == 0) return size;
...@@ -370,7 +588,7 @@ fn commandSize(min_size: anytype) u32 {...@@ -370,7 +588,7 @@ fn commandSize(min_size: anytype) u32 {
370 return alignSize(u32, min_size, @sizeOf(u64));588 return alignSize(u32, min_size, @sizeOf(u64));
371}589}
372590
373fn addPadding(self: *MachO, size: u32, file_offset: u64) !void {591fn addPadding(self: *MachO, size: u64, file_offset: u64) !void {
374 if (size == 0) return;592 if (size == 0) return;
375593
376 const buf = try self.base.allocator.alloc(u8, size);594 const buf = try self.base.allocator.alloc(u8, size);
...@@ -380,3 +598,151 @@ fn addPadding(self: *MachO, size: u32, file_offset: u64) !void {...@@ -380,3 +598,151 @@ fn addPadding(self: *MachO, size: u32, file_offset: u64) !void {
380598
381 try self.base.file.?.pwriteAll(buf, file_offset);599 try self.base.file.?.pwriteAll(buf, file_offset);
382}600}
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(...@@ -15,7 +15,7 @@ pub fn analyze(
1515
16 var table = std.AutoHashMap(*ir.Inst, void).init(gpa);16 var table = std.AutoHashMap(*ir.Inst, void).init(gpa);
17 defer table.deinit();17 defer table.deinit();
18 try table.ensureCapacity(body.instructions.len);18 try table.ensureCapacity(@intCast(u32, body.instructions.len));
19 try analyzeWithTable(arena, &table, null, body);19 try analyzeWithTable(arena, &table, null, body);
20}20}
2121
...@@ -84,8 +84,11 @@ fn analyzeInst(...@@ -84,8 +84,11 @@ fn analyzeInst(
84 try analyzeWithTable(arena, table, &then_table, inst.then_body);84 try analyzeWithTable(arena, table, &then_table, inst.then_body);
8585
86 // Reset the table back to its state from before the branch.86 // Reset the table back to its state from before the branch.
87 for (then_table.items()) |entry| {87 {
88 table.removeAssertDiscard(entry.key);88 var it = then_table.iterator();
89 while (it.next()) |entry| {
90 table.removeAssertDiscard(entry.key);
91 }
89 }92 }
9093
91 var else_table = std.AutoHashMap(*ir.Inst, void).init(table.allocator);94 var else_table = std.AutoHashMap(*ir.Inst, void).init(table.allocator);
...@@ -97,28 +100,36 @@ fn analyzeInst(...@@ -97,28 +100,36 @@ fn analyzeInst(
97 var else_entry_deaths = std.ArrayList(*ir.Inst).init(table.allocator);100 var else_entry_deaths = std.ArrayList(*ir.Inst).init(table.allocator);
98 defer else_entry_deaths.deinit();101 defer else_entry_deaths.deinit();
99102
100 for (else_table.items()) |entry| {103 {
101 const else_death = entry.key;104 var it = else_table.iterator();
102 if (!then_table.contains(else_death)) {105 while (it.next()) |entry| {
103 try then_entry_deaths.append(else_death);106 const else_death = entry.key;
107 if (!then_table.contains(else_death)) {
108 try then_entry_deaths.append(else_death);
109 }
104 }110 }
105 }111 }
106 // This loop is the same, except it's for the then branch, and it additionally112 // This loop is the same, except it's for the then branch, and it additionally
107 // has to put its items back into the table to undo the reset.113 // has to put its items back into the table to undo the reset.
108 for (then_table.items()) |entry| {114 {
109 const then_death = entry.key;115 var it = then_table.iterator();
110 if (!else_table.contains(then_death)) {116 while (it.next()) |entry| {
111 try else_entry_deaths.append(then_death);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, {});
112 }122 }
113 _ = try table.put(then_death, {});
114 }123 }
115 // Now we have to correctly populate new_set.124 // Now we have to correctly populate new_set.
116 if (new_set) |ns| {125 if (new_set) |ns| {
117 try ns.ensureCapacity(ns.items().len + then_table.items().len + else_table.items().len);126 try ns.ensureCapacity(@intCast(u32, ns.count() + then_table.count() + else_table.count()));
118 for (then_table.items()) |entry| {127 var it = then_table.iterator();
128 while (it.next()) |entry| {
119 _ = ns.putAssumeCapacity(entry.key, {});129 _ = ns.putAssumeCapacity(entry.key, {});
120 }130 }
121 for (else_table.items()) |entry| {131 it = else_table.iterator();
132 while (it.next()) |entry| {
122 _ = ns.putAssumeCapacity(entry.key, {});133 _ = ns.putAssumeCapacity(entry.key, {});
123 }134 }
124 }135 }
src-self-hosted/main.zig+2-1
...@@ -839,7 +839,8 @@ fn fmtPathFile(...@@ -839,7 +839,8 @@ fn fmtPathFile(
839 // As a heuristic, we make enough capacity for the same as the input source.839 // As a heuristic, we make enough capacity for the same as the input source.
840 try fmt.out_buffer.ensureCapacity(source_code.len);840 try fmt.out_buffer.ensureCapacity(source_code.len);
841 fmt.out_buffer.items.len = 0;841 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);
843 if (!anything_changed)844 if (!anything_changed)
844 return; // Good thing we didn't waste any file system access on this.845 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 {...@@ -474,15 +474,15 @@ pub const TestContext = struct {
474 var all_errors = try module.getAllErrorsAlloc();474 var all_errors = try module.getAllErrorsAlloc();
475 defer all_errors.deinit(allocator);475 defer all_errors.deinit(allocator);
476 if (all_errors.list.len != 0) {476 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", .{});
478 for (all_errors.list) |err| {478 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 });
480 }480 }
481 if (case.cbe) {481 if (case.cbe) {
482 const C = module.bin_file.cast(link.File.C).?;482 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});
484 }484 }
485 std.debug.warn("Test failed.\n", .{});485 std.debug.print("Test failed.\n", .{});
486 std.process.exit(1);486 std.process.exit(1);
487 }487 }
488 }488 }
...@@ -497,12 +497,12 @@ pub const TestContext = struct {...@@ -497,12 +497,12 @@ pub const TestContext = struct {
497 var out = file.reader().readAllAlloc(arena, 1024 * 1024) catch @panic("Unable to read C output!");497 var out = file.reader().readAllAlloc(arena, 1024 * 1024) catch @panic("Unable to read C output!");
498498
499 if (expected_output.len != out.len) {499 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 });
501 std.process.exit(1);501 std.process.exit(1);
502 }502 }
503 for (expected_output) |e, i| {503 for (expected_output) |e, i| {
504 if (out[i] != e) {504 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 });
506 std.process.exit(1);506 std.process.exit(1);
507 }507 }
508 }508 }
...@@ -526,12 +526,12 @@ pub const TestContext = struct {...@@ -526,12 +526,12 @@ pub const TestContext = struct {
526 defer test_node.end();526 defer test_node.end();
527527
528 if (expected_output.len != out_zir.items.len) {528 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 });
530 std.process.exit(1);530 std.process.exit(1);
531 }531 }
532 for (expected_output) |e, i| {532 for (expected_output) |e, i| {
533 if (out_zir.items[i] != e) {533 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 });
535 std.process.exit(1);535 std.process.exit(1);
536 }536 }
537 }537 }
...@@ -554,7 +554,7 @@ pub const TestContext = struct {...@@ -554,7 +554,7 @@ pub const TestContext = struct {
554 break;554 break;
555 }555 }
556 } else {556 } 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 });
558 std.process.exit(1);558 std.process.exit(1);
559 }559 }
560 }560 }
...@@ -562,7 +562,7 @@ pub const TestContext = struct {...@@ -562,7 +562,7 @@ pub const TestContext = struct {
562 for (handled_errors) |h, i| {562 for (handled_errors) |h, i| {
563 if (!h) {563 if (!h) {
564 const er = e[i];564 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 });
566 std.process.exit(1);566 std.process.exit(1);
567 }567 }
568 }568 }
...@@ -643,7 +643,7 @@ pub const TestContext = struct {...@@ -643,7 +643,7 @@ pub const TestContext = struct {
643 switch (exec_result.term) {643 switch (exec_result.term) {
644 .Exited => |code| {644 .Exited => |code| {
645 if (code != 0) {645 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});
647 return error.BinaryBadExitCode;647 return error.BinaryBadExitCode;
648 }648 }
649 },649 },
src-self-hosted/translate_c.zig+6-19
...@@ -19,23 +19,9 @@ pub const Error = error{OutOfMemory};...@@ -19,23 +19,9 @@ pub const Error = error{OutOfMemory};
19const TypeError = Error || error{UnsupportedType};19const TypeError = Error || error{UnsupportedType};
20const TransError = TypeError || error{UnsupportedTranslation};20const 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 {24const SymbolTable = std.StringArrayHashMap(*ast.Node);
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);
39const AliasList = std.ArrayList(struct {25const AliasList = std.ArrayList(struct {
40 alias: []const u8,26 alias: []const u8,
41 name: []const u8,27 name: []const u8,
...@@ -285,7 +271,7 @@ pub const Context = struct {...@@ -285,7 +271,7 @@ pub const Context = struct {
285 /// a list of names that we found by visiting all the top level decls without271 /// a list of names that we found by visiting all the top level decls without
286 /// translating them. The other maps are updated as we translate; this one is updated272 /// translating them. The other maps are updated as we translate; this one is updated
287 /// up front in a pre-processing step.273 /// up front in a pre-processing step.
288 global_names: std.StringHashMap(void),274 global_names: std.StringArrayHashMap(void),
289275
290 fn getMangle(c: *Context) u32 {276 fn getMangle(c: *Context) u32 {
291 c.mangle_count += 1;277 c.mangle_count += 1;
...@@ -380,7 +366,7 @@ pub fn translate(...@@ -380,7 +366,7 @@ pub fn translate(
380 .alias_list = AliasList.init(gpa),366 .alias_list = AliasList.init(gpa),
381 .global_scope = try arena.allocator.create(Scope.Root),367 .global_scope = try arena.allocator.create(Scope.Root),
382 .clang_context = ZigClangASTUnit_getASTContext(ast_unit).?,368 .clang_context = ZigClangASTUnit_getASTContext(ast_unit).?,
383 .global_names = std.StringHashMap(void).init(gpa),369 .global_names = std.StringArrayHashMap(void).init(gpa),
384 .token_ids = .{},370 .token_ids = .{},
385 .token_locs = .{},371 .token_locs = .{},
386 .errors = .{},372 .errors = .{},
...@@ -6424,7 +6410,8 @@ fn getFnProto(c: *Context, ref: *ast.Node) ?*ast.Node.FnProto {...@@ -6424,7 +6410,8 @@ fn getFnProto(c: *Context, ref: *ast.Node) ?*ast.Node.FnProto {
6424}6410}
64256411
6426fn addMacros(c: *Context) !void {6412fn 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| {
6428 if (getFnProto(c, kv.value)) |proto_node| {6415 if (getFnProto(c, kv.value)) |proto_node| {
6429 // If a macro aliases a global variable which is a function pointer, we conclude that6416 // If a macro aliases a global variable which is a function pointer, we conclude that
6430 // the macro is intended to represent a function that assumes the function pointer6417 // 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 {...@@ -163,7 +163,7 @@ pub const Type = extern union {
163 // Hot path for common case:163 // Hot path for common case:
164 if (a.castPointer()) |a_payload| {164 if (a.castPointer()) |a_payload| {
165 if (b.castPointer()) |b_payload| {165 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);
167 }167 }
168 }168 }
169 const is_slice_a = isSlice(a);169 const is_slice_a = isSlice(a);
...@@ -189,10 +189,10 @@ pub const Type = extern union {...@@ -189,10 +189,10 @@ pub const Type = extern union {
189 .Array => {189 .Array => {
190 if (a.arrayLen() != b.arrayLen())190 if (a.arrayLen() != b.arrayLen())
191 return false;191 return false;
192 if (a.elemType().eql(b.elemType()))192 if (!a.elemType().eql(b.elemType()))
193 return false;193 return false;
194 const sentinel_a = a.arraySentinel();194 const sentinel_a = a.sentinel();
195 const sentinel_b = b.arraySentinel();195 const sentinel_b = b.sentinel();
196 if (sentinel_a) |sa| {196 if (sentinel_a) |sa| {
197 if (sentinel_b) |sb| {197 if (sentinel_b) |sb| {
198 return sa.eql(sb);198 return sa.eql(sb);
...@@ -238,7 +238,7 @@ pub const Type = extern union {...@@ -238,7 +238,7 @@ pub const Type = extern union {
238 }238 }
239 }239 }
240240
241 pub fn hash(self: Type) u32 {241 pub fn hash(self: Type) u64 {
242 var hasher = std.hash.Wyhash.init(0);242 var hasher = std.hash.Wyhash.init(0);
243 const zig_type_tag = self.zigTypeTag();243 const zig_type_tag = self.zigTypeTag();
244 std.hash.autoHash(&hasher, zig_type_tag);244 std.hash.autoHash(&hasher, zig_type_tag);
...@@ -303,7 +303,7 @@ pub const Type = extern union {...@@ -303,7 +303,7 @@ pub const Type = extern union {
303 // TODO implement more type hashing303 // TODO implement more type hashing
304 },304 },
305 }305 }
306 return @truncate(u32, hasher.final());306 return hasher.final();
307 }307 }
308308
309 pub fn copy(self: Type, allocator: *Allocator) error{OutOfMemory}!Type {309 pub fn copy(self: Type, allocator: *Allocator) error{OutOfMemory}!Type {
...@@ -501,9 +501,9 @@ pub const Type = extern union {...@@ -501,9 +501,9 @@ pub const Type = extern union {
501 .noreturn,501 .noreturn,
502 => return out_stream.writeAll(@tagName(t)),502 => return out_stream.writeAll(@tagName(t)),
503503
504 .enum_literal => return out_stream.writeAll("@TypeOf(.EnumLiteral)"),504 .enum_literal => return out_stream.writeAll("@Type(.EnumLiteral)"),
505 .@"null" => return out_stream.writeAll("@TypeOf(null)"),505 .@"null" => return out_stream.writeAll("@Type(.Null)"),
506 .@"undefined" => return out_stream.writeAll("@TypeOf(undefined)"),506 .@"undefined" => return out_stream.writeAll("@Type(.Undefined)"),
507507
508 .@"anyframe" => return out_stream.writeAll("anyframe"),508 .@"anyframe" => return out_stream.writeAll("anyframe"),
509 .anyerror_void_error_union => return out_stream.writeAll("anyerror!void"),509 .anyerror_void_error_union => return out_stream.writeAll("anyerror!void"),
...@@ -630,8 +630,8 @@ pub const Type = extern union {...@@ -630,8 +630,8 @@ pub const Type = extern union {
630 const payload = @fieldParentPtr(Payload.Pointer, "base", ty.ptr_otherwise);630 const payload = @fieldParentPtr(Payload.Pointer, "base", ty.ptr_otherwise);
631 if (payload.sentinel) |some| switch (payload.size) {631 if (payload.sentinel) |some| switch (payload.size) {
632 .One, .C => unreachable,632 .One, .C => unreachable,
633 .Many => try out_stream.writeAll("[*:{}]"),633 .Many => try out_stream.print("[*:{}]", .{some}),
634 .Slice => try out_stream.writeAll("[:{}]"),634 .Slice => try out_stream.print("[:{}]", .{some}),
635 } else switch (payload.size) {635 } else switch (payload.size) {
636 .One => try out_stream.writeAll("*"),636 .One => try out_stream.writeAll("*"),
637 .Many => try out_stream.writeAll("[*]"),637 .Many => try out_stream.writeAll("[*]"),
...@@ -1341,6 +1341,81 @@ pub const Type = extern union {...@@ -1341,6 +1341,81 @@ pub const Type = extern union {
1341 };1341 };
1342 }1342 }
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
1344 /// Asserts that the type is an optional1419 /// Asserts that the type is an optional
1345 pub fn isPtrLikeOptional(self: Type) bool {1420 pub fn isPtrLikeOptional(self: Type) bool {
1346 switch (self.tag()) {1421 switch (self.tag()) {
...@@ -1585,8 +1660,8 @@ pub const Type = extern union {...@@ -1585,8 +1660,8 @@ pub const Type = extern union {
1585 };1660 };
1586 }1661 }
15871662
1588 /// Asserts the type is an array or vector.1663 /// Asserts the type is an array, pointer or vector.
1589 pub fn arraySentinel(self: Type) ?Value {1664 pub fn sentinel(self: Type) ?Value {
1590 return switch (self.tag()) {1665 return switch (self.tag()) {
1591 .u8,1666 .u8,
1592 .i8,1667 .i8,
...@@ -1626,16 +1701,8 @@ pub const Type = extern union {...@@ -1626,16 +1701,8 @@ pub const Type = extern union {
1626 .fn_naked_noreturn_no_args,1701 .fn_naked_noreturn_no_args,
1627 .fn_ccc_void_no_args,1702 .fn_ccc_void_no_args,
1628 .function,1703 .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,
1636 .const_slice,1704 .const_slice,
1637 .mut_slice,1705 .mut_slice,
1638 .single_const_pointer_to_comptime_int,
1639 .const_slice_u8,1706 .const_slice_u8,
1640 .int_unsigned,1707 .int_unsigned,
1641 .int_signed,1708 .int_signed,
...@@ -1651,7 +1718,18 @@ pub const Type = extern union {...@@ -1651,7 +1718,18 @@ pub const Type = extern union {
1651 .error_set_single,1718 .error_set_single,
1652 => unreachable,1719 => 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,
1655 .array_sentinel => return self.cast(Payload.ArraySentinel).?.sentinel,1733 .array_sentinel => return self.cast(Payload.ArraySentinel).?.sentinel,
1656 .array_u8_sentinel_0 => return Value.initTag(.zero),1734 .array_u8_sentinel_0 => return Value.initTag(.zero),
1657 };1735 };
src-self-hosted/value.zig+5-4
...@@ -301,15 +301,15 @@ pub const Value = extern union {...@@ -301,15 +301,15 @@ pub const Value = extern union {
301 .comptime_int_type => return out_stream.writeAll("comptime_int"),301 .comptime_int_type => return out_stream.writeAll("comptime_int"),
302 .comptime_float_type => return out_stream.writeAll("comptime_float"),302 .comptime_float_type => return out_stream.writeAll("comptime_float"),
303 .noreturn_type => return out_stream.writeAll("noreturn"),303 .noreturn_type => return out_stream.writeAll("noreturn"),
304 .null_type => return out_stream.writeAll("@TypeOf(null)"),304 .null_type => return out_stream.writeAll("@Type(.Null)"),
305 .undefined_type => return out_stream.writeAll("@TypeOf(undefined)"),305 .undefined_type => return out_stream.writeAll("@Type(.Undefined)"),
306 .fn_noreturn_no_args_type => return out_stream.writeAll("fn() noreturn"),306 .fn_noreturn_no_args_type => return out_stream.writeAll("fn() noreturn"),
307 .fn_void_no_args_type => return out_stream.writeAll("fn() void"),307 .fn_void_no_args_type => return out_stream.writeAll("fn() void"),
308 .fn_naked_noreturn_no_args_type => return out_stream.writeAll("fn() callconv(.Naked) noreturn"),308 .fn_naked_noreturn_no_args_type => return out_stream.writeAll("fn() callconv(.Naked) noreturn"),
309 .fn_ccc_void_no_args_type => return out_stream.writeAll("fn() callconv(.C) void"),309 .fn_ccc_void_no_args_type => return out_stream.writeAll("fn() callconv(.C) void"),
310 .single_const_pointer_to_comptime_int_type => return out_stream.writeAll("*const comptime_int"),310 .single_const_pointer_to_comptime_int_type => return out_stream.writeAll("*const comptime_int"),
311 .const_slice_u8_type => return out_stream.writeAll("[]const u8"),311 .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)"),
313 .anyframe_type => return out_stream.writeAll("anyframe"),313 .anyframe_type => return out_stream.writeAll("anyframe"),
314314
315 .null_value => return out_stream.writeAll("null"),315 .null_value => return out_stream.writeAll("null"),
...@@ -358,7 +358,8 @@ pub const Value = extern union {...@@ -358,7 +358,8 @@ pub const Value = extern union {
358 .error_set => {358 .error_set => {
359 const error_set = val.cast(Payload.ErrorSet).?;359 const error_set = val.cast(Payload.ErrorSet).?;
360 try out_stream.writeAll("error{");360 try out_stream.writeAll("error{");
361 for (error_set.fields.items()) |entry| {361 var it = error_set.fields.iterator();
362 while (it.next()) |entry| {
362 try out_stream.print("{},", .{entry.value});363 try out_stream.print("{},", .{entry.value});
363 }364 }
364 return out_stream.writeAll("}");365 return out_stream.writeAll("}");
src-self-hosted/zir.zig+42-5
...@@ -78,6 +78,13 @@ pub const Inst = struct {...@@ -78,6 +78,13 @@ pub const Inst = struct {
78 bitor,78 bitor,
79 /// A labeled block of code, which can return a value.79 /// A labeled block of code, which can return a value.
80 block,80 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,
81 /// Boolean NOT. See also `bitnot`.88 /// Boolean NOT. See also `bitnot`.
82 boolnot,89 boolnot,
83 /// Return a value from a `Block`.90 /// Return a value from a `Block`.
...@@ -224,6 +231,10 @@ pub const Inst = struct {...@@ -224,6 +231,10 @@ pub const Inst = struct {
224 const_slice_type,231 const_slice_type,
225 /// Create a pointer type with attributes232 /// Create a pointer type with attributes
226 ptr_type,233 ptr_type,
234 /// Slice operation `array_ptr[start..end:sentinel]`
235 slice,
236 /// Slice operation with just start `lhs[rhs..]`
237 slice_start,
227 /// Write a value to a pointer. For loading, see `deref`.238 /// Write a value to a pointer. For loading, see `deref`.
228 store,239 store,
229 /// String Literal. Makes an anonymous Decl and then takes a pointer to it.240 /// String Literal. Makes an anonymous Decl and then takes a pointer to it.
...@@ -336,11 +347,17 @@ pub const Inst = struct {...@@ -336,11 +347,17 @@ pub const Inst = struct {
336 .xor,347 .xor,
337 .error_union_type,348 .error_union_type,
338 .merge_error_sets,349 .merge_error_sets,
350 .slice_start,
339 => BinOp,351 => BinOp,
340352
353 .block,
354 .block_flat,
355 .block_comptime,
356 .block_comptime_flat,
357 => Block,
358
341 .arg => Arg,359 .arg => Arg,
342 .array_type_sentinel => ArrayTypeSentinel,360 .array_type_sentinel => ArrayTypeSentinel,
343 .block => Block,
344 .@"break" => Break,361 .@"break" => Break,
345 .breakvoid => BreakVoid,362 .breakvoid => BreakVoid,
346 .call => Call,363 .call => Call,
...@@ -368,6 +385,7 @@ pub const Inst = struct {...@@ -368,6 +385,7 @@ pub const Inst = struct {
368 .ptr_type => PtrType,385 .ptr_type => PtrType,
369 .enum_literal => EnumLiteral,386 .enum_literal => EnumLiteral,
370 .error_set => ErrorSet,387 .error_set => ErrorSet,
388 .slice => Slice,
371 };389 };
372 }390 }
373391
...@@ -392,6 +410,9 @@ pub const Inst = struct {...@@ -392,6 +410,9 @@ pub const Inst = struct {
392 .bitcast_result_ptr,410 .bitcast_result_ptr,
393 .bitor,411 .bitor,
394 .block,412 .block,
413 .block_flat,
414 .block_comptime,
415 .block_comptime_flat,
395 .boolnot,416 .boolnot,
396 .breakpoint,417 .breakpoint,
397 .call,418 .call,
...@@ -466,6 +487,8 @@ pub const Inst = struct {...@@ -466,6 +487,8 @@ pub const Inst = struct {
466 .error_union_type,487 .error_union_type,
467 .bitnot,488 .bitnot,
468 .error_set,489 .error_set,
490 .slice,
491 .slice_start,
469 => false,492 => false,
470493
471 .@"break",494 .@"break",
...@@ -946,6 +969,20 @@ pub const Inst = struct {...@@ -946,6 +969,20 @@ pub const Inst = struct {
946 },969 },
947 kw_args: struct {},970 kw_args: struct {},
948 };971 };
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 };
949};986};
950987
951pub const ErrorMsg = struct {988pub const ErrorMsg = struct {
...@@ -1034,7 +1071,7 @@ pub const Module = struct {...@@ -1034,7 +1071,7 @@ pub const Module = struct {
1034 defer write.loop_table.deinit();1071 defer write.loop_table.deinit();
10351072
1036 // First, build a map of *Inst to @ or % indexes1073 // 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
1039 for (self.decls) |decl, decl_i| {1076 for (self.decls) |decl, decl_i| {
1040 try write.inst_table.putNoClobber(decl.inst, .{ .inst = decl.inst, .index = null, .name = decl.name });1077 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 {...@@ -1670,7 +1707,7 @@ pub fn emit(allocator: *Allocator, old_module: IrModule) !Module {
1670 .arena = std.heap.ArenaAllocator.init(allocator),1707 .arena = std.heap.ArenaAllocator.init(allocator),
1671 .old_module = &old_module,1708 .old_module = &old_module,
1672 .next_auto_name = 0,1709 .next_auto_name = 0,
1673 .names = std.StringHashMap(void).init(allocator),1710 .names = std.StringArrayHashMap(void).init(allocator),
1674 .primitive_table = std.AutoHashMap(Inst.Primitive.Builtin, *Decl).init(allocator),1711 .primitive_table = std.AutoHashMap(Inst.Primitive.Builtin, *Decl).init(allocator),
1675 .indent = 0,1712 .indent = 0,
1676 .block_table = std.AutoHashMap(*ir.Inst.Block, *Inst.Block).init(allocator),1713 .block_table = std.AutoHashMap(*ir.Inst.Block, *Inst.Block).init(allocator),
...@@ -1743,7 +1780,7 @@ const EmitZIR = struct {...@@ -1743,7 +1780,7 @@ const EmitZIR = struct {
1743 arena: std.heap.ArenaAllocator,1780 arena: std.heap.ArenaAllocator,
1744 old_module: *const IrModule,1781 old_module: *const IrModule,
1745 decls: std.ArrayListUnmanaged(*Decl),1782 decls: std.ArrayListUnmanaged(*Decl),
1746 names: std.StringHashMap(void),1783 names: std.StringArrayHashMap(void),
1747 next_auto_name: usize,1784 next_auto_name: usize,
1748 primitive_table: std.AutoHashMap(Inst.Primitive.Builtin, *Decl),1785 primitive_table: std.AutoHashMap(Inst.Primitive.Builtin, *Decl),
1749 indent: usize,1786 indent: usize,
...@@ -2559,7 +2596,7 @@ const EmitZIR = struct {...@@ -2559,7 +2596,7 @@ const EmitZIR = struct {
2559 var len_pl = Value.Payload.Int_u64{ .int = ty.arrayLen() };2596 var len_pl = Value.Payload.Int_u64{ .int = ty.arrayLen() };
2560 const len = Value.initPayload(&len_pl.base);2597 const len = Value.initPayload(&len_pl.base);
25612598
2562 const inst = if (ty.arraySentinel()) |sentinel| blk: {2599 const inst = if (ty.sentinel()) |sentinel| blk: {
2563 const inst = try self.arena.allocator.create(Inst.ArrayTypeSentinel);2600 const inst = try self.arena.allocator.create(Inst.ArrayTypeSentinel);
2564 inst.* = .{2601 inst.* = .{
2565 .base = .{2602 .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!...@@ -31,7 +31,10 @@ pub fn analyzeInst(mod: *Module, scope: *Scope, old_inst: *zir.Inst) InnerError!
31 .arg => return analyzeInstArg(mod, scope, old_inst.castTag(.arg).?),31 .arg => return analyzeInstArg(mod, scope, old_inst.castTag(.arg).?),
32 .bitcast_ref => return analyzeInstBitCastRef(mod, scope, old_inst.castTag(.bitcast_ref).?),32 .bitcast_ref => return analyzeInstBitCastRef(mod, scope, old_inst.castTag(.bitcast_ref).?),
33 .bitcast_result_ptr => return analyzeInstBitCastResultPtr(mod, scope, old_inst.castTag(.bitcast_result_ptr).?),33 .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),
35 .@"break" => return analyzeInstBreak(mod, scope, old_inst.castTag(.@"break").?),38 .@"break" => return analyzeInstBreak(mod, scope, old_inst.castTag(.@"break").?),
36 .breakpoint => return analyzeInstBreakpoint(mod, scope, old_inst.castTag(.breakpoint).?),39 .breakpoint => return analyzeInstBreakpoint(mod, scope, old_inst.castTag(.breakpoint).?),
37 .breakvoid => return analyzeInstBreakVoid(mod, scope, old_inst.castTag(.breakvoid).?),40 .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!...@@ -129,6 +132,8 @@ pub fn analyzeInst(mod: *Module, scope: *Scope, old_inst: *zir.Inst) InnerError!
129 .error_union_type => return analyzeInstErrorUnionType(mod, scope, old_inst.castTag(.error_union_type).?),132 .error_union_type => return analyzeInstErrorUnionType(mod, scope, old_inst.castTag(.error_union_type).?),
130 .anyframe_type => return analyzeInstAnyframeType(mod, scope, old_inst.castTag(.anyframe_type).?),133 .anyframe_type => return analyzeInstAnyframeType(mod, scope, old_inst.castTag(.anyframe_type).?),
131 .error_set => return analyzeInstErrorSet(mod, scope, old_inst.castTag(.error_set).?),134 .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).?),
132 }137 }
133}138}
134139
...@@ -147,17 +152,16 @@ pub fn analyzeBody(mod: *Module, scope: *Scope, body: zir.Module.Body) !void {...@@ -147,17 +152,16 @@ pub fn analyzeBody(mod: *Module, scope: *Scope, body: zir.Module.Body) !void {
147 }152 }
148}153}
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 {
151 try analyzeBody(mod, &block_scope.base, body);161 try analyzeBody(mod, &block_scope.base, body);
152 for (block_scope.instructions.items) |inst| {162 const result_inst = zir_result_inst.analyzed_inst.?;
153 if (inst.castTag(.ret)) |ret| {163 const val = try mod.resolveConstValue(&block_scope.base, result_inst);
154 const val = try mod.resolveConstValue(&block_scope.base, ret.operand);164 return val.toType(block_scope.base.arena());
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;
161}165}
162166
163pub fn analyzeZirDecl(mod: *Module, decl: *Decl, src_decl: *zir.Decl) InnerError!bool {167pub 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!...@@ -362,7 +366,7 @@ fn analyzeInstRef(mod: *Module, scope: *Scope, inst: *zir.Inst.UnOp) InnerError!
362}366}
363367
364fn analyzeInstRetType(mod: *Module, scope: *Scope, inst: *zir.Inst.NoOp) InnerError!*Inst {368fn 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);
366 const fn_ty = b.func.?.owner_decl.typed_value.most_recent.typed_value.ty;370 const fn_ty = b.func.?.owner_decl.typed_value.most_recent.typed_value.ty;
367 const ret_type = fn_ty.fnReturnType();371 const ret_type = fn_ty.fnReturnType();
368 return mod.constType(scope, inst.base.src, ret_type);372 return mod.constType(scope, inst.base.src, ret_type);
...@@ -517,6 +521,7 @@ fn analyzeInstLoop(mod: *Module, scope: *Scope, inst: *zir.Inst.Loop) InnerError...@@ -517,6 +521,7 @@ fn analyzeInstLoop(mod: *Module, scope: *Scope, inst: *zir.Inst.Loop) InnerError
517 .decl = parent_block.decl,521 .decl = parent_block.decl,
518 .instructions = .{},522 .instructions = .{},
519 .arena = parent_block.arena,523 .arena = parent_block.arena,
524 .is_comptime = parent_block.is_comptime,
520 };525 };
521 defer child_block.instructions.deinit(mod.gpa);526 defer child_block.instructions.deinit(mod.gpa);
522527
...@@ -529,7 +534,29 @@ fn analyzeInstLoop(mod: *Module, scope: *Scope, inst: *zir.Inst.Loop) InnerError...@@ -529,7 +534,29 @@ fn analyzeInstLoop(mod: *Module, scope: *Scope, inst: *zir.Inst.Loop) InnerError
529 return &loop_inst.base;534 return &loop_inst.base;
530}535}
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 {
533 const parent_block = scope.cast(Scope.Block).?;560 const parent_block = scope.cast(Scope.Block).?;
534561
535 // Reserve space for a Block instruction so that generated Break instructions can562 // 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...@@ -557,6 +584,7 @@ fn analyzeInstBlock(mod: *Module, scope: *Scope, inst: *zir.Inst.Block) InnerErr
557 .results = .{},584 .results = .{},
558 .block_inst = block_inst,585 .block_inst = block_inst,
559 }),586 }),
587 .is_comptime = is_comptime or parent_block.is_comptime,
560 };588 };
561 const label = &child_block.label.?;589 const label = &child_block.label.?;
562590
...@@ -569,6 +597,28 @@ fn analyzeInstBlock(mod: *Module, scope: *Scope, inst: *zir.Inst.Block) InnerErr...@@ -569,6 +597,28 @@ fn analyzeInstBlock(mod: *Module, scope: *Scope, inst: *zir.Inst.Block) InnerErr
569 assert(child_block.instructions.items.len != 0);597 assert(child_block.instructions.items.len != 0);
570 assert(child_block.instructions.items[child_block.instructions.items.len - 1].ty.isNoReturn());598 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
572 // Need to set the type and emit the Block instruction. This allows machine code generation622 // Need to set the type and emit the Block instruction. This allows machine code generation
573 // to emit a jump instruction to after the block when it encounters the break.623 // to emit a jump instruction to after the block when it encounters the break.
574 try parent_block.instructions.append(mod.gpa, &block_inst.base);624 try parent_block.instructions.append(mod.gpa, &block_inst.base);
...@@ -595,8 +645,12 @@ fn analyzeInstBreakVoid(mod: *Module, scope: *Scope, inst: *zir.Inst.BreakVoid)...@@ -595,8 +645,12 @@ fn analyzeInstBreakVoid(mod: *Module, scope: *Scope, inst: *zir.Inst.BreakVoid)
595}645}
596646
597fn analyzeInstDbgStmt(mod: *Module, scope: *Scope, inst: *zir.Inst.NoOp) InnerError!*Inst {647fn analyzeInstDbgStmt(mod: *Module, scope: *Scope, inst: *zir.Inst.NoOp) InnerError!*Inst {
598 const b = try mod.requireRuntimeBlock(scope, inst.base.src);648 if (scope.cast(Scope.Block)) |b| {
599 return mod.addNoOp(b, inst.base.src, Type.initTag(.void), .dbg_stmt);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);
600}654}
601655
602fn analyzeInstDeclRefStr(mod: *Module, scope: *Scope, inst: *zir.Inst.DeclRefStr) InnerError!*Inst {656fn 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...@@ -764,7 +818,7 @@ fn analyzeInstErrorSet(mod: *Module, scope: *Scope, inst: *zir.Inst.ErrorSet) In
764 .fields = .{},818 .fields = .{},
765 .decl = undefined, // populated below819 .decl = undefined, // populated below
766 };820 };
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
769 for (inst.positionals.fields) |field_name| {823 for (inst.positionals.fields) |field_name| {
770 const entry = try mod.getErrorValue(field_name);824 const entry = try mod.getErrorValue(field_name);
...@@ -1083,7 +1137,7 @@ fn analyzeInstElemPtr(mod: *Module, scope: *Scope, inst: *zir.Inst.ElemPtr) Inne...@@ -1083,7 +1137,7 @@ fn analyzeInstElemPtr(mod: *Module, scope: *Scope, inst: *zir.Inst.ElemPtr) Inne
1083 const array_ptr = try resolveInst(mod, scope, inst.positionals.array_ptr);1137 const array_ptr = try resolveInst(mod, scope, inst.positionals.array_ptr);
1084 const uncasted_index = try resolveInst(mod, scope, inst.positionals.index);1138 const uncasted_index = try resolveInst(mod, scope, inst.positionals.index);
1085 const elem_index = try mod.coerce(scope, Type.initTag(.usize), uncasted_index);1139 const elem_index = try mod.coerce(scope, Type.initTag(.usize), uncasted_index);
1086 1140
1087 const elem_ty = switch (array_ptr.ty.zigTypeTag()) {1141 const elem_ty = switch (array_ptr.ty.zigTypeTag()) {
1088 .Pointer => array_ptr.ty.elemType(),1142 .Pointer => array_ptr.ty.elemType(),
1089 else => return mod.fail(scope, inst.positionals.array_ptr.src, "expected pointer, found '{}'", .{array_ptr.ty}),1143 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...@@ -1120,6 +1174,22 @@ fn analyzeInstElemPtr(mod: *Module, scope: *Scope, inst: *zir.Inst.ElemPtr) Inne
1120 return mod.fail(scope, inst.base.src, "TODO implement more analyze elemptr", .{});1174 return mod.fail(scope, inst.base.src, "TODO implement more analyze elemptr", .{});
1121}1175}
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
1123fn analyzeInstShl(mod: *Module, scope: *Scope, inst: *zir.Inst.BinOp) InnerError!*Inst {1193fn analyzeInstShl(mod: *Module, scope: *Scope, inst: *zir.Inst.BinOp) InnerError!*Inst {
1124 return mod.fail(scope, inst.base.src, "TODO implement analyzeInstShl", .{});1194 return mod.fail(scope, inst.base.src, "TODO implement analyzeInstShl", .{});
1125}1195}
...@@ -1187,6 +1257,12 @@ fn analyzeInstArithmetic(mod: *Module, scope: *Scope, inst: *zir.Inst.BinOp) Inn...@@ -1187,6 +1257,12 @@ fn analyzeInstArithmetic(mod: *Module, scope: *Scope, inst: *zir.Inst.BinOp) Inn
11871257
1188 if (casted_lhs.value()) |lhs_val| {1258 if (casted_lhs.value()) |lhs_val| {
1189 if (casted_rhs.value()) |rhs_val| {1259 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 }
1190 return analyzeInstComptimeOp(mod, scope, scalar_type, inst, lhs_val, rhs_val);1266 return analyzeInstComptimeOp(mod, scope, scalar_type, inst, lhs_val, rhs_val);
1191 }1267 }
1192 }1268 }
...@@ -1376,6 +1452,7 @@ fn analyzeInstCondBr(mod: *Module, scope: *Scope, inst: *zir.Inst.CondBr) InnerE...@@ -1376,6 +1452,7 @@ fn analyzeInstCondBr(mod: *Module, scope: *Scope, inst: *zir.Inst.CondBr) InnerE
1376 .decl = parent_block.decl,1452 .decl = parent_block.decl,
1377 .instructions = .{},1453 .instructions = .{},
1378 .arena = parent_block.arena,1454 .arena = parent_block.arena,
1455 .is_comptime = parent_block.is_comptime,
1379 };1456 };
1380 defer true_block.instructions.deinit(mod.gpa);1457 defer true_block.instructions.deinit(mod.gpa);
1381 try analyzeBody(mod, &true_block.base, inst.positionals.then_body);1458 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...@@ -1386,6 +1463,7 @@ fn analyzeInstCondBr(mod: *Module, scope: *Scope, inst: *zir.Inst.CondBr) InnerE
1386 .decl = parent_block.decl,1463 .decl = parent_block.decl,
1387 .instructions = .{},1464 .instructions = .{},
1388 .arena = parent_block.arena,1465 .arena = parent_block.arena,
1466 .is_comptime = parent_block.is_comptime,
1389 };1467 };
1390 defer false_block.instructions.deinit(mod.gpa);1468 defer false_block.instructions.deinit(mod.gpa);
1391 try analyzeBody(mod, &false_block.base, inst.positionals.else_body);1469 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,...@@ -15342,9 +15342,14 @@ static IrInstGen *ir_analyze_cast(IrAnalyze *ira, IrInst *source_instr,
15342 ZigType *array_type = actual_type->data.pointer.child_type;15342 ZigType *array_type = actual_type->data.pointer.child_type;
15343 bool const_ok = (slice_ptr_type->data.pointer.is_const || array_type->data.array.len == 015343 bool const_ok = (slice_ptr_type->data.pointer.is_const || array_type->data.array.len == 0
15344 || !actual_type->data.pointer.is_const);15344 || !actual_type->data.pointer.is_const);
15345
15345 if (const_ok && types_match_const_cast_only(ira, slice_ptr_type->data.pointer.child_type,15346 if (const_ok && types_match_const_cast_only(ira, slice_ptr_type->data.pointer.child_type,
15346 array_type->data.array.child_type, source_node,15347 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))))
15348 {15353 {
15349 // If the pointers both have ABI align, it works.15354 // If the pointers both have ABI align, it works.
15350 // Or if the array length is 0, alignment doesn't matter.15355 // 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...@@ -25684,6 +25689,10 @@ static Error ir_make_type_info_value(IrAnalyze *ira, IrInst* source_instr, ZigTy
25684 }25689 }
25685 set_optional_payload(inner_fields[2], struct_field->init_val);25690 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
25687 ZigValue *name = create_const_str_lit(ira->codegen, struct_field->name)->data.x_ptr.data.ref.pointee;25696 ZigValue *name = create_const_str_lit(ira->codegen, struct_field->name)->data.x_ptr.data.ref.pointee;
25688 init_const_slice(ira->codegen, inner_fields[0], name, 0, buf_len(struct_field->name), true);25697 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...@@ -26292,6 +26301,8 @@ static ZigType *type_info_to_type(IrAnalyze *ira, IrInst *source_instr, ZigTypeI
26292 buf_ptr(&field->type_entry->name), buf_ptr(&field->type_entry->name)));26301 buf_ptr(&field->type_entry->name), buf_ptr(&field->type_entry->name)));
26293 return ira->codegen->invalid_inst_gen->value->type;26302 return ira->codegen->invalid_inst_gen->value->type;
26294 }26303 }
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;
26295 }26306 }
2629626307
26297 return entry;26308 return entry;
test/compile_errors.zig+8
...@@ -2,6 +2,14 @@ const tests = @import("tests.zig");...@@ -2,6 +2,14 @@ const tests = @import("tests.zig");
2const std = @import("std");2const std = @import("std");
33
4pub fn addCases(cases: *tests.CompileErrorContext) void {4pub 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
5 cases.add("@Type with undefined",13 cases.add("@Type with undefined",
6 \\comptime {14 \\comptime {
7 \\ _ = @Type(.{ .Array = .{ .len = 0, .child = u8, .sentinel = undefined } });15 \\ _ = @Type(.{ .Array = .{ .len = 0, .child = u8, .sentinel = undefined } });
test/stage1/behavior/type_info.zig+6
...@@ -418,3 +418,9 @@ test "Struct.is_tuple" {...@@ -418,3 +418,9 @@ test "Struct.is_tuple" {
418 expect(@typeInfo(@TypeOf(.{0})).Struct.is_tuple);418 expect(@typeInfo(@TypeOf(.{0})).Struct.is_tuple);
419 expect(!@typeInfo(@TypeOf(.{ .a = 0 })).Struct.is_tuple);419 expect(!@typeInfo(@TypeOf(.{ .a = 0 })).Struct.is_tuple);
420}420}
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 {...@@ -274,7 +274,7 @@ pub fn addCases(ctx: *TestContext) !void {
274 }274 }
275275
276 {276 {
277 var case = ctx.exe("substracting numbers at runtime", linux_x64);277 var case = ctx.exe("subtracting numbers at runtime", linux_x64);
278 case.addCompareOutput(278 case.addCompareOutput(
279 \\export fn _start() noreturn {279 \\export fn _start() noreturn {
280 \\ sub(7, 4);280 \\ sub(7, 4);
...@@ -967,10 +967,19 @@ pub fn addCases(ctx: *TestContext) !void {...@@ -967,10 +967,19 @@ pub fn addCases(ctx: *TestContext) !void {
967 \\fn entry() void {}967 \\fn entry() void {}
968 , &[_][]const u8{":2:4: error: redefinition of 'entry'"});968 , &[_][]const u8{":2:4: error: redefinition of 'entry'"});
969969
970 ctx.compileError("extern variable has no type", linux_x64,970 {
971 \\comptime {971 var case = ctx.obj("extern variable has no type", linux_x64);
972 \\ _ = foo;972 case.addError(
973 \\}973 \\comptime {
974 \\extern var foo;974 \\ _ = foo;
975 , &[_][]const u8{":4:1: error: unable to infer variable type"});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 }
976}985}