authorgravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2025-04-17 13:42:21-07:00
committergravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2025-07-01 16:35:27-07:00
log98f463ad599bebd46fbc1e4f8ff365ef781bfe5f
tree6348a8c539dece831ca1542126fa4749cb05bb7c
parentb76b4c9c36d5cdbbaa9da8f49bcd0b83304a3875

revert introduction of `@errorCast` in this branch


22 files changed, 3597 insertions(+), 3587 deletions(-)

CMakeLists.txt+1-1
......@@ -495,6 +495,7 @@ set(ZIG_STAGE2_SOURCES
495495 lib/std/unicode.zig
496496 lib/std/zig.zig
497497 lib/std/zig/Ast.zig
498 lib/std/zig/Ast/Render.zig
498499 lib/std/zig/AstGen.zig
499500 lib/std/zig/AstRlAnnotate.zig
500501 lib/std/zig/LibCInstallation.zig
......@@ -503,7 +504,6 @@ set(ZIG_STAGE2_SOURCES
503504 lib/std/zig/WindowsSdk.zig
504505 lib/std/zig/Zir.zig
505506 lib/std/zig/c_builtins.zig
506 lib/std/zig/render.zig
507507 lib/std/zig/string_literal.zig
508508 lib/std/zig/system.zig
509509 lib/std/zig/system/NativePaths.zig
lib/compiler/reduce.zig+2-2
......@@ -138,10 +138,10 @@ pub fn main() !void {
138138 }
139139 }
140140
141 var fixups: Ast.Fixups = .{};
141 var fixups: Ast.Render.Fixups = .{};
142142 defer fixups.deinit(gpa);
143143
144 var more_fixups: Ast.Fixups = .{};
144 var more_fixups: Ast.Render.Fixups = .{};
145145 defer more_fixups.deinit(gpa);
146146
147147 var rng = std.Random.DefaultPrng.init(seed);
lib/std/Uri.zig+2-2
......@@ -428,11 +428,11 @@ fn merge_paths(base: Component, new: []u8, aux_buf: *[]u8) error{NoSpaceLeft}!Co
428428 var aux: std.io.BufferedWriter = undefined;
429429 aux.initFixed(aux_buf.*);
430430 if (!base.isEmpty()) {
431 aux.print("{fpath}", .{base}) catch |err| return @errorCast(err);
431 aux.print("{fpath}", .{base}) catch return error.NoSpaceLeft;
432432 aux.end = std.mem.lastIndexOfScalar(u8, aux.getWritten(), '/') orelse
433433 return remove_dot_segments(new);
434434 }
435 aux.print("/{s}", .{new}) catch |err| return @errorCast(err);
435 aux.print("/{s}", .{new}) catch return error.NoSpaceLeft;
436436 const merged_path = remove_dot_segments(aux.getWritten());
437437 aux_buf.* = aux_buf.*[merged_path.percent_encoded.len..];
438438 return merged_path;
lib/std/debug/FixedBufferReader.zig+4-1
......@@ -57,7 +57,10 @@ pub fn readLeb128(fbr: *FixedBufferReader, comptime T: type) Error!T {
5757 br.seek = fbr.pos;
5858 const result = br.takeLeb128(T);
5959 fbr.pos = br.seek;
60 return @errorCast(result);
60 return result catch |err| switch (err) {
61 error.ReadFailed => return error.EndOfStream,
62 else => |e| return e,
63 };
6164}
6265
6366pub fn readUleb128(fbr: *FixedBufferReader, comptime T: type) Error!T {
lib/std/http/Server.zig+2-2
......@@ -902,8 +902,8 @@ pub const Response = struct {
902902 /// when the end of stream occurs by calling `end`.
903903 pub fn write(r: *Response, bytes: []const u8) std.io.Writer.Error!usize {
904904 switch (r.transfer_encoding) {
905 .content_length, .none => return @errorCast(cl_writeSplat(r, &.{bytes}, 1)),
906 .chunked => return @errorCast(chunked_writeSplat(r, &.{bytes}, 1)),
905 .content_length, .none => return cl_writeSplat(r, &.{bytes}, 1),
906 .chunked => return chunked_writeSplat(r, &.{bytes}, 1),
907907 }
908908 }
909909
lib/std/json/Stringify.zig+1-1
......@@ -619,7 +619,7 @@ pub fn valueAlloc(gpa: Allocator, v: anytype, options: Options) error{OutOfMemor
619619 var aw: std.io.AllocatingWriter = undefined;
620620 const writer = aw.init(gpa);
621621 defer aw.deinit();
622 value(v, options, writer) catch return error.OutOfMemory; // TODO: try @errorCast(...)
622 value(v, options, writer) catch return error.OutOfMemory;
623623 return aw.toOwnedSlice();
624624}
625625
lib/std/zig/Ast.zig+9-14
......@@ -128,12 +128,6 @@ pub fn deinit(tree: *Ast, gpa: Allocator) void {
128128 tree.* = undefined;
129129}
130130
131pub const RenderError = error{
132 /// Ran out of memory allocating call stack frames to complete rendering, or
133 /// ran out of memory allocating space in the output buffer.
134 OutOfMemory,
135};
136
137131pub const Mode = enum { zig, zon };
138132
139133/// Result should be freed with tree.deinit() when there are
......@@ -199,19 +193,21 @@ pub fn parse(gpa: Allocator, source: [:0]const u8, mode: Mode) Allocator.Error!A
199193
200194/// `gpa` is used for allocating the resulting formatted source code.
201195/// Caller owns the returned slice of bytes, allocated with `gpa`.
202pub fn renderAlloc(tree: Ast, gpa: Allocator) RenderError![]u8 {
196pub fn renderAlloc(tree: Ast, gpa: Allocator) error{OutOfMemory}![]u8 {
203197 var aw: std.io.AllocatingWriter = undefined;
204198 const bw = aw.init(gpa);
205199 errdefer aw.deinit();
206 render(tree, gpa, bw, .{}) catch |err| return @errorCast(err); // TODO try @errorCast(...)
200 render(tree, gpa, bw, .{}) catch |err| switch (err) {
201 error.WriteFailed => return error.OutOfMemory,
202 };
207203 return aw.toOwnedSlice();
208204}
209205
210pub fn render(tree: Ast, gpa: Allocator, bw: *std.io.BufferedWriter, fixups: Fixups) RenderError!void {
211 return @import("./render.zig").renderTree(gpa, bw, tree, fixups);
212}
206pub const Render = @import("Ast/Render.zig");
213207
214pub const Fixups = private_render.Fixups;
208pub fn render(tree: Ast, gpa: Allocator, bw: *std.io.BufferedWriter, fixups: Render.Fixups) Render.Error!void {
209 return Render.tree(gpa, bw, tree, fixups);
210}
215211
216212/// Returns an extra offset for column and byte offset of errors that
217213/// should point after the token in the error message.
......@@ -4130,9 +4126,8 @@ const Token = std.zig.Token;
41304126const Ast = @This();
41314127const Allocator = std.mem.Allocator;
41324128const Parse = @import("Parse.zig");
4133const private_render = @import("./render.zig");
41344129
41354130test {
41364131 _ = Parse;
4137 _ = private_render;
4132 _ = Render;
41384133}
lib/std/zig/Ast/Render.zig created+3508
......@@ -0,0 +1,3508 @@
1const std = @import("../../std.zig");
2const assert = std.debug.assert;
3const mem = std.mem;
4const Allocator = std.mem.Allocator;
5const meta = std.meta;
6const Ast = std.zig.Ast;
7const Token = std.zig.Token;
8const primitives = std.zig.primitives;
9
10const Render = @This();
11
12const indent_delta = 4;
13const asm_indent_delta = 2;
14
15gpa: Allocator,
16ais: *AutoIndentingStream,
17tree: Ast,
18fixups: Fixups,
19
20pub const Error = error{
21 /// Ran out of memory allocating call stack frames to complete rendering.
22 OutOfMemory,
23 /// Transitive failure from
24 WriteFailed,
25};
26
27pub const Fixups = struct {
28 /// The key is the mut token (`var`/`const`) of the variable declaration
29 /// that should have a `_ = foo;` inserted afterwards.
30 unused_var_decls: std.AutoHashMapUnmanaged(Ast.TokenIndex, void) = .empty,
31 /// The functions in this unordered set of AST fn decl nodes will render
32 /// with a function body of `@trap()` instead, with all parameters
33 /// discarded.
34 gut_functions: std.AutoHashMapUnmanaged(Ast.Node.Index, void) = .empty,
35 /// These global declarations will be omitted.
36 omit_nodes: std.AutoHashMapUnmanaged(Ast.Node.Index, void) = .empty,
37 /// These expressions will be replaced with the string value.
38 replace_nodes_with_string: std.AutoHashMapUnmanaged(Ast.Node.Index, []const u8) = .empty,
39 /// The string value will be inserted directly after the node.
40 append_string_after_node: std.AutoHashMapUnmanaged(Ast.Node.Index, []const u8) = .empty,
41 /// These nodes will be replaced with a different node.
42 replace_nodes_with_node: std.AutoHashMapUnmanaged(Ast.Node.Index, Ast.Node.Index) = .empty,
43 /// Change all identifier names matching the key to be value instead.
44 rename_identifiers: std.StringArrayHashMapUnmanaged([]const u8) = .empty,
45
46 /// All `@import` builtin calls which refer to a file path will be prefixed
47 /// with this path.
48 rebase_imported_paths: ?[]const u8 = null,
49
50 pub fn count(f: Fixups) usize {
51 return f.unused_var_decls.count() +
52 f.gut_functions.count() +
53 f.omit_nodes.count() +
54 f.replace_nodes_with_string.count() +
55 f.append_string_after_node.count() +
56 f.replace_nodes_with_node.count() +
57 f.rename_identifiers.count() +
58 @intFromBool(f.rebase_imported_paths != null);
59 }
60
61 pub fn clearRetainingCapacity(f: *Fixups) void {
62 f.unused_var_decls.clearRetainingCapacity();
63 f.gut_functions.clearRetainingCapacity();
64 f.omit_nodes.clearRetainingCapacity();
65 f.replace_nodes_with_string.clearRetainingCapacity();
66 f.append_string_after_node.clearRetainingCapacity();
67 f.replace_nodes_with_node.clearRetainingCapacity();
68 f.rename_identifiers.clearRetainingCapacity();
69
70 f.rebase_imported_paths = null;
71 }
72
73 pub fn deinit(f: *Fixups, gpa: Allocator) void {
74 f.unused_var_decls.deinit(gpa);
75 f.gut_functions.deinit(gpa);
76 f.omit_nodes.deinit(gpa);
77 f.replace_nodes_with_string.deinit(gpa);
78 f.append_string_after_node.deinit(gpa);
79 f.replace_nodes_with_node.deinit(gpa);
80 f.rename_identifiers.deinit(gpa);
81 f.* = undefined;
82 }
83};
84
85pub fn renderTree(gpa: Allocator, bw: *std.io.BufferedWriter, tree: Ast, fixups: Fixups) Error!void {
86 assert(tree.errors.len == 0); // Cannot render an invalid tree.
87 var auto_indenting_stream: AutoIndentingStream = .init(gpa, bw, indent_delta);
88 defer auto_indenting_stream.deinit();
89 var r: Render = .{
90 .gpa = gpa,
91 .ais = &auto_indenting_stream,
92 .tree = tree,
93 .fixups = fixups,
94 };
95
96 // Render all the line comments at the beginning of the file.
97 const comment_end_loc = tree.tokenStart(0);
98 _ = try renderComments(&r, 0, comment_end_loc);
99
100 if (tree.tokenTag(0) == .container_doc_comment) {
101 try renderContainerDocComments(&r, 0);
102 }
103
104 switch (tree.mode) {
105 .zig => try renderMembers(&r, tree.rootDecls()),
106 .zon => {
107 try renderExpression(
108 &r,
109 tree.rootDecls()[0],
110 .newline,
111 );
112 },
113 }
114
115 if (auto_indenting_stream.disabled_offset) |disabled_offset| {
116 try writeFixingWhitespace(auto_indenting_stream.underlying_writer, tree.source[disabled_offset..]);
117 }
118}
119
120/// Render all members in the given slice, keeping empty lines where appropriate
121fn renderMembers(r: *Render, members: []const Ast.Node.Index) Error!void {
122 const tree = r.tree;
123 if (members.len == 0) return;
124 const container: Container = for (members) |member| {
125 if (tree.fullContainerField(member)) |field| if (!field.ast.tuple_like) break .other;
126 } else .tuple;
127 try renderMember(r, container, members[0], .newline);
128 for (members[1..]) |member| {
129 try renderExtraNewline(r, member);
130 try renderMember(r, container, member, .newline);
131 }
132}
133
134const Container = enum {
135 @"enum",
136 tuple,
137 other,
138};
139
140fn renderMember(
141 r: *Render,
142 container: Container,
143 decl: Ast.Node.Index,
144 space: Space,
145) Error!void {
146 const tree = r.tree;
147 const ais = r.ais;
148 if (r.fixups.omit_nodes.contains(decl)) return;
149 try renderDocComments(r, tree.firstToken(decl));
150 switch (tree.nodeTag(decl)) {
151 .fn_decl => {
152 // Some examples:
153 // pub extern "foo" fn ...
154 // export fn ...
155 const fn_proto, const body_node = tree.nodeData(decl).node_and_node;
156 const fn_token = tree.nodeMainToken(fn_proto);
157 // Go back to the first token we should render here.
158 var i = fn_token;
159 while (i > 0) {
160 i -= 1;
161 switch (tree.tokenTag(i)) {
162 .keyword_extern,
163 .keyword_export,
164 .keyword_pub,
165 .string_literal,
166 .keyword_inline,
167 .keyword_noinline,
168 => continue,
169
170 else => {
171 i += 1;
172 break;
173 },
174 }
175 }
176
177 while (i < fn_token) : (i += 1) {
178 try renderToken(r, i, .space);
179 }
180 switch (tree.nodeTag(fn_proto)) {
181 .fn_proto_one, .fn_proto => {
182 var buf: [1]Ast.Node.Index = undefined;
183 const opt_callconv_expr = if (tree.nodeTag(fn_proto) == .fn_proto_one)
184 tree.fnProtoOne(&buf, fn_proto).ast.callconv_expr
185 else
186 tree.fnProto(fn_proto).ast.callconv_expr;
187
188 // Keep in sync with logic in `renderFnProto`. Search this file for the marker PROMOTE_CALLCONV_INLINE
189 if (opt_callconv_expr.unwrap()) |callconv_expr| {
190 if (tree.nodeTag(callconv_expr) == .enum_literal) {
191 if (mem.eql(u8, "@\"inline\"", tree.tokenSlice(tree.nodeMainToken(callconv_expr)))) {
192 try ais.underlying_writer.writeAll("inline ");
193 }
194 }
195 }
196 },
197 .fn_proto_simple, .fn_proto_multi => {},
198 else => unreachable,
199 }
200 try renderExpression(r, fn_proto, .space);
201 if (r.fixups.gut_functions.contains(decl)) {
202 try ais.pushIndent(.normal);
203 const lbrace = tree.nodeMainToken(body_node);
204 try renderToken(r, lbrace, .newline);
205 try discardAllParams(r, fn_proto);
206 try ais.writeAll("@trap();");
207 ais.popIndent();
208 try ais.insertNewline();
209 try renderToken(r, tree.lastToken(body_node), space); // rbrace
210 } else if (r.fixups.unused_var_decls.count() != 0) {
211 try ais.pushIndent(.normal);
212 const lbrace = tree.nodeMainToken(body_node);
213 try renderToken(r, lbrace, .newline);
214
215 var fn_proto_buf: [1]Ast.Node.Index = undefined;
216 const full_fn_proto = tree.fullFnProto(&fn_proto_buf, fn_proto).?;
217 var it = full_fn_proto.iterate(&tree);
218 while (it.next()) |param| {
219 const name_ident = param.name_token.?;
220 assert(tree.tokenTag(name_ident) == .identifier);
221 if (r.fixups.unused_var_decls.contains(name_ident)) {
222 try ais.writeAll("_ = ");
223 try ais.writeAll(tokenSliceForRender(r.tree, name_ident));
224 try ais.writeAll(";\n");
225 }
226 }
227 var statements_buf: [2]Ast.Node.Index = undefined;
228 const statements = tree.blockStatements(&statements_buf, body_node).?;
229 return finishRenderBlock(r, body_node, statements, space);
230 } else {
231 return renderExpression(r, body_node, space);
232 }
233 },
234 .fn_proto_simple,
235 .fn_proto_multi,
236 .fn_proto_one,
237 .fn_proto,
238 => {
239 // Extern function prototypes are parsed as these tags.
240 // Go back to the first token we should render here.
241 const fn_token = tree.nodeMainToken(decl);
242 var i = fn_token;
243 while (i > 0) {
244 i -= 1;
245 switch (tree.tokenTag(i)) {
246 .keyword_extern,
247 .keyword_export,
248 .keyword_pub,
249 .string_literal,
250 .keyword_inline,
251 .keyword_noinline,
252 => continue,
253
254 else => {
255 i += 1;
256 break;
257 },
258 }
259 }
260 while (i < fn_token) : (i += 1) {
261 try renderToken(r, i, .space);
262 }
263 try renderExpression(r, decl, .none);
264 return renderToken(r, tree.lastToken(decl) + 1, space); // semicolon
265 },
266
267 .@"usingnamespace" => {
268 const main_token = tree.nodeMainToken(decl);
269 const expr = tree.nodeData(decl).node;
270 if (tree.isTokenPrecededByTags(main_token, &.{.keyword_pub})) {
271 try renderToken(r, main_token - 1, .space); // pub
272 }
273 try renderToken(r, main_token, .space); // usingnamespace
274 try renderExpression(r, expr, .none);
275 return renderToken(r, tree.lastToken(expr) + 1, space); // ;
276 },
277
278 .global_var_decl,
279 .local_var_decl,
280 .simple_var_decl,
281 .aligned_var_decl,
282 => {
283 try ais.pushSpace(.semicolon);
284 try renderVarDecl(r, tree.fullVarDecl(decl).?, false, .semicolon);
285 ais.popSpace();
286 },
287
288 .test_decl => {
289 const test_token = tree.nodeMainToken(decl);
290 const opt_name_token, const block_node = tree.nodeData(decl).opt_token_and_node;
291 try renderToken(r, test_token, .space);
292 if (opt_name_token.unwrap()) |name_token| {
293 switch (tree.tokenTag(name_token)) {
294 .string_literal => try renderToken(r, name_token, .space),
295 .identifier => try renderIdentifier(r, name_token, .space, .preserve_when_shadowing),
296 else => unreachable,
297 }
298 }
299 try renderExpression(r, block_node, space);
300 },
301
302 .container_field_init,
303 .container_field_align,
304 .container_field,
305 => return renderContainerField(r, container, tree.fullContainerField(decl).?, space),
306
307 .@"comptime" => return renderExpression(r, decl, space),
308
309 .root => unreachable,
310 else => unreachable,
311 }
312}
313
314/// Render all expressions in the slice, keeping empty lines where appropriate
315fn renderExpressions(r: *Render, expressions: []const Ast.Node.Index, space: Space) Error!void {
316 if (expressions.len == 0) return;
317 try renderExpression(r, expressions[0], space);
318 for (expressions[1..]) |expression| {
319 try renderExtraNewline(r, expression);
320 try renderExpression(r, expression, space);
321 }
322}
323
324fn renderExpression(r: *Render, node: Ast.Node.Index, space: Space) Error!void {
325 const tree = r.tree;
326 const ais = r.ais;
327 if (r.fixups.replace_nodes_with_string.get(node)) |replacement| {
328 try ais.writeAll(replacement);
329 try renderOnlySpace(r, space);
330 return;
331 } else if (r.fixups.replace_nodes_with_node.get(node)) |replacement| {
332 return renderExpression(r, replacement, space);
333 }
334 switch (tree.nodeTag(node)) {
335 .identifier => {
336 const token_index = tree.nodeMainToken(node);
337 return renderIdentifier(r, token_index, space, .preserve_when_shadowing);
338 },
339
340 .number_literal,
341 .char_literal,
342 .unreachable_literal,
343 .anyframe_literal,
344 .string_literal,
345 => return renderToken(r, tree.nodeMainToken(node), space),
346
347 .multiline_string_literal => {
348 try ais.maybeInsertNewline();
349
350 const first_tok, const last_tok = tree.nodeData(node).token_and_token;
351 for (first_tok..last_tok + 1) |i| {
352 try renderToken(r, @intCast(i), .newline);
353 }
354
355 const next_token = last_tok + 1;
356 const next_token_tag = tree.tokenTag(next_token);
357
358 // dedent the next thing that comes after a multiline string literal
359 if (!ais.indentStackEmpty() and
360 next_token_tag != .colon and
361 ((next_token_tag != .semicolon and next_token_tag != .comma) or
362 ais.lastSpaceModeIndent() < ais.currentIndent()))
363 {
364 ais.popIndent();
365 try ais.pushIndent(.normal);
366 }
367
368 switch (space) {
369 .none, .space, .newline, .skip => {},
370 .semicolon => if (next_token_tag == .semicolon) try renderTokenOverrideSpaceMode(r, next_token, .newline, .semicolon),
371 .comma => if (next_token_tag == .comma) try renderTokenOverrideSpaceMode(r, next_token, .newline, .comma),
372 .comma_space => if (next_token_tag == .comma) try renderToken(r, next_token, .space),
373 }
374 },
375
376 .error_value => {
377 const main_token = tree.nodeMainToken(node);
378 try renderToken(r, main_token, .none);
379 try renderToken(r, main_token + 1, .none);
380 return renderIdentifier(r, main_token + 2, space, .eagerly_unquote);
381 },
382
383 .block_two,
384 .block_two_semicolon,
385 .block,
386 .block_semicolon,
387 => {
388 var buf: [2]Ast.Node.Index = undefined;
389 const statements = tree.blockStatements(&buf, node).?;
390 return renderBlock(r, node, statements, space);
391 },
392
393 .@"errdefer" => {
394 const defer_token = tree.nodeMainToken(node);
395 const maybe_payload_token, const expr = tree.nodeData(node).opt_token_and_node;
396
397 try renderToken(r, defer_token, .space);
398 if (maybe_payload_token.unwrap()) |payload_token| {
399 try renderToken(r, payload_token - 1, .none); // |
400 try renderIdentifier(r, payload_token, .none, .preserve_when_shadowing); // identifier
401 try renderToken(r, payload_token + 1, .space); // |
402 }
403 return renderExpression(r, expr, space);
404 },
405
406 .@"defer",
407 .@"comptime",
408 .@"nosuspend",
409 .@"suspend",
410 => {
411 const main_token = tree.nodeMainToken(node);
412 const item = tree.nodeData(node).node;
413 try renderToken(r, main_token, .space);
414 return renderExpression(r, item, space);
415 },
416
417 .@"catch" => {
418 const main_token = tree.nodeMainToken(node);
419 const lhs, const rhs = tree.nodeData(node).node_and_node;
420 const fallback_first = tree.firstToken(rhs);
421
422 const same_line = tree.tokensOnSameLine(main_token, fallback_first);
423 const after_op_space = if (same_line) Space.space else Space.newline;
424
425 try renderExpression(r, lhs, .space); // target
426
427 try ais.pushIndent(.normal);
428 if (tree.tokenTag(fallback_first - 1) == .pipe) {
429 try renderToken(r, main_token, .space); // catch keyword
430 try renderToken(r, main_token + 1, .none); // pipe
431 try renderIdentifier(r, main_token + 2, .none, .preserve_when_shadowing); // payload identifier
432 try renderToken(r, main_token + 3, after_op_space); // pipe
433 } else {
434 assert(tree.tokenTag(fallback_first - 1) == .keyword_catch);
435 try renderToken(r, main_token, after_op_space); // catch keyword
436 }
437 try renderExpression(r, rhs, space); // fallback
438 ais.popIndent();
439 },
440
441 .field_access => {
442 const lhs, const name_token = tree.nodeData(node).node_and_token;
443 const dot_token = name_token - 1;
444
445 try ais.pushIndent(.field_access);
446 try renderExpression(r, lhs, .none);
447
448 // Allow a line break between the lhs and the dot if the lhs and rhs
449 // are on different lines.
450 const lhs_last_token = tree.lastToken(lhs);
451 const same_line = tree.tokensOnSameLine(lhs_last_token, name_token);
452 if (!same_line and !hasComment(tree, lhs_last_token, dot_token)) try ais.insertNewline();
453
454 try renderToken(r, dot_token, .none);
455
456 try renderIdentifier(r, name_token, space, .eagerly_unquote); // field
457 ais.popIndent();
458 },
459
460 .error_union,
461 .switch_range,
462 => {
463 const lhs, const rhs = tree.nodeData(node).node_and_node;
464 try renderExpression(r, lhs, .none);
465 try renderToken(r, tree.nodeMainToken(node), .none);
466 return renderExpression(r, rhs, space);
467 },
468 .for_range => {
469 const start, const opt_end = tree.nodeData(node).node_and_opt_node;
470 try renderExpression(r, start, .none);
471 if (opt_end.unwrap()) |end| {
472 try renderToken(r, tree.nodeMainToken(node), .none);
473 return renderExpression(r, end, space);
474 } else {
475 return renderToken(r, tree.nodeMainToken(node), space);
476 }
477 },
478
479 .assign,
480 .assign_bit_and,
481 .assign_bit_or,
482 .assign_shl,
483 .assign_shl_sat,
484 .assign_shr,
485 .assign_bit_xor,
486 .assign_div,
487 .assign_sub,
488 .assign_sub_wrap,
489 .assign_sub_sat,
490 .assign_mod,
491 .assign_add,
492 .assign_add_wrap,
493 .assign_add_sat,
494 .assign_mul,
495 .assign_mul_wrap,
496 .assign_mul_sat,
497 => {
498 const lhs, const rhs = tree.nodeData(node).node_and_node;
499 try renderExpression(r, lhs, .space);
500 const op_token = tree.nodeMainToken(node);
501 try ais.pushIndent(.after_equals);
502 if (tree.tokensOnSameLine(op_token, op_token + 1)) {
503 try renderToken(r, op_token, .space);
504 } else {
505 try renderToken(r, op_token, .newline);
506 }
507 try renderExpression(r, rhs, space);
508 ais.popIndent();
509 },
510
511 .add,
512 .add_wrap,
513 .add_sat,
514 .array_cat,
515 .array_mult,
516 .bang_equal,
517 .bit_and,
518 .bit_or,
519 .shl,
520 .shl_sat,
521 .shr,
522 .bit_xor,
523 .bool_and,
524 .bool_or,
525 .div,
526 .equal_equal,
527 .greater_or_equal,
528 .greater_than,
529 .less_or_equal,
530 .less_than,
531 .merge_error_sets,
532 .mod,
533 .mul,
534 .mul_wrap,
535 .mul_sat,
536 .sub,
537 .sub_wrap,
538 .sub_sat,
539 .@"orelse",
540 => {
541 const lhs, const rhs = tree.nodeData(node).node_and_node;
542 try renderExpression(r, lhs, .space);
543 const op_token = tree.nodeMainToken(node);
544 try ais.pushIndent(.binop);
545 if (tree.tokensOnSameLine(op_token, op_token + 1)) {
546 try renderToken(r, op_token, .space);
547 } else {
548 try renderToken(r, op_token, .newline);
549 }
550 try renderExpression(r, rhs, space);
551 ais.popIndent();
552 },
553
554 .assign_destructure => {
555 const full = tree.assignDestructure(node);
556 if (full.comptime_token) |comptime_token| {
557 try renderToken(r, comptime_token, .space);
558 }
559
560 for (full.ast.variables, 0..) |variable_node, i| {
561 const variable_space: Space = if (i == full.ast.variables.len - 1) .space else .comma_space;
562 switch (tree.nodeTag(variable_node)) {
563 .global_var_decl,
564 .local_var_decl,
565 .simple_var_decl,
566 .aligned_var_decl,
567 => {
568 try renderVarDecl(r, tree.fullVarDecl(variable_node).?, true, variable_space);
569 },
570 else => try renderExpression(r, variable_node, variable_space),
571 }
572 }
573 try ais.pushIndent(.after_equals);
574 if (tree.tokensOnSameLine(full.ast.equal_token, full.ast.equal_token + 1)) {
575 try renderToken(r, full.ast.equal_token, .space);
576 } else {
577 try renderToken(r, full.ast.equal_token, .newline);
578 }
579 try renderExpression(r, full.ast.value_expr, space);
580 ais.popIndent();
581 },
582
583 .bit_not,
584 .bool_not,
585 .negation,
586 .negation_wrap,
587 .optional_type,
588 .address_of,
589 => {
590 try renderToken(r, tree.nodeMainToken(node), .none);
591 return renderExpression(r, tree.nodeData(node).node, space);
592 },
593
594 .@"try",
595 .@"resume",
596 .@"await",
597 => {
598 try renderToken(r, tree.nodeMainToken(node), .space);
599 return renderExpression(r, tree.nodeData(node).node, space);
600 },
601
602 .array_type,
603 .array_type_sentinel,
604 => return renderArrayType(r, tree.fullArrayType(node).?, space),
605
606 .ptr_type_aligned,
607 .ptr_type_sentinel,
608 .ptr_type,
609 .ptr_type_bit_range,
610 => return renderPtrType(r, tree.fullPtrType(node).?, space),
611
612 .array_init_one,
613 .array_init_one_comma,
614 .array_init_dot_two,
615 .array_init_dot_two_comma,
616 .array_init_dot,
617 .array_init_dot_comma,
618 .array_init,
619 .array_init_comma,
620 => {
621 var elements: [2]Ast.Node.Index = undefined;
622 return renderArrayInit(r, tree.fullArrayInit(&elements, node).?, space);
623 },
624
625 .struct_init_one,
626 .struct_init_one_comma,
627 .struct_init_dot_two,
628 .struct_init_dot_two_comma,
629 .struct_init_dot,
630 .struct_init_dot_comma,
631 .struct_init,
632 .struct_init_comma,
633 => {
634 var buf: [2]Ast.Node.Index = undefined;
635 return renderStructInit(r, node, tree.fullStructInit(&buf, node).?, space);
636 },
637
638 .call_one,
639 .call_one_comma,
640 .async_call_one,
641 .async_call_one_comma,
642 .call,
643 .call_comma,
644 .async_call,
645 .async_call_comma,
646 => {
647 var buf: [1]Ast.Node.Index = undefined;
648 return renderCall(r, tree.fullCall(&buf, node).?, space);
649 },
650
651 .array_access => {
652 const lhs, const rhs = tree.nodeData(node).node_and_node;
653 const lbracket = tree.firstToken(rhs) - 1;
654 const rbracket = tree.lastToken(rhs) + 1;
655 const one_line = tree.tokensOnSameLine(lbracket, rbracket);
656 const inner_space = if (one_line) Space.none else Space.newline;
657 try renderExpression(r, lhs, .none);
658 try ais.pushIndent(.normal);
659 try renderToken(r, lbracket, inner_space); // [
660 try renderExpression(r, rhs, inner_space);
661 ais.popIndent();
662 return renderToken(r, rbracket, space); // ]
663 },
664
665 .slice_open,
666 .slice,
667 .slice_sentinel,
668 => return renderSlice(r, node, tree.fullSlice(node).?, space),
669
670 .deref => {
671 try renderExpression(r, tree.nodeData(node).node, .none);
672 return renderToken(r, tree.nodeMainToken(node), space);
673 },
674
675 .unwrap_optional => {
676 const lhs, const question_mark = tree.nodeData(node).node_and_token;
677 const dot_token = question_mark - 1;
678 try renderExpression(r, lhs, .none);
679 try renderToken(r, dot_token, .none);
680 return renderToken(r, question_mark, space);
681 },
682
683 .@"break", .@"continue" => {
684 const main_token = tree.nodeMainToken(node);
685 const opt_label_token, const opt_target = tree.nodeData(node).opt_token_and_opt_node;
686 if (opt_label_token == .none and opt_target == .none) {
687 try renderToken(r, main_token, space); // break/continue
688 } else if (opt_label_token == .none and opt_target != .none) {
689 const target = opt_target.unwrap().?;
690 try renderToken(r, main_token, .space); // break/continue
691 try renderExpression(r, target, space);
692 } else if (opt_label_token != .none and opt_target == .none) {
693 const label_token = opt_label_token.unwrap().?;
694 try renderToken(r, main_token, .space); // break/continue
695 try renderToken(r, label_token - 1, .none); // :
696 try renderIdentifier(r, label_token, space, .eagerly_unquote); // identifier
697 } else if (opt_label_token != .none and opt_target != .none) {
698 const label_token = opt_label_token.unwrap().?;
699 const target = opt_target.unwrap().?;
700 try renderToken(r, main_token, .space); // break/continue
701 try renderToken(r, label_token - 1, .none); // :
702 try renderIdentifier(r, label_token, .space, .eagerly_unquote); // identifier
703 try renderExpression(r, target, space);
704 } else unreachable;
705 },
706
707 .@"return" => {
708 if (tree.nodeData(node).opt_node.unwrap()) |expr| {
709 try renderToken(r, tree.nodeMainToken(node), .space);
710 try renderExpression(r, expr, space);
711 } else {
712 try renderToken(r, tree.nodeMainToken(node), space);
713 }
714 },
715
716 .grouped_expression => {
717 const expr, const rparen = tree.nodeData(node).node_and_token;
718 try ais.pushIndent(.normal);
719 try renderToken(r, tree.nodeMainToken(node), .none); // lparen
720 try renderExpression(r, expr, .none);
721 ais.popIndent();
722 return renderToken(r, rparen, space);
723 },
724
725 .container_decl,
726 .container_decl_trailing,
727 .container_decl_arg,
728 .container_decl_arg_trailing,
729 .container_decl_two,
730 .container_decl_two_trailing,
731 .tagged_union,
732 .tagged_union_trailing,
733 .tagged_union_enum_tag,
734 .tagged_union_enum_tag_trailing,
735 .tagged_union_two,
736 .tagged_union_two_trailing,
737 => {
738 var buf: [2]Ast.Node.Index = undefined;
739 return renderContainerDecl(r, node, tree.fullContainerDecl(&buf, node).?, space);
740 },
741
742 .error_set_decl => {
743 const error_token = tree.nodeMainToken(node);
744 const lbrace, const rbrace = tree.nodeData(node).token_and_token;
745
746 try renderToken(r, error_token, .none);
747
748 if (lbrace + 1 == rbrace) {
749 // There is nothing between the braces so render condensed: `error{}`
750 try renderToken(r, lbrace, .none);
751 return renderToken(r, rbrace, space);
752 } else if (lbrace + 2 == rbrace and tree.tokenTag(lbrace + 1) == .identifier) {
753 // There is exactly one member and no trailing comma or
754 // comments, so render without surrounding spaces: `error{Foo}`
755 try renderToken(r, lbrace, .none);
756 try renderIdentifier(r, lbrace + 1, .none, .eagerly_unquote); // identifier
757 return renderToken(r, rbrace, space);
758 } else if (tree.tokenTag(rbrace - 1) == .comma) {
759 // There is a trailing comma so render each member on a new line.
760 try ais.pushIndent(.normal);
761 try renderToken(r, lbrace, .newline);
762 var i = lbrace + 1;
763 while (i < rbrace) : (i += 1) {
764 if (i > lbrace + 1) try renderExtraNewlineToken(r, i);
765 switch (tree.tokenTag(i)) {
766 .doc_comment => try renderToken(r, i, .newline),
767 .identifier => {
768 try ais.pushSpace(.comma);
769 try renderIdentifier(r, i, .comma, .eagerly_unquote);
770 ais.popSpace();
771 },
772 .comma => {},
773 else => unreachable,
774 }
775 }
776 ais.popIndent();
777 return renderToken(r, rbrace, space);
778 } else {
779 // There is no trailing comma so render everything on one line.
780 try renderToken(r, lbrace, .space);
781 var i = lbrace + 1;
782 while (i < rbrace) : (i += 1) {
783 switch (tree.tokenTag(i)) {
784 .doc_comment => unreachable, // TODO
785 .identifier => try renderIdentifier(r, i, .comma_space, .eagerly_unquote),
786 .comma => {},
787 else => unreachable,
788 }
789 }
790 return renderToken(r, rbrace, space);
791 }
792 },
793
794 .builtin_call_two,
795 .builtin_call_two_comma,
796 .builtin_call,
797 .builtin_call_comma,
798 => {
799 var buf: [2]Ast.Node.Index = undefined;
800 const params = tree.builtinCallParams(&buf, node).?;
801 return renderBuiltinCall(r, tree.nodeMainToken(node), params, space);
802 },
803
804 .fn_proto_simple,
805 .fn_proto_multi,
806 .fn_proto_one,
807 .fn_proto,
808 => {
809 var buf: [1]Ast.Node.Index = undefined;
810 return renderFnProto(r, tree.fullFnProto(&buf, node).?, space);
811 },
812
813 .anyframe_type => {
814 const main_token = tree.nodeMainToken(node);
815 try renderToken(r, main_token, .none); // anyframe
816 try renderToken(r, main_token + 1, .none); // ->
817 return renderExpression(r, tree.nodeData(node).token_and_node[1], space);
818 },
819
820 .@"switch",
821 .switch_comma,
822 => {
823 const full = tree.switchFull(node);
824
825 if (full.label_token) |label_token| {
826 try renderIdentifier(r, label_token, .none, .eagerly_unquote); // label
827 try renderToken(r, label_token + 1, .space); // :
828 }
829
830 const rparen = tree.lastToken(full.ast.condition) + 1;
831
832 try renderToken(r, full.ast.switch_token, .space); // switch
833 try renderToken(r, full.ast.switch_token + 1, .none); // (
834 try renderExpression(r, full.ast.condition, .none); // condition expression
835 try renderToken(r, rparen, .space); // )
836
837 try ais.pushIndent(.normal);
838 if (full.ast.cases.len == 0) {
839 try renderToken(r, rparen + 1, .none); // {
840 } else {
841 try renderToken(r, rparen + 1, .newline); // {
842 try ais.pushSpace(.comma);
843 try renderExpressions(r, full.ast.cases, .comma);
844 ais.popSpace();
845 }
846 ais.popIndent();
847 return renderToken(r, tree.lastToken(node), space); // }
848 },
849
850 .switch_case_one,
851 .switch_case_inline_one,
852 .switch_case,
853 .switch_case_inline,
854 => return renderSwitchCase(r, tree.fullSwitchCase(node).?, space),
855
856 .while_simple,
857 .while_cont,
858 .@"while",
859 => return renderWhile(r, tree.fullWhile(node).?, space),
860
861 .for_simple,
862 .@"for",
863 => return renderFor(r, tree.fullFor(node).?, space),
864
865 .if_simple,
866 .@"if",
867 => return renderIf(r, tree.fullIf(node).?, space),
868
869 .asm_simple,
870 .@"asm",
871 => return renderAsm(r, tree.fullAsm(node).?, space),
872
873 .enum_literal => {
874 try renderToken(r, tree.nodeMainToken(node) - 1, .none); // .
875 return renderIdentifier(r, tree.nodeMainToken(node), space, .eagerly_unquote); // name
876 },
877
878 .fn_decl => unreachable,
879 .container_field => unreachable,
880 .container_field_init => unreachable,
881 .container_field_align => unreachable,
882 .root => unreachable,
883 .global_var_decl => unreachable,
884 .local_var_decl => unreachable,
885 .simple_var_decl => unreachable,
886 .aligned_var_decl => unreachable,
887 .@"usingnamespace" => unreachable,
888 .test_decl => unreachable,
889 .asm_output => unreachable,
890 .asm_input => unreachable,
891 }
892}
893
894/// Same as `renderExpression`, but afterwards looks for any
895/// append_string_after_node fixups to apply
896fn renderExpressionFixup(r: *Render, node: Ast.Node.Index, space: Space) Error!void {
897 const ais = r.ais;
898 try renderExpression(r, node, space);
899 if (r.fixups.append_string_after_node.get(node)) |bytes| {
900 try ais.writeAll(bytes);
901 }
902}
903
904fn renderArrayType(
905 r: *Render,
906 array_type: Ast.full.ArrayType,
907 space: Space,
908) Error!void {
909 const tree = r.tree;
910 const ais = r.ais;
911 const rbracket = tree.firstToken(array_type.ast.elem_type) - 1;
912 const one_line = tree.tokensOnSameLine(array_type.ast.lbracket, rbracket);
913 const inner_space = if (one_line) Space.none else Space.newline;
914 try ais.pushIndent(.normal);
915 try renderToken(r, array_type.ast.lbracket, inner_space); // lbracket
916 try renderExpression(r, array_type.ast.elem_count, inner_space);
917 if (array_type.ast.sentinel.unwrap()) |sentinel| {
918 try renderToken(r, tree.firstToken(sentinel) - 1, inner_space); // colon
919 try renderExpression(r, sentinel, inner_space);
920 }
921 ais.popIndent();
922 try renderToken(r, rbracket, .none); // rbracket
923 return renderExpression(r, array_type.ast.elem_type, space);
924}
925
926fn renderPtrType(r: *Render, ptr_type: Ast.full.PtrType, space: Space) Error!void {
927 const tree = r.tree;
928 const main_token = ptr_type.ast.main_token;
929 switch (ptr_type.size) {
930 .one => {
931 // Since ** tokens exist and the same token is shared by two
932 // nested pointer types, we check to see if we are the parent
933 // in such a relationship. If so, skip rendering anything for
934 // this pointer type and rely on the child to render our asterisk
935 // as well when it renders the ** token.
936 if (tree.tokenTag(main_token) == .asterisk_asterisk and
937 main_token == tree.nodeMainToken(ptr_type.ast.child_type))
938 {
939 return renderExpression(r, ptr_type.ast.child_type, space);
940 }
941 try renderToken(r, main_token, .none); // asterisk
942 },
943 .many => {
944 if (ptr_type.ast.sentinel.unwrap()) |sentinel| {
945 try renderToken(r, main_token, .none); // lbracket
946 try renderToken(r, main_token + 1, .none); // asterisk
947 try renderToken(r, main_token + 2, .none); // colon
948 try renderExpression(r, sentinel, .none);
949 try renderToken(r, tree.lastToken(sentinel) + 1, .none); // rbracket
950 } else {
951 try renderToken(r, main_token, .none); // lbracket
952 try renderToken(r, main_token + 1, .none); // asterisk
953 try renderToken(r, main_token + 2, .none); // rbracket
954 }
955 },
956 .c => {
957 try renderToken(r, main_token, .none); // lbracket
958 try renderToken(r, main_token + 1, .none); // asterisk
959 try renderToken(r, main_token + 2, .none); // c
960 try renderToken(r, main_token + 3, .none); // rbracket
961 },
962 .slice => {
963 if (ptr_type.ast.sentinel.unwrap()) |sentinel| {
964 try renderToken(r, main_token, .none); // lbracket
965 try renderToken(r, main_token + 1, .none); // colon
966 try renderExpression(r, sentinel, .none);
967 try renderToken(r, tree.lastToken(sentinel) + 1, .none); // rbracket
968 } else {
969 try renderToken(r, main_token, .none); // lbracket
970 try renderToken(r, main_token + 1, .none); // rbracket
971 }
972 },
973 }
974
975 if (ptr_type.allowzero_token) |allowzero_token| {
976 try renderToken(r, allowzero_token, .space);
977 }
978
979 if (ptr_type.ast.align_node.unwrap()) |align_node| {
980 const align_first = tree.firstToken(align_node);
981 try renderToken(r, align_first - 2, .none); // align
982 try renderToken(r, align_first - 1, .none); // lparen
983 try renderExpression(r, align_node, .none);
984 if (ptr_type.ast.bit_range_start.unwrap()) |bit_range_start| {
985 const bit_range_end = ptr_type.ast.bit_range_end.unwrap().?;
986 try renderToken(r, tree.firstToken(bit_range_start) - 1, .none); // colon
987 try renderExpression(r, bit_range_start, .none);
988 try renderToken(r, tree.firstToken(bit_range_end) - 1, .none); // colon
989 try renderExpression(r, bit_range_end, .none);
990 try renderToken(r, tree.lastToken(bit_range_end) + 1, .space); // rparen
991 } else {
992 try renderToken(r, tree.lastToken(align_node) + 1, .space); // rparen
993 }
994 }
995
996 if (ptr_type.ast.addrspace_node.unwrap()) |addrspace_node| {
997 const addrspace_first = tree.firstToken(addrspace_node);
998 try renderToken(r, addrspace_first - 2, .none); // addrspace
999 try renderToken(r, addrspace_first - 1, .none); // lparen
1000 try renderExpression(r, addrspace_node, .none);
1001 try renderToken(r, tree.lastToken(addrspace_node) + 1, .space); // rparen
1002 }
1003
1004 if (ptr_type.const_token) |const_token| {
1005 try renderToken(r, const_token, .space);
1006 }
1007
1008 if (ptr_type.volatile_token) |volatile_token| {
1009 try renderToken(r, volatile_token, .space);
1010 }
1011
1012 try renderExpression(r, ptr_type.ast.child_type, space);
1013}
1014
1015fn renderSlice(
1016 r: *Render,
1017 slice_node: Ast.Node.Index,
1018 slice: Ast.full.Slice,
1019 space: Space,
1020) Error!void {
1021 const tree = r.tree;
1022 const after_start_space_bool = nodeCausesSliceOpSpace(tree.nodeTag(slice.ast.start)) or
1023 if (slice.ast.end.unwrap()) |end| nodeCausesSliceOpSpace(tree.nodeTag(end)) else false;
1024 const after_start_space = if (after_start_space_bool) Space.space else Space.none;
1025 const after_dots_space = if (slice.ast.end != .none)
1026 after_start_space
1027 else if (slice.ast.sentinel != .none) Space.space else Space.none;
1028
1029 try renderExpression(r, slice.ast.sliced, .none);
1030 try renderToken(r, slice.ast.lbracket, .none); // lbracket
1031
1032 const start_last = tree.lastToken(slice.ast.start);
1033 try renderExpression(r, slice.ast.start, after_start_space);
1034 try renderToken(r, start_last + 1, after_dots_space); // ellipsis2 ("..")
1035
1036 if (slice.ast.end.unwrap()) |end| {
1037 const after_end_space = if (slice.ast.sentinel != .none) Space.space else Space.none;
1038 try renderExpression(r, end, after_end_space);
1039 }
1040
1041 if (slice.ast.sentinel.unwrap()) |sentinel| {
1042 try renderToken(r, tree.firstToken(sentinel) - 1, .none); // colon
1043 try renderExpression(r, sentinel, .none);
1044 }
1045
1046 try renderToken(r, tree.lastToken(slice_node), space); // rbracket
1047}
1048
1049fn renderAsmOutput(
1050 r: *Render,
1051 asm_output: Ast.Node.Index,
1052 space: Space,
1053) Error!void {
1054 const tree = r.tree;
1055 assert(tree.nodeTag(asm_output) == .asm_output);
1056 const symbolic_name = tree.nodeMainToken(asm_output);
1057
1058 try renderToken(r, symbolic_name - 1, .none); // lbracket
1059 try renderIdentifier(r, symbolic_name, .none, .eagerly_unquote); // ident
1060 try renderToken(r, symbolic_name + 1, .space); // rbracket
1061 try renderToken(r, symbolic_name + 2, .space); // "constraint"
1062 try renderToken(r, symbolic_name + 3, .none); // lparen
1063
1064 if (tree.tokenTag(symbolic_name + 4) == .arrow) {
1065 const type_expr, const rparen = tree.nodeData(asm_output).opt_node_and_token;
1066 try renderToken(r, symbolic_name + 4, .space); // ->
1067 try renderExpression(r, type_expr.unwrap().?, Space.none);
1068 return renderToken(r, rparen, space);
1069 } else {
1070 try renderIdentifier(r, symbolic_name + 4, .none, .eagerly_unquote); // ident
1071 return renderToken(r, symbolic_name + 5, space); // rparen
1072 }
1073}
1074
1075fn renderAsmInput(
1076 r: *Render,
1077 asm_input: Ast.Node.Index,
1078 space: Space,
1079) Error!void {
1080 const tree = r.tree;
1081 assert(tree.nodeTag(asm_input) == .asm_input);
1082 const symbolic_name = tree.nodeMainToken(asm_input);
1083 const expr, const rparen = tree.nodeData(asm_input).node_and_token;
1084
1085 try renderToken(r, symbolic_name - 1, .none); // lbracket
1086 try renderIdentifier(r, symbolic_name, .none, .eagerly_unquote); // ident
1087 try renderToken(r, symbolic_name + 1, .space); // rbracket
1088 try renderToken(r, symbolic_name + 2, .space); // "constraint"
1089 try renderToken(r, symbolic_name + 3, .none); // lparen
1090 try renderExpression(r, expr, Space.none);
1091 return renderToken(r, rparen, space);
1092}
1093
1094fn renderVarDecl(
1095 r: *Render,
1096 var_decl: Ast.full.VarDecl,
1097 /// Destructures intentionally ignore leading `comptime` tokens.
1098 ignore_comptime_token: bool,
1099 /// `comma_space` and `space` are used for destructure LHS decls.
1100 space: Space,
1101) Error!void {
1102 try renderVarDeclWithoutFixups(r, var_decl, ignore_comptime_token, space);
1103 if (r.fixups.unused_var_decls.contains(var_decl.ast.mut_token + 1)) {
1104 // Discard the variable like this: `_ = foo;`
1105 const ais = r.ais;
1106 try ais.writeAll("_ = ");
1107 try ais.writeAll(tokenSliceForRender(r.tree, var_decl.ast.mut_token + 1));
1108 try ais.writeAll(";\n");
1109 }
1110}
1111
1112fn renderVarDeclWithoutFixups(
1113 r: *Render,
1114 var_decl: Ast.full.VarDecl,
1115 /// Destructures intentionally ignore leading `comptime` tokens.
1116 ignore_comptime_token: bool,
1117 /// `comma_space` and `space` are used for destructure LHS decls.
1118 space: Space,
1119) Error!void {
1120 const tree = r.tree;
1121 const ais = r.ais;
1122
1123 if (var_decl.visib_token) |visib_token| {
1124 try renderToken(r, visib_token, Space.space); // pub
1125 }
1126
1127 if (var_decl.extern_export_token) |extern_export_token| {
1128 try renderToken(r, extern_export_token, Space.space); // extern
1129
1130 if (var_decl.lib_name) |lib_name| {
1131 try renderToken(r, lib_name, Space.space); // "lib"
1132 }
1133 }
1134
1135 if (var_decl.threadlocal_token) |thread_local_token| {
1136 try renderToken(r, thread_local_token, Space.space); // threadlocal
1137 }
1138
1139 if (!ignore_comptime_token) {
1140 if (var_decl.comptime_token) |comptime_token| {
1141 try renderToken(r, comptime_token, Space.space); // comptime
1142 }
1143 }
1144
1145 try renderToken(r, var_decl.ast.mut_token, .space); // var
1146
1147 if (var_decl.ast.type_node != .none or var_decl.ast.align_node != .none or
1148 var_decl.ast.addrspace_node != .none or var_decl.ast.section_node != .none or
1149 var_decl.ast.init_node != .none)
1150 {
1151 const name_space = if (var_decl.ast.type_node == .none and
1152 (var_decl.ast.align_node != .none or
1153 var_decl.ast.addrspace_node != .none or
1154 var_decl.ast.section_node != .none or
1155 var_decl.ast.init_node != .none))
1156 Space.space
1157 else
1158 Space.none;
1159
1160 try renderIdentifier(r, var_decl.ast.mut_token + 1, name_space, .preserve_when_shadowing); // name
1161 } else {
1162 return renderIdentifier(r, var_decl.ast.mut_token + 1, space, .preserve_when_shadowing); // name
1163 }
1164
1165 if (var_decl.ast.type_node.unwrap()) |type_node| {
1166 try renderToken(r, var_decl.ast.mut_token + 2, Space.space); // :
1167 if (var_decl.ast.align_node != .none or var_decl.ast.addrspace_node != .none or
1168 var_decl.ast.section_node != .none or var_decl.ast.init_node != .none)
1169 {
1170 try renderExpression(r, type_node, .space);
1171 } else {
1172 return renderExpression(r, type_node, space);
1173 }
1174 }
1175
1176 if (var_decl.ast.align_node.unwrap()) |align_node| {
1177 const lparen = tree.firstToken(align_node) - 1;
1178 const align_kw = lparen - 1;
1179 const rparen = tree.lastToken(align_node) + 1;
1180 try renderToken(r, align_kw, Space.none); // align
1181 try renderToken(r, lparen, Space.none); // (
1182 try renderExpression(r, align_node, Space.none);
1183 if (var_decl.ast.addrspace_node != .none or var_decl.ast.section_node != .none or
1184 var_decl.ast.init_node != .none)
1185 {
1186 try renderToken(r, rparen, .space); // )
1187 } else {
1188 return renderToken(r, rparen, space); // )
1189 }
1190 }
1191
1192 if (var_decl.ast.addrspace_node.unwrap()) |addrspace_node| {
1193 const lparen = tree.firstToken(addrspace_node) - 1;
1194 const addrspace_kw = lparen - 1;
1195 const rparen = tree.lastToken(addrspace_node) + 1;
1196 try renderToken(r, addrspace_kw, Space.none); // addrspace
1197 try renderToken(r, lparen, Space.none); // (
1198 try renderExpression(r, addrspace_node, Space.none);
1199 if (var_decl.ast.section_node != .none or var_decl.ast.init_node != .none) {
1200 try renderToken(r, rparen, .space); // )
1201 } else {
1202 try renderToken(r, rparen, .none); // )
1203 return renderToken(r, rparen + 1, Space.newline); // ;
1204 }
1205 }
1206
1207 if (var_decl.ast.section_node.unwrap()) |section_node| {
1208 const lparen = tree.firstToken(section_node) - 1;
1209 const section_kw = lparen - 1;
1210 const rparen = tree.lastToken(section_node) + 1;
1211 try renderToken(r, section_kw, Space.none); // linksection
1212 try renderToken(r, lparen, Space.none); // (
1213 try renderExpression(r, section_node, Space.none);
1214 if (var_decl.ast.init_node != .none) {
1215 try renderToken(r, rparen, .space); // )
1216 } else {
1217 return renderToken(r, rparen, space); // )
1218 }
1219 }
1220
1221 const init_node = var_decl.ast.init_node.unwrap().?;
1222
1223 const eq_token = tree.firstToken(init_node) - 1;
1224 const eq_space: Space = if (tree.tokensOnSameLine(eq_token, eq_token + 1)) .space else .newline;
1225 try ais.pushIndent(.after_equals);
1226 try renderToken(r, eq_token, eq_space); // =
1227 try renderExpression(r, init_node, space); // ;
1228 ais.popIndent();
1229}
1230
1231fn renderIf(r: *Render, if_node: Ast.full.If, space: Space) Error!void {
1232 return renderWhile(r, .{
1233 .ast = .{
1234 .while_token = if_node.ast.if_token,
1235 .cond_expr = if_node.ast.cond_expr,
1236 .cont_expr = .none,
1237 .then_expr = if_node.ast.then_expr,
1238 .else_expr = if_node.ast.else_expr,
1239 },
1240 .inline_token = null,
1241 .label_token = null,
1242 .payload_token = if_node.payload_token,
1243 .else_token = if_node.else_token,
1244 .error_token = if_node.error_token,
1245 }, space);
1246}
1247
1248/// Note that this function is additionally used to render if expressions, with
1249/// respective values set to null.
1250fn renderWhile(r: *Render, while_node: Ast.full.While, space: Space) Error!void {
1251 const tree = r.tree;
1252
1253 if (while_node.label_token) |label| {
1254 try renderIdentifier(r, label, .none, .eagerly_unquote); // label
1255 try renderToken(r, label + 1, .space); // :
1256 }
1257
1258 if (while_node.inline_token) |inline_token| {
1259 try renderToken(r, inline_token, .space); // inline
1260 }
1261
1262 try renderToken(r, while_node.ast.while_token, .space); // if/for/while
1263 try renderToken(r, while_node.ast.while_token + 1, .none); // lparen
1264 try renderExpression(r, while_node.ast.cond_expr, .none); // condition
1265
1266 var last_prefix_token = tree.lastToken(while_node.ast.cond_expr) + 1; // rparen
1267
1268 if (while_node.payload_token) |payload_token| {
1269 try renderToken(r, last_prefix_token, .space);
1270 try renderToken(r, payload_token - 1, .none); // |
1271 const ident = blk: {
1272 if (tree.tokenTag(payload_token) == .asterisk) {
1273 try renderToken(r, payload_token, .none); // *
1274 break :blk payload_token + 1;
1275 } else {
1276 break :blk payload_token;
1277 }
1278 };
1279 try renderIdentifier(r, ident, .none, .preserve_when_shadowing); // identifier
1280 const pipe = blk: {
1281 if (tree.tokenTag(ident + 1) == .comma) {
1282 try renderToken(r, ident + 1, .space); // ,
1283 try renderIdentifier(r, ident + 2, .none, .preserve_when_shadowing); // index
1284 break :blk ident + 3;
1285 } else {
1286 break :blk ident + 1;
1287 }
1288 };
1289 last_prefix_token = pipe;
1290 }
1291
1292 if (while_node.ast.cont_expr.unwrap()) |cont_expr| {
1293 try renderToken(r, last_prefix_token, .space);
1294 const lparen = tree.firstToken(cont_expr) - 1;
1295 try renderToken(r, lparen - 1, .space); // :
1296 try renderToken(r, lparen, .none); // lparen
1297 try renderExpression(r, cont_expr, .none);
1298 last_prefix_token = tree.lastToken(cont_expr) + 1; // rparen
1299 }
1300
1301 try renderThenElse(
1302 r,
1303 last_prefix_token,
1304 while_node.ast.then_expr,
1305 while_node.else_token,
1306 while_node.error_token,
1307 while_node.ast.else_expr,
1308 space,
1309 );
1310}
1311
1312fn renderThenElse(
1313 r: *Render,
1314 last_prefix_token: Ast.TokenIndex,
1315 then_expr: Ast.Node.Index,
1316 else_token: ?Ast.TokenIndex,
1317 maybe_error_token: ?Ast.TokenIndex,
1318 opt_else_expr: Ast.Node.OptionalIndex,
1319 space: Space,
1320) Error!void {
1321 const tree = r.tree;
1322 const ais = r.ais;
1323 const then_expr_is_block = nodeIsBlock(tree.nodeTag(then_expr));
1324 const indent_then_expr = !then_expr_is_block and
1325 !tree.tokensOnSameLine(last_prefix_token, tree.firstToken(then_expr));
1326
1327 if (indent_then_expr) try ais.pushIndent(.normal);
1328
1329 if (then_expr_is_block and ais.isLineOverIndented()) {
1330 ais.disableIndentCommitting();
1331 try renderToken(r, last_prefix_token, .newline);
1332 ais.enableIndentCommitting();
1333 } else if (indent_then_expr) {
1334 try renderToken(r, last_prefix_token, .newline);
1335 } else {
1336 try renderToken(r, last_prefix_token, .space);
1337 }
1338
1339 if (opt_else_expr.unwrap()) |else_expr| {
1340 if (indent_then_expr) {
1341 try renderExpression(r, then_expr, .newline);
1342 } else {
1343 try renderExpression(r, then_expr, .space);
1344 }
1345
1346 if (indent_then_expr) ais.popIndent();
1347
1348 var last_else_token = else_token.?;
1349
1350 if (maybe_error_token) |error_token| {
1351 try renderToken(r, last_else_token, .space); // else
1352 try renderToken(r, error_token - 1, .none); // |
1353 try renderIdentifier(r, error_token, .none, .preserve_when_shadowing); // identifier
1354 last_else_token = error_token + 1; // |
1355 }
1356
1357 const indent_else_expr = indent_then_expr and
1358 !nodeIsBlock(tree.nodeTag(else_expr)) and
1359 !nodeIsIfForWhileSwitch(tree.nodeTag(else_expr));
1360 if (indent_else_expr) {
1361 try ais.pushIndent(.normal);
1362 try renderToken(r, last_else_token, .newline);
1363 try renderExpression(r, else_expr, space);
1364 ais.popIndent();
1365 } else {
1366 try renderToken(r, last_else_token, .space);
1367 try renderExpression(r, else_expr, space);
1368 }
1369 } else {
1370 try renderExpression(r, then_expr, space);
1371 if (indent_then_expr) ais.popIndent();
1372 }
1373}
1374
1375fn renderFor(r: *Render, for_node: Ast.full.For, space: Space) Error!void {
1376 const tree = r.tree;
1377 const ais = r.ais;
1378 const token_tags = tree.tokens.items(.tag);
1379
1380 if (for_node.label_token) |label| {
1381 try renderIdentifier(r, label, .none, .eagerly_unquote); // label
1382 try renderToken(r, label + 1, .space); // :
1383 }
1384
1385 if (for_node.inline_token) |inline_token| {
1386 try renderToken(r, inline_token, .space); // inline
1387 }
1388
1389 try renderToken(r, for_node.ast.for_token, .space); // if/for/while
1390
1391 const lparen = for_node.ast.for_token + 1;
1392 try renderParamList(r, lparen, for_node.ast.inputs, .space);
1393
1394 var cur = for_node.payload_token;
1395 const pipe = std.mem.indexOfScalarPos(std.zig.Token.Tag, token_tags, cur, .pipe).?;
1396 if (tree.tokenTag(@intCast(pipe - 1)) == .comma) {
1397 try ais.pushIndent(.normal);
1398 try renderToken(r, cur - 1, .newline); // |
1399 while (true) {
1400 if (tree.tokenTag(cur) == .asterisk) {
1401 try renderToken(r, cur, .none); // *
1402 cur += 1;
1403 }
1404 try renderIdentifier(r, cur, .none, .preserve_when_shadowing); // identifier
1405 cur += 1;
1406 if (tree.tokenTag(cur) == .comma) {
1407 try renderToken(r, cur, .newline); // ,
1408 cur += 1;
1409 }
1410 if (tree.tokenTag(cur) == .pipe) {
1411 break;
1412 }
1413 }
1414 ais.popIndent();
1415 } else {
1416 try renderToken(r, cur - 1, .none); // |
1417 while (true) {
1418 if (tree.tokenTag(cur) == .asterisk) {
1419 try renderToken(r, cur, .none); // *
1420 cur += 1;
1421 }
1422 try renderIdentifier(r, cur, .none, .preserve_when_shadowing); // identifier
1423 cur += 1;
1424 if (tree.tokenTag(cur) == .comma) {
1425 try renderToken(r, cur, .space); // ,
1426 cur += 1;
1427 }
1428 if (tree.tokenTag(cur) == .pipe) {
1429 break;
1430 }
1431 }
1432 }
1433
1434 try renderThenElse(
1435 r,
1436 cur,
1437 for_node.ast.then_expr,
1438 for_node.else_token,
1439 null,
1440 for_node.ast.else_expr,
1441 space,
1442 );
1443}
1444
1445fn renderContainerField(
1446 r: *Render,
1447 container: Container,
1448 field_param: Ast.full.ContainerField,
1449 space: Space,
1450) Error!void {
1451 const tree = r.tree;
1452 const ais = r.ais;
1453 var field = field_param;
1454 if (container != .tuple) field.convertToNonTupleLike(&tree);
1455 const quote: QuoteBehavior = switch (container) {
1456 .@"enum" => .eagerly_unquote_except_underscore,
1457 .tuple, .other => .eagerly_unquote,
1458 };
1459
1460 if (field.comptime_token) |t| {
1461 try renderToken(r, t, .space); // comptime
1462 }
1463 if (field.ast.type_expr == .none and field.ast.value_expr == .none) {
1464 if (field.ast.align_expr.unwrap()) |align_expr| {
1465 try renderIdentifier(r, field.ast.main_token, .space, quote); // name
1466 const lparen_token = tree.firstToken(align_expr) - 1;
1467 const align_kw = lparen_token - 1;
1468 const rparen_token = tree.lastToken(align_expr) + 1;
1469 try renderToken(r, align_kw, .none); // align
1470 try renderToken(r, lparen_token, .none); // (
1471 try renderExpression(r, align_expr, .none); // alignment
1472 return renderToken(r, rparen_token, .space); // )
1473 }
1474 return renderIdentifierComma(r, field.ast.main_token, space, quote); // name
1475 }
1476 if (field.ast.type_expr != .none and field.ast.value_expr == .none) {
1477 const type_expr = field.ast.type_expr.unwrap().?;
1478 if (!field.ast.tuple_like) {
1479 try renderIdentifier(r, field.ast.main_token, .none, quote); // name
1480 try renderToken(r, field.ast.main_token + 1, .space); // :
1481 }
1482
1483 if (field.ast.align_expr.unwrap()) |align_expr| {
1484 try renderExpression(r, type_expr, .space); // type
1485 const align_token = tree.firstToken(align_expr) - 2;
1486 try renderToken(r, align_token, .none); // align
1487 try renderToken(r, align_token + 1, .none); // (
1488 try renderExpression(r, align_expr, .none); // alignment
1489 const rparen = tree.lastToken(align_expr) + 1;
1490 return renderTokenComma(r, rparen, space); // )
1491 } else {
1492 return renderExpressionComma(r, type_expr, space); // type
1493 }
1494 }
1495 if (field.ast.type_expr == .none and field.ast.value_expr != .none) {
1496 const value_expr = field.ast.value_expr.unwrap().?;
1497
1498 try renderIdentifier(r, field.ast.main_token, .space, quote); // name
1499 if (field.ast.align_expr.unwrap()) |align_expr| {
1500 const lparen_token = tree.firstToken(align_expr) - 1;
1501 const align_kw = lparen_token - 1;
1502 const rparen_token = tree.lastToken(align_expr) + 1;
1503 try renderToken(r, align_kw, .none); // align
1504 try renderToken(r, lparen_token, .none); // (
1505 try renderExpression(r, align_expr, .none); // alignment
1506 try renderToken(r, rparen_token, .space); // )
1507 }
1508 try renderToken(r, field.ast.main_token + 1, .space); // =
1509 return renderExpressionComma(r, value_expr, space); // value
1510 }
1511 if (!field.ast.tuple_like) {
1512 try renderIdentifier(r, field.ast.main_token, .none, quote); // name
1513 try renderToken(r, field.ast.main_token + 1, .space); // :
1514 }
1515
1516 const type_expr = field.ast.type_expr.unwrap().?;
1517 const value_expr = field.ast.value_expr.unwrap().?;
1518
1519 try renderExpression(r, type_expr, .space); // type
1520
1521 if (field.ast.align_expr.unwrap()) |align_expr| {
1522 const lparen_token = tree.firstToken(align_expr) - 1;
1523 const align_kw = lparen_token - 1;
1524 const rparen_token = tree.lastToken(align_expr) + 1;
1525 try renderToken(r, align_kw, .none); // align
1526 try renderToken(r, lparen_token, .none); // (
1527 try renderExpression(r, align_expr, .none); // alignment
1528 try renderToken(r, rparen_token, .space); // )
1529 }
1530 const eq_token = tree.firstToken(value_expr) - 1;
1531 const eq_space: Space = if (tree.tokensOnSameLine(eq_token, eq_token + 1)) .space else .newline;
1532
1533 try ais.pushIndent(.after_equals);
1534 try renderToken(r, eq_token, eq_space); // =
1535
1536 if (eq_space == .space) {
1537 ais.popIndent();
1538 try renderExpressionComma(r, value_expr, space); // value
1539 return;
1540 }
1541
1542 const maybe_comma = tree.lastToken(value_expr) + 1;
1543
1544 if (tree.tokenTag(maybe_comma) == .comma) {
1545 try renderExpression(r, value_expr, .none); // value
1546 ais.popIndent();
1547 try renderToken(r, maybe_comma, .newline);
1548 } else {
1549 try renderExpression(r, value_expr, space); // value
1550 ais.popIndent();
1551 }
1552}
1553
1554fn renderBuiltinCall(
1555 r: *Render,
1556 builtin_token: Ast.TokenIndex,
1557 params: []const Ast.Node.Index,
1558 space: Space,
1559) Error!void {
1560 const tree = r.tree;
1561 const ais = r.ais;
1562
1563 try renderToken(r, builtin_token, .none); // @name
1564
1565 if (params.len == 0) {
1566 try renderToken(r, builtin_token + 1, .none); // (
1567 return renderToken(r, builtin_token + 2, space); // )
1568 }
1569
1570 if (r.fixups.rebase_imported_paths) |prefix| {
1571 const slice = tree.tokenSlice(builtin_token);
1572 if (mem.eql(u8, slice, "@import")) f: {
1573 const param = params[0];
1574 const str_lit_token = tree.nodeMainToken(param);
1575 assert(tree.tokenTag(str_lit_token) == .string_literal);
1576 const token_bytes = tree.tokenSlice(str_lit_token);
1577 const imported_string = std.zig.string_literal.parseAlloc(r.gpa, token_bytes) catch |err| switch (err) {
1578 error.OutOfMemory => return error.OutOfMemory,
1579 error.InvalidLiteral => break :f,
1580 };
1581 defer r.gpa.free(imported_string);
1582 const new_string = try std.fs.path.resolvePosix(r.gpa, &.{ prefix, imported_string });
1583 defer r.gpa.free(new_string);
1584
1585 try renderToken(r, builtin_token + 1, .none); // (
1586 try ais.print("\"{f}\"", .{std.zig.fmtEscapes(new_string)});
1587 return renderToken(r, str_lit_token + 1, space); // )
1588 }
1589 }
1590
1591 const last_param = params[params.len - 1];
1592 const after_last_param_token = tree.lastToken(last_param) + 1;
1593
1594 if (tree.tokenTag(after_last_param_token) != .comma) {
1595 // Render all on one line, no trailing comma.
1596 try renderToken(r, builtin_token + 1, .none); // (
1597
1598 for (params, 0..) |param_node, i| {
1599 const first_param_token = tree.firstToken(param_node);
1600 if (tree.tokenTag(first_param_token) == .multiline_string_literal_line or
1601 hasSameLineComment(tree, first_param_token - 1))
1602 {
1603 try ais.pushIndent(.normal);
1604 try renderExpression(r, param_node, .none);
1605 ais.popIndent();
1606 } else {
1607 try renderExpression(r, param_node, .none);
1608 }
1609
1610 if (i + 1 < params.len) {
1611 const comma_token = tree.lastToken(param_node) + 1;
1612 try renderToken(r, comma_token, .space); // ,
1613 }
1614 }
1615 return renderToken(r, after_last_param_token, space); // )
1616 } else {
1617 // Render one param per line.
1618 try ais.pushIndent(.normal);
1619 try renderToken(r, builtin_token + 1, Space.newline); // (
1620
1621 for (params) |param_node| {
1622 try ais.pushSpace(.comma);
1623 try renderExpression(r, param_node, .comma);
1624 ais.popSpace();
1625 }
1626 ais.popIndent();
1627
1628 return renderToken(r, after_last_param_token + 1, space); // )
1629 }
1630}
1631
1632fn renderFnProto(r: *Render, fn_proto: Ast.full.FnProto, space: Space) Error!void {
1633 const tree = r.tree;
1634 const ais = r.ais;
1635
1636 const after_fn_token = fn_proto.ast.fn_token + 1;
1637 const lparen = if (tree.tokenTag(after_fn_token) == .identifier) blk: {
1638 try renderToken(r, fn_proto.ast.fn_token, .space); // fn
1639 try renderIdentifier(r, after_fn_token, .none, .preserve_when_shadowing); // name
1640 break :blk after_fn_token + 1;
1641 } else blk: {
1642 try renderToken(r, fn_proto.ast.fn_token, .space); // fn
1643 break :blk fn_proto.ast.fn_token + 1;
1644 };
1645 assert(tree.tokenTag(lparen) == .l_paren);
1646
1647 const return_type = fn_proto.ast.return_type.unwrap().?;
1648 const maybe_bang = tree.firstToken(return_type) - 1;
1649 const rparen = blk: {
1650 // These may appear in any order, so we have to check the token_starts array
1651 // to find out which is first.
1652 var rparen = if (tree.tokenTag(maybe_bang) == .bang) maybe_bang - 1 else maybe_bang;
1653 var smallest_start = tree.tokenStart(maybe_bang);
1654 if (fn_proto.ast.align_expr.unwrap()) |align_expr| {
1655 const tok = tree.firstToken(align_expr) - 3;
1656 const start = tree.tokenStart(tok);
1657 if (start < smallest_start) {
1658 rparen = tok;
1659 smallest_start = start;
1660 }
1661 }
1662 if (fn_proto.ast.addrspace_expr.unwrap()) |addrspace_expr| {
1663 const tok = tree.firstToken(addrspace_expr) - 3;
1664 const start = tree.tokenStart(tok);
1665 if (start < smallest_start) {
1666 rparen = tok;
1667 smallest_start = start;
1668 }
1669 }
1670 if (fn_proto.ast.section_expr.unwrap()) |section_expr| {
1671 const tok = tree.firstToken(section_expr) - 3;
1672 const start = tree.tokenStart(tok);
1673 if (start < smallest_start) {
1674 rparen = tok;
1675 smallest_start = start;
1676 }
1677 }
1678 if (fn_proto.ast.callconv_expr.unwrap()) |callconv_expr| {
1679 const tok = tree.firstToken(callconv_expr) - 3;
1680 const start = tree.tokenStart(tok);
1681 if (start < smallest_start) {
1682 rparen = tok;
1683 smallest_start = start;
1684 }
1685 }
1686 break :blk rparen;
1687 };
1688 assert(tree.tokenTag(rparen) == .r_paren);
1689
1690 // The params list is a sparse set that does *not* include anytype or ... parameters.
1691
1692 const trailing_comma = tree.tokenTag(rparen - 1) == .comma;
1693 if (!trailing_comma and !hasComment(tree, lparen, rparen)) {
1694 // Render all on one line, no trailing comma.
1695 try renderToken(r, lparen, .none); // (
1696
1697 var param_i: usize = 0;
1698 var last_param_token = lparen;
1699 while (true) {
1700 last_param_token += 1;
1701 switch (tree.tokenTag(last_param_token)) {
1702 .doc_comment => {
1703 try renderToken(r, last_param_token, .newline);
1704 continue;
1705 },
1706 .ellipsis3 => {
1707 try renderToken(r, last_param_token, .none); // ...
1708 break;
1709 },
1710 .keyword_noalias, .keyword_comptime => {
1711 try renderToken(r, last_param_token, .space);
1712 last_param_token += 1;
1713 },
1714 .identifier => {},
1715 .keyword_anytype => {
1716 try renderToken(r, last_param_token, .none); // anytype
1717 continue;
1718 },
1719 .r_paren => break,
1720 .comma => {
1721 try renderToken(r, last_param_token, .space); // ,
1722 continue;
1723 },
1724 else => {}, // Parameter type without a name.
1725 }
1726 if (tree.tokenTag(last_param_token) == .identifier and
1727 tree.tokenTag(last_param_token + 1) == .colon)
1728 {
1729 try renderIdentifier(r, last_param_token, .none, .preserve_when_shadowing); // name
1730 last_param_token = last_param_token + 1;
1731 try renderToken(r, last_param_token, .space); // :
1732 last_param_token += 1;
1733 }
1734 if (tree.tokenTag(last_param_token) == .keyword_anytype) {
1735 try renderToken(r, last_param_token, .none); // anytype
1736 continue;
1737 }
1738 const param = fn_proto.ast.params[param_i];
1739 param_i += 1;
1740 try renderExpression(r, param, .none);
1741 last_param_token = tree.lastToken(param);
1742 }
1743 } else {
1744 // One param per line.
1745 try ais.pushIndent(.normal);
1746 try renderToken(r, lparen, .newline); // (
1747
1748 var param_i: usize = 0;
1749 var last_param_token = lparen;
1750 while (true) {
1751 last_param_token += 1;
1752 switch (tree.tokenTag(last_param_token)) {
1753 .doc_comment => {
1754 try renderToken(r, last_param_token, .newline);
1755 continue;
1756 },
1757 .ellipsis3 => {
1758 try renderToken(r, last_param_token, .comma); // ...
1759 break;
1760 },
1761 .keyword_noalias, .keyword_comptime => {
1762 try renderToken(r, last_param_token, .space);
1763 last_param_token += 1;
1764 },
1765 .identifier => {},
1766 .keyword_anytype => {
1767 try renderToken(r, last_param_token, .comma); // anytype
1768 if (tree.tokenTag(last_param_token + 1) == .comma)
1769 last_param_token += 1;
1770 continue;
1771 },
1772 .r_paren => break,
1773 else => {}, // Parameter type without a name.
1774 }
1775 if (tree.tokenTag(last_param_token) == .identifier and
1776 tree.tokenTag(last_param_token + 1) == .colon)
1777 {
1778 try renderIdentifier(r, last_param_token, .none, .preserve_when_shadowing); // name
1779 last_param_token += 1;
1780 try renderToken(r, last_param_token, .space); // :
1781 last_param_token += 1;
1782 }
1783 if (tree.tokenTag(last_param_token) == .keyword_anytype) {
1784 try renderToken(r, last_param_token, .comma); // anytype
1785 if (tree.tokenTag(last_param_token + 1) == .comma)
1786 last_param_token += 1;
1787 continue;
1788 }
1789 const param = fn_proto.ast.params[param_i];
1790 param_i += 1;
1791 try ais.pushSpace(.comma);
1792 try renderExpression(r, param, .comma);
1793 ais.popSpace();
1794 last_param_token = tree.lastToken(param);
1795 if (tree.tokenTag(last_param_token + 1) == .comma) last_param_token += 1;
1796 }
1797 ais.popIndent();
1798 }
1799
1800 try renderToken(r, rparen, .space); // )
1801
1802 if (fn_proto.ast.align_expr.unwrap()) |align_expr| {
1803 const align_lparen = tree.firstToken(align_expr) - 1;
1804 const align_rparen = tree.lastToken(align_expr) + 1;
1805
1806 try renderToken(r, align_lparen - 1, .none); // align
1807 try renderToken(r, align_lparen, .none); // (
1808 try renderExpression(r, align_expr, .none);
1809 try renderToken(r, align_rparen, .space); // )
1810 }
1811
1812 if (fn_proto.ast.addrspace_expr.unwrap()) |addrspace_expr| {
1813 const align_lparen = tree.firstToken(addrspace_expr) - 1;
1814 const align_rparen = tree.lastToken(addrspace_expr) + 1;
1815
1816 try renderToken(r, align_lparen - 1, .none); // addrspace
1817 try renderToken(r, align_lparen, .none); // (
1818 try renderExpression(r, addrspace_expr, .none);
1819 try renderToken(r, align_rparen, .space); // )
1820 }
1821
1822 if (fn_proto.ast.section_expr.unwrap()) |section_expr| {
1823 const section_lparen = tree.firstToken(section_expr) - 1;
1824 const section_rparen = tree.lastToken(section_expr) + 1;
1825
1826 try renderToken(r, section_lparen - 1, .none); // section
1827 try renderToken(r, section_lparen, .none); // (
1828 try renderExpression(r, section_expr, .none);
1829 try renderToken(r, section_rparen, .space); // )
1830 }
1831
1832 if (fn_proto.ast.callconv_expr.unwrap()) |callconv_expr| {
1833 // Keep in sync with logic in `renderMember`. Search this file for the marker PROMOTE_CALLCONV_INLINE
1834 const is_callconv_inline = mem.eql(u8, "@\"inline\"", tree.tokenSlice(tree.nodeMainToken(callconv_expr)));
1835 const is_declaration = fn_proto.name_token != null;
1836 if (!(is_declaration and is_callconv_inline)) {
1837 const callconv_lparen = tree.firstToken(callconv_expr) - 1;
1838 const callconv_rparen = tree.lastToken(callconv_expr) + 1;
1839
1840 try renderToken(r, callconv_lparen - 1, .none); // callconv
1841 try renderToken(r, callconv_lparen, .none); // (
1842 try renderExpression(r, callconv_expr, .none);
1843 try renderToken(r, callconv_rparen, .space); // )
1844 }
1845 }
1846
1847 if (tree.tokenTag(maybe_bang) == .bang) {
1848 try renderToken(r, maybe_bang, .none); // !
1849 }
1850 return renderExpression(r, return_type, space);
1851}
1852
1853fn renderSwitchCase(
1854 r: *Render,
1855 switch_case: Ast.full.SwitchCase,
1856 space: Space,
1857) Error!void {
1858 const ais = r.ais;
1859 const tree = r.tree;
1860 const trailing_comma = tree.tokenTag(switch_case.ast.arrow_token - 1) == .comma;
1861 const has_comment_before_arrow = blk: {
1862 if (switch_case.ast.values.len == 0) break :blk false;
1863 break :blk hasComment(tree, tree.firstToken(switch_case.ast.values[0]), switch_case.ast.arrow_token);
1864 };
1865
1866 // render inline keyword
1867 if (switch_case.inline_token) |some| {
1868 try renderToken(r, some, .space);
1869 }
1870
1871 // Render everything before the arrow
1872 if (switch_case.ast.values.len == 0) {
1873 try renderToken(r, switch_case.ast.arrow_token - 1, .space); // else keyword
1874 } else if (trailing_comma or has_comment_before_arrow) {
1875 // Render each value on a new line
1876 try ais.pushSpace(.comma);
1877 try renderExpressions(r, switch_case.ast.values, .comma);
1878 ais.popSpace();
1879 } else {
1880 // Render on one line
1881 for (switch_case.ast.values) |value_expr| {
1882 try renderExpression(r, value_expr, .comma_space);
1883 }
1884 }
1885
1886 // Render the arrow and everything after it
1887 const pre_target_space = if (tree.nodeTag(switch_case.ast.target_expr) == .multiline_string_literal)
1888 // Newline gets inserted when rendering the target expr.
1889 Space.none
1890 else
1891 Space.space;
1892 const after_arrow_space: Space = if (switch_case.payload_token == null) pre_target_space else .space;
1893 try renderToken(r, switch_case.ast.arrow_token, after_arrow_space); // =>
1894
1895 if (switch_case.payload_token) |payload_token| {
1896 try renderToken(r, payload_token - 1, .none); // pipe
1897 const ident = payload_token + @intFromBool(tree.tokenTag(payload_token) == .asterisk);
1898 if (tree.tokenTag(payload_token) == .asterisk) {
1899 try renderToken(r, payload_token, .none); // asterisk
1900 }
1901 try renderIdentifier(r, ident, .none, .preserve_when_shadowing); // identifier
1902 if (tree.tokenTag(ident + 1) == .comma) {
1903 try renderToken(r, ident + 1, .space); // ,
1904 try renderIdentifier(r, ident + 2, .none, .preserve_when_shadowing); // identifier
1905 try renderToken(r, ident + 3, pre_target_space); // pipe
1906 } else {
1907 try renderToken(r, ident + 1, pre_target_space); // pipe
1908 }
1909 }
1910
1911 try renderExpression(r, switch_case.ast.target_expr, space);
1912}
1913
1914fn renderBlock(
1915 r: *Render,
1916 block_node: Ast.Node.Index,
1917 statements: []const Ast.Node.Index,
1918 space: Space,
1919) Error!void {
1920 const tree = r.tree;
1921 const ais = r.ais;
1922 const lbrace = tree.nodeMainToken(block_node);
1923
1924 if (tree.isTokenPrecededByTags(lbrace, &.{ .identifier, .colon })) {
1925 try renderIdentifier(r, lbrace - 2, .none, .eagerly_unquote); // identifier
1926 try renderToken(r, lbrace - 1, .space); // :
1927 }
1928 try ais.pushIndent(.normal);
1929 if (statements.len == 0) {
1930 try renderToken(r, lbrace, .none);
1931 ais.popIndent();
1932 try renderToken(r, tree.lastToken(block_node), space); // rbrace
1933 return;
1934 }
1935 try renderToken(r, lbrace, .newline);
1936 return finishRenderBlock(r, block_node, statements, space);
1937}
1938
1939fn finishRenderBlock(
1940 r: *Render,
1941 block_node: Ast.Node.Index,
1942 statements: []const Ast.Node.Index,
1943 space: Space,
1944) Error!void {
1945 const tree = r.tree;
1946 const ais = r.ais;
1947 for (statements, 0..) |stmt, i| {
1948 if (i != 0) try renderExtraNewline(r, stmt);
1949 if (r.fixups.omit_nodes.contains(stmt)) continue;
1950 try ais.pushSpace(.semicolon);
1951 switch (tree.nodeTag(stmt)) {
1952 .global_var_decl,
1953 .local_var_decl,
1954 .simple_var_decl,
1955 .aligned_var_decl,
1956 => try renderVarDecl(r, tree.fullVarDecl(stmt).?, false, .semicolon),
1957
1958 else => try renderExpression(r, stmt, .semicolon),
1959 }
1960 ais.popSpace();
1961 }
1962 ais.popIndent();
1963
1964 try renderToken(r, tree.lastToken(block_node), space); // rbrace
1965}
1966
1967fn renderStructInit(
1968 r: *Render,
1969 struct_node: Ast.Node.Index,
1970 struct_init: Ast.full.StructInit,
1971 space: Space,
1972) Error!void {
1973 const tree = r.tree;
1974 const ais = r.ais;
1975
1976 if (struct_init.ast.type_expr.unwrap()) |type_expr| {
1977 try renderExpression(r, type_expr, .none); // T
1978 } else {
1979 try renderToken(r, struct_init.ast.lbrace - 1, .none); // .
1980 }
1981
1982 if (struct_init.ast.fields.len == 0) {
1983 try ais.pushIndent(.normal);
1984 try renderToken(r, struct_init.ast.lbrace, .none); // lbrace
1985 ais.popIndent();
1986 return renderToken(r, struct_init.ast.lbrace + 1, space); // rbrace
1987 }
1988
1989 const rbrace = tree.lastToken(struct_node);
1990 const trailing_comma = tree.tokenTag(rbrace - 1) == .comma;
1991 if (trailing_comma or hasComment(tree, struct_init.ast.lbrace, rbrace)) {
1992 // Render one field init per line.
1993 try ais.pushIndent(.normal);
1994 try renderToken(r, struct_init.ast.lbrace, .newline);
1995
1996 try renderToken(r, struct_init.ast.lbrace + 1, .none); // .
1997 try renderIdentifier(r, struct_init.ast.lbrace + 2, .space, .eagerly_unquote); // name
1998 // Don't output a space after the = if expression is a multiline string,
1999 // since then it will start on the next line.
2000 const field_node = struct_init.ast.fields[0];
2001 const expr = tree.nodeTag(field_node);
2002 var space_after_equal: Space = if (expr == .multiline_string_literal) .none else .space;
2003 try renderToken(r, struct_init.ast.lbrace + 3, space_after_equal); // =
2004
2005 try ais.pushSpace(.comma);
2006 try renderExpressionFixup(r, field_node, .comma);
2007 ais.popSpace();
2008
2009 for (struct_init.ast.fields[1..]) |field_init| {
2010 const init_token = tree.firstToken(field_init);
2011 try renderExtraNewlineToken(r, init_token - 3);
2012 try renderToken(r, init_token - 3, .none); // .
2013 try renderIdentifier(r, init_token - 2, .space, .eagerly_unquote); // name
2014 space_after_equal = if (tree.nodeTag(field_init) == .multiline_string_literal) .none else .space;
2015 try renderToken(r, init_token - 1, space_after_equal); // =
2016
2017 try ais.pushSpace(.comma);
2018 try renderExpressionFixup(r, field_init, .comma);
2019 ais.popSpace();
2020 }
2021
2022 ais.popIndent();
2023 } else {
2024 // Render all on one line, no trailing comma.
2025 try renderToken(r, struct_init.ast.lbrace, .space);
2026
2027 for (struct_init.ast.fields) |field_init| {
2028 const init_token = tree.firstToken(field_init);
2029 try renderToken(r, init_token - 3, .none); // .
2030 try renderIdentifier(r, init_token - 2, .space, .eagerly_unquote); // name
2031 try renderToken(r, init_token - 1, .space); // =
2032 try renderExpressionFixup(r, field_init, .comma_space);
2033 }
2034 }
2035
2036 return renderToken(r, rbrace, space);
2037}
2038
2039fn renderArrayInit(
2040 r: *Render,
2041 array_init: Ast.full.ArrayInit,
2042 space: Space,
2043) Error!void {
2044 const tree = r.tree;
2045 const ais = r.ais;
2046 const gpa = r.gpa;
2047
2048 if (array_init.ast.type_expr.unwrap()) |type_expr| {
2049 try renderExpression(r, type_expr, .none); // T
2050 } else {
2051 try renderToken(r, array_init.ast.lbrace - 1, .none); // .
2052 }
2053
2054 if (array_init.ast.elements.len == 0) {
2055 try ais.pushIndent(.normal);
2056 try renderToken(r, array_init.ast.lbrace, .none); // lbrace
2057 ais.popIndent();
2058 return renderToken(r, array_init.ast.lbrace + 1, space); // rbrace
2059 }
2060
2061 const last_elem = array_init.ast.elements[array_init.ast.elements.len - 1];
2062 const last_elem_token = tree.lastToken(last_elem);
2063 const trailing_comma = tree.tokenTag(last_elem_token + 1) == .comma;
2064 const rbrace = if (trailing_comma) last_elem_token + 2 else last_elem_token + 1;
2065 assert(tree.tokenTag(rbrace) == .r_brace);
2066
2067 if (array_init.ast.elements.len == 1) {
2068 const only_elem = array_init.ast.elements[0];
2069 const first_token = tree.firstToken(only_elem);
2070 if (tree.tokenTag(first_token) != .multiline_string_literal_line and
2071 !anythingBetween(tree, last_elem_token, rbrace))
2072 {
2073 try renderToken(r, array_init.ast.lbrace, .none);
2074 try renderExpression(r, only_elem, .none);
2075 return renderToken(r, rbrace, space);
2076 }
2077 }
2078
2079 const contains_comment = hasComment(tree, array_init.ast.lbrace, rbrace);
2080 const contains_multiline_string = hasMultilineString(tree, array_init.ast.lbrace, rbrace);
2081
2082 if (!trailing_comma and !contains_comment and !contains_multiline_string) {
2083 // Render all on one line, no trailing comma.
2084 if (array_init.ast.elements.len == 1) {
2085 // If there is only one element, we don't use spaces
2086 try renderToken(r, array_init.ast.lbrace, .none);
2087 try renderExpression(r, array_init.ast.elements[0], .none);
2088 } else {
2089 try renderToken(r, array_init.ast.lbrace, .space);
2090 for (array_init.ast.elements) |elem| {
2091 try renderExpression(r, elem, .comma_space);
2092 }
2093 }
2094 return renderToken(r, last_elem_token + 1, space); // rbrace
2095 }
2096
2097 try ais.pushIndent(.normal);
2098 try renderToken(r, array_init.ast.lbrace, .newline);
2099
2100 var expr_index: usize = 0;
2101 while (true) {
2102 const row_size = rowSize(tree, array_init.ast.elements[expr_index..], rbrace);
2103 const row_exprs = array_init.ast.elements[expr_index..];
2104 // A place to store the width of each expression and its column's maximum
2105 const widths = try gpa.alloc(usize, row_exprs.len + row_size);
2106 defer gpa.free(widths);
2107 @memset(widths, 0);
2108
2109 const expr_newlines = try gpa.alloc(bool, row_exprs.len);
2110 defer gpa.free(expr_newlines);
2111 @memset(expr_newlines, false);
2112
2113 const expr_widths = widths[0..row_exprs.len];
2114 const column_widths = widths[row_exprs.len..];
2115
2116 // Find next row with trailing comment (if any) to end the current section.
2117 const section_end = sec_end: {
2118 var this_line_first_expr: usize = 0;
2119 var this_line_size = rowSize(tree, row_exprs, rbrace);
2120 for (row_exprs, 0..) |expr, i| {
2121 // Ignore comment on first line of this section.
2122 if (i == 0) continue;
2123 const expr_last_token = tree.lastToken(expr);
2124 if (tree.tokensOnSameLine(tree.firstToken(row_exprs[0]), expr_last_token))
2125 continue;
2126 // Track start of line containing comment.
2127 if (!tree.tokensOnSameLine(tree.firstToken(row_exprs[this_line_first_expr]), expr_last_token)) {
2128 this_line_first_expr = i;
2129 this_line_size = rowSize(tree, row_exprs[this_line_first_expr..], rbrace);
2130 }
2131
2132 const maybe_comma = expr_last_token + 1;
2133 if (tree.tokenTag(maybe_comma) == .comma) {
2134 if (hasSameLineComment(tree, maybe_comma))
2135 break :sec_end i - this_line_size + 1;
2136 }
2137 }
2138 break :sec_end row_exprs.len;
2139 };
2140 expr_index += section_end;
2141
2142 const section_exprs = row_exprs[0..section_end];
2143
2144 var sub_expr_buffer: std.io.AllocatingWriter = undefined;
2145 sub_expr_buffer.init(gpa);
2146 defer sub_expr_buffer.deinit();
2147
2148 const sub_expr_buffer_starts = try gpa.alloc(usize, section_exprs.len + 1);
2149 defer gpa.free(sub_expr_buffer_starts);
2150
2151 var auto_indenting_stream: AutoIndentingStream = .init(gpa, &sub_expr_buffer.buffered_writer, indent_delta);
2152 defer auto_indenting_stream.deinit();
2153 var sub_render: Render = .{
2154 .gpa = r.gpa,
2155 .ais = &auto_indenting_stream,
2156 .tree = r.tree,
2157 .fixups = r.fixups,
2158 };
2159
2160 // Calculate size of columns in current section
2161 var column_counter: usize = 0;
2162 var single_line = true;
2163 var contains_newline = false;
2164 for (section_exprs, 0..) |expr, i| {
2165 const start = sub_expr_buffer.getWritten().len;
2166 sub_expr_buffer_starts[i] = start;
2167
2168 if (i + 1 < section_exprs.len) {
2169 try renderExpression(&sub_render, expr, .none);
2170 const written = sub_expr_buffer.getWritten();
2171 const width = written.len - start;
2172 const this_contains_newline = mem.indexOfScalar(u8, written[start..], '\n') != null;
2173 contains_newline = contains_newline or this_contains_newline;
2174 expr_widths[i] = width;
2175 expr_newlines[i] = this_contains_newline;
2176
2177 if (!this_contains_newline) {
2178 const column = column_counter % row_size;
2179 column_widths[column] = @max(column_widths[column], width);
2180
2181 const expr_last_token = tree.lastToken(expr) + 1;
2182 const next_expr = section_exprs[i + 1];
2183 column_counter += 1;
2184 if (!tree.tokensOnSameLine(expr_last_token, tree.firstToken(next_expr))) single_line = false;
2185 } else {
2186 single_line = false;
2187 column_counter = 0;
2188 }
2189 } else {
2190 try ais.pushSpace(.comma);
2191 try renderExpression(&sub_render, expr, .comma);
2192 ais.popSpace();
2193
2194 const written = sub_expr_buffer.getWritten();
2195 const width = written.len - start - 2;
2196 const this_contains_newline = mem.indexOfScalar(u8, written[start .. written.len - 1], '\n') != null;
2197 contains_newline = contains_newline or this_contains_newline;
2198 expr_widths[i] = width;
2199 expr_newlines[i] = contains_newline;
2200
2201 if (!contains_newline) {
2202 const column = column_counter % row_size;
2203 column_widths[column] = @max(column_widths[column], width);
2204 }
2205 }
2206 }
2207 sub_expr_buffer_starts[section_exprs.len] = sub_expr_buffer.getWritten().len;
2208
2209 // Render exprs in current section.
2210 column_counter = 0;
2211 for (section_exprs, 0..) |expr, i| {
2212 const start = sub_expr_buffer_starts[i];
2213 const end = sub_expr_buffer_starts[i + 1];
2214 const expr_text = sub_expr_buffer.getWritten()[start..end];
2215 if (!expr_newlines[i]) {
2216 try ais.writeAll(expr_text);
2217 } else {
2218 var by_line = std.mem.splitScalar(u8, expr_text, '\n');
2219 var last_line_was_empty = false;
2220 try ais.writeAll(by_line.first());
2221 while (by_line.next()) |line| {
2222 if (std.mem.startsWith(u8, line, "//") and last_line_was_empty) {
2223 try ais.insertNewline();
2224 } else {
2225 try ais.maybeInsertNewline();
2226 }
2227 last_line_was_empty = (line.len == 0);
2228 try ais.writeAll(line);
2229 }
2230 }
2231
2232 if (i + 1 < section_exprs.len) {
2233 const next_expr = section_exprs[i + 1];
2234 const comma = tree.lastToken(expr) + 1;
2235
2236 if (column_counter != row_size - 1) {
2237 if (!expr_newlines[i] and !expr_newlines[i + 1]) {
2238 // Neither the current or next expression is multiline
2239 try renderToken(r, comma, .space); // ,
2240 assert(column_widths[column_counter % row_size] >= expr_widths[i]);
2241 const padding = column_widths[column_counter % row_size] - expr_widths[i];
2242 try ais.splatByteAll(' ', padding);
2243
2244 column_counter += 1;
2245 continue;
2246 }
2247 }
2248
2249 if (single_line and row_size != 1) {
2250 try renderToken(r, comma, .space); // ,
2251 continue;
2252 }
2253
2254 column_counter = 0;
2255 try renderToken(r, comma, .newline); // ,
2256 try renderExtraNewline(r, next_expr);
2257 }
2258 }
2259
2260 if (expr_index == array_init.ast.elements.len)
2261 break;
2262 }
2263
2264 ais.popIndent();
2265 return renderToken(r, rbrace, space); // rbrace
2266}
2267
2268fn renderContainerDecl(
2269 r: *Render,
2270 container_decl_node: Ast.Node.Index,
2271 container_decl: Ast.full.ContainerDecl,
2272 space: Space,
2273) Error!void {
2274 const tree = r.tree;
2275 const ais = r.ais;
2276
2277 if (container_decl.layout_token) |layout_token| {
2278 try renderToken(r, layout_token, .space);
2279 }
2280
2281 const container: Container = switch (tree.tokenTag(container_decl.ast.main_token)) {
2282 .keyword_enum => .@"enum",
2283 .keyword_struct => for (container_decl.ast.members) |member| {
2284 if (tree.fullContainerField(member)) |field| if (!field.ast.tuple_like) break .other;
2285 } else .tuple,
2286 else => .other,
2287 };
2288
2289 var lbrace: Ast.TokenIndex = undefined;
2290 if (container_decl.ast.enum_token) |enum_token| {
2291 try renderToken(r, container_decl.ast.main_token, .none); // union
2292 try renderToken(r, enum_token - 1, .none); // lparen
2293 try renderToken(r, enum_token, .none); // enum
2294 if (container_decl.ast.arg.unwrap()) |arg| {
2295 try renderToken(r, enum_token + 1, .none); // lparen
2296 try renderExpression(r, arg, .none);
2297 const rparen = tree.lastToken(arg) + 1;
2298 try renderToken(r, rparen, .none); // rparen
2299 try renderToken(r, rparen + 1, .space); // rparen
2300 lbrace = rparen + 2;
2301 } else {
2302 try renderToken(r, enum_token + 1, .space); // rparen
2303 lbrace = enum_token + 2;
2304 }
2305 } else if (container_decl.ast.arg.unwrap()) |arg| {
2306 try renderToken(r, container_decl.ast.main_token, .none); // union
2307 try renderToken(r, container_decl.ast.main_token + 1, .none); // lparen
2308 try renderExpression(r, arg, .none);
2309 const rparen = tree.lastToken(arg) + 1;
2310 try renderToken(r, rparen, .space); // rparen
2311 lbrace = rparen + 1;
2312 } else {
2313 try renderToken(r, container_decl.ast.main_token, .space); // union
2314 lbrace = container_decl.ast.main_token + 1;
2315 }
2316
2317 const rbrace = tree.lastToken(container_decl_node);
2318
2319 if (container_decl.ast.members.len == 0) {
2320 try ais.pushIndent(.normal);
2321 if (tree.tokenTag(lbrace + 1) == .container_doc_comment) {
2322 try renderToken(r, lbrace, .newline); // lbrace
2323 try renderContainerDocComments(r, lbrace + 1);
2324 } else {
2325 try renderToken(r, lbrace, .none); // lbrace
2326 }
2327 ais.popIndent();
2328 return renderToken(r, rbrace, space); // rbrace
2329 }
2330
2331 const src_has_trailing_comma = tree.tokenTag(rbrace - 1) == .comma;
2332 if (!src_has_trailing_comma) one_line: {
2333 // We print all the members in-line unless one of the following conditions are true:
2334
2335 // 1. The container has comments or multiline strings.
2336 if (hasComment(tree, lbrace, rbrace) or hasMultilineString(tree, lbrace, rbrace)) {
2337 break :one_line;
2338 }
2339
2340 // 2. The container has a container comment.
2341 if (tree.tokenTag(lbrace + 1) == .container_doc_comment) break :one_line;
2342
2343 // 3. A member of the container has a doc comment.
2344 for (tree.tokens.items(.tag)[lbrace + 1 .. rbrace - 1]) |tag| {
2345 if (tag == .doc_comment) break :one_line;
2346 }
2347
2348 // 4. The container has non-field members.
2349 for (container_decl.ast.members) |member| {
2350 if (tree.fullContainerField(member) == null) break :one_line;
2351 }
2352
2353 // Print all the declarations on the same line.
2354 try renderToken(r, lbrace, .space); // lbrace
2355 for (container_decl.ast.members) |member| {
2356 try renderMember(r, container, member, .space);
2357 }
2358 return renderToken(r, rbrace, space); // rbrace
2359 }
2360
2361 // One member per line.
2362 try ais.pushIndent(.normal);
2363 try renderToken(r, lbrace, .newline); // lbrace
2364 if (tree.tokenTag(lbrace + 1) == .container_doc_comment) {
2365 try renderContainerDocComments(r, lbrace + 1);
2366 }
2367 for (container_decl.ast.members, 0..) |member, i| {
2368 if (i != 0) try renderExtraNewline(r, member);
2369 switch (tree.nodeTag(member)) {
2370 // For container fields, ensure a trailing comma is added if necessary.
2371 .container_field_init,
2372 .container_field_align,
2373 .container_field,
2374 => {
2375 try ais.pushSpace(.comma);
2376 try renderMember(r, container, member, .comma);
2377 ais.popSpace();
2378 },
2379
2380 else => try renderMember(r, container, member, .newline),
2381 }
2382 }
2383 ais.popIndent();
2384
2385 return renderToken(r, rbrace, space); // rbrace
2386}
2387
2388fn renderAsm(
2389 r: *Render,
2390 asm_node: Ast.full.Asm,
2391 space: Space,
2392) Error!void {
2393 const tree = r.tree;
2394 const ais = r.ais;
2395
2396 try renderToken(r, asm_node.ast.asm_token, .space); // asm
2397
2398 if (asm_node.volatile_token) |volatile_token| {
2399 try renderToken(r, volatile_token, .space); // volatile
2400 try renderToken(r, volatile_token + 1, .none); // lparen
2401 } else {
2402 try renderToken(r, asm_node.ast.asm_token + 1, .none); // lparen
2403 }
2404
2405 if (asm_node.ast.items.len == 0) {
2406 try ais.forcePushIndent(.normal);
2407 if (asm_node.first_clobber) |first_clobber| {
2408 // asm ("foo" ::: "a", "b")
2409 // asm ("foo" ::: "a", "b",)
2410 try renderExpression(r, asm_node.ast.template, .space);
2411 // Render the three colons.
2412 try renderToken(r, first_clobber - 3, .none);
2413 try renderToken(r, first_clobber - 2, .none);
2414 try renderToken(r, first_clobber - 1, .space);
2415
2416 var tok_i = first_clobber;
2417 while (true) : (tok_i += 1) {
2418 try renderToken(r, tok_i, .none);
2419 tok_i += 1;
2420 switch (tree.tokenTag(tok_i)) {
2421 .r_paren => {
2422 ais.popIndent();
2423 return renderToken(r, tok_i, space);
2424 },
2425 .comma => {
2426 if (tree.tokenTag(tok_i + 1) == .r_paren) {
2427 ais.popIndent();
2428 return renderToken(r, tok_i + 1, space);
2429 } else {
2430 try renderToken(r, tok_i, .space);
2431 }
2432 },
2433 else => unreachable,
2434 }
2435 }
2436 } else {
2437 // asm ("foo")
2438 try renderExpression(r, asm_node.ast.template, .none);
2439 ais.popIndent();
2440 return renderToken(r, asm_node.ast.rparen, space); // rparen
2441 }
2442 }
2443
2444 try ais.forcePushIndent(.normal);
2445 try renderExpression(r, asm_node.ast.template, .newline);
2446 ais.setIndentDelta(asm_indent_delta);
2447 const colon1 = tree.lastToken(asm_node.ast.template) + 1;
2448
2449 const colon2 = if (asm_node.outputs.len == 0) colon2: {
2450 try renderToken(r, colon1, .newline); // :
2451 break :colon2 colon1 + 1;
2452 } else colon2: {
2453 try renderToken(r, colon1, .space); // :
2454
2455 try ais.forcePushIndent(.normal);
2456 for (asm_node.outputs, 0..) |asm_output, i| {
2457 if (i + 1 < asm_node.outputs.len) {
2458 const next_asm_output = asm_node.outputs[i + 1];
2459 try renderAsmOutput(r, asm_output, .none);
2460
2461 const comma = tree.firstToken(next_asm_output) - 1;
2462 try renderToken(r, comma, .newline); // ,
2463 try renderExtraNewlineToken(r, tree.firstToken(next_asm_output));
2464 } else if (asm_node.inputs.len == 0 and asm_node.first_clobber == null) {
2465 try ais.pushSpace(.comma);
2466 try renderAsmOutput(r, asm_output, .comma);
2467 ais.popSpace();
2468 ais.popIndent();
2469 ais.setIndentDelta(indent_delta);
2470 ais.popIndent();
2471 return renderToken(r, asm_node.ast.rparen, space); // rparen
2472 } else {
2473 try ais.pushSpace(.comma);
2474 try renderAsmOutput(r, asm_output, .comma);
2475 ais.popSpace();
2476 const comma_or_colon = tree.lastToken(asm_output) + 1;
2477 ais.popIndent();
2478 break :colon2 switch (tree.tokenTag(comma_or_colon)) {
2479 .comma => comma_or_colon + 1,
2480 else => comma_or_colon,
2481 };
2482 }
2483 } else unreachable;
2484 };
2485
2486 const colon3 = if (asm_node.inputs.len == 0) colon3: {
2487 try renderToken(r, colon2, .newline); // :
2488 break :colon3 colon2 + 1;
2489 } else colon3: {
2490 try renderToken(r, colon2, .space); // :
2491 try ais.forcePushIndent(.normal);
2492 for (asm_node.inputs, 0..) |asm_input, i| {
2493 if (i + 1 < asm_node.inputs.len) {
2494 const next_asm_input = asm_node.inputs[i + 1];
2495 try renderAsmInput(r, asm_input, .none);
2496
2497 const first_token = tree.firstToken(next_asm_input);
2498 try renderToken(r, first_token - 1, .newline); // ,
2499 try renderExtraNewlineToken(r, first_token);
2500 } else if (asm_node.first_clobber == null) {
2501 try ais.pushSpace(.comma);
2502 try renderAsmInput(r, asm_input, .comma);
2503 ais.popSpace();
2504 ais.popIndent();
2505 ais.setIndentDelta(indent_delta);
2506 ais.popIndent();
2507 return renderToken(r, asm_node.ast.rparen, space); // rparen
2508 } else {
2509 try ais.pushSpace(.comma);
2510 try renderAsmInput(r, asm_input, .comma);
2511 ais.popSpace();
2512 const comma_or_colon = tree.lastToken(asm_input) + 1;
2513 ais.popIndent();
2514 break :colon3 switch (tree.tokenTag(comma_or_colon)) {
2515 .comma => comma_or_colon + 1,
2516 else => comma_or_colon,
2517 };
2518 }
2519 }
2520 unreachable;
2521 };
2522
2523 try renderToken(r, colon3, .space); // :
2524 const first_clobber = asm_node.first_clobber.?;
2525 var tok_i = first_clobber;
2526 while (true) {
2527 switch (tree.tokenTag(tok_i + 1)) {
2528 .r_paren => {
2529 ais.setIndentDelta(indent_delta);
2530 try renderToken(r, tok_i, .newline);
2531 ais.popIndent();
2532 return renderToken(r, tok_i + 1, space);
2533 },
2534 .comma => {
2535 switch (tree.tokenTag(tok_i + 2)) {
2536 .r_paren => {
2537 ais.setIndentDelta(indent_delta);
2538 try renderToken(r, tok_i, .newline);
2539 ais.popIndent();
2540 return renderToken(r, tok_i + 2, space);
2541 },
2542 else => {
2543 try renderToken(r, tok_i, .none);
2544 try renderToken(r, tok_i + 1, .space);
2545 tok_i += 2;
2546 },
2547 }
2548 },
2549 else => unreachable,
2550 }
2551 }
2552}
2553
2554fn renderCall(
2555 r: *Render,
2556 call: Ast.full.Call,
2557 space: Space,
2558) Error!void {
2559 if (call.async_token) |async_token| {
2560 try renderToken(r, async_token, .space);
2561 }
2562 try renderExpression(r, call.ast.fn_expr, .none);
2563 try renderParamList(r, call.ast.lparen, call.ast.params, space);
2564}
2565
2566fn renderParamList(
2567 r: *Render,
2568 lparen: Ast.TokenIndex,
2569 params: []const Ast.Node.Index,
2570 space: Space,
2571) Error!void {
2572 const tree = r.tree;
2573 const ais = r.ais;
2574
2575 if (params.len == 0) {
2576 try ais.pushIndent(.normal);
2577 try renderToken(r, lparen, .none);
2578 ais.popIndent();
2579 return renderToken(r, lparen + 1, space); // )
2580 }
2581
2582 const last_param = params[params.len - 1];
2583 const after_last_param_tok = tree.lastToken(last_param) + 1;
2584 if (tree.tokenTag(after_last_param_tok) == .comma) {
2585 try ais.pushIndent(.normal);
2586 try renderToken(r, lparen, .newline); // (
2587 for (params, 0..) |param_node, i| {
2588 if (i + 1 < params.len) {
2589 try renderExpression(r, param_node, .none);
2590
2591 const comma = tree.lastToken(param_node) + 1;
2592 try renderToken(r, comma, .newline); // ,
2593
2594 try renderExtraNewline(r, params[i + 1]);
2595 } else {
2596 try ais.pushSpace(.comma);
2597 try renderExpression(r, param_node, .comma);
2598 ais.popSpace();
2599 }
2600 }
2601 ais.popIndent();
2602 return renderToken(r, after_last_param_tok + 1, space); // )
2603 }
2604
2605 try ais.pushIndent(.normal);
2606 try renderToken(r, lparen, .none); // (
2607 for (params, 0..) |param_node, i| {
2608 try renderExpression(r, param_node, .none);
2609
2610 if (i + 1 < params.len) {
2611 const comma = tree.lastToken(param_node) + 1;
2612 const next_multiline_string =
2613 tree.tokenTag(tree.firstToken(params[i + 1])) == .multiline_string_literal_line;
2614 const comma_space: Space = if (next_multiline_string) .none else .space;
2615 try renderToken(r, comma, comma_space);
2616 }
2617 }
2618 ais.popIndent();
2619 return renderToken(r, after_last_param_tok, space); // )
2620}
2621
2622/// Render an expression, and the comma that follows it, if it is present in the source.
2623/// If a comma is present, and `space` is `Space.comma`, render only a single comma.
2624fn renderExpressionComma(r: *Render, node: Ast.Node.Index, space: Space) Error!void {
2625 const tree = r.tree;
2626 const maybe_comma = tree.lastToken(node) + 1;
2627 if (tree.tokenTag(maybe_comma) == .comma and space != .comma) {
2628 try renderExpression(r, node, .none);
2629 return renderToken(r, maybe_comma, space);
2630 } else {
2631 return renderExpression(r, node, space);
2632 }
2633}
2634
2635/// Render a token, and the comma that follows it, if it is present in the source.
2636/// If a comma is present, and `space` is `Space.comma`, render only a single comma.
2637fn renderTokenComma(r: *Render, token: Ast.TokenIndex, space: Space) Error!void {
2638 const tree = r.tree;
2639 const maybe_comma = token + 1;
2640 if (tree.tokenTag(maybe_comma) == .comma and space != .comma) {
2641 try renderToken(r, token, .none);
2642 return renderToken(r, maybe_comma, space);
2643 } else {
2644 return renderToken(r, token, space);
2645 }
2646}
2647
2648/// Render an identifier, and the comma that follows it, if it is present in the source.
2649/// If a comma is present, and `space` is `Space.comma`, render only a single comma.
2650fn renderIdentifierComma(r: *Render, token: Ast.TokenIndex, space: Space, quote: QuoteBehavior) Error!void {
2651 const tree = r.tree;
2652 const maybe_comma = token + 1;
2653 if (tree.tokenTag(maybe_comma) == .comma and space != .comma) {
2654 try renderIdentifier(r, token, .none, quote);
2655 return renderToken(r, maybe_comma, space);
2656 } else {
2657 return renderIdentifier(r, token, space, quote);
2658 }
2659}
2660
2661const Space = enum {
2662 /// Output the token lexeme only.
2663 none,
2664 /// Output the token lexeme followed by a single space.
2665 space,
2666 /// Output the token lexeme followed by a newline.
2667 newline,
2668 /// If the next token is a comma, render it as well. If not, insert one.
2669 /// In either case, a newline will be inserted afterwards.
2670 comma,
2671 /// Additionally consume the next token if it is a comma.
2672 /// In either case, a space will be inserted afterwards.
2673 comma_space,
2674 /// Additionally consume the next token if it is a semicolon.
2675 /// In either case, a newline will be inserted afterwards.
2676 semicolon,
2677 /// Skip rendering whitespace and comments. If this is used, the caller
2678 /// *must* handle whitespace and comments manually.
2679 skip,
2680};
2681
2682fn renderToken(r: *Render, token_index: Ast.TokenIndex, space: Space) Error!void {
2683 const tree = r.tree;
2684 const ais = r.ais;
2685 const lexeme = tokenSliceForRender(tree, token_index);
2686 try ais.writeAll(lexeme);
2687 try renderSpace(r, token_index, lexeme.len, space);
2688}
2689
2690fn renderTokenOverrideSpaceMode(r: *Render, token_index: Ast.TokenIndex, space: Space, override_space: Space) Error!void {
2691 const tree = r.tree;
2692 const ais = r.ais;
2693 const lexeme = tokenSliceForRender(tree, token_index);
2694 try ais.writeAll(lexeme);
2695 ais.enableSpaceMode(override_space);
2696 defer ais.disableSpaceMode();
2697 try renderSpace(r, token_index, lexeme.len, space);
2698}
2699
2700fn renderSpace(r: *Render, token_index: Ast.TokenIndex, lexeme_len: usize, space: Space) Error!void {
2701 const tree = r.tree;
2702 const ais = r.ais;
2703
2704 const next_token_tag = tree.tokenTag(token_index + 1);
2705
2706 if (space == .skip) return;
2707
2708 if (space == .comma and next_token_tag != .comma) {
2709 try ais.underlying_writer.writeByte(',');
2710 }
2711 if (space == .semicolon or space == .comma) ais.enableSpaceMode(space);
2712 defer ais.disableSpaceMode();
2713 const comment = try renderComments(
2714 r,
2715 tree.tokenStart(token_index) + lexeme_len,
2716 tree.tokenStart(token_index + 1),
2717 );
2718 switch (space) {
2719 .none => {},
2720 .space => if (!comment) try ais.writeByte(' '),
2721 .newline => if (!comment) try ais.insertNewline(),
2722
2723 .comma => if (next_token_tag == .comma) {
2724 try renderToken(r, token_index + 1, .newline);
2725 } else if (!comment) {
2726 try ais.insertNewline();
2727 },
2728
2729 .comma_space => if (next_token_tag == .comma) {
2730 try renderToken(r, token_index + 1, .space);
2731 } else if (!comment) {
2732 try ais.writeByte(' ');
2733 },
2734
2735 .semicolon => if (next_token_tag == .semicolon) {
2736 try renderToken(r, token_index + 1, .newline);
2737 } else if (!comment) {
2738 try ais.insertNewline();
2739 },
2740
2741 .skip => unreachable,
2742 }
2743}
2744
2745fn renderOnlySpace(r: *Render, space: Space) Error!void {
2746 const ais = r.ais;
2747 switch (space) {
2748 .none => {},
2749 .space => try ais.writeByte(' '),
2750 .newline => try ais.insertNewline(),
2751 .comma => try ais.writeAll(",\n"),
2752 .comma_space => try ais.writeAll(", "),
2753 .semicolon => try ais.writeAll(";\n"),
2754 .skip => unreachable,
2755 }
2756}
2757
2758const QuoteBehavior = enum {
2759 preserve_when_shadowing,
2760 eagerly_unquote,
2761 eagerly_unquote_except_underscore,
2762};
2763
2764fn renderIdentifier(r: *Render, token_index: Ast.TokenIndex, space: Space, quote: QuoteBehavior) Error!void {
2765 const tree = r.tree;
2766 assert(tree.tokenTag(token_index) == .identifier);
2767 const lexeme = tokenSliceForRender(tree, token_index);
2768
2769 if (r.fixups.rename_identifiers.get(lexeme)) |mangled| {
2770 try r.ais.writeAll(mangled);
2771 try renderSpace(r, token_index, lexeme.len, space);
2772 return;
2773 }
2774
2775 if (lexeme[0] != '@') {
2776 return renderToken(r, token_index, space);
2777 }
2778
2779 assert(lexeme.len >= 3);
2780 assert(lexeme[0] == '@');
2781 assert(lexeme[1] == '\"');
2782 assert(lexeme[lexeme.len - 1] == '\"');
2783 const contents = lexeme[2 .. lexeme.len - 1]; // inside the @"" quotation
2784
2785 // Empty name can't be unquoted.
2786 if (contents.len == 0) {
2787 return renderQuotedIdentifier(r, token_index, space, false);
2788 }
2789
2790 // Special case for _.
2791 if (std.zig.isUnderscore(contents)) switch (quote) {
2792 .eagerly_unquote => return renderQuotedIdentifier(r, token_index, space, true),
2793 .eagerly_unquote_except_underscore,
2794 .preserve_when_shadowing,
2795 => return renderQuotedIdentifier(r, token_index, space, false),
2796 };
2797
2798 // Scan the entire name for characters that would (after un-escaping) be illegal in a symbol,
2799 // i.e. contents don't match: [A-Za-z_][A-Za-z0-9_]*
2800 var contents_i: usize = 0;
2801 while (contents_i < contents.len) {
2802 switch (contents[contents_i]) {
2803 '0'...'9' => if (contents_i == 0) return renderQuotedIdentifier(r, token_index, space, false),
2804 'A'...'Z', 'a'...'z', '_' => {},
2805 '\\' => {
2806 var esc_offset = contents_i;
2807 const res = std.zig.string_literal.parseEscapeSequence(contents, &esc_offset);
2808 switch (res) {
2809 .success => |char| switch (char) {
2810 '0'...'9' => if (contents_i == 0) return renderQuotedIdentifier(r, token_index, space, false),
2811 'A'...'Z', 'a'...'z', '_' => {},
2812 else => return renderQuotedIdentifier(r, token_index, space, false),
2813 },
2814 .failure => return renderQuotedIdentifier(r, token_index, space, false),
2815 }
2816 contents_i += esc_offset;
2817 continue;
2818 },
2819 else => return renderQuotedIdentifier(r, token_index, space, false),
2820 }
2821 contents_i += 1;
2822 }
2823
2824 // Read enough of the name (while un-escaping) to determine if it's a keyword or primitive.
2825 // If it's too long to fit in this buffer, we know it's neither and quoting is unnecessary.
2826 // If we read the whole thing, we have to do further checks.
2827 const longest_keyword_or_primitive_len = comptime blk: {
2828 var longest = 0;
2829 for (primitives.names.keys()) |key| {
2830 if (key.len > longest) longest = key.len;
2831 }
2832 for (std.zig.Token.keywords.keys()) |key| {
2833 if (key.len > longest) longest = key.len;
2834 }
2835 break :blk longest;
2836 };
2837 var buf: [longest_keyword_or_primitive_len]u8 = undefined;
2838
2839 contents_i = 0;
2840 var buf_i: usize = 0;
2841 while (contents_i < contents.len and buf_i < longest_keyword_or_primitive_len) {
2842 if (contents[contents_i] == '\\') {
2843 const res = std.zig.string_literal.parseEscapeSequence(contents, &contents_i).success;
2844 buf[buf_i] = @as(u8, @intCast(res));
2845 buf_i += 1;
2846 } else {
2847 buf[buf_i] = contents[contents_i];
2848 contents_i += 1;
2849 buf_i += 1;
2850 }
2851 }
2852
2853 // We read the whole thing, so it could be a keyword or primitive.
2854 if (contents_i == contents.len) {
2855 if (!std.zig.isValidId(buf[0..buf_i])) {
2856 return renderQuotedIdentifier(r, token_index, space, false);
2857 }
2858 if (primitives.isPrimitive(buf[0..buf_i])) switch (quote) {
2859 .eagerly_unquote,
2860 .eagerly_unquote_except_underscore,
2861 => return renderQuotedIdentifier(r, token_index, space, true),
2862 .preserve_when_shadowing => return renderQuotedIdentifier(r, token_index, space, false),
2863 };
2864 }
2865
2866 try renderQuotedIdentifier(r, token_index, space, true);
2867}
2868
2869// Renders a @"" quoted identifier, normalizing escapes.
2870// Unnecessary escapes are un-escaped, and \u escapes are normalized to \x when they fit.
2871// If unquote is true, the @"" is removed and the result is a bare symbol whose validity is asserted.
2872fn renderQuotedIdentifier(r: *Render, token_index: Ast.TokenIndex, space: Space, comptime unquote: bool) !void {
2873 const tree = r.tree;
2874 const ais = r.ais;
2875 assert(tree.tokenTag(token_index) == .identifier);
2876 const lexeme = tokenSliceForRender(tree, token_index);
2877 assert(lexeme.len >= 3 and lexeme[0] == '@');
2878
2879 if (!unquote) try ais.writeAll("@\"");
2880 const contents = lexeme[2 .. lexeme.len - 1];
2881 try renderIdentifierContents(ais, contents);
2882 if (!unquote) try ais.writeByte('\"');
2883
2884 try renderSpace(r, token_index, lexeme.len, space);
2885}
2886
2887fn renderIdentifierContents(ais: *AutoIndentingStream, bytes: []const u8) !void {
2888 var pos: usize = 0;
2889 while (pos < bytes.len) {
2890 const byte = bytes[pos];
2891 switch (byte) {
2892 '\\' => {
2893 const old_pos = pos;
2894 const res = std.zig.string_literal.parseEscapeSequence(bytes, &pos);
2895 const escape_sequence = bytes[old_pos..pos];
2896 switch (res) {
2897 .success => |codepoint| {
2898 if (codepoint <= 0x7f) {
2899 const buf = [1]u8{@as(u8, @intCast(codepoint))};
2900 try ais.print("{f}", .{std.zig.fmtEscapes(&buf)});
2901 } else {
2902 try ais.writeAll(escape_sequence);
2903 }
2904 },
2905 .failure => {
2906 try ais.writeAll(escape_sequence);
2907 },
2908 }
2909 },
2910 0x00...('\\' - 1), ('\\' + 1)...0x7f => {
2911 const buf = [1]u8{byte};
2912 try ais.print("{f}", .{std.zig.fmtEscapes(&buf)});
2913 pos += 1;
2914 },
2915 0x80...0xff => {
2916 try ais.writeByte(byte);
2917 pos += 1;
2918 },
2919 }
2920 }
2921}
2922
2923/// Returns true if there exists a line comment between any of the tokens from
2924/// `start_token` to `end_token`. This is used to determine if e.g. a
2925/// fn_proto should be wrapped and have a trailing comma inserted even if
2926/// there is none in the source.
2927fn hasComment(tree: Ast, start_token: Ast.TokenIndex, end_token: Ast.TokenIndex) bool {
2928 for (start_token..end_token) |i| {
2929 const token: Ast.TokenIndex = @intCast(i);
2930 const start = tree.tokenStart(token) + tree.tokenSlice(token).len;
2931 const end = tree.tokenStart(token + 1);
2932 if (mem.indexOf(u8, tree.source[start..end], "//") != null) return true;
2933 }
2934
2935 return false;
2936}
2937
2938/// Returns true if there exists a multiline string literal between the start
2939/// of token `start_token` and the start of token `end_token`.
2940fn hasMultilineString(tree: Ast, start_token: Ast.TokenIndex, end_token: Ast.TokenIndex) bool {
2941 return std.mem.indexOfScalar(
2942 Token.Tag,
2943 tree.tokens.items(.tag)[start_token..end_token],
2944 .multiline_string_literal_line,
2945 ) != null;
2946}
2947
2948/// Assumes that start is the first byte past the previous token and
2949/// that end is the last byte before the next token.
2950fn renderComments(r: *Render, start: usize, end: usize) Error!bool {
2951 const tree = r.tree;
2952 const ais = r.ais;
2953
2954 var index: usize = start;
2955 while (mem.indexOf(u8, tree.source[index..end], "//")) |offset| {
2956 const comment_start = index + offset;
2957
2958 // If there is no newline, the comment ends with EOF
2959 const newline_index = mem.indexOfScalar(u8, tree.source[comment_start..end], '\n');
2960 const newline = if (newline_index) |i| comment_start + i else null;
2961
2962 const untrimmed_comment = tree.source[comment_start .. newline orelse tree.source.len];
2963 const trimmed_comment = mem.trimEnd(u8, untrimmed_comment, &std.ascii.whitespace);
2964
2965 // Don't leave any whitespace at the start of the file
2966 if (index != 0) {
2967 if (index == start and mem.containsAtLeast(u8, tree.source[index..comment_start], 2, "\n")) {
2968 // Leave up to one empty line before the first comment
2969 try ais.insertNewline();
2970 try ais.insertNewline();
2971 } else if (mem.indexOfScalar(u8, tree.source[index..comment_start], '\n') != null) {
2972 // Respect the newline directly before the comment.
2973 // Note: This allows an empty line between comments
2974 try ais.insertNewline();
2975 } else if (index == start) {
2976 // Otherwise if the first comment is on the same line as
2977 // the token before it, prefix it with a single space.
2978 try ais.writeByte(' ');
2979 }
2980 }
2981
2982 index = 1 + (newline orelse end - 1);
2983
2984 const comment_content = mem.trimStart(u8, trimmed_comment["//".len..], &std.ascii.whitespace);
2985 if (ais.disabled_offset != null and mem.eql(u8, comment_content, "zig fmt: on")) {
2986 // Write the source for which formatting was disabled directly
2987 // to the underlying writer, fixing up invalid whitespace.
2988 const disabled_source = tree.source[ais.disabled_offset.?..comment_start];
2989 try writeFixingWhitespace(ais.underlying_writer, disabled_source);
2990 // Write with the canonical single space.
2991 try ais.underlying_writer.writeAll("// zig fmt: on\n");
2992 ais.disabled_offset = null;
2993 } else if (ais.disabled_offset == null and mem.eql(u8, comment_content, "zig fmt: off")) {
2994 // Write with the canonical single space.
2995 try ais.writeAll("// zig fmt: off\n");
2996 ais.disabled_offset = index;
2997 } else {
2998 // Write the comment minus trailing whitespace.
2999 try ais.print("{s}\n", .{trimmed_comment});
3000 }
3001 }
3002
3003 if (index != start and mem.containsAtLeast(u8, tree.source[index - 1 .. end], 2, "\n")) {
3004 // Don't leave any whitespace at the end of the file
3005 if (end != tree.source.len) {
3006 try ais.insertNewline();
3007 }
3008 }
3009
3010 return index != start;
3011}
3012
3013fn renderExtraNewline(r: *Render, node: Ast.Node.Index) Error!void {
3014 return renderExtraNewlineToken(r, r.tree.firstToken(node));
3015}
3016
3017/// Check if there is an empty line immediately before the given token. If so, render it.
3018fn renderExtraNewlineToken(r: *Render, token_index: Ast.TokenIndex) Error!void {
3019 const tree = r.tree;
3020 const ais = r.ais;
3021 const token_start = tree.tokenStart(token_index);
3022 if (token_start == 0) return;
3023 const prev_token_end = if (token_index == 0)
3024 0
3025 else
3026 tree.tokenStart(token_index - 1) + tokenSliceForRender(tree, token_index - 1).len;
3027
3028 // If there is a immediately preceding comment or doc_comment,
3029 // skip it because required extra newline has already been rendered.
3030 if (mem.indexOf(u8, tree.source[prev_token_end..token_start], "//") != null) return;
3031 if (tree.isTokenPrecededByTags(token_index, &.{.doc_comment})) return;
3032
3033 // Iterate backwards to the end of the previous token, stopping if a
3034 // non-whitespace character is encountered or two newlines have been found.
3035 var i = token_start - 1;
3036 var newlines: u2 = 0;
3037 while (std.ascii.isWhitespace(tree.source[i])) : (i -= 1) {
3038 if (tree.source[i] == '\n') newlines += 1;
3039 if (newlines == 2) return ais.insertNewline();
3040 if (i == prev_token_end) break;
3041 }
3042}
3043
3044/// end_token is the token one past the last doc comment token. This function
3045/// searches backwards from there.
3046fn renderDocComments(r: *Render, end_token: Ast.TokenIndex) Error!void {
3047 const tree = r.tree;
3048 // Search backwards for the first doc comment.
3049 if (end_token == 0) return;
3050 var tok = end_token - 1;
3051 while (tree.tokenTag(tok) == .doc_comment) {
3052 if (tok == 0) break;
3053 tok -= 1;
3054 } else {
3055 tok += 1;
3056 }
3057 const first_tok = tok;
3058 if (first_tok == end_token) return;
3059
3060 if (first_tok != 0) {
3061 const prev_token_tag = tree.tokenTag(first_tok - 1);
3062
3063 // Prevent accidental use of `renderDocComments` for a function argument doc comment
3064 assert(prev_token_tag != .l_paren);
3065
3066 if (prev_token_tag != .l_brace) {
3067 try renderExtraNewlineToken(r, first_tok);
3068 }
3069 }
3070
3071 while (tree.tokenTag(tok) == .doc_comment) : (tok += 1) {
3072 try renderToken(r, tok, .newline);
3073 }
3074}
3075
3076/// start_token is first container doc comment token.
3077fn renderContainerDocComments(r: *Render, start_token: Ast.TokenIndex) Error!void {
3078 const tree = r.tree;
3079 var tok = start_token;
3080 while (tree.tokenTag(tok) == .container_doc_comment) : (tok += 1) {
3081 try renderToken(r, tok, .newline);
3082 }
3083 // Render extra newline if there is one between final container doc comment and
3084 // the next token. If the next token is a doc comment, that code path
3085 // will have its own logic to insert a newline.
3086 if (tree.tokenTag(tok) != .doc_comment) {
3087 try renderExtraNewlineToken(r, tok);
3088 }
3089}
3090
3091fn discardAllParams(r: *Render, fn_proto_node: Ast.Node.Index) Error!void {
3092 const tree = &r.tree;
3093 const ais = r.ais;
3094 var buf: [1]Ast.Node.Index = undefined;
3095 const fn_proto = tree.fullFnProto(&buf, fn_proto_node).?;
3096 var it = fn_proto.iterate(tree);
3097 while (it.next()) |param| {
3098 const name_ident = param.name_token.?;
3099 assert(tree.tokenTag(name_ident) == .identifier);
3100 try ais.writeAll("_ = ");
3101 try ais.writeAll(tokenSliceForRender(r.tree, name_ident));
3102 try ais.writeAll(";\n");
3103 }
3104}
3105
3106fn tokenSliceForRender(tree: Ast, token_index: Ast.TokenIndex) []const u8 {
3107 var ret = tree.tokenSlice(token_index);
3108 switch (tree.tokenTag(token_index)) {
3109 .container_doc_comment, .doc_comment => {
3110 ret = mem.trimEnd(u8, ret, &std.ascii.whitespace);
3111 },
3112 else => {},
3113 }
3114 return ret;
3115}
3116
3117fn hasSameLineComment(tree: Ast, token_index: Ast.TokenIndex) bool {
3118 const between_source = tree.source[tree.tokenStart(token_index)..tree.tokenStart(token_index + 1)];
3119 for (between_source) |byte| switch (byte) {
3120 '\n' => return false,
3121 '/' => return true,
3122 else => continue,
3123 };
3124 return false;
3125}
3126
3127/// Returns `true` if and only if there are any tokens or line comments between
3128/// start_token and end_token.
3129fn anythingBetween(tree: Ast, start_token: Ast.TokenIndex, end_token: Ast.TokenIndex) bool {
3130 if (start_token + 1 != end_token) return true;
3131 const between_source = tree.source[tree.tokenStart(start_token)..tree.tokenStart(start_token + 1)];
3132 for (between_source) |byte| switch (byte) {
3133 '/' => return true,
3134 else => continue,
3135 };
3136 return false;
3137}
3138
3139fn writeFixingWhitespace(bw: *std.io.BufferedWriter, slice: []const u8) Error!void {
3140 for (slice) |byte| switch (byte) {
3141 '\t' => try bw.splatByteAll(' ', indent_delta),
3142 '\r' => {},
3143 else => try bw.writeByte(byte),
3144 };
3145}
3146
3147fn nodeIsBlock(tag: Ast.Node.Tag) bool {
3148 return switch (tag) {
3149 .block,
3150 .block_semicolon,
3151 .block_two,
3152 .block_two_semicolon,
3153 => true,
3154 else => false,
3155 };
3156}
3157
3158fn nodeIsIfForWhileSwitch(tag: Ast.Node.Tag) bool {
3159 return switch (tag) {
3160 .@"if",
3161 .if_simple,
3162 .@"for",
3163 .for_simple,
3164 .@"while",
3165 .while_simple,
3166 .while_cont,
3167 .@"switch",
3168 .switch_comma,
3169 => true,
3170 else => false,
3171 };
3172}
3173
3174fn nodeCausesSliceOpSpace(tag: Ast.Node.Tag) bool {
3175 return switch (tag) {
3176 .@"catch",
3177 .add,
3178 .add_wrap,
3179 .array_cat,
3180 .array_mult,
3181 .assign,
3182 .assign_bit_and,
3183 .assign_bit_or,
3184 .assign_shl,
3185 .assign_shr,
3186 .assign_bit_xor,
3187 .assign_div,
3188 .assign_sub,
3189 .assign_sub_wrap,
3190 .assign_mod,
3191 .assign_add,
3192 .assign_add_wrap,
3193 .assign_mul,
3194 .assign_mul_wrap,
3195 .bang_equal,
3196 .bit_and,
3197 .bit_or,
3198 .shl,
3199 .shr,
3200 .bit_xor,
3201 .bool_and,
3202 .bool_or,
3203 .div,
3204 .equal_equal,
3205 .error_union,
3206 .greater_or_equal,
3207 .greater_than,
3208 .less_or_equal,
3209 .less_than,
3210 .merge_error_sets,
3211 .mod,
3212 .mul,
3213 .mul_wrap,
3214 .sub,
3215 .sub_wrap,
3216 .@"orelse",
3217 => true,
3218
3219 else => false,
3220 };
3221}
3222
3223// Returns the number of nodes in `exprs` that are on the same line as `rtoken`.
3224fn rowSize(tree: Ast, exprs: []const Ast.Node.Index, rtoken: Ast.TokenIndex) usize {
3225 const first_token = tree.firstToken(exprs[0]);
3226 if (tree.tokensOnSameLine(first_token, rtoken)) {
3227 const maybe_comma = rtoken - 1;
3228 if (tree.tokenTag(maybe_comma) == .comma)
3229 return 1;
3230 return exprs.len; // no newlines
3231 }
3232
3233 var count: usize = 1;
3234 for (exprs, 0..) |expr, i| {
3235 if (i + 1 < exprs.len) {
3236 const expr_last_token = tree.lastToken(expr) + 1;
3237 if (!tree.tokensOnSameLine(expr_last_token, tree.firstToken(exprs[i + 1]))) return count;
3238 count += 1;
3239 } else {
3240 return count;
3241 }
3242 }
3243 unreachable;
3244}
3245
3246/// Automatically inserts indentation of written data by keeping
3247/// track of the current indentation level
3248///
3249/// We introduce a new indentation scope with pushIndent/popIndent whenever
3250/// we potentially want to introduce an indent after the next newline.
3251///
3252/// Indentation should only ever increment by one from one line to the next,
3253/// no matter how many new indentation scopes are introduced. This is done by
3254/// only realizing the indentation from the most recent scope. As an example:
3255///
3256/// while (foo) if (bar)
3257/// f(x);
3258///
3259/// The body of `while` introduces a new indentation scope and the body of
3260/// `if` also introduces a new indentation scope. When the newline is seen,
3261/// only the indentation scope of the `if` is realized, and the `while` is
3262/// not.
3263///
3264/// As comments are rendered during space rendering, we need to keep track
3265/// of the appropriate indentation level for them with pushSpace/popSpace.
3266/// This should be done whenever a scope that ends in a .semicolon or a
3267/// .comma is introduced.
3268const AutoIndentingStream = struct {
3269 underlying_writer: *std.io.BufferedWriter,
3270
3271 /// Offset into the source at which formatting has been disabled with
3272 /// a `zig fmt: off` comment.
3273 ///
3274 /// If non-null, the AutoIndentingStream will not write any bytes
3275 /// to the underlying writer. It will however continue to track the
3276 /// indentation level.
3277 disabled_offset: ?usize = null,
3278
3279 indent_count: usize = 0,
3280 indent_delta: usize,
3281 indent_stack: std.ArrayList(StackElem),
3282 space_stack: std.ArrayList(SpaceElem),
3283 space_mode: ?usize = null,
3284 disable_indent_committing: usize = 0,
3285 current_line_empty: bool = true,
3286 /// the most recently applied indent
3287 applied_indent: usize = 0,
3288
3289 pub const IndentType = enum {
3290 normal,
3291 after_equals,
3292 binop,
3293 field_access,
3294 };
3295 const StackElem = struct {
3296 indent_type: IndentType,
3297 realized: bool,
3298 };
3299 const SpaceElem = struct {
3300 space: Space,
3301 indent_count: usize,
3302 };
3303
3304 pub fn init(gpa: Allocator, bw: *std.io.BufferedWriter, indent_delta_: usize) AutoIndentingStream {
3305 return .{
3306 .underlying_writer = bw,
3307 .indent_delta = indent_delta_,
3308 .indent_stack = .init(gpa),
3309 .space_stack = .init(gpa),
3310 };
3311 }
3312
3313 pub fn deinit(self: *AutoIndentingStream) void {
3314 self.indent_stack.deinit();
3315 self.space_stack.deinit();
3316 }
3317
3318 pub fn writeAll(ais: *AutoIndentingStream, bytes: []const u8) Error!void {
3319 if (bytes.len == 0) return;
3320 try ais.applyIndent();
3321 if (ais.disabled_offset == null) try ais.underlying_writer.writeAll(bytes);
3322 if (bytes[bytes.len - 1] == '\n') ais.resetLine();
3323 }
3324
3325 /// Assumes that if the printed data ends with a newline, it is directly
3326 /// contained in the format string.
3327 pub fn print(ais: *AutoIndentingStream, comptime format: []const u8, args: anytype) Error!void {
3328 try ais.applyIndent();
3329 if (ais.disabled_offset == null) try ais.underlying_writer.print(format, args);
3330 if (format[format.len - 1] == '\n') ais.resetLine();
3331 }
3332
3333 pub fn writeByte(ais: *AutoIndentingStream, byte: u8) Error!void {
3334 try ais.applyIndent();
3335 if (ais.disabled_offset == null) try ais.underlying_writer.writeByte(byte);
3336 assert(byte != '\n');
3337 }
3338
3339 pub fn splatByteAll(ais: *AutoIndentingStream, byte: u8, n: usize) Error!void {
3340 assert(byte != '\n');
3341 try ais.applyIndent();
3342 if (ais.disabled_offset == null) try ais.underlying_writer.splatByteAll(byte, n);
3343 }
3344
3345 // Change the indent delta without changing the final indentation level
3346 pub fn setIndentDelta(ais: *AutoIndentingStream, new_indent_delta: usize) void {
3347 if (ais.indent_delta == new_indent_delta) {
3348 return;
3349 } else if (ais.indent_delta > new_indent_delta) {
3350 assert(ais.indent_delta % new_indent_delta == 0);
3351 ais.indent_count = ais.indent_count * (ais.indent_delta / new_indent_delta);
3352 } else {
3353 // assert that the current indentation (in spaces) in a multiple of the new delta
3354 assert((ais.indent_count * ais.indent_delta) % new_indent_delta == 0);
3355 ais.indent_count = ais.indent_count / (new_indent_delta / ais.indent_delta);
3356 }
3357 ais.indent_delta = new_indent_delta;
3358 }
3359
3360 pub fn insertNewline(ais: *AutoIndentingStream) Error!void {
3361 if (ais.disabled_offset == null) try ais.underlying_writer.writeByte('\n');
3362 ais.resetLine();
3363 }
3364
3365 /// Insert a newline unless the current line is blank
3366 pub fn maybeInsertNewline(ais: *AutoIndentingStream) Error!void {
3367 if (!ais.current_line_empty)
3368 try ais.insertNewline();
3369 }
3370
3371 /// Push an indent that is automatically popped after being applied
3372 pub fn pushIndentOneShot(ais: *AutoIndentingStream) void {
3373 ais.indent_one_shot_count += 1;
3374 ais.pushIndent();
3375 }
3376
3377 /// Turns all one-shot indents into regular indents
3378 /// Returns number of indents that must now be manually popped
3379 pub fn lockOneShotIndent(ais: *AutoIndentingStream) usize {
3380 const locked_count = ais.indent_one_shot_count;
3381 ais.indent_one_shot_count = 0;
3382 return locked_count;
3383 }
3384
3385 /// Push an indent that should not take effect until the next line
3386 pub fn pushIndentNextLine(ais: *AutoIndentingStream) void {
3387 ais.indent_next_line += 1;
3388 ais.pushIndent();
3389 }
3390
3391 /// Checks to see if the most recent indentation exceeds the currently pushed indents
3392 pub fn isLineOverIndented(ais: *AutoIndentingStream) bool {
3393 if (ais.current_line_empty) return false;
3394 return ais.applied_indent > ais.currentIndent();
3395 }
3396
3397 fn resetLine(ais: *AutoIndentingStream) void {
3398 ais.current_line_empty = true;
3399
3400 if (ais.disable_indent_committing > 0) return;
3401
3402 if (ais.indent_stack.items.len > 0) {
3403 // By default, we realize the most recent indentation scope.
3404 var to_realize = ais.indent_stack.items.len - 1;
3405
3406 if (ais.indent_stack.items.len >= 2 and
3407 ais.indent_stack.items[to_realize - 1].indent_type == .after_equals and
3408 ais.indent_stack.items[to_realize - 1].realized and
3409 ais.indent_stack.items[to_realize].indent_type == .binop)
3410 {
3411 // If we are in a .binop scope and our direct parent is .after_equals, don't indent.
3412 // This ensures correct indentation in the below example:
3413 //
3414 // const foo =
3415 // (x >= 'a' and x <= 'z') or //<-- we are here
3416 // (x >= 'A' and x <= 'Z');
3417 //
3418 return;
3419 }
3420
3421 if (ais.indent_stack.items[to_realize].indent_type == .field_access) {
3422 // Only realize the top-most field_access in a chain.
3423 while (to_realize > 0 and ais.indent_stack.items[to_realize - 1].indent_type == .field_access)
3424 to_realize -= 1;
3425 }
3426
3427 if (ais.indent_stack.items[to_realize].realized) return;
3428 ais.indent_stack.items[to_realize].realized = true;
3429 ais.indent_count += 1;
3430 }
3431 }
3432
3433 /// Disables indentation level changes during the next newlines until re-enabled.
3434 pub fn disableIndentCommitting(ais: *AutoIndentingStream) void {
3435 ais.disable_indent_committing += 1;
3436 }
3437
3438 pub fn enableIndentCommitting(ais: *AutoIndentingStream) void {
3439 assert(ais.disable_indent_committing > 0);
3440 ais.disable_indent_committing -= 1;
3441 }
3442
3443 pub fn pushSpace(ais: *AutoIndentingStream, space: Space) !void {
3444 try ais.space_stack.append(.{ .space = space, .indent_count = ais.indent_count });
3445 }
3446
3447 pub fn popSpace(ais: *AutoIndentingStream) void {
3448 _ = ais.space_stack.pop();
3449 }
3450
3451 /// Sets current indentation level to be the same as that of the last pushSpace.
3452 pub fn enableSpaceMode(ais: *AutoIndentingStream, space: Space) void {
3453 if (ais.space_stack.items.len == 0) return;
3454 const curr = ais.space_stack.getLast();
3455 if (curr.space != space) return;
3456 ais.space_mode = curr.indent_count;
3457 }
3458
3459 pub fn disableSpaceMode(ais: *AutoIndentingStream) void {
3460 ais.space_mode = null;
3461 }
3462
3463 pub fn lastSpaceModeIndent(ais: *AutoIndentingStream) usize {
3464 if (ais.space_stack.items.len == 0) return 0;
3465 return ais.space_stack.getLast().indent_count * ais.indent_delta;
3466 }
3467
3468 /// Push default indentation
3469 /// Doesn't actually write any indentation.
3470 /// Just primes the stream to be able to write the correct indentation if it needs to.
3471 pub fn pushIndent(ais: *AutoIndentingStream, indent_type: IndentType) !void {
3472 try ais.indent_stack.append(.{ .indent_type = indent_type, .realized = false });
3473 }
3474
3475 /// Forces an indentation level to be realized.
3476 pub fn forcePushIndent(ais: *AutoIndentingStream, indent_type: IndentType) !void {
3477 try ais.indent_stack.append(.{ .indent_type = indent_type, .realized = true });
3478 ais.indent_count += 1;
3479 }
3480
3481 pub fn popIndent(ais: *AutoIndentingStream) void {
3482 if (ais.indent_stack.pop().?.realized) {
3483 assert(ais.indent_count > 0);
3484 ais.indent_count -= 1;
3485 }
3486 }
3487
3488 pub fn indentStackEmpty(ais: *AutoIndentingStream) bool {
3489 return ais.indent_stack.items.len == 0;
3490 }
3491
3492 /// Writes ' ' bytes if the current line is empty
3493 fn applyIndent(ais: *AutoIndentingStream) Error!void {
3494 const current_indent = ais.currentIndent();
3495 if (ais.current_line_empty and current_indent > 0) {
3496 if (ais.disabled_offset == null) {
3497 try ais.underlying_writer.splatByteAll(' ', current_indent);
3498 }
3499 ais.applied_indent = current_indent;
3500 }
3501 ais.current_line_empty = false;
3502 }
3503
3504 fn currentIndent(ais: *AutoIndentingStream) usize {
3505 const indent_count = ais.space_mode orelse ais.indent_count;
3506 return indent_count * ais.indent_delta;
3507 }
3508};
lib/std/zig/AstGen.zig+8-6
......@@ -11445,7 +11445,9 @@ fn parseStrLit(
1144511445 var aw: std.io.AllocatingWriter = undefined;
1144611446 const bw = aw.fromArrayList(astgen.gpa, buf);
1144711447 defer buf.* = aw.toArrayList();
11448 break :r std.zig.string_literal.parseWrite(bw, raw_string) catch |err| return @errorCast(err);
11448 break :r std.zig.string_literal.parseWrite(bw, raw_string) catch |err| switch (err) {
11449 error.WriteFailed => return error.OutOfMemory,
11450 };
1144911451 };
1145011452 switch (result) {
1145111453 .success => return,
......@@ -13928,25 +13930,25 @@ fn lowerAstErrors(astgen: *AstGen) error{OutOfMemory}!void {
1392813930 break :blk idx - tok_start;
1392913931 };
1393013932
13931 const err: Ast.Error = .{
13933 const ast_err: Ast.Error = .{
1393213934 .tag = Ast.Error.Tag.invalid_byte,
1393313935 .token = tok,
1393413936 .extra = .{ .offset = bad_off },
1393513937 };
1393613938 msg.clearRetainingCapacity();
13937 tree.renderError(err, msg_bw) catch |e| return @errorCast(e); // TODO try @errorCast(...)
13939 tree.renderError(ast_err, msg_bw) catch return error.OutOfMemory;
1393813940 return try astgen.appendErrorTokNotesOff(tok, bad_off, "{s}", .{msg.getWritten()}, notes.items);
1393913941 }
1394013942
1394113943 var cur_err = tree.errors[0];
1394213944 for (tree.errors[1..]) |err| {
1394313945 if (err.is_note) {
13944 tree.renderError(err, msg_bw) catch |e| return @errorCast(e); // TODO try @errorCast(...)
13946 tree.renderError(err, msg_bw) catch return error.OutOfMemory;
1394513947 try notes.append(gpa, try astgen.errNoteTok(err.token, "{s}", .{msg.getWritten()}));
1394613948 } else {
1394713949 // Flush error
1394813950 const extra_offset = tree.errorOffset(cur_err);
13949 tree.renderError(cur_err, msg_bw) catch |e| return @errorCast(e); // TODO try @errorCast(...)
13951 tree.renderError(cur_err, msg_bw) catch return error.OutOfMemory;
1395013952 try astgen.appendErrorTokNotesOff(cur_err.token, extra_offset, "{s}", .{msg.getWritten()}, notes.items);
1395113953 notes.clearRetainingCapacity();
1395213954 cur_err = err;
......@@ -13960,7 +13962,7 @@ fn lowerAstErrors(astgen: *AstGen) error{OutOfMemory}!void {
1396013962
1396113963 // Flush error
1396213964 const extra_offset = tree.errorOffset(cur_err);
13963 tree.renderError(cur_err, msg_bw) catch |e| return @errorCast(e); // TODO try @errorCast(...)
13965 tree.renderError(cur_err, msg_bw) catch return error.OutOfMemory;
1396413966 try astgen.appendErrorTokNotesOff(cur_err.token, extra_offset, "{s}", .{msg.getWritten()}, notes.items);
1396513967}
1396613968
lib/std/zig/ZonGen.zig+9-5
......@@ -470,7 +470,9 @@ fn appendIdentStr(zg: *ZonGen, ident_token: Ast.TokenIndex) error{ OutOfMemory,
470470 var aw: std.io.AllocatingWriter = undefined;
471471 const bw = aw.fromArrayList(gpa, &zg.string_bytes);
472472 defer zg.string_bytes = aw.toArrayList();
473 break :r std.zig.string_literal.parseWrite(bw, raw_string) catch |err| return @errorCast(err);
473 break :r std.zig.string_literal.parseWrite(bw, raw_string) catch |err| switch (err) {
474 error.WriteFailed => return error.OutOfMemory,
475 };
474476 };
475477 switch (result) {
476478 .success => {},
......@@ -567,7 +569,9 @@ fn strLitAsString(zg: *ZonGen, str_node: Ast.Node.Index) error{ OutOfMemory, Bad
567569 var aw: std.io.AllocatingWriter = undefined;
568570 const bw = aw.fromArrayList(gpa, &zg.string_bytes);
569571 defer zg.string_bytes = aw.toArrayList();
570 break :r parseStrLit(zg.tree, str_node, bw) catch |err| return @errorCast(err);
572 break :r parseStrLit(zg.tree, str_node, bw) catch |err| switch (err) {
573 error.WriteFailed => return error.OutOfMemory,
574 };
571575 };
572576 switch (result) {
573577 .success => {},
......@@ -895,11 +899,11 @@ fn lowerAstErrors(zg: *ZonGen) Allocator.Error!void {
895899 var cur_err = tree.errors[0];
896900 for (tree.errors[1..]) |err| {
897901 if (err.is_note) {
898 tree.renderError(err, msg_bw) catch |e| return @errorCast(e); // TODO: try @errorCast(...)
902 tree.renderError(err, msg_bw) catch return error.OutOfMemory;
899903 try notes.append(gpa, try zg.errNoteTok(err.token, "{s}", .{msg.getWritten()}));
900904 } else {
901905 // Flush error
902 tree.renderError(cur_err, msg_bw) catch |e| return @errorCast(e); // TODO try @errorCast(...)
906 tree.renderError(cur_err, msg_bw) catch return error.OutOfMemory;
903907 const extra_offset = tree.errorOffset(cur_err);
904908 try zg.addErrorTokNotesOff(cur_err.token, extra_offset, "{s}", .{msg.getWritten()}, notes.items);
905909 notes.clearRetainingCapacity();
......@@ -916,7 +920,7 @@ fn lowerAstErrors(zg: *ZonGen) Allocator.Error!void {
916920
917921 // Flush error
918922 const extra_offset = tree.errorOffset(cur_err);
919 tree.renderError(cur_err, msg_bw) catch |e| return @errorCast(e); // TODO try @errorCast(...)
923 tree.renderError(cur_err, msg_bw) catch return error.OutOfMemory;
920924 try zg.addErrorTokNotesOff(cur_err.token, extra_offset, "{s}", .{msg.getWritten()}, notes.items);
921925}
922926
lib/std/zig/llvm/Builder.zig+2-2
......@@ -8945,8 +8945,8 @@ pub fn getIntrinsic(
89458945 var aw: std.io.AllocatingWriter = undefined;
89468946 const bw = aw.fromArrayList(self.gpa, &self.strtab_string_bytes);
89478947 defer self.strtab_string_bytes = aw.toArrayList();
8948 bw.print("llvm.{s}", .{@tagName(id)}) catch |err| return @errorCast(err);
8949 for (overload) |ty| bw.print(".{fm}", .{ty.fmt(self)}) catch |err| return @errorCast(err);
8948 bw.print("llvm.{s}", .{@tagName(id)}) catch return error.OutOfMemory;
8949 for (overload) |ty| bw.print(".{fm}", .{ty.fmt(self)}) catch return error.OutOfMemory;
89508950 }
89518951 break :name try self.trailingStrtabString();
89528952 };
lib/std/zig/render.zig deleted-3503
......@@ -1,3503 +0,0 @@
1const std = @import("../std.zig");
2const assert = std.debug.assert;
3const mem = std.mem;
4const Allocator = std.mem.Allocator;
5const meta = std.meta;
6const Ast = std.zig.Ast;
7const Token = std.zig.Token;
8const primitives = std.zig.primitives;
9
10const indent_delta = 4;
11const asm_indent_delta = 2;
12
13pub const Error = Ast.RenderError;
14
15pub const Fixups = struct {
16 /// The key is the mut token (`var`/`const`) of the variable declaration
17 /// that should have a `_ = foo;` inserted afterwards.
18 unused_var_decls: std.AutoHashMapUnmanaged(Ast.TokenIndex, void) = .empty,
19 /// The functions in this unordered set of AST fn decl nodes will render
20 /// with a function body of `@trap()` instead, with all parameters
21 /// discarded.
22 gut_functions: std.AutoHashMapUnmanaged(Ast.Node.Index, void) = .empty,
23 /// These global declarations will be omitted.
24 omit_nodes: std.AutoHashMapUnmanaged(Ast.Node.Index, void) = .empty,
25 /// These expressions will be replaced with the string value.
26 replace_nodes_with_string: std.AutoHashMapUnmanaged(Ast.Node.Index, []const u8) = .empty,
27 /// The string value will be inserted directly after the node.
28 append_string_after_node: std.AutoHashMapUnmanaged(Ast.Node.Index, []const u8) = .empty,
29 /// These nodes will be replaced with a different node.
30 replace_nodes_with_node: std.AutoHashMapUnmanaged(Ast.Node.Index, Ast.Node.Index) = .empty,
31 /// Change all identifier names matching the key to be value instead.
32 rename_identifiers: std.StringArrayHashMapUnmanaged([]const u8) = .empty,
33
34 /// All `@import` builtin calls which refer to a file path will be prefixed
35 /// with this path.
36 rebase_imported_paths: ?[]const u8 = null,
37
38 pub fn count(f: Fixups) usize {
39 return f.unused_var_decls.count() +
40 f.gut_functions.count() +
41 f.omit_nodes.count() +
42 f.replace_nodes_with_string.count() +
43 f.append_string_after_node.count() +
44 f.replace_nodes_with_node.count() +
45 f.rename_identifiers.count() +
46 @intFromBool(f.rebase_imported_paths != null);
47 }
48
49 pub fn clearRetainingCapacity(f: *Fixups) void {
50 f.unused_var_decls.clearRetainingCapacity();
51 f.gut_functions.clearRetainingCapacity();
52 f.omit_nodes.clearRetainingCapacity();
53 f.replace_nodes_with_string.clearRetainingCapacity();
54 f.append_string_after_node.clearRetainingCapacity();
55 f.replace_nodes_with_node.clearRetainingCapacity();
56 f.rename_identifiers.clearRetainingCapacity();
57
58 f.rebase_imported_paths = null;
59 }
60
61 pub fn deinit(f: *Fixups, gpa: Allocator) void {
62 f.unused_var_decls.deinit(gpa);
63 f.gut_functions.deinit(gpa);
64 f.omit_nodes.deinit(gpa);
65 f.replace_nodes_with_string.deinit(gpa);
66 f.append_string_after_node.deinit(gpa);
67 f.replace_nodes_with_node.deinit(gpa);
68 f.rename_identifiers.deinit(gpa);
69 f.* = undefined;
70 }
71};
72
73const Render = struct {
74 gpa: Allocator,
75 ais: *AutoIndentingStream,
76 tree: Ast,
77 fixups: Fixups,
78};
79
80pub fn renderTree(gpa: Allocator, bw: *std.io.BufferedWriter, tree: Ast, fixups: Fixups) Error!void {
81 assert(tree.errors.len == 0); // Cannot render an invalid tree.
82 var auto_indenting_stream: AutoIndentingStream = .init(gpa, bw, indent_delta);
83 defer auto_indenting_stream.deinit();
84 var r: Render = .{
85 .gpa = gpa,
86 .ais = &auto_indenting_stream,
87 .tree = tree,
88 .fixups = fixups,
89 };
90
91 // Render all the line comments at the beginning of the file.
92 const comment_end_loc = tree.tokenStart(0);
93 _ = try renderComments(&r, 0, comment_end_loc);
94
95 if (tree.tokenTag(0) == .container_doc_comment) {
96 try renderContainerDocComments(&r, 0);
97 }
98
99 switch (tree.mode) {
100 .zig => try renderMembers(&r, tree.rootDecls()),
101 .zon => {
102 try renderExpression(
103 &r,
104 tree.rootDecls()[0],
105 .newline,
106 );
107 },
108 }
109
110 if (auto_indenting_stream.disabled_offset) |disabled_offset| {
111 try writeFixingWhitespace(auto_indenting_stream.underlying_writer, tree.source[disabled_offset..]);
112 }
113}
114
115/// Render all members in the given slice, keeping empty lines where appropriate
116fn renderMembers(r: *Render, members: []const Ast.Node.Index) Error!void {
117 const tree = r.tree;
118 if (members.len == 0) return;
119 const container: Container = for (members) |member| {
120 if (tree.fullContainerField(member)) |field| if (!field.ast.tuple_like) break .other;
121 } else .tuple;
122 try renderMember(r, container, members[0], .newline);
123 for (members[1..]) |member| {
124 try renderExtraNewline(r, member);
125 try renderMember(r, container, member, .newline);
126 }
127}
128
129const Container = enum {
130 @"enum",
131 tuple,
132 other,
133};
134
135fn renderMember(
136 r: *Render,
137 container: Container,
138 decl: Ast.Node.Index,
139 space: Space,
140) Error!void {
141 const tree = r.tree;
142 const ais = r.ais;
143 if (r.fixups.omit_nodes.contains(decl)) return;
144 try renderDocComments(r, tree.firstToken(decl));
145 switch (tree.nodeTag(decl)) {
146 .fn_decl => {
147 // Some examples:
148 // pub extern "foo" fn ...
149 // export fn ...
150 const fn_proto, const body_node = tree.nodeData(decl).node_and_node;
151 const fn_token = tree.nodeMainToken(fn_proto);
152 // Go back to the first token we should render here.
153 var i = fn_token;
154 while (i > 0) {
155 i -= 1;
156 switch (tree.tokenTag(i)) {
157 .keyword_extern,
158 .keyword_export,
159 .keyword_pub,
160 .string_literal,
161 .keyword_inline,
162 .keyword_noinline,
163 => continue,
164
165 else => {
166 i += 1;
167 break;
168 },
169 }
170 }
171
172 while (i < fn_token) : (i += 1) {
173 try renderToken(r, i, .space);
174 }
175 switch (tree.nodeTag(fn_proto)) {
176 .fn_proto_one, .fn_proto => {
177 var buf: [1]Ast.Node.Index = undefined;
178 const opt_callconv_expr = if (tree.nodeTag(fn_proto) == .fn_proto_one)
179 tree.fnProtoOne(&buf, fn_proto).ast.callconv_expr
180 else
181 tree.fnProto(fn_proto).ast.callconv_expr;
182
183 // Keep in sync with logic in `renderFnProto`. Search this file for the marker PROMOTE_CALLCONV_INLINE
184 if (opt_callconv_expr.unwrap()) |callconv_expr| {
185 if (tree.nodeTag(callconv_expr) == .enum_literal) {
186 if (mem.eql(u8, "@\"inline\"", tree.tokenSlice(tree.nodeMainToken(callconv_expr)))) {
187 try ais.underlying_writer.writeAll("inline ");
188 }
189 }
190 }
191 },
192 .fn_proto_simple, .fn_proto_multi => {},
193 else => unreachable,
194 }
195 try renderExpression(r, fn_proto, .space);
196 if (r.fixups.gut_functions.contains(decl)) {
197 try ais.pushIndent(.normal);
198 const lbrace = tree.nodeMainToken(body_node);
199 try renderToken(r, lbrace, .newline);
200 try discardAllParams(r, fn_proto);
201 try ais.writeAll("@trap();");
202 ais.popIndent();
203 try ais.insertNewline();
204 try renderToken(r, tree.lastToken(body_node), space); // rbrace
205 } else if (r.fixups.unused_var_decls.count() != 0) {
206 try ais.pushIndent(.normal);
207 const lbrace = tree.nodeMainToken(body_node);
208 try renderToken(r, lbrace, .newline);
209
210 var fn_proto_buf: [1]Ast.Node.Index = undefined;
211 const full_fn_proto = tree.fullFnProto(&fn_proto_buf, fn_proto).?;
212 var it = full_fn_proto.iterate(&tree);
213 while (it.next()) |param| {
214 const name_ident = param.name_token.?;
215 assert(tree.tokenTag(name_ident) == .identifier);
216 if (r.fixups.unused_var_decls.contains(name_ident)) {
217 try ais.writeAll("_ = ");
218 try ais.writeAll(tokenSliceForRender(r.tree, name_ident));
219 try ais.writeAll(";\n");
220 }
221 }
222 var statements_buf: [2]Ast.Node.Index = undefined;
223 const statements = tree.blockStatements(&statements_buf, body_node).?;
224 return finishRenderBlock(r, body_node, statements, space);
225 } else {
226 return renderExpression(r, body_node, space);
227 }
228 },
229 .fn_proto_simple,
230 .fn_proto_multi,
231 .fn_proto_one,
232 .fn_proto,
233 => {
234 // Extern function prototypes are parsed as these tags.
235 // Go back to the first token we should render here.
236 const fn_token = tree.nodeMainToken(decl);
237 var i = fn_token;
238 while (i > 0) {
239 i -= 1;
240 switch (tree.tokenTag(i)) {
241 .keyword_extern,
242 .keyword_export,
243 .keyword_pub,
244 .string_literal,
245 .keyword_inline,
246 .keyword_noinline,
247 => continue,
248
249 else => {
250 i += 1;
251 break;
252 },
253 }
254 }
255 while (i < fn_token) : (i += 1) {
256 try renderToken(r, i, .space);
257 }
258 try renderExpression(r, decl, .none);
259 return renderToken(r, tree.lastToken(decl) + 1, space); // semicolon
260 },
261
262 .@"usingnamespace" => {
263 const main_token = tree.nodeMainToken(decl);
264 const expr = tree.nodeData(decl).node;
265 if (tree.isTokenPrecededByTags(main_token, &.{.keyword_pub})) {
266 try renderToken(r, main_token - 1, .space); // pub
267 }
268 try renderToken(r, main_token, .space); // usingnamespace
269 try renderExpression(r, expr, .none);
270 return renderToken(r, tree.lastToken(expr) + 1, space); // ;
271 },
272
273 .global_var_decl,
274 .local_var_decl,
275 .simple_var_decl,
276 .aligned_var_decl,
277 => {
278 try ais.pushSpace(.semicolon);
279 try renderVarDecl(r, tree.fullVarDecl(decl).?, false, .semicolon);
280 ais.popSpace();
281 },
282
283 .test_decl => {
284 const test_token = tree.nodeMainToken(decl);
285 const opt_name_token, const block_node = tree.nodeData(decl).opt_token_and_node;
286 try renderToken(r, test_token, .space);
287 if (opt_name_token.unwrap()) |name_token| {
288 switch (tree.tokenTag(name_token)) {
289 .string_literal => try renderToken(r, name_token, .space),
290 .identifier => try renderIdentifier(r, name_token, .space, .preserve_when_shadowing),
291 else => unreachable,
292 }
293 }
294 try renderExpression(r, block_node, space);
295 },
296
297 .container_field_init,
298 .container_field_align,
299 .container_field,
300 => return renderContainerField(r, container, tree.fullContainerField(decl).?, space),
301
302 .@"comptime" => return renderExpression(r, decl, space),
303
304 .root => unreachable,
305 else => unreachable,
306 }
307}
308
309/// Render all expressions in the slice, keeping empty lines where appropriate
310fn renderExpressions(r: *Render, expressions: []const Ast.Node.Index, space: Space) Error!void {
311 if (expressions.len == 0) return;
312 try renderExpression(r, expressions[0], space);
313 for (expressions[1..]) |expression| {
314 try renderExtraNewline(r, expression);
315 try renderExpression(r, expression, space);
316 }
317}
318
319fn renderExpression(r: *Render, node: Ast.Node.Index, space: Space) Error!void {
320 const tree = r.tree;
321 const ais = r.ais;
322 if (r.fixups.replace_nodes_with_string.get(node)) |replacement| {
323 try ais.writeAll(replacement);
324 try renderOnlySpace(r, space);
325 return;
326 } else if (r.fixups.replace_nodes_with_node.get(node)) |replacement| {
327 return renderExpression(r, replacement, space);
328 }
329 switch (tree.nodeTag(node)) {
330 .identifier => {
331 const token_index = tree.nodeMainToken(node);
332 return renderIdentifier(r, token_index, space, .preserve_when_shadowing);
333 },
334
335 .number_literal,
336 .char_literal,
337 .unreachable_literal,
338 .anyframe_literal,
339 .string_literal,
340 => return renderToken(r, tree.nodeMainToken(node), space),
341
342 .multiline_string_literal => {
343 try ais.maybeInsertNewline();
344
345 const first_tok, const last_tok = tree.nodeData(node).token_and_token;
346 for (first_tok..last_tok + 1) |i| {
347 try renderToken(r, @intCast(i), .newline);
348 }
349
350 const next_token = last_tok + 1;
351 const next_token_tag = tree.tokenTag(next_token);
352
353 // dedent the next thing that comes after a multiline string literal
354 if (!ais.indentStackEmpty() and
355 next_token_tag != .colon and
356 ((next_token_tag != .semicolon and next_token_tag != .comma) or
357 ais.lastSpaceModeIndent() < ais.currentIndent()))
358 {
359 ais.popIndent();
360 try ais.pushIndent(.normal);
361 }
362
363 switch (space) {
364 .none, .space, .newline, .skip => {},
365 .semicolon => if (next_token_tag == .semicolon) try renderTokenOverrideSpaceMode(r, next_token, .newline, .semicolon),
366 .comma => if (next_token_tag == .comma) try renderTokenOverrideSpaceMode(r, next_token, .newline, .comma),
367 .comma_space => if (next_token_tag == .comma) try renderToken(r, next_token, .space),
368 }
369 },
370
371 .error_value => {
372 const main_token = tree.nodeMainToken(node);
373 try renderToken(r, main_token, .none);
374 try renderToken(r, main_token + 1, .none);
375 return renderIdentifier(r, main_token + 2, space, .eagerly_unquote);
376 },
377
378 .block_two,
379 .block_two_semicolon,
380 .block,
381 .block_semicolon,
382 => {
383 var buf: [2]Ast.Node.Index = undefined;
384 const statements = tree.blockStatements(&buf, node).?;
385 return renderBlock(r, node, statements, space);
386 },
387
388 .@"errdefer" => {
389 const defer_token = tree.nodeMainToken(node);
390 const maybe_payload_token, const expr = tree.nodeData(node).opt_token_and_node;
391
392 try renderToken(r, defer_token, .space);
393 if (maybe_payload_token.unwrap()) |payload_token| {
394 try renderToken(r, payload_token - 1, .none); // |
395 try renderIdentifier(r, payload_token, .none, .preserve_when_shadowing); // identifier
396 try renderToken(r, payload_token + 1, .space); // |
397 }
398 return renderExpression(r, expr, space);
399 },
400
401 .@"defer",
402 .@"comptime",
403 .@"nosuspend",
404 .@"suspend",
405 => {
406 const main_token = tree.nodeMainToken(node);
407 const item = tree.nodeData(node).node;
408 try renderToken(r, main_token, .space);
409 return renderExpression(r, item, space);
410 },
411
412 .@"catch" => {
413 const main_token = tree.nodeMainToken(node);
414 const lhs, const rhs = tree.nodeData(node).node_and_node;
415 const fallback_first = tree.firstToken(rhs);
416
417 const same_line = tree.tokensOnSameLine(main_token, fallback_first);
418 const after_op_space = if (same_line) Space.space else Space.newline;
419
420 try renderExpression(r, lhs, .space); // target
421
422 try ais.pushIndent(.normal);
423 if (tree.tokenTag(fallback_first - 1) == .pipe) {
424 try renderToken(r, main_token, .space); // catch keyword
425 try renderToken(r, main_token + 1, .none); // pipe
426 try renderIdentifier(r, main_token + 2, .none, .preserve_when_shadowing); // payload identifier
427 try renderToken(r, main_token + 3, after_op_space); // pipe
428 } else {
429 assert(tree.tokenTag(fallback_first - 1) == .keyword_catch);
430 try renderToken(r, main_token, after_op_space); // catch keyword
431 }
432 try renderExpression(r, rhs, space); // fallback
433 ais.popIndent();
434 },
435
436 .field_access => {
437 const lhs, const name_token = tree.nodeData(node).node_and_token;
438 const dot_token = name_token - 1;
439
440 try ais.pushIndent(.field_access);
441 try renderExpression(r, lhs, .none);
442
443 // Allow a line break between the lhs and the dot if the lhs and rhs
444 // are on different lines.
445 const lhs_last_token = tree.lastToken(lhs);
446 const same_line = tree.tokensOnSameLine(lhs_last_token, name_token);
447 if (!same_line and !hasComment(tree, lhs_last_token, dot_token)) try ais.insertNewline();
448
449 try renderToken(r, dot_token, .none);
450
451 try renderIdentifier(r, name_token, space, .eagerly_unquote); // field
452 ais.popIndent();
453 },
454
455 .error_union,
456 .switch_range,
457 => {
458 const lhs, const rhs = tree.nodeData(node).node_and_node;
459 try renderExpression(r, lhs, .none);
460 try renderToken(r, tree.nodeMainToken(node), .none);
461 return renderExpression(r, rhs, space);
462 },
463 .for_range => {
464 const start, const opt_end = tree.nodeData(node).node_and_opt_node;
465 try renderExpression(r, start, .none);
466 if (opt_end.unwrap()) |end| {
467 try renderToken(r, tree.nodeMainToken(node), .none);
468 return renderExpression(r, end, space);
469 } else {
470 return renderToken(r, tree.nodeMainToken(node), space);
471 }
472 },
473
474 .assign,
475 .assign_bit_and,
476 .assign_bit_or,
477 .assign_shl,
478 .assign_shl_sat,
479 .assign_shr,
480 .assign_bit_xor,
481 .assign_div,
482 .assign_sub,
483 .assign_sub_wrap,
484 .assign_sub_sat,
485 .assign_mod,
486 .assign_add,
487 .assign_add_wrap,
488 .assign_add_sat,
489 .assign_mul,
490 .assign_mul_wrap,
491 .assign_mul_sat,
492 => {
493 const lhs, const rhs = tree.nodeData(node).node_and_node;
494 try renderExpression(r, lhs, .space);
495 const op_token = tree.nodeMainToken(node);
496 try ais.pushIndent(.after_equals);
497 if (tree.tokensOnSameLine(op_token, op_token + 1)) {
498 try renderToken(r, op_token, .space);
499 } else {
500 try renderToken(r, op_token, .newline);
501 }
502 try renderExpression(r, rhs, space);
503 ais.popIndent();
504 },
505
506 .add,
507 .add_wrap,
508 .add_sat,
509 .array_cat,
510 .array_mult,
511 .bang_equal,
512 .bit_and,
513 .bit_or,
514 .shl,
515 .shl_sat,
516 .shr,
517 .bit_xor,
518 .bool_and,
519 .bool_or,
520 .div,
521 .equal_equal,
522 .greater_or_equal,
523 .greater_than,
524 .less_or_equal,
525 .less_than,
526 .merge_error_sets,
527 .mod,
528 .mul,
529 .mul_wrap,
530 .mul_sat,
531 .sub,
532 .sub_wrap,
533 .sub_sat,
534 .@"orelse",
535 => {
536 const lhs, const rhs = tree.nodeData(node).node_and_node;
537 try renderExpression(r, lhs, .space);
538 const op_token = tree.nodeMainToken(node);
539 try ais.pushIndent(.binop);
540 if (tree.tokensOnSameLine(op_token, op_token + 1)) {
541 try renderToken(r, op_token, .space);
542 } else {
543 try renderToken(r, op_token, .newline);
544 }
545 try renderExpression(r, rhs, space);
546 ais.popIndent();
547 },
548
549 .assign_destructure => {
550 const full = tree.assignDestructure(node);
551 if (full.comptime_token) |comptime_token| {
552 try renderToken(r, comptime_token, .space);
553 }
554
555 for (full.ast.variables, 0..) |variable_node, i| {
556 const variable_space: Space = if (i == full.ast.variables.len - 1) .space else .comma_space;
557 switch (tree.nodeTag(variable_node)) {
558 .global_var_decl,
559 .local_var_decl,
560 .simple_var_decl,
561 .aligned_var_decl,
562 => {
563 try renderVarDecl(r, tree.fullVarDecl(variable_node).?, true, variable_space);
564 },
565 else => try renderExpression(r, variable_node, variable_space),
566 }
567 }
568 try ais.pushIndent(.after_equals);
569 if (tree.tokensOnSameLine(full.ast.equal_token, full.ast.equal_token + 1)) {
570 try renderToken(r, full.ast.equal_token, .space);
571 } else {
572 try renderToken(r, full.ast.equal_token, .newline);
573 }
574 try renderExpression(r, full.ast.value_expr, space);
575 ais.popIndent();
576 },
577
578 .bit_not,
579 .bool_not,
580 .negation,
581 .negation_wrap,
582 .optional_type,
583 .address_of,
584 => {
585 try renderToken(r, tree.nodeMainToken(node), .none);
586 return renderExpression(r, tree.nodeData(node).node, space);
587 },
588
589 .@"try",
590 .@"resume",
591 .@"await",
592 => {
593 try renderToken(r, tree.nodeMainToken(node), .space);
594 return renderExpression(r, tree.nodeData(node).node, space);
595 },
596
597 .array_type,
598 .array_type_sentinel,
599 => return renderArrayType(r, tree.fullArrayType(node).?, space),
600
601 .ptr_type_aligned,
602 .ptr_type_sentinel,
603 .ptr_type,
604 .ptr_type_bit_range,
605 => return renderPtrType(r, tree.fullPtrType(node).?, space),
606
607 .array_init_one,
608 .array_init_one_comma,
609 .array_init_dot_two,
610 .array_init_dot_two_comma,
611 .array_init_dot,
612 .array_init_dot_comma,
613 .array_init,
614 .array_init_comma,
615 => {
616 var elements: [2]Ast.Node.Index = undefined;
617 return renderArrayInit(r, tree.fullArrayInit(&elements, node).?, space);
618 },
619
620 .struct_init_one,
621 .struct_init_one_comma,
622 .struct_init_dot_two,
623 .struct_init_dot_two_comma,
624 .struct_init_dot,
625 .struct_init_dot_comma,
626 .struct_init,
627 .struct_init_comma,
628 => {
629 var buf: [2]Ast.Node.Index = undefined;
630 return renderStructInit(r, node, tree.fullStructInit(&buf, node).?, space);
631 },
632
633 .call_one,
634 .call_one_comma,
635 .async_call_one,
636 .async_call_one_comma,
637 .call,
638 .call_comma,
639 .async_call,
640 .async_call_comma,
641 => {
642 var buf: [1]Ast.Node.Index = undefined;
643 return renderCall(r, tree.fullCall(&buf, node).?, space);
644 },
645
646 .array_access => {
647 const lhs, const rhs = tree.nodeData(node).node_and_node;
648 const lbracket = tree.firstToken(rhs) - 1;
649 const rbracket = tree.lastToken(rhs) + 1;
650 const one_line = tree.tokensOnSameLine(lbracket, rbracket);
651 const inner_space = if (one_line) Space.none else Space.newline;
652 try renderExpression(r, lhs, .none);
653 try ais.pushIndent(.normal);
654 try renderToken(r, lbracket, inner_space); // [
655 try renderExpression(r, rhs, inner_space);
656 ais.popIndent();
657 return renderToken(r, rbracket, space); // ]
658 },
659
660 .slice_open,
661 .slice,
662 .slice_sentinel,
663 => return renderSlice(r, node, tree.fullSlice(node).?, space),
664
665 .deref => {
666 try renderExpression(r, tree.nodeData(node).node, .none);
667 return renderToken(r, tree.nodeMainToken(node), space);
668 },
669
670 .unwrap_optional => {
671 const lhs, const question_mark = tree.nodeData(node).node_and_token;
672 const dot_token = question_mark - 1;
673 try renderExpression(r, lhs, .none);
674 try renderToken(r, dot_token, .none);
675 return renderToken(r, question_mark, space);
676 },
677
678 .@"break", .@"continue" => {
679 const main_token = tree.nodeMainToken(node);
680 const opt_label_token, const opt_target = tree.nodeData(node).opt_token_and_opt_node;
681 if (opt_label_token == .none and opt_target == .none) {
682 try renderToken(r, main_token, space); // break/continue
683 } else if (opt_label_token == .none and opt_target != .none) {
684 const target = opt_target.unwrap().?;
685 try renderToken(r, main_token, .space); // break/continue
686 try renderExpression(r, target, space);
687 } else if (opt_label_token != .none and opt_target == .none) {
688 const label_token = opt_label_token.unwrap().?;
689 try renderToken(r, main_token, .space); // break/continue
690 try renderToken(r, label_token - 1, .none); // :
691 try renderIdentifier(r, label_token, space, .eagerly_unquote); // identifier
692 } else if (opt_label_token != .none and opt_target != .none) {
693 const label_token = opt_label_token.unwrap().?;
694 const target = opt_target.unwrap().?;
695 try renderToken(r, main_token, .space); // break/continue
696 try renderToken(r, label_token - 1, .none); // :
697 try renderIdentifier(r, label_token, .space, .eagerly_unquote); // identifier
698 try renderExpression(r, target, space);
699 } else unreachable;
700 },
701
702 .@"return" => {
703 if (tree.nodeData(node).opt_node.unwrap()) |expr| {
704 try renderToken(r, tree.nodeMainToken(node), .space);
705 try renderExpression(r, expr, space);
706 } else {
707 try renderToken(r, tree.nodeMainToken(node), space);
708 }
709 },
710
711 .grouped_expression => {
712 const expr, const rparen = tree.nodeData(node).node_and_token;
713 try ais.pushIndent(.normal);
714 try renderToken(r, tree.nodeMainToken(node), .none); // lparen
715 try renderExpression(r, expr, .none);
716 ais.popIndent();
717 return renderToken(r, rparen, space);
718 },
719
720 .container_decl,
721 .container_decl_trailing,
722 .container_decl_arg,
723 .container_decl_arg_trailing,
724 .container_decl_two,
725 .container_decl_two_trailing,
726 .tagged_union,
727 .tagged_union_trailing,
728 .tagged_union_enum_tag,
729 .tagged_union_enum_tag_trailing,
730 .tagged_union_two,
731 .tagged_union_two_trailing,
732 => {
733 var buf: [2]Ast.Node.Index = undefined;
734 return renderContainerDecl(r, node, tree.fullContainerDecl(&buf, node).?, space);
735 },
736
737 .error_set_decl => {
738 const error_token = tree.nodeMainToken(node);
739 const lbrace, const rbrace = tree.nodeData(node).token_and_token;
740
741 try renderToken(r, error_token, .none);
742
743 if (lbrace + 1 == rbrace) {
744 // There is nothing between the braces so render condensed: `error{}`
745 try renderToken(r, lbrace, .none);
746 return renderToken(r, rbrace, space);
747 } else if (lbrace + 2 == rbrace and tree.tokenTag(lbrace + 1) == .identifier) {
748 // There is exactly one member and no trailing comma or
749 // comments, so render without surrounding spaces: `error{Foo}`
750 try renderToken(r, lbrace, .none);
751 try renderIdentifier(r, lbrace + 1, .none, .eagerly_unquote); // identifier
752 return renderToken(r, rbrace, space);
753 } else if (tree.tokenTag(rbrace - 1) == .comma) {
754 // There is a trailing comma so render each member on a new line.
755 try ais.pushIndent(.normal);
756 try renderToken(r, lbrace, .newline);
757 var i = lbrace + 1;
758 while (i < rbrace) : (i += 1) {
759 if (i > lbrace + 1) try renderExtraNewlineToken(r, i);
760 switch (tree.tokenTag(i)) {
761 .doc_comment => try renderToken(r, i, .newline),
762 .identifier => {
763 try ais.pushSpace(.comma);
764 try renderIdentifier(r, i, .comma, .eagerly_unquote);
765 ais.popSpace();
766 },
767 .comma => {},
768 else => unreachable,
769 }
770 }
771 ais.popIndent();
772 return renderToken(r, rbrace, space);
773 } else {
774 // There is no trailing comma so render everything on one line.
775 try renderToken(r, lbrace, .space);
776 var i = lbrace + 1;
777 while (i < rbrace) : (i += 1) {
778 switch (tree.tokenTag(i)) {
779 .doc_comment => unreachable, // TODO
780 .identifier => try renderIdentifier(r, i, .comma_space, .eagerly_unquote),
781 .comma => {},
782 else => unreachable,
783 }
784 }
785 return renderToken(r, rbrace, space);
786 }
787 },
788
789 .builtin_call_two,
790 .builtin_call_two_comma,
791 .builtin_call,
792 .builtin_call_comma,
793 => {
794 var buf: [2]Ast.Node.Index = undefined;
795 const params = tree.builtinCallParams(&buf, node).?;
796 return renderBuiltinCall(r, tree.nodeMainToken(node), params, space);
797 },
798
799 .fn_proto_simple,
800 .fn_proto_multi,
801 .fn_proto_one,
802 .fn_proto,
803 => {
804 var buf: [1]Ast.Node.Index = undefined;
805 return renderFnProto(r, tree.fullFnProto(&buf, node).?, space);
806 },
807
808 .anyframe_type => {
809 const main_token = tree.nodeMainToken(node);
810 try renderToken(r, main_token, .none); // anyframe
811 try renderToken(r, main_token + 1, .none); // ->
812 return renderExpression(r, tree.nodeData(node).token_and_node[1], space);
813 },
814
815 .@"switch",
816 .switch_comma,
817 => {
818 const full = tree.switchFull(node);
819
820 if (full.label_token) |label_token| {
821 try renderIdentifier(r, label_token, .none, .eagerly_unquote); // label
822 try renderToken(r, label_token + 1, .space); // :
823 }
824
825 const rparen = tree.lastToken(full.ast.condition) + 1;
826
827 try renderToken(r, full.ast.switch_token, .space); // switch
828 try renderToken(r, full.ast.switch_token + 1, .none); // (
829 try renderExpression(r, full.ast.condition, .none); // condition expression
830 try renderToken(r, rparen, .space); // )
831
832 try ais.pushIndent(.normal);
833 if (full.ast.cases.len == 0) {
834 try renderToken(r, rparen + 1, .none); // {
835 } else {
836 try renderToken(r, rparen + 1, .newline); // {
837 try ais.pushSpace(.comma);
838 try renderExpressions(r, full.ast.cases, .comma);
839 ais.popSpace();
840 }
841 ais.popIndent();
842 return renderToken(r, tree.lastToken(node), space); // }
843 },
844
845 .switch_case_one,
846 .switch_case_inline_one,
847 .switch_case,
848 .switch_case_inline,
849 => return renderSwitchCase(r, tree.fullSwitchCase(node).?, space),
850
851 .while_simple,
852 .while_cont,
853 .@"while",
854 => return renderWhile(r, tree.fullWhile(node).?, space),
855
856 .for_simple,
857 .@"for",
858 => return renderFor(r, tree.fullFor(node).?, space),
859
860 .if_simple,
861 .@"if",
862 => return renderIf(r, tree.fullIf(node).?, space),
863
864 .asm_simple,
865 .@"asm",
866 => return renderAsm(r, tree.fullAsm(node).?, space),
867
868 .enum_literal => {
869 try renderToken(r, tree.nodeMainToken(node) - 1, .none); // .
870 return renderIdentifier(r, tree.nodeMainToken(node), space, .eagerly_unquote); // name
871 },
872
873 .fn_decl => unreachable,
874 .container_field => unreachable,
875 .container_field_init => unreachable,
876 .container_field_align => unreachable,
877 .root => unreachable,
878 .global_var_decl => unreachable,
879 .local_var_decl => unreachable,
880 .simple_var_decl => unreachable,
881 .aligned_var_decl => unreachable,
882 .@"usingnamespace" => unreachable,
883 .test_decl => unreachable,
884 .asm_output => unreachable,
885 .asm_input => unreachable,
886 }
887}
888
889/// Same as `renderExpression`, but afterwards looks for any
890/// append_string_after_node fixups to apply
891fn renderExpressionFixup(r: *Render, node: Ast.Node.Index, space: Space) Error!void {
892 const ais = r.ais;
893 try renderExpression(r, node, space);
894 if (r.fixups.append_string_after_node.get(node)) |bytes| {
895 try ais.writeAll(bytes);
896 }
897}
898
899fn renderArrayType(
900 r: *Render,
901 array_type: Ast.full.ArrayType,
902 space: Space,
903) Error!void {
904 const tree = r.tree;
905 const ais = r.ais;
906 const rbracket = tree.firstToken(array_type.ast.elem_type) - 1;
907 const one_line = tree.tokensOnSameLine(array_type.ast.lbracket, rbracket);
908 const inner_space = if (one_line) Space.none else Space.newline;
909 try ais.pushIndent(.normal);
910 try renderToken(r, array_type.ast.lbracket, inner_space); // lbracket
911 try renderExpression(r, array_type.ast.elem_count, inner_space);
912 if (array_type.ast.sentinel.unwrap()) |sentinel| {
913 try renderToken(r, tree.firstToken(sentinel) - 1, inner_space); // colon
914 try renderExpression(r, sentinel, inner_space);
915 }
916 ais.popIndent();
917 try renderToken(r, rbracket, .none); // rbracket
918 return renderExpression(r, array_type.ast.elem_type, space);
919}
920
921fn renderPtrType(r: *Render, ptr_type: Ast.full.PtrType, space: Space) Error!void {
922 const tree = r.tree;
923 const main_token = ptr_type.ast.main_token;
924 switch (ptr_type.size) {
925 .one => {
926 // Since ** tokens exist and the same token is shared by two
927 // nested pointer types, we check to see if we are the parent
928 // in such a relationship. If so, skip rendering anything for
929 // this pointer type and rely on the child to render our asterisk
930 // as well when it renders the ** token.
931 if (tree.tokenTag(main_token) == .asterisk_asterisk and
932 main_token == tree.nodeMainToken(ptr_type.ast.child_type))
933 {
934 return renderExpression(r, ptr_type.ast.child_type, space);
935 }
936 try renderToken(r, main_token, .none); // asterisk
937 },
938 .many => {
939 if (ptr_type.ast.sentinel.unwrap()) |sentinel| {
940 try renderToken(r, main_token, .none); // lbracket
941 try renderToken(r, main_token + 1, .none); // asterisk
942 try renderToken(r, main_token + 2, .none); // colon
943 try renderExpression(r, sentinel, .none);
944 try renderToken(r, tree.lastToken(sentinel) + 1, .none); // rbracket
945 } else {
946 try renderToken(r, main_token, .none); // lbracket
947 try renderToken(r, main_token + 1, .none); // asterisk
948 try renderToken(r, main_token + 2, .none); // rbracket
949 }
950 },
951 .c => {
952 try renderToken(r, main_token, .none); // lbracket
953 try renderToken(r, main_token + 1, .none); // asterisk
954 try renderToken(r, main_token + 2, .none); // c
955 try renderToken(r, main_token + 3, .none); // rbracket
956 },
957 .slice => {
958 if (ptr_type.ast.sentinel.unwrap()) |sentinel| {
959 try renderToken(r, main_token, .none); // lbracket
960 try renderToken(r, main_token + 1, .none); // colon
961 try renderExpression(r, sentinel, .none);
962 try renderToken(r, tree.lastToken(sentinel) + 1, .none); // rbracket
963 } else {
964 try renderToken(r, main_token, .none); // lbracket
965 try renderToken(r, main_token + 1, .none); // rbracket
966 }
967 },
968 }
969
970 if (ptr_type.allowzero_token) |allowzero_token| {
971 try renderToken(r, allowzero_token, .space);
972 }
973
974 if (ptr_type.ast.align_node.unwrap()) |align_node| {
975 const align_first = tree.firstToken(align_node);
976 try renderToken(r, align_first - 2, .none); // align
977 try renderToken(r, align_first - 1, .none); // lparen
978 try renderExpression(r, align_node, .none);
979 if (ptr_type.ast.bit_range_start.unwrap()) |bit_range_start| {
980 const bit_range_end = ptr_type.ast.bit_range_end.unwrap().?;
981 try renderToken(r, tree.firstToken(bit_range_start) - 1, .none); // colon
982 try renderExpression(r, bit_range_start, .none);
983 try renderToken(r, tree.firstToken(bit_range_end) - 1, .none); // colon
984 try renderExpression(r, bit_range_end, .none);
985 try renderToken(r, tree.lastToken(bit_range_end) + 1, .space); // rparen
986 } else {
987 try renderToken(r, tree.lastToken(align_node) + 1, .space); // rparen
988 }
989 }
990
991 if (ptr_type.ast.addrspace_node.unwrap()) |addrspace_node| {
992 const addrspace_first = tree.firstToken(addrspace_node);
993 try renderToken(r, addrspace_first - 2, .none); // addrspace
994 try renderToken(r, addrspace_first - 1, .none); // lparen
995 try renderExpression(r, addrspace_node, .none);
996 try renderToken(r, tree.lastToken(addrspace_node) + 1, .space); // rparen
997 }
998
999 if (ptr_type.const_token) |const_token| {
1000 try renderToken(r, const_token, .space);
1001 }
1002
1003 if (ptr_type.volatile_token) |volatile_token| {
1004 try renderToken(r, volatile_token, .space);
1005 }
1006
1007 try renderExpression(r, ptr_type.ast.child_type, space);
1008}
1009
1010fn renderSlice(
1011 r: *Render,
1012 slice_node: Ast.Node.Index,
1013 slice: Ast.full.Slice,
1014 space: Space,
1015) Error!void {
1016 const tree = r.tree;
1017 const after_start_space_bool = nodeCausesSliceOpSpace(tree.nodeTag(slice.ast.start)) or
1018 if (slice.ast.end.unwrap()) |end| nodeCausesSliceOpSpace(tree.nodeTag(end)) else false;
1019 const after_start_space = if (after_start_space_bool) Space.space else Space.none;
1020 const after_dots_space = if (slice.ast.end != .none)
1021 after_start_space
1022 else if (slice.ast.sentinel != .none) Space.space else Space.none;
1023
1024 try renderExpression(r, slice.ast.sliced, .none);
1025 try renderToken(r, slice.ast.lbracket, .none); // lbracket
1026
1027 const start_last = tree.lastToken(slice.ast.start);
1028 try renderExpression(r, slice.ast.start, after_start_space);
1029 try renderToken(r, start_last + 1, after_dots_space); // ellipsis2 ("..")
1030
1031 if (slice.ast.end.unwrap()) |end| {
1032 const after_end_space = if (slice.ast.sentinel != .none) Space.space else Space.none;
1033 try renderExpression(r, end, after_end_space);
1034 }
1035
1036 if (slice.ast.sentinel.unwrap()) |sentinel| {
1037 try renderToken(r, tree.firstToken(sentinel) - 1, .none); // colon
1038 try renderExpression(r, sentinel, .none);
1039 }
1040
1041 try renderToken(r, tree.lastToken(slice_node), space); // rbracket
1042}
1043
1044fn renderAsmOutput(
1045 r: *Render,
1046 asm_output: Ast.Node.Index,
1047 space: Space,
1048) Error!void {
1049 const tree = r.tree;
1050 assert(tree.nodeTag(asm_output) == .asm_output);
1051 const symbolic_name = tree.nodeMainToken(asm_output);
1052
1053 try renderToken(r, symbolic_name - 1, .none); // lbracket
1054 try renderIdentifier(r, symbolic_name, .none, .eagerly_unquote); // ident
1055 try renderToken(r, symbolic_name + 1, .space); // rbracket
1056 try renderToken(r, symbolic_name + 2, .space); // "constraint"
1057 try renderToken(r, symbolic_name + 3, .none); // lparen
1058
1059 if (tree.tokenTag(symbolic_name + 4) == .arrow) {
1060 const type_expr, const rparen = tree.nodeData(asm_output).opt_node_and_token;
1061 try renderToken(r, symbolic_name + 4, .space); // ->
1062 try renderExpression(r, type_expr.unwrap().?, Space.none);
1063 return renderToken(r, rparen, space);
1064 } else {
1065 try renderIdentifier(r, symbolic_name + 4, .none, .eagerly_unquote); // ident
1066 return renderToken(r, symbolic_name + 5, space); // rparen
1067 }
1068}
1069
1070fn renderAsmInput(
1071 r: *Render,
1072 asm_input: Ast.Node.Index,
1073 space: Space,
1074) Error!void {
1075 const tree = r.tree;
1076 assert(tree.nodeTag(asm_input) == .asm_input);
1077 const symbolic_name = tree.nodeMainToken(asm_input);
1078 const expr, const rparen = tree.nodeData(asm_input).node_and_token;
1079
1080 try renderToken(r, symbolic_name - 1, .none); // lbracket
1081 try renderIdentifier(r, symbolic_name, .none, .eagerly_unquote); // ident
1082 try renderToken(r, symbolic_name + 1, .space); // rbracket
1083 try renderToken(r, symbolic_name + 2, .space); // "constraint"
1084 try renderToken(r, symbolic_name + 3, .none); // lparen
1085 try renderExpression(r, expr, Space.none);
1086 return renderToken(r, rparen, space);
1087}
1088
1089fn renderVarDecl(
1090 r: *Render,
1091 var_decl: Ast.full.VarDecl,
1092 /// Destructures intentionally ignore leading `comptime` tokens.
1093 ignore_comptime_token: bool,
1094 /// `comma_space` and `space` are used for destructure LHS decls.
1095 space: Space,
1096) Error!void {
1097 try renderVarDeclWithoutFixups(r, var_decl, ignore_comptime_token, space);
1098 if (r.fixups.unused_var_decls.contains(var_decl.ast.mut_token + 1)) {
1099 // Discard the variable like this: `_ = foo;`
1100 const ais = r.ais;
1101 try ais.writeAll("_ = ");
1102 try ais.writeAll(tokenSliceForRender(r.tree, var_decl.ast.mut_token + 1));
1103 try ais.writeAll(";\n");
1104 }
1105}
1106
1107fn renderVarDeclWithoutFixups(
1108 r: *Render,
1109 var_decl: Ast.full.VarDecl,
1110 /// Destructures intentionally ignore leading `comptime` tokens.
1111 ignore_comptime_token: bool,
1112 /// `comma_space` and `space` are used for destructure LHS decls.
1113 space: Space,
1114) Error!void {
1115 const tree = r.tree;
1116 const ais = r.ais;
1117
1118 if (var_decl.visib_token) |visib_token| {
1119 try renderToken(r, visib_token, Space.space); // pub
1120 }
1121
1122 if (var_decl.extern_export_token) |extern_export_token| {
1123 try renderToken(r, extern_export_token, Space.space); // extern
1124
1125 if (var_decl.lib_name) |lib_name| {
1126 try renderToken(r, lib_name, Space.space); // "lib"
1127 }
1128 }
1129
1130 if (var_decl.threadlocal_token) |thread_local_token| {
1131 try renderToken(r, thread_local_token, Space.space); // threadlocal
1132 }
1133
1134 if (!ignore_comptime_token) {
1135 if (var_decl.comptime_token) |comptime_token| {
1136 try renderToken(r, comptime_token, Space.space); // comptime
1137 }
1138 }
1139
1140 try renderToken(r, var_decl.ast.mut_token, .space); // var
1141
1142 if (var_decl.ast.type_node != .none or var_decl.ast.align_node != .none or
1143 var_decl.ast.addrspace_node != .none or var_decl.ast.section_node != .none or
1144 var_decl.ast.init_node != .none)
1145 {
1146 const name_space = if (var_decl.ast.type_node == .none and
1147 (var_decl.ast.align_node != .none or
1148 var_decl.ast.addrspace_node != .none or
1149 var_decl.ast.section_node != .none or
1150 var_decl.ast.init_node != .none))
1151 Space.space
1152 else
1153 Space.none;
1154
1155 try renderIdentifier(r, var_decl.ast.mut_token + 1, name_space, .preserve_when_shadowing); // name
1156 } else {
1157 return renderIdentifier(r, var_decl.ast.mut_token + 1, space, .preserve_when_shadowing); // name
1158 }
1159
1160 if (var_decl.ast.type_node.unwrap()) |type_node| {
1161 try renderToken(r, var_decl.ast.mut_token + 2, Space.space); // :
1162 if (var_decl.ast.align_node != .none or var_decl.ast.addrspace_node != .none or
1163 var_decl.ast.section_node != .none or var_decl.ast.init_node != .none)
1164 {
1165 try renderExpression(r, type_node, .space);
1166 } else {
1167 return renderExpression(r, type_node, space);
1168 }
1169 }
1170
1171 if (var_decl.ast.align_node.unwrap()) |align_node| {
1172 const lparen = tree.firstToken(align_node) - 1;
1173 const align_kw = lparen - 1;
1174 const rparen = tree.lastToken(align_node) + 1;
1175 try renderToken(r, align_kw, Space.none); // align
1176 try renderToken(r, lparen, Space.none); // (
1177 try renderExpression(r, align_node, Space.none);
1178 if (var_decl.ast.addrspace_node != .none or var_decl.ast.section_node != .none or
1179 var_decl.ast.init_node != .none)
1180 {
1181 try renderToken(r, rparen, .space); // )
1182 } else {
1183 return renderToken(r, rparen, space); // )
1184 }
1185 }
1186
1187 if (var_decl.ast.addrspace_node.unwrap()) |addrspace_node| {
1188 const lparen = tree.firstToken(addrspace_node) - 1;
1189 const addrspace_kw = lparen - 1;
1190 const rparen = tree.lastToken(addrspace_node) + 1;
1191 try renderToken(r, addrspace_kw, Space.none); // addrspace
1192 try renderToken(r, lparen, Space.none); // (
1193 try renderExpression(r, addrspace_node, Space.none);
1194 if (var_decl.ast.section_node != .none or var_decl.ast.init_node != .none) {
1195 try renderToken(r, rparen, .space); // )
1196 } else {
1197 try renderToken(r, rparen, .none); // )
1198 return renderToken(r, rparen + 1, Space.newline); // ;
1199 }
1200 }
1201
1202 if (var_decl.ast.section_node.unwrap()) |section_node| {
1203 const lparen = tree.firstToken(section_node) - 1;
1204 const section_kw = lparen - 1;
1205 const rparen = tree.lastToken(section_node) + 1;
1206 try renderToken(r, section_kw, Space.none); // linksection
1207 try renderToken(r, lparen, Space.none); // (
1208 try renderExpression(r, section_node, Space.none);
1209 if (var_decl.ast.init_node != .none) {
1210 try renderToken(r, rparen, .space); // )
1211 } else {
1212 return renderToken(r, rparen, space); // )
1213 }
1214 }
1215
1216 const init_node = var_decl.ast.init_node.unwrap().?;
1217
1218 const eq_token = tree.firstToken(init_node) - 1;
1219 const eq_space: Space = if (tree.tokensOnSameLine(eq_token, eq_token + 1)) .space else .newline;
1220 try ais.pushIndent(.after_equals);
1221 try renderToken(r, eq_token, eq_space); // =
1222 try renderExpression(r, init_node, space); // ;
1223 ais.popIndent();
1224}
1225
1226fn renderIf(r: *Render, if_node: Ast.full.If, space: Space) Error!void {
1227 return renderWhile(r, .{
1228 .ast = .{
1229 .while_token = if_node.ast.if_token,
1230 .cond_expr = if_node.ast.cond_expr,
1231 .cont_expr = .none,
1232 .then_expr = if_node.ast.then_expr,
1233 .else_expr = if_node.ast.else_expr,
1234 },
1235 .inline_token = null,
1236 .label_token = null,
1237 .payload_token = if_node.payload_token,
1238 .else_token = if_node.else_token,
1239 .error_token = if_node.error_token,
1240 }, space);
1241}
1242
1243/// Note that this function is additionally used to render if expressions, with
1244/// respective values set to null.
1245fn renderWhile(r: *Render, while_node: Ast.full.While, space: Space) Error!void {
1246 const tree = r.tree;
1247
1248 if (while_node.label_token) |label| {
1249 try renderIdentifier(r, label, .none, .eagerly_unquote); // label
1250 try renderToken(r, label + 1, .space); // :
1251 }
1252
1253 if (while_node.inline_token) |inline_token| {
1254 try renderToken(r, inline_token, .space); // inline
1255 }
1256
1257 try renderToken(r, while_node.ast.while_token, .space); // if/for/while
1258 try renderToken(r, while_node.ast.while_token + 1, .none); // lparen
1259 try renderExpression(r, while_node.ast.cond_expr, .none); // condition
1260
1261 var last_prefix_token = tree.lastToken(while_node.ast.cond_expr) + 1; // rparen
1262
1263 if (while_node.payload_token) |payload_token| {
1264 try renderToken(r, last_prefix_token, .space);
1265 try renderToken(r, payload_token - 1, .none); // |
1266 const ident = blk: {
1267 if (tree.tokenTag(payload_token) == .asterisk) {
1268 try renderToken(r, payload_token, .none); // *
1269 break :blk payload_token + 1;
1270 } else {
1271 break :blk payload_token;
1272 }
1273 };
1274 try renderIdentifier(r, ident, .none, .preserve_when_shadowing); // identifier
1275 const pipe = blk: {
1276 if (tree.tokenTag(ident + 1) == .comma) {
1277 try renderToken(r, ident + 1, .space); // ,
1278 try renderIdentifier(r, ident + 2, .none, .preserve_when_shadowing); // index
1279 break :blk ident + 3;
1280 } else {
1281 break :blk ident + 1;
1282 }
1283 };
1284 last_prefix_token = pipe;
1285 }
1286
1287 if (while_node.ast.cont_expr.unwrap()) |cont_expr| {
1288 try renderToken(r, last_prefix_token, .space);
1289 const lparen = tree.firstToken(cont_expr) - 1;
1290 try renderToken(r, lparen - 1, .space); // :
1291 try renderToken(r, lparen, .none); // lparen
1292 try renderExpression(r, cont_expr, .none);
1293 last_prefix_token = tree.lastToken(cont_expr) + 1; // rparen
1294 }
1295
1296 try renderThenElse(
1297 r,
1298 last_prefix_token,
1299 while_node.ast.then_expr,
1300 while_node.else_token,
1301 while_node.error_token,
1302 while_node.ast.else_expr,
1303 space,
1304 );
1305}
1306
1307fn renderThenElse(
1308 r: *Render,
1309 last_prefix_token: Ast.TokenIndex,
1310 then_expr: Ast.Node.Index,
1311 else_token: ?Ast.TokenIndex,
1312 maybe_error_token: ?Ast.TokenIndex,
1313 opt_else_expr: Ast.Node.OptionalIndex,
1314 space: Space,
1315) Error!void {
1316 const tree = r.tree;
1317 const ais = r.ais;
1318 const then_expr_is_block = nodeIsBlock(tree.nodeTag(then_expr));
1319 const indent_then_expr = !then_expr_is_block and
1320 !tree.tokensOnSameLine(last_prefix_token, tree.firstToken(then_expr));
1321
1322 if (indent_then_expr) try ais.pushIndent(.normal);
1323
1324 if (then_expr_is_block and ais.isLineOverIndented()) {
1325 ais.disableIndentCommitting();
1326 try renderToken(r, last_prefix_token, .newline);
1327 ais.enableIndentCommitting();
1328 } else if (indent_then_expr) {
1329 try renderToken(r, last_prefix_token, .newline);
1330 } else {
1331 try renderToken(r, last_prefix_token, .space);
1332 }
1333
1334 if (opt_else_expr.unwrap()) |else_expr| {
1335 if (indent_then_expr) {
1336 try renderExpression(r, then_expr, .newline);
1337 } else {
1338 try renderExpression(r, then_expr, .space);
1339 }
1340
1341 if (indent_then_expr) ais.popIndent();
1342
1343 var last_else_token = else_token.?;
1344
1345 if (maybe_error_token) |error_token| {
1346 try renderToken(r, last_else_token, .space); // else
1347 try renderToken(r, error_token - 1, .none); // |
1348 try renderIdentifier(r, error_token, .none, .preserve_when_shadowing); // identifier
1349 last_else_token = error_token + 1; // |
1350 }
1351
1352 const indent_else_expr = indent_then_expr and
1353 !nodeIsBlock(tree.nodeTag(else_expr)) and
1354 !nodeIsIfForWhileSwitch(tree.nodeTag(else_expr));
1355 if (indent_else_expr) {
1356 try ais.pushIndent(.normal);
1357 try renderToken(r, last_else_token, .newline);
1358 try renderExpression(r, else_expr, space);
1359 ais.popIndent();
1360 } else {
1361 try renderToken(r, last_else_token, .space);
1362 try renderExpression(r, else_expr, space);
1363 }
1364 } else {
1365 try renderExpression(r, then_expr, space);
1366 if (indent_then_expr) ais.popIndent();
1367 }
1368}
1369
1370fn renderFor(r: *Render, for_node: Ast.full.For, space: Space) Error!void {
1371 const tree = r.tree;
1372 const ais = r.ais;
1373 const token_tags = tree.tokens.items(.tag);
1374
1375 if (for_node.label_token) |label| {
1376 try renderIdentifier(r, label, .none, .eagerly_unquote); // label
1377 try renderToken(r, label + 1, .space); // :
1378 }
1379
1380 if (for_node.inline_token) |inline_token| {
1381 try renderToken(r, inline_token, .space); // inline
1382 }
1383
1384 try renderToken(r, for_node.ast.for_token, .space); // if/for/while
1385
1386 const lparen = for_node.ast.for_token + 1;
1387 try renderParamList(r, lparen, for_node.ast.inputs, .space);
1388
1389 var cur = for_node.payload_token;
1390 const pipe = std.mem.indexOfScalarPos(std.zig.Token.Tag, token_tags, cur, .pipe).?;
1391 if (tree.tokenTag(@intCast(pipe - 1)) == .comma) {
1392 try ais.pushIndent(.normal);
1393 try renderToken(r, cur - 1, .newline); // |
1394 while (true) {
1395 if (tree.tokenTag(cur) == .asterisk) {
1396 try renderToken(r, cur, .none); // *
1397 cur += 1;
1398 }
1399 try renderIdentifier(r, cur, .none, .preserve_when_shadowing); // identifier
1400 cur += 1;
1401 if (tree.tokenTag(cur) == .comma) {
1402 try renderToken(r, cur, .newline); // ,
1403 cur += 1;
1404 }
1405 if (tree.tokenTag(cur) == .pipe) {
1406 break;
1407 }
1408 }
1409 ais.popIndent();
1410 } else {
1411 try renderToken(r, cur - 1, .none); // |
1412 while (true) {
1413 if (tree.tokenTag(cur) == .asterisk) {
1414 try renderToken(r, cur, .none); // *
1415 cur += 1;
1416 }
1417 try renderIdentifier(r, cur, .none, .preserve_when_shadowing); // identifier
1418 cur += 1;
1419 if (tree.tokenTag(cur) == .comma) {
1420 try renderToken(r, cur, .space); // ,
1421 cur += 1;
1422 }
1423 if (tree.tokenTag(cur) == .pipe) {
1424 break;
1425 }
1426 }
1427 }
1428
1429 try renderThenElse(
1430 r,
1431 cur,
1432 for_node.ast.then_expr,
1433 for_node.else_token,
1434 null,
1435 for_node.ast.else_expr,
1436 space,
1437 );
1438}
1439
1440fn renderContainerField(
1441 r: *Render,
1442 container: Container,
1443 field_param: Ast.full.ContainerField,
1444 space: Space,
1445) Error!void {
1446 const tree = r.tree;
1447 const ais = r.ais;
1448 var field = field_param;
1449 if (container != .tuple) field.convertToNonTupleLike(&tree);
1450 const quote: QuoteBehavior = switch (container) {
1451 .@"enum" => .eagerly_unquote_except_underscore,
1452 .tuple, .other => .eagerly_unquote,
1453 };
1454
1455 if (field.comptime_token) |t| {
1456 try renderToken(r, t, .space); // comptime
1457 }
1458 if (field.ast.type_expr == .none and field.ast.value_expr == .none) {
1459 if (field.ast.align_expr.unwrap()) |align_expr| {
1460 try renderIdentifier(r, field.ast.main_token, .space, quote); // name
1461 const lparen_token = tree.firstToken(align_expr) - 1;
1462 const align_kw = lparen_token - 1;
1463 const rparen_token = tree.lastToken(align_expr) + 1;
1464 try renderToken(r, align_kw, .none); // align
1465 try renderToken(r, lparen_token, .none); // (
1466 try renderExpression(r, align_expr, .none); // alignment
1467 return renderToken(r, rparen_token, .space); // )
1468 }
1469 return renderIdentifierComma(r, field.ast.main_token, space, quote); // name
1470 }
1471 if (field.ast.type_expr != .none and field.ast.value_expr == .none) {
1472 const type_expr = field.ast.type_expr.unwrap().?;
1473 if (!field.ast.tuple_like) {
1474 try renderIdentifier(r, field.ast.main_token, .none, quote); // name
1475 try renderToken(r, field.ast.main_token + 1, .space); // :
1476 }
1477
1478 if (field.ast.align_expr.unwrap()) |align_expr| {
1479 try renderExpression(r, type_expr, .space); // type
1480 const align_token = tree.firstToken(align_expr) - 2;
1481 try renderToken(r, align_token, .none); // align
1482 try renderToken(r, align_token + 1, .none); // (
1483 try renderExpression(r, align_expr, .none); // alignment
1484 const rparen = tree.lastToken(align_expr) + 1;
1485 return renderTokenComma(r, rparen, space); // )
1486 } else {
1487 return renderExpressionComma(r, type_expr, space); // type
1488 }
1489 }
1490 if (field.ast.type_expr == .none and field.ast.value_expr != .none) {
1491 const value_expr = field.ast.value_expr.unwrap().?;
1492
1493 try renderIdentifier(r, field.ast.main_token, .space, quote); // name
1494 if (field.ast.align_expr.unwrap()) |align_expr| {
1495 const lparen_token = tree.firstToken(align_expr) - 1;
1496 const align_kw = lparen_token - 1;
1497 const rparen_token = tree.lastToken(align_expr) + 1;
1498 try renderToken(r, align_kw, .none); // align
1499 try renderToken(r, lparen_token, .none); // (
1500 try renderExpression(r, align_expr, .none); // alignment
1501 try renderToken(r, rparen_token, .space); // )
1502 }
1503 try renderToken(r, field.ast.main_token + 1, .space); // =
1504 return renderExpressionComma(r, value_expr, space); // value
1505 }
1506 if (!field.ast.tuple_like) {
1507 try renderIdentifier(r, field.ast.main_token, .none, quote); // name
1508 try renderToken(r, field.ast.main_token + 1, .space); // :
1509 }
1510
1511 const type_expr = field.ast.type_expr.unwrap().?;
1512 const value_expr = field.ast.value_expr.unwrap().?;
1513
1514 try renderExpression(r, type_expr, .space); // type
1515
1516 if (field.ast.align_expr.unwrap()) |align_expr| {
1517 const lparen_token = tree.firstToken(align_expr) - 1;
1518 const align_kw = lparen_token - 1;
1519 const rparen_token = tree.lastToken(align_expr) + 1;
1520 try renderToken(r, align_kw, .none); // align
1521 try renderToken(r, lparen_token, .none); // (
1522 try renderExpression(r, align_expr, .none); // alignment
1523 try renderToken(r, rparen_token, .space); // )
1524 }
1525 const eq_token = tree.firstToken(value_expr) - 1;
1526 const eq_space: Space = if (tree.tokensOnSameLine(eq_token, eq_token + 1)) .space else .newline;
1527
1528 try ais.pushIndent(.after_equals);
1529 try renderToken(r, eq_token, eq_space); // =
1530
1531 if (eq_space == .space) {
1532 ais.popIndent();
1533 try renderExpressionComma(r, value_expr, space); // value
1534 return;
1535 }
1536
1537 const maybe_comma = tree.lastToken(value_expr) + 1;
1538
1539 if (tree.tokenTag(maybe_comma) == .comma) {
1540 try renderExpression(r, value_expr, .none); // value
1541 ais.popIndent();
1542 try renderToken(r, maybe_comma, .newline);
1543 } else {
1544 try renderExpression(r, value_expr, space); // value
1545 ais.popIndent();
1546 }
1547}
1548
1549fn renderBuiltinCall(
1550 r: *Render,
1551 builtin_token: Ast.TokenIndex,
1552 params: []const Ast.Node.Index,
1553 space: Space,
1554) Error!void {
1555 const tree = r.tree;
1556 const ais = r.ais;
1557
1558 try renderToken(r, builtin_token, .none); // @name
1559
1560 if (params.len == 0) {
1561 try renderToken(r, builtin_token + 1, .none); // (
1562 return renderToken(r, builtin_token + 2, space); // )
1563 }
1564
1565 if (r.fixups.rebase_imported_paths) |prefix| {
1566 const slice = tree.tokenSlice(builtin_token);
1567 if (mem.eql(u8, slice, "@import")) f: {
1568 const param = params[0];
1569 const str_lit_token = tree.nodeMainToken(param);
1570 assert(tree.tokenTag(str_lit_token) == .string_literal);
1571 const token_bytes = tree.tokenSlice(str_lit_token);
1572 const imported_string = std.zig.string_literal.parseAlloc(r.gpa, token_bytes) catch |err| switch (err) {
1573 error.OutOfMemory => return error.OutOfMemory,
1574 error.InvalidLiteral => break :f,
1575 };
1576 defer r.gpa.free(imported_string);
1577 const new_string = try std.fs.path.resolvePosix(r.gpa, &.{ prefix, imported_string });
1578 defer r.gpa.free(new_string);
1579
1580 try renderToken(r, builtin_token + 1, .none); // (
1581 try ais.print("\"{f}\"", .{std.zig.fmtEscapes(new_string)});
1582 return renderToken(r, str_lit_token + 1, space); // )
1583 }
1584 }
1585
1586 const last_param = params[params.len - 1];
1587 const after_last_param_token = tree.lastToken(last_param) + 1;
1588
1589 if (tree.tokenTag(after_last_param_token) != .comma) {
1590 // Render all on one line, no trailing comma.
1591 try renderToken(r, builtin_token + 1, .none); // (
1592
1593 for (params, 0..) |param_node, i| {
1594 const first_param_token = tree.firstToken(param_node);
1595 if (tree.tokenTag(first_param_token) == .multiline_string_literal_line or
1596 hasSameLineComment(tree, first_param_token - 1))
1597 {
1598 try ais.pushIndent(.normal);
1599 try renderExpression(r, param_node, .none);
1600 ais.popIndent();
1601 } else {
1602 try renderExpression(r, param_node, .none);
1603 }
1604
1605 if (i + 1 < params.len) {
1606 const comma_token = tree.lastToken(param_node) + 1;
1607 try renderToken(r, comma_token, .space); // ,
1608 }
1609 }
1610 return renderToken(r, after_last_param_token, space); // )
1611 } else {
1612 // Render one param per line.
1613 try ais.pushIndent(.normal);
1614 try renderToken(r, builtin_token + 1, Space.newline); // (
1615
1616 for (params) |param_node| {
1617 try ais.pushSpace(.comma);
1618 try renderExpression(r, param_node, .comma);
1619 ais.popSpace();
1620 }
1621 ais.popIndent();
1622
1623 return renderToken(r, after_last_param_token + 1, space); // )
1624 }
1625}
1626
1627fn renderFnProto(r: *Render, fn_proto: Ast.full.FnProto, space: Space) Error!void {
1628 const tree = r.tree;
1629 const ais = r.ais;
1630
1631 const after_fn_token = fn_proto.ast.fn_token + 1;
1632 const lparen = if (tree.tokenTag(after_fn_token) == .identifier) blk: {
1633 try renderToken(r, fn_proto.ast.fn_token, .space); // fn
1634 try renderIdentifier(r, after_fn_token, .none, .preserve_when_shadowing); // name
1635 break :blk after_fn_token + 1;
1636 } else blk: {
1637 try renderToken(r, fn_proto.ast.fn_token, .space); // fn
1638 break :blk fn_proto.ast.fn_token + 1;
1639 };
1640 assert(tree.tokenTag(lparen) == .l_paren);
1641
1642 const return_type = fn_proto.ast.return_type.unwrap().?;
1643 const maybe_bang = tree.firstToken(return_type) - 1;
1644 const rparen = blk: {
1645 // These may appear in any order, so we have to check the token_starts array
1646 // to find out which is first.
1647 var rparen = if (tree.tokenTag(maybe_bang) == .bang) maybe_bang - 1 else maybe_bang;
1648 var smallest_start = tree.tokenStart(maybe_bang);
1649 if (fn_proto.ast.align_expr.unwrap()) |align_expr| {
1650 const tok = tree.firstToken(align_expr) - 3;
1651 const start = tree.tokenStart(tok);
1652 if (start < smallest_start) {
1653 rparen = tok;
1654 smallest_start = start;
1655 }
1656 }
1657 if (fn_proto.ast.addrspace_expr.unwrap()) |addrspace_expr| {
1658 const tok = tree.firstToken(addrspace_expr) - 3;
1659 const start = tree.tokenStart(tok);
1660 if (start < smallest_start) {
1661 rparen = tok;
1662 smallest_start = start;
1663 }
1664 }
1665 if (fn_proto.ast.section_expr.unwrap()) |section_expr| {
1666 const tok = tree.firstToken(section_expr) - 3;
1667 const start = tree.tokenStart(tok);
1668 if (start < smallest_start) {
1669 rparen = tok;
1670 smallest_start = start;
1671 }
1672 }
1673 if (fn_proto.ast.callconv_expr.unwrap()) |callconv_expr| {
1674 const tok = tree.firstToken(callconv_expr) - 3;
1675 const start = tree.tokenStart(tok);
1676 if (start < smallest_start) {
1677 rparen = tok;
1678 smallest_start = start;
1679 }
1680 }
1681 break :blk rparen;
1682 };
1683 assert(tree.tokenTag(rparen) == .r_paren);
1684
1685 // The params list is a sparse set that does *not* include anytype or ... parameters.
1686
1687 const trailing_comma = tree.tokenTag(rparen - 1) == .comma;
1688 if (!trailing_comma and !hasComment(tree, lparen, rparen)) {
1689 // Render all on one line, no trailing comma.
1690 try renderToken(r, lparen, .none); // (
1691
1692 var param_i: usize = 0;
1693 var last_param_token = lparen;
1694 while (true) {
1695 last_param_token += 1;
1696 switch (tree.tokenTag(last_param_token)) {
1697 .doc_comment => {
1698 try renderToken(r, last_param_token, .newline);
1699 continue;
1700 },
1701 .ellipsis3 => {
1702 try renderToken(r, last_param_token, .none); // ...
1703 break;
1704 },
1705 .keyword_noalias, .keyword_comptime => {
1706 try renderToken(r, last_param_token, .space);
1707 last_param_token += 1;
1708 },
1709 .identifier => {},
1710 .keyword_anytype => {
1711 try renderToken(r, last_param_token, .none); // anytype
1712 continue;
1713 },
1714 .r_paren => break,
1715 .comma => {
1716 try renderToken(r, last_param_token, .space); // ,
1717 continue;
1718 },
1719 else => {}, // Parameter type without a name.
1720 }
1721 if (tree.tokenTag(last_param_token) == .identifier and
1722 tree.tokenTag(last_param_token + 1) == .colon)
1723 {
1724 try renderIdentifier(r, last_param_token, .none, .preserve_when_shadowing); // name
1725 last_param_token = last_param_token + 1;
1726 try renderToken(r, last_param_token, .space); // :
1727 last_param_token += 1;
1728 }
1729 if (tree.tokenTag(last_param_token) == .keyword_anytype) {
1730 try renderToken(r, last_param_token, .none); // anytype
1731 continue;
1732 }
1733 const param = fn_proto.ast.params[param_i];
1734 param_i += 1;
1735 try renderExpression(r, param, .none);
1736 last_param_token = tree.lastToken(param);
1737 }
1738 } else {
1739 // One param per line.
1740 try ais.pushIndent(.normal);
1741 try renderToken(r, lparen, .newline); // (
1742
1743 var param_i: usize = 0;
1744 var last_param_token = lparen;
1745 while (true) {
1746 last_param_token += 1;
1747 switch (tree.tokenTag(last_param_token)) {
1748 .doc_comment => {
1749 try renderToken(r, last_param_token, .newline);
1750 continue;
1751 },
1752 .ellipsis3 => {
1753 try renderToken(r, last_param_token, .comma); // ...
1754 break;
1755 },
1756 .keyword_noalias, .keyword_comptime => {
1757 try renderToken(r, last_param_token, .space);
1758 last_param_token += 1;
1759 },
1760 .identifier => {},
1761 .keyword_anytype => {
1762 try renderToken(r, last_param_token, .comma); // anytype
1763 if (tree.tokenTag(last_param_token + 1) == .comma)
1764 last_param_token += 1;
1765 continue;
1766 },
1767 .r_paren => break,
1768 else => {}, // Parameter type without a name.
1769 }
1770 if (tree.tokenTag(last_param_token) == .identifier and
1771 tree.tokenTag(last_param_token + 1) == .colon)
1772 {
1773 try renderIdentifier(r, last_param_token, .none, .preserve_when_shadowing); // name
1774 last_param_token += 1;
1775 try renderToken(r, last_param_token, .space); // :
1776 last_param_token += 1;
1777 }
1778 if (tree.tokenTag(last_param_token) == .keyword_anytype) {
1779 try renderToken(r, last_param_token, .comma); // anytype
1780 if (tree.tokenTag(last_param_token + 1) == .comma)
1781 last_param_token += 1;
1782 continue;
1783 }
1784 const param = fn_proto.ast.params[param_i];
1785 param_i += 1;
1786 try ais.pushSpace(.comma);
1787 try renderExpression(r, param, .comma);
1788 ais.popSpace();
1789 last_param_token = tree.lastToken(param);
1790 if (tree.tokenTag(last_param_token + 1) == .comma) last_param_token += 1;
1791 }
1792 ais.popIndent();
1793 }
1794
1795 try renderToken(r, rparen, .space); // )
1796
1797 if (fn_proto.ast.align_expr.unwrap()) |align_expr| {
1798 const align_lparen = tree.firstToken(align_expr) - 1;
1799 const align_rparen = tree.lastToken(align_expr) + 1;
1800
1801 try renderToken(r, align_lparen - 1, .none); // align
1802 try renderToken(r, align_lparen, .none); // (
1803 try renderExpression(r, align_expr, .none);
1804 try renderToken(r, align_rparen, .space); // )
1805 }
1806
1807 if (fn_proto.ast.addrspace_expr.unwrap()) |addrspace_expr| {
1808 const align_lparen = tree.firstToken(addrspace_expr) - 1;
1809 const align_rparen = tree.lastToken(addrspace_expr) + 1;
1810
1811 try renderToken(r, align_lparen - 1, .none); // addrspace
1812 try renderToken(r, align_lparen, .none); // (
1813 try renderExpression(r, addrspace_expr, .none);
1814 try renderToken(r, align_rparen, .space); // )
1815 }
1816
1817 if (fn_proto.ast.section_expr.unwrap()) |section_expr| {
1818 const section_lparen = tree.firstToken(section_expr) - 1;
1819 const section_rparen = tree.lastToken(section_expr) + 1;
1820
1821 try renderToken(r, section_lparen - 1, .none); // section
1822 try renderToken(r, section_lparen, .none); // (
1823 try renderExpression(r, section_expr, .none);
1824 try renderToken(r, section_rparen, .space); // )
1825 }
1826
1827 if (fn_proto.ast.callconv_expr.unwrap()) |callconv_expr| {
1828 // Keep in sync with logic in `renderMember`. Search this file for the marker PROMOTE_CALLCONV_INLINE
1829 const is_callconv_inline = mem.eql(u8, "@\"inline\"", tree.tokenSlice(tree.nodeMainToken(callconv_expr)));
1830 const is_declaration = fn_proto.name_token != null;
1831 if (!(is_declaration and is_callconv_inline)) {
1832 const callconv_lparen = tree.firstToken(callconv_expr) - 1;
1833 const callconv_rparen = tree.lastToken(callconv_expr) + 1;
1834
1835 try renderToken(r, callconv_lparen - 1, .none); // callconv
1836 try renderToken(r, callconv_lparen, .none); // (
1837 try renderExpression(r, callconv_expr, .none);
1838 try renderToken(r, callconv_rparen, .space); // )
1839 }
1840 }
1841
1842 if (tree.tokenTag(maybe_bang) == .bang) {
1843 try renderToken(r, maybe_bang, .none); // !
1844 }
1845 return renderExpression(r, return_type, space);
1846}
1847
1848fn renderSwitchCase(
1849 r: *Render,
1850 switch_case: Ast.full.SwitchCase,
1851 space: Space,
1852) Error!void {
1853 const ais = r.ais;
1854 const tree = r.tree;
1855 const trailing_comma = tree.tokenTag(switch_case.ast.arrow_token - 1) == .comma;
1856 const has_comment_before_arrow = blk: {
1857 if (switch_case.ast.values.len == 0) break :blk false;
1858 break :blk hasComment(tree, tree.firstToken(switch_case.ast.values[0]), switch_case.ast.arrow_token);
1859 };
1860
1861 // render inline keyword
1862 if (switch_case.inline_token) |some| {
1863 try renderToken(r, some, .space);
1864 }
1865
1866 // Render everything before the arrow
1867 if (switch_case.ast.values.len == 0) {
1868 try renderToken(r, switch_case.ast.arrow_token - 1, .space); // else keyword
1869 } else if (trailing_comma or has_comment_before_arrow) {
1870 // Render each value on a new line
1871 try ais.pushSpace(.comma);
1872 try renderExpressions(r, switch_case.ast.values, .comma);
1873 ais.popSpace();
1874 } else {
1875 // Render on one line
1876 for (switch_case.ast.values) |value_expr| {
1877 try renderExpression(r, value_expr, .comma_space);
1878 }
1879 }
1880
1881 // Render the arrow and everything after it
1882 const pre_target_space = if (tree.nodeTag(switch_case.ast.target_expr) == .multiline_string_literal)
1883 // Newline gets inserted when rendering the target expr.
1884 Space.none
1885 else
1886 Space.space;
1887 const after_arrow_space: Space = if (switch_case.payload_token == null) pre_target_space else .space;
1888 try renderToken(r, switch_case.ast.arrow_token, after_arrow_space); // =>
1889
1890 if (switch_case.payload_token) |payload_token| {
1891 try renderToken(r, payload_token - 1, .none); // pipe
1892 const ident = payload_token + @intFromBool(tree.tokenTag(payload_token) == .asterisk);
1893 if (tree.tokenTag(payload_token) == .asterisk) {
1894 try renderToken(r, payload_token, .none); // asterisk
1895 }
1896 try renderIdentifier(r, ident, .none, .preserve_when_shadowing); // identifier
1897 if (tree.tokenTag(ident + 1) == .comma) {
1898 try renderToken(r, ident + 1, .space); // ,
1899 try renderIdentifier(r, ident + 2, .none, .preserve_when_shadowing); // identifier
1900 try renderToken(r, ident + 3, pre_target_space); // pipe
1901 } else {
1902 try renderToken(r, ident + 1, pre_target_space); // pipe
1903 }
1904 }
1905
1906 try renderExpression(r, switch_case.ast.target_expr, space);
1907}
1908
1909fn renderBlock(
1910 r: *Render,
1911 block_node: Ast.Node.Index,
1912 statements: []const Ast.Node.Index,
1913 space: Space,
1914) Error!void {
1915 const tree = r.tree;
1916 const ais = r.ais;
1917 const lbrace = tree.nodeMainToken(block_node);
1918
1919 if (tree.isTokenPrecededByTags(lbrace, &.{ .identifier, .colon })) {
1920 try renderIdentifier(r, lbrace - 2, .none, .eagerly_unquote); // identifier
1921 try renderToken(r, lbrace - 1, .space); // :
1922 }
1923 try ais.pushIndent(.normal);
1924 if (statements.len == 0) {
1925 try renderToken(r, lbrace, .none);
1926 ais.popIndent();
1927 try renderToken(r, tree.lastToken(block_node), space); // rbrace
1928 return;
1929 }
1930 try renderToken(r, lbrace, .newline);
1931 return finishRenderBlock(r, block_node, statements, space);
1932}
1933
1934fn finishRenderBlock(
1935 r: *Render,
1936 block_node: Ast.Node.Index,
1937 statements: []const Ast.Node.Index,
1938 space: Space,
1939) Error!void {
1940 const tree = r.tree;
1941 const ais = r.ais;
1942 for (statements, 0..) |stmt, i| {
1943 if (i != 0) try renderExtraNewline(r, stmt);
1944 if (r.fixups.omit_nodes.contains(stmt)) continue;
1945 try ais.pushSpace(.semicolon);
1946 switch (tree.nodeTag(stmt)) {
1947 .global_var_decl,
1948 .local_var_decl,
1949 .simple_var_decl,
1950 .aligned_var_decl,
1951 => try renderVarDecl(r, tree.fullVarDecl(stmt).?, false, .semicolon),
1952
1953 else => try renderExpression(r, stmt, .semicolon),
1954 }
1955 ais.popSpace();
1956 }
1957 ais.popIndent();
1958
1959 try renderToken(r, tree.lastToken(block_node), space); // rbrace
1960}
1961
1962fn renderStructInit(
1963 r: *Render,
1964 struct_node: Ast.Node.Index,
1965 struct_init: Ast.full.StructInit,
1966 space: Space,
1967) Error!void {
1968 const tree = r.tree;
1969 const ais = r.ais;
1970
1971 if (struct_init.ast.type_expr.unwrap()) |type_expr| {
1972 try renderExpression(r, type_expr, .none); // T
1973 } else {
1974 try renderToken(r, struct_init.ast.lbrace - 1, .none); // .
1975 }
1976
1977 if (struct_init.ast.fields.len == 0) {
1978 try ais.pushIndent(.normal);
1979 try renderToken(r, struct_init.ast.lbrace, .none); // lbrace
1980 ais.popIndent();
1981 return renderToken(r, struct_init.ast.lbrace + 1, space); // rbrace
1982 }
1983
1984 const rbrace = tree.lastToken(struct_node);
1985 const trailing_comma = tree.tokenTag(rbrace - 1) == .comma;
1986 if (trailing_comma or hasComment(tree, struct_init.ast.lbrace, rbrace)) {
1987 // Render one field init per line.
1988 try ais.pushIndent(.normal);
1989 try renderToken(r, struct_init.ast.lbrace, .newline);
1990
1991 try renderToken(r, struct_init.ast.lbrace + 1, .none); // .
1992 try renderIdentifier(r, struct_init.ast.lbrace + 2, .space, .eagerly_unquote); // name
1993 // Don't output a space after the = if expression is a multiline string,
1994 // since then it will start on the next line.
1995 const field_node = struct_init.ast.fields[0];
1996 const expr = tree.nodeTag(field_node);
1997 var space_after_equal: Space = if (expr == .multiline_string_literal) .none else .space;
1998 try renderToken(r, struct_init.ast.lbrace + 3, space_after_equal); // =
1999
2000 try ais.pushSpace(.comma);
2001 try renderExpressionFixup(r, field_node, .comma);
2002 ais.popSpace();
2003
2004 for (struct_init.ast.fields[1..]) |field_init| {
2005 const init_token = tree.firstToken(field_init);
2006 try renderExtraNewlineToken(r, init_token - 3);
2007 try renderToken(r, init_token - 3, .none); // .
2008 try renderIdentifier(r, init_token - 2, .space, .eagerly_unquote); // name
2009 space_after_equal = if (tree.nodeTag(field_init) == .multiline_string_literal) .none else .space;
2010 try renderToken(r, init_token - 1, space_after_equal); // =
2011
2012 try ais.pushSpace(.comma);
2013 try renderExpressionFixup(r, field_init, .comma);
2014 ais.popSpace();
2015 }
2016
2017 ais.popIndent();
2018 } else {
2019 // Render all on one line, no trailing comma.
2020 try renderToken(r, struct_init.ast.lbrace, .space);
2021
2022 for (struct_init.ast.fields) |field_init| {
2023 const init_token = tree.firstToken(field_init);
2024 try renderToken(r, init_token - 3, .none); // .
2025 try renderIdentifier(r, init_token - 2, .space, .eagerly_unquote); // name
2026 try renderToken(r, init_token - 1, .space); // =
2027 try renderExpressionFixup(r, field_init, .comma_space);
2028 }
2029 }
2030
2031 return renderToken(r, rbrace, space);
2032}
2033
2034fn renderArrayInit(
2035 r: *Render,
2036 array_init: Ast.full.ArrayInit,
2037 space: Space,
2038) Error!void {
2039 const tree = r.tree;
2040 const ais = r.ais;
2041 const gpa = r.gpa;
2042
2043 if (array_init.ast.type_expr.unwrap()) |type_expr| {
2044 try renderExpression(r, type_expr, .none); // T
2045 } else {
2046 try renderToken(r, array_init.ast.lbrace - 1, .none); // .
2047 }
2048
2049 if (array_init.ast.elements.len == 0) {
2050 try ais.pushIndent(.normal);
2051 try renderToken(r, array_init.ast.lbrace, .none); // lbrace
2052 ais.popIndent();
2053 return renderToken(r, array_init.ast.lbrace + 1, space); // rbrace
2054 }
2055
2056 const last_elem = array_init.ast.elements[array_init.ast.elements.len - 1];
2057 const last_elem_token = tree.lastToken(last_elem);
2058 const trailing_comma = tree.tokenTag(last_elem_token + 1) == .comma;
2059 const rbrace = if (trailing_comma) last_elem_token + 2 else last_elem_token + 1;
2060 assert(tree.tokenTag(rbrace) == .r_brace);
2061
2062 if (array_init.ast.elements.len == 1) {
2063 const only_elem = array_init.ast.elements[0];
2064 const first_token = tree.firstToken(only_elem);
2065 if (tree.tokenTag(first_token) != .multiline_string_literal_line and
2066 !anythingBetween(tree, last_elem_token, rbrace))
2067 {
2068 try renderToken(r, array_init.ast.lbrace, .none);
2069 try renderExpression(r, only_elem, .none);
2070 return renderToken(r, rbrace, space);
2071 }
2072 }
2073
2074 const contains_comment = hasComment(tree, array_init.ast.lbrace, rbrace);
2075 const contains_multiline_string = hasMultilineString(tree, array_init.ast.lbrace, rbrace);
2076
2077 if (!trailing_comma and !contains_comment and !contains_multiline_string) {
2078 // Render all on one line, no trailing comma.
2079 if (array_init.ast.elements.len == 1) {
2080 // If there is only one element, we don't use spaces
2081 try renderToken(r, array_init.ast.lbrace, .none);
2082 try renderExpression(r, array_init.ast.elements[0], .none);
2083 } else {
2084 try renderToken(r, array_init.ast.lbrace, .space);
2085 for (array_init.ast.elements) |elem| {
2086 try renderExpression(r, elem, .comma_space);
2087 }
2088 }
2089 return renderToken(r, last_elem_token + 1, space); // rbrace
2090 }
2091
2092 try ais.pushIndent(.normal);
2093 try renderToken(r, array_init.ast.lbrace, .newline);
2094
2095 var expr_index: usize = 0;
2096 while (true) {
2097 const row_size = rowSize(tree, array_init.ast.elements[expr_index..], rbrace);
2098 const row_exprs = array_init.ast.elements[expr_index..];
2099 // A place to store the width of each expression and its column's maximum
2100 const widths = try gpa.alloc(usize, row_exprs.len + row_size);
2101 defer gpa.free(widths);
2102 @memset(widths, 0);
2103
2104 const expr_newlines = try gpa.alloc(bool, row_exprs.len);
2105 defer gpa.free(expr_newlines);
2106 @memset(expr_newlines, false);
2107
2108 const expr_widths = widths[0..row_exprs.len];
2109 const column_widths = widths[row_exprs.len..];
2110
2111 // Find next row with trailing comment (if any) to end the current section.
2112 const section_end = sec_end: {
2113 var this_line_first_expr: usize = 0;
2114 var this_line_size = rowSize(tree, row_exprs, rbrace);
2115 for (row_exprs, 0..) |expr, i| {
2116 // Ignore comment on first line of this section.
2117 if (i == 0) continue;
2118 const expr_last_token = tree.lastToken(expr);
2119 if (tree.tokensOnSameLine(tree.firstToken(row_exprs[0]), expr_last_token))
2120 continue;
2121 // Track start of line containing comment.
2122 if (!tree.tokensOnSameLine(tree.firstToken(row_exprs[this_line_first_expr]), expr_last_token)) {
2123 this_line_first_expr = i;
2124 this_line_size = rowSize(tree, row_exprs[this_line_first_expr..], rbrace);
2125 }
2126
2127 const maybe_comma = expr_last_token + 1;
2128 if (tree.tokenTag(maybe_comma) == .comma) {
2129 if (hasSameLineComment(tree, maybe_comma))
2130 break :sec_end i - this_line_size + 1;
2131 }
2132 }
2133 break :sec_end row_exprs.len;
2134 };
2135 expr_index += section_end;
2136
2137 const section_exprs = row_exprs[0..section_end];
2138
2139 var sub_expr_buffer: std.io.AllocatingWriter = undefined;
2140 sub_expr_buffer.init(gpa);
2141 defer sub_expr_buffer.deinit();
2142
2143 const sub_expr_buffer_starts = try gpa.alloc(usize, section_exprs.len + 1);
2144 defer gpa.free(sub_expr_buffer_starts);
2145
2146 var auto_indenting_stream: AutoIndentingStream = .init(gpa, &sub_expr_buffer.buffered_writer, indent_delta);
2147 defer auto_indenting_stream.deinit();
2148 var sub_render: Render = .{
2149 .gpa = r.gpa,
2150 .ais = &auto_indenting_stream,
2151 .tree = r.tree,
2152 .fixups = r.fixups,
2153 };
2154
2155 // Calculate size of columns in current section
2156 var column_counter: usize = 0;
2157 var single_line = true;
2158 var contains_newline = false;
2159 for (section_exprs, 0..) |expr, i| {
2160 const start = sub_expr_buffer.getWritten().len;
2161 sub_expr_buffer_starts[i] = start;
2162
2163 if (i + 1 < section_exprs.len) {
2164 try renderExpression(&sub_render, expr, .none);
2165 const written = sub_expr_buffer.getWritten();
2166 const width = written.len - start;
2167 const this_contains_newline = mem.indexOfScalar(u8, written[start..], '\n') != null;
2168 contains_newline = contains_newline or this_contains_newline;
2169 expr_widths[i] = width;
2170 expr_newlines[i] = this_contains_newline;
2171
2172 if (!this_contains_newline) {
2173 const column = column_counter % row_size;
2174 column_widths[column] = @max(column_widths[column], width);
2175
2176 const expr_last_token = tree.lastToken(expr) + 1;
2177 const next_expr = section_exprs[i + 1];
2178 column_counter += 1;
2179 if (!tree.tokensOnSameLine(expr_last_token, tree.firstToken(next_expr))) single_line = false;
2180 } else {
2181 single_line = false;
2182 column_counter = 0;
2183 }
2184 } else {
2185 try ais.pushSpace(.comma);
2186 try renderExpression(&sub_render, expr, .comma);
2187 ais.popSpace();
2188
2189 const written = sub_expr_buffer.getWritten();
2190 const width = written.len - start - 2;
2191 const this_contains_newline = mem.indexOfScalar(u8, written[start .. written.len - 1], '\n') != null;
2192 contains_newline = contains_newline or this_contains_newline;
2193 expr_widths[i] = width;
2194 expr_newlines[i] = contains_newline;
2195
2196 if (!contains_newline) {
2197 const column = column_counter % row_size;
2198 column_widths[column] = @max(column_widths[column], width);
2199 }
2200 }
2201 }
2202 sub_expr_buffer_starts[section_exprs.len] = sub_expr_buffer.getWritten().len;
2203
2204 // Render exprs in current section.
2205 column_counter = 0;
2206 for (section_exprs, 0..) |expr, i| {
2207 const start = sub_expr_buffer_starts[i];
2208 const end = sub_expr_buffer_starts[i + 1];
2209 const expr_text = sub_expr_buffer.getWritten()[start..end];
2210 if (!expr_newlines[i]) {
2211 try ais.writeAll(expr_text);
2212 } else {
2213 var by_line = std.mem.splitScalar(u8, expr_text, '\n');
2214 var last_line_was_empty = false;
2215 try ais.writeAll(by_line.first());
2216 while (by_line.next()) |line| {
2217 if (std.mem.startsWith(u8, line, "//") and last_line_was_empty) {
2218 try ais.insertNewline();
2219 } else {
2220 try ais.maybeInsertNewline();
2221 }
2222 last_line_was_empty = (line.len == 0);
2223 try ais.writeAll(line);
2224 }
2225 }
2226
2227 if (i + 1 < section_exprs.len) {
2228 const next_expr = section_exprs[i + 1];
2229 const comma = tree.lastToken(expr) + 1;
2230
2231 if (column_counter != row_size - 1) {
2232 if (!expr_newlines[i] and !expr_newlines[i + 1]) {
2233 // Neither the current or next expression is multiline
2234 try renderToken(r, comma, .space); // ,
2235 assert(column_widths[column_counter % row_size] >= expr_widths[i]);
2236 const padding = column_widths[column_counter % row_size] - expr_widths[i];
2237 try ais.splatByteAll(' ', padding);
2238
2239 column_counter += 1;
2240 continue;
2241 }
2242 }
2243
2244 if (single_line and row_size != 1) {
2245 try renderToken(r, comma, .space); // ,
2246 continue;
2247 }
2248
2249 column_counter = 0;
2250 try renderToken(r, comma, .newline); // ,
2251 try renderExtraNewline(r, next_expr);
2252 }
2253 }
2254
2255 if (expr_index == array_init.ast.elements.len)
2256 break;
2257 }
2258
2259 ais.popIndent();
2260 return renderToken(r, rbrace, space); // rbrace
2261}
2262
2263fn renderContainerDecl(
2264 r: *Render,
2265 container_decl_node: Ast.Node.Index,
2266 container_decl: Ast.full.ContainerDecl,
2267 space: Space,
2268) Error!void {
2269 const tree = r.tree;
2270 const ais = r.ais;
2271
2272 if (container_decl.layout_token) |layout_token| {
2273 try renderToken(r, layout_token, .space);
2274 }
2275
2276 const container: Container = switch (tree.tokenTag(container_decl.ast.main_token)) {
2277 .keyword_enum => .@"enum",
2278 .keyword_struct => for (container_decl.ast.members) |member| {
2279 if (tree.fullContainerField(member)) |field| if (!field.ast.tuple_like) break .other;
2280 } else .tuple,
2281 else => .other,
2282 };
2283
2284 var lbrace: Ast.TokenIndex = undefined;
2285 if (container_decl.ast.enum_token) |enum_token| {
2286 try renderToken(r, container_decl.ast.main_token, .none); // union
2287 try renderToken(r, enum_token - 1, .none); // lparen
2288 try renderToken(r, enum_token, .none); // enum
2289 if (container_decl.ast.arg.unwrap()) |arg| {
2290 try renderToken(r, enum_token + 1, .none); // lparen
2291 try renderExpression(r, arg, .none);
2292 const rparen = tree.lastToken(arg) + 1;
2293 try renderToken(r, rparen, .none); // rparen
2294 try renderToken(r, rparen + 1, .space); // rparen
2295 lbrace = rparen + 2;
2296 } else {
2297 try renderToken(r, enum_token + 1, .space); // rparen
2298 lbrace = enum_token + 2;
2299 }
2300 } else if (container_decl.ast.arg.unwrap()) |arg| {
2301 try renderToken(r, container_decl.ast.main_token, .none); // union
2302 try renderToken(r, container_decl.ast.main_token + 1, .none); // lparen
2303 try renderExpression(r, arg, .none);
2304 const rparen = tree.lastToken(arg) + 1;
2305 try renderToken(r, rparen, .space); // rparen
2306 lbrace = rparen + 1;
2307 } else {
2308 try renderToken(r, container_decl.ast.main_token, .space); // union
2309 lbrace = container_decl.ast.main_token + 1;
2310 }
2311
2312 const rbrace = tree.lastToken(container_decl_node);
2313
2314 if (container_decl.ast.members.len == 0) {
2315 try ais.pushIndent(.normal);
2316 if (tree.tokenTag(lbrace + 1) == .container_doc_comment) {
2317 try renderToken(r, lbrace, .newline); // lbrace
2318 try renderContainerDocComments(r, lbrace + 1);
2319 } else {
2320 try renderToken(r, lbrace, .none); // lbrace
2321 }
2322 ais.popIndent();
2323 return renderToken(r, rbrace, space); // rbrace
2324 }
2325
2326 const src_has_trailing_comma = tree.tokenTag(rbrace - 1) == .comma;
2327 if (!src_has_trailing_comma) one_line: {
2328 // We print all the members in-line unless one of the following conditions are true:
2329
2330 // 1. The container has comments or multiline strings.
2331 if (hasComment(tree, lbrace, rbrace) or hasMultilineString(tree, lbrace, rbrace)) {
2332 break :one_line;
2333 }
2334
2335 // 2. The container has a container comment.
2336 if (tree.tokenTag(lbrace + 1) == .container_doc_comment) break :one_line;
2337
2338 // 3. A member of the container has a doc comment.
2339 for (tree.tokens.items(.tag)[lbrace + 1 .. rbrace - 1]) |tag| {
2340 if (tag == .doc_comment) break :one_line;
2341 }
2342
2343 // 4. The container has non-field members.
2344 for (container_decl.ast.members) |member| {
2345 if (tree.fullContainerField(member) == null) break :one_line;
2346 }
2347
2348 // Print all the declarations on the same line.
2349 try renderToken(r, lbrace, .space); // lbrace
2350 for (container_decl.ast.members) |member| {
2351 try renderMember(r, container, member, .space);
2352 }
2353 return renderToken(r, rbrace, space); // rbrace
2354 }
2355
2356 // One member per line.
2357 try ais.pushIndent(.normal);
2358 try renderToken(r, lbrace, .newline); // lbrace
2359 if (tree.tokenTag(lbrace + 1) == .container_doc_comment) {
2360 try renderContainerDocComments(r, lbrace + 1);
2361 }
2362 for (container_decl.ast.members, 0..) |member, i| {
2363 if (i != 0) try renderExtraNewline(r, member);
2364 switch (tree.nodeTag(member)) {
2365 // For container fields, ensure a trailing comma is added if necessary.
2366 .container_field_init,
2367 .container_field_align,
2368 .container_field,
2369 => {
2370 try ais.pushSpace(.comma);
2371 try renderMember(r, container, member, .comma);
2372 ais.popSpace();
2373 },
2374
2375 else => try renderMember(r, container, member, .newline),
2376 }
2377 }
2378 ais.popIndent();
2379
2380 return renderToken(r, rbrace, space); // rbrace
2381}
2382
2383fn renderAsm(
2384 r: *Render,
2385 asm_node: Ast.full.Asm,
2386 space: Space,
2387) Error!void {
2388 const tree = r.tree;
2389 const ais = r.ais;
2390
2391 try renderToken(r, asm_node.ast.asm_token, .space); // asm
2392
2393 if (asm_node.volatile_token) |volatile_token| {
2394 try renderToken(r, volatile_token, .space); // volatile
2395 try renderToken(r, volatile_token + 1, .none); // lparen
2396 } else {
2397 try renderToken(r, asm_node.ast.asm_token + 1, .none); // lparen
2398 }
2399
2400 if (asm_node.ast.items.len == 0) {
2401 try ais.forcePushIndent(.normal);
2402 if (asm_node.first_clobber) |first_clobber| {
2403 // asm ("foo" ::: "a", "b")
2404 // asm ("foo" ::: "a", "b",)
2405 try renderExpression(r, asm_node.ast.template, .space);
2406 // Render the three colons.
2407 try renderToken(r, first_clobber - 3, .none);
2408 try renderToken(r, first_clobber - 2, .none);
2409 try renderToken(r, first_clobber - 1, .space);
2410
2411 var tok_i = first_clobber;
2412 while (true) : (tok_i += 1) {
2413 try renderToken(r, tok_i, .none);
2414 tok_i += 1;
2415 switch (tree.tokenTag(tok_i)) {
2416 .r_paren => {
2417 ais.popIndent();
2418 return renderToken(r, tok_i, space);
2419 },
2420 .comma => {
2421 if (tree.tokenTag(tok_i + 1) == .r_paren) {
2422 ais.popIndent();
2423 return renderToken(r, tok_i + 1, space);
2424 } else {
2425 try renderToken(r, tok_i, .space);
2426 }
2427 },
2428 else => unreachable,
2429 }
2430 }
2431 } else {
2432 // asm ("foo")
2433 try renderExpression(r, asm_node.ast.template, .none);
2434 ais.popIndent();
2435 return renderToken(r, asm_node.ast.rparen, space); // rparen
2436 }
2437 }
2438
2439 try ais.forcePushIndent(.normal);
2440 try renderExpression(r, asm_node.ast.template, .newline);
2441 ais.setIndentDelta(asm_indent_delta);
2442 const colon1 = tree.lastToken(asm_node.ast.template) + 1;
2443
2444 const colon2 = if (asm_node.outputs.len == 0) colon2: {
2445 try renderToken(r, colon1, .newline); // :
2446 break :colon2 colon1 + 1;
2447 } else colon2: {
2448 try renderToken(r, colon1, .space); // :
2449
2450 try ais.forcePushIndent(.normal);
2451 for (asm_node.outputs, 0..) |asm_output, i| {
2452 if (i + 1 < asm_node.outputs.len) {
2453 const next_asm_output = asm_node.outputs[i + 1];
2454 try renderAsmOutput(r, asm_output, .none);
2455
2456 const comma = tree.firstToken(next_asm_output) - 1;
2457 try renderToken(r, comma, .newline); // ,
2458 try renderExtraNewlineToken(r, tree.firstToken(next_asm_output));
2459 } else if (asm_node.inputs.len == 0 and asm_node.first_clobber == null) {
2460 try ais.pushSpace(.comma);
2461 try renderAsmOutput(r, asm_output, .comma);
2462 ais.popSpace();
2463 ais.popIndent();
2464 ais.setIndentDelta(indent_delta);
2465 ais.popIndent();
2466 return renderToken(r, asm_node.ast.rparen, space); // rparen
2467 } else {
2468 try ais.pushSpace(.comma);
2469 try renderAsmOutput(r, asm_output, .comma);
2470 ais.popSpace();
2471 const comma_or_colon = tree.lastToken(asm_output) + 1;
2472 ais.popIndent();
2473 break :colon2 switch (tree.tokenTag(comma_or_colon)) {
2474 .comma => comma_or_colon + 1,
2475 else => comma_or_colon,
2476 };
2477 }
2478 } else unreachable;
2479 };
2480
2481 const colon3 = if (asm_node.inputs.len == 0) colon3: {
2482 try renderToken(r, colon2, .newline); // :
2483 break :colon3 colon2 + 1;
2484 } else colon3: {
2485 try renderToken(r, colon2, .space); // :
2486 try ais.forcePushIndent(.normal);
2487 for (asm_node.inputs, 0..) |asm_input, i| {
2488 if (i + 1 < asm_node.inputs.len) {
2489 const next_asm_input = asm_node.inputs[i + 1];
2490 try renderAsmInput(r, asm_input, .none);
2491
2492 const first_token = tree.firstToken(next_asm_input);
2493 try renderToken(r, first_token - 1, .newline); // ,
2494 try renderExtraNewlineToken(r, first_token);
2495 } else if (asm_node.first_clobber == null) {
2496 try ais.pushSpace(.comma);
2497 try renderAsmInput(r, asm_input, .comma);
2498 ais.popSpace();
2499 ais.popIndent();
2500 ais.setIndentDelta(indent_delta);
2501 ais.popIndent();
2502 return renderToken(r, asm_node.ast.rparen, space); // rparen
2503 } else {
2504 try ais.pushSpace(.comma);
2505 try renderAsmInput(r, asm_input, .comma);
2506 ais.popSpace();
2507 const comma_or_colon = tree.lastToken(asm_input) + 1;
2508 ais.popIndent();
2509 break :colon3 switch (tree.tokenTag(comma_or_colon)) {
2510 .comma => comma_or_colon + 1,
2511 else => comma_or_colon,
2512 };
2513 }
2514 }
2515 unreachable;
2516 };
2517
2518 try renderToken(r, colon3, .space); // :
2519 const first_clobber = asm_node.first_clobber.?;
2520 var tok_i = first_clobber;
2521 while (true) {
2522 switch (tree.tokenTag(tok_i + 1)) {
2523 .r_paren => {
2524 ais.setIndentDelta(indent_delta);
2525 try renderToken(r, tok_i, .newline);
2526 ais.popIndent();
2527 return renderToken(r, tok_i + 1, space);
2528 },
2529 .comma => {
2530 switch (tree.tokenTag(tok_i + 2)) {
2531 .r_paren => {
2532 ais.setIndentDelta(indent_delta);
2533 try renderToken(r, tok_i, .newline);
2534 ais.popIndent();
2535 return renderToken(r, tok_i + 2, space);
2536 },
2537 else => {
2538 try renderToken(r, tok_i, .none);
2539 try renderToken(r, tok_i + 1, .space);
2540 tok_i += 2;
2541 },
2542 }
2543 },
2544 else => unreachable,
2545 }
2546 }
2547}
2548
2549fn renderCall(
2550 r: *Render,
2551 call: Ast.full.Call,
2552 space: Space,
2553) Error!void {
2554 if (call.async_token) |async_token| {
2555 try renderToken(r, async_token, .space);
2556 }
2557 try renderExpression(r, call.ast.fn_expr, .none);
2558 try renderParamList(r, call.ast.lparen, call.ast.params, space);
2559}
2560
2561fn renderParamList(
2562 r: *Render,
2563 lparen: Ast.TokenIndex,
2564 params: []const Ast.Node.Index,
2565 space: Space,
2566) Error!void {
2567 const tree = r.tree;
2568 const ais = r.ais;
2569
2570 if (params.len == 0) {
2571 try ais.pushIndent(.normal);
2572 try renderToken(r, lparen, .none);
2573 ais.popIndent();
2574 return renderToken(r, lparen + 1, space); // )
2575 }
2576
2577 const last_param = params[params.len - 1];
2578 const after_last_param_tok = tree.lastToken(last_param) + 1;
2579 if (tree.tokenTag(after_last_param_tok) == .comma) {
2580 try ais.pushIndent(.normal);
2581 try renderToken(r, lparen, .newline); // (
2582 for (params, 0..) |param_node, i| {
2583 if (i + 1 < params.len) {
2584 try renderExpression(r, param_node, .none);
2585
2586 const comma = tree.lastToken(param_node) + 1;
2587 try renderToken(r, comma, .newline); // ,
2588
2589 try renderExtraNewline(r, params[i + 1]);
2590 } else {
2591 try ais.pushSpace(.comma);
2592 try renderExpression(r, param_node, .comma);
2593 ais.popSpace();
2594 }
2595 }
2596 ais.popIndent();
2597 return renderToken(r, after_last_param_tok + 1, space); // )
2598 }
2599
2600 try ais.pushIndent(.normal);
2601 try renderToken(r, lparen, .none); // (
2602 for (params, 0..) |param_node, i| {
2603 try renderExpression(r, param_node, .none);
2604
2605 if (i + 1 < params.len) {
2606 const comma = tree.lastToken(param_node) + 1;
2607 const next_multiline_string =
2608 tree.tokenTag(tree.firstToken(params[i + 1])) == .multiline_string_literal_line;
2609 const comma_space: Space = if (next_multiline_string) .none else .space;
2610 try renderToken(r, comma, comma_space);
2611 }
2612 }
2613 ais.popIndent();
2614 return renderToken(r, after_last_param_tok, space); // )
2615}
2616
2617/// Render an expression, and the comma that follows it, if it is present in the source.
2618/// If a comma is present, and `space` is `Space.comma`, render only a single comma.
2619fn renderExpressionComma(r: *Render, node: Ast.Node.Index, space: Space) Error!void {
2620 const tree = r.tree;
2621 const maybe_comma = tree.lastToken(node) + 1;
2622 if (tree.tokenTag(maybe_comma) == .comma and space != .comma) {
2623 try renderExpression(r, node, .none);
2624 return renderToken(r, maybe_comma, space);
2625 } else {
2626 return renderExpression(r, node, space);
2627 }
2628}
2629
2630/// Render a token, and the comma that follows it, if it is present in the source.
2631/// If a comma is present, and `space` is `Space.comma`, render only a single comma.
2632fn renderTokenComma(r: *Render, token: Ast.TokenIndex, space: Space) Error!void {
2633 const tree = r.tree;
2634 const maybe_comma = token + 1;
2635 if (tree.tokenTag(maybe_comma) == .comma and space != .comma) {
2636 try renderToken(r, token, .none);
2637 return renderToken(r, maybe_comma, space);
2638 } else {
2639 return renderToken(r, token, space);
2640 }
2641}
2642
2643/// Render an identifier, and the comma that follows it, if it is present in the source.
2644/// If a comma is present, and `space` is `Space.comma`, render only a single comma.
2645fn renderIdentifierComma(r: *Render, token: Ast.TokenIndex, space: Space, quote: QuoteBehavior) Error!void {
2646 const tree = r.tree;
2647 const maybe_comma = token + 1;
2648 if (tree.tokenTag(maybe_comma) == .comma and space != .comma) {
2649 try renderIdentifier(r, token, .none, quote);
2650 return renderToken(r, maybe_comma, space);
2651 } else {
2652 return renderIdentifier(r, token, space, quote);
2653 }
2654}
2655
2656const Space = enum {
2657 /// Output the token lexeme only.
2658 none,
2659 /// Output the token lexeme followed by a single space.
2660 space,
2661 /// Output the token lexeme followed by a newline.
2662 newline,
2663 /// If the next token is a comma, render it as well. If not, insert one.
2664 /// In either case, a newline will be inserted afterwards.
2665 comma,
2666 /// Additionally consume the next token if it is a comma.
2667 /// In either case, a space will be inserted afterwards.
2668 comma_space,
2669 /// Additionally consume the next token if it is a semicolon.
2670 /// In either case, a newline will be inserted afterwards.
2671 semicolon,
2672 /// Skip rendering whitespace and comments. If this is used, the caller
2673 /// *must* handle whitespace and comments manually.
2674 skip,
2675};
2676
2677fn renderToken(r: *Render, token_index: Ast.TokenIndex, space: Space) Error!void {
2678 const tree = r.tree;
2679 const ais = r.ais;
2680 const lexeme = tokenSliceForRender(tree, token_index);
2681 try ais.writeAll(lexeme);
2682 try renderSpace(r, token_index, lexeme.len, space);
2683}
2684
2685fn renderTokenOverrideSpaceMode(r: *Render, token_index: Ast.TokenIndex, space: Space, override_space: Space) Error!void {
2686 const tree = r.tree;
2687 const ais = r.ais;
2688 const lexeme = tokenSliceForRender(tree, token_index);
2689 try ais.writeAll(lexeme);
2690 ais.enableSpaceMode(override_space);
2691 defer ais.disableSpaceMode();
2692 try renderSpace(r, token_index, lexeme.len, space);
2693}
2694
2695fn renderSpace(r: *Render, token_index: Ast.TokenIndex, lexeme_len: usize, space: Space) Error!void {
2696 const tree = r.tree;
2697 const ais = r.ais;
2698
2699 const next_token_tag = tree.tokenTag(token_index + 1);
2700
2701 if (space == .skip) return;
2702
2703 if (space == .comma and next_token_tag != .comma) {
2704 try ais.underlying_writer.writeByte(',');
2705 }
2706 if (space == .semicolon or space == .comma) ais.enableSpaceMode(space);
2707 defer ais.disableSpaceMode();
2708 const comment = try renderComments(
2709 r,
2710 tree.tokenStart(token_index) + lexeme_len,
2711 tree.tokenStart(token_index + 1),
2712 );
2713 switch (space) {
2714 .none => {},
2715 .space => if (!comment) try ais.writeByte(' '),
2716 .newline => if (!comment) try ais.insertNewline(),
2717
2718 .comma => if (next_token_tag == .comma) {
2719 try renderToken(r, token_index + 1, .newline);
2720 } else if (!comment) {
2721 try ais.insertNewline();
2722 },
2723
2724 .comma_space => if (next_token_tag == .comma) {
2725 try renderToken(r, token_index + 1, .space);
2726 } else if (!comment) {
2727 try ais.writeByte(' ');
2728 },
2729
2730 .semicolon => if (next_token_tag == .semicolon) {
2731 try renderToken(r, token_index + 1, .newline);
2732 } else if (!comment) {
2733 try ais.insertNewline();
2734 },
2735
2736 .skip => unreachable,
2737 }
2738}
2739
2740fn renderOnlySpace(r: *Render, space: Space) Error!void {
2741 const ais = r.ais;
2742 switch (space) {
2743 .none => {},
2744 .space => try ais.writeByte(' '),
2745 .newline => try ais.insertNewline(),
2746 .comma => try ais.writeAll(",\n"),
2747 .comma_space => try ais.writeAll(", "),
2748 .semicolon => try ais.writeAll(";\n"),
2749 .skip => unreachable,
2750 }
2751}
2752
2753const QuoteBehavior = enum {
2754 preserve_when_shadowing,
2755 eagerly_unquote,
2756 eagerly_unquote_except_underscore,
2757};
2758
2759fn renderIdentifier(r: *Render, token_index: Ast.TokenIndex, space: Space, quote: QuoteBehavior) Error!void {
2760 const tree = r.tree;
2761 assert(tree.tokenTag(token_index) == .identifier);
2762 const lexeme = tokenSliceForRender(tree, token_index);
2763
2764 if (r.fixups.rename_identifiers.get(lexeme)) |mangled| {
2765 try r.ais.writeAll(mangled);
2766 try renderSpace(r, token_index, lexeme.len, space);
2767 return;
2768 }
2769
2770 if (lexeme[0] != '@') {
2771 return renderToken(r, token_index, space);
2772 }
2773
2774 assert(lexeme.len >= 3);
2775 assert(lexeme[0] == '@');
2776 assert(lexeme[1] == '\"');
2777 assert(lexeme[lexeme.len - 1] == '\"');
2778 const contents = lexeme[2 .. lexeme.len - 1]; // inside the @"" quotation
2779
2780 // Empty name can't be unquoted.
2781 if (contents.len == 0) {
2782 return renderQuotedIdentifier(r, token_index, space, false);
2783 }
2784
2785 // Special case for _.
2786 if (std.zig.isUnderscore(contents)) switch (quote) {
2787 .eagerly_unquote => return renderQuotedIdentifier(r, token_index, space, true),
2788 .eagerly_unquote_except_underscore,
2789 .preserve_when_shadowing,
2790 => return renderQuotedIdentifier(r, token_index, space, false),
2791 };
2792
2793 // Scan the entire name for characters that would (after un-escaping) be illegal in a symbol,
2794 // i.e. contents don't match: [A-Za-z_][A-Za-z0-9_]*
2795 var contents_i: usize = 0;
2796 while (contents_i < contents.len) {
2797 switch (contents[contents_i]) {
2798 '0'...'9' => if (contents_i == 0) return renderQuotedIdentifier(r, token_index, space, false),
2799 'A'...'Z', 'a'...'z', '_' => {},
2800 '\\' => {
2801 var esc_offset = contents_i;
2802 const res = std.zig.string_literal.parseEscapeSequence(contents, &esc_offset);
2803 switch (res) {
2804 .success => |char| switch (char) {
2805 '0'...'9' => if (contents_i == 0) return renderQuotedIdentifier(r, token_index, space, false),
2806 'A'...'Z', 'a'...'z', '_' => {},
2807 else => return renderQuotedIdentifier(r, token_index, space, false),
2808 },
2809 .failure => return renderQuotedIdentifier(r, token_index, space, false),
2810 }
2811 contents_i += esc_offset;
2812 continue;
2813 },
2814 else => return renderQuotedIdentifier(r, token_index, space, false),
2815 }
2816 contents_i += 1;
2817 }
2818
2819 // Read enough of the name (while un-escaping) to determine if it's a keyword or primitive.
2820 // If it's too long to fit in this buffer, we know it's neither and quoting is unnecessary.
2821 // If we read the whole thing, we have to do further checks.
2822 const longest_keyword_or_primitive_len = comptime blk: {
2823 var longest = 0;
2824 for (primitives.names.keys()) |key| {
2825 if (key.len > longest) longest = key.len;
2826 }
2827 for (std.zig.Token.keywords.keys()) |key| {
2828 if (key.len > longest) longest = key.len;
2829 }
2830 break :blk longest;
2831 };
2832 var buf: [longest_keyword_or_primitive_len]u8 = undefined;
2833
2834 contents_i = 0;
2835 var buf_i: usize = 0;
2836 while (contents_i < contents.len and buf_i < longest_keyword_or_primitive_len) {
2837 if (contents[contents_i] == '\\') {
2838 const res = std.zig.string_literal.parseEscapeSequence(contents, &contents_i).success;
2839 buf[buf_i] = @as(u8, @intCast(res));
2840 buf_i += 1;
2841 } else {
2842 buf[buf_i] = contents[contents_i];
2843 contents_i += 1;
2844 buf_i += 1;
2845 }
2846 }
2847
2848 // We read the whole thing, so it could be a keyword or primitive.
2849 if (contents_i == contents.len) {
2850 if (!std.zig.isValidId(buf[0..buf_i])) {
2851 return renderQuotedIdentifier(r, token_index, space, false);
2852 }
2853 if (primitives.isPrimitive(buf[0..buf_i])) switch (quote) {
2854 .eagerly_unquote,
2855 .eagerly_unquote_except_underscore,
2856 => return renderQuotedIdentifier(r, token_index, space, true),
2857 .preserve_when_shadowing => return renderQuotedIdentifier(r, token_index, space, false),
2858 };
2859 }
2860
2861 try renderQuotedIdentifier(r, token_index, space, true);
2862}
2863
2864// Renders a @"" quoted identifier, normalizing escapes.
2865// Unnecessary escapes are un-escaped, and \u escapes are normalized to \x when they fit.
2866// If unquote is true, the @"" is removed and the result is a bare symbol whose validity is asserted.
2867fn renderQuotedIdentifier(r: *Render, token_index: Ast.TokenIndex, space: Space, comptime unquote: bool) !void {
2868 const tree = r.tree;
2869 const ais = r.ais;
2870 assert(tree.tokenTag(token_index) == .identifier);
2871 const lexeme = tokenSliceForRender(tree, token_index);
2872 assert(lexeme.len >= 3 and lexeme[0] == '@');
2873
2874 if (!unquote) try ais.writeAll("@\"");
2875 const contents = lexeme[2 .. lexeme.len - 1];
2876 try renderIdentifierContents(ais, contents);
2877 if (!unquote) try ais.writeByte('\"');
2878
2879 try renderSpace(r, token_index, lexeme.len, space);
2880}
2881
2882fn renderIdentifierContents(ais: *AutoIndentingStream, bytes: []const u8) !void {
2883 var pos: usize = 0;
2884 while (pos < bytes.len) {
2885 const byte = bytes[pos];
2886 switch (byte) {
2887 '\\' => {
2888 const old_pos = pos;
2889 const res = std.zig.string_literal.parseEscapeSequence(bytes, &pos);
2890 const escape_sequence = bytes[old_pos..pos];
2891 switch (res) {
2892 .success => |codepoint| {
2893 if (codepoint <= 0x7f) {
2894 const buf = [1]u8{@as(u8, @intCast(codepoint))};
2895 try ais.print("{f}", .{std.zig.fmtEscapes(&buf)});
2896 } else {
2897 try ais.writeAll(escape_sequence);
2898 }
2899 },
2900 .failure => {
2901 try ais.writeAll(escape_sequence);
2902 },
2903 }
2904 },
2905 0x00...('\\' - 1), ('\\' + 1)...0x7f => {
2906 const buf = [1]u8{byte};
2907 try ais.print("{f}", .{std.zig.fmtEscapes(&buf)});
2908 pos += 1;
2909 },
2910 0x80...0xff => {
2911 try ais.writeByte(byte);
2912 pos += 1;
2913 },
2914 }
2915 }
2916}
2917
2918/// Returns true if there exists a line comment between any of the tokens from
2919/// `start_token` to `end_token`. This is used to determine if e.g. a
2920/// fn_proto should be wrapped and have a trailing comma inserted even if
2921/// there is none in the source.
2922fn hasComment(tree: Ast, start_token: Ast.TokenIndex, end_token: Ast.TokenIndex) bool {
2923 for (start_token..end_token) |i| {
2924 const token: Ast.TokenIndex = @intCast(i);
2925 const start = tree.tokenStart(token) + tree.tokenSlice(token).len;
2926 const end = tree.tokenStart(token + 1);
2927 if (mem.indexOf(u8, tree.source[start..end], "//") != null) return true;
2928 }
2929
2930 return false;
2931}
2932
2933/// Returns true if there exists a multiline string literal between the start
2934/// of token `start_token` and the start of token `end_token`.
2935fn hasMultilineString(tree: Ast, start_token: Ast.TokenIndex, end_token: Ast.TokenIndex) bool {
2936 return std.mem.indexOfScalar(
2937 Token.Tag,
2938 tree.tokens.items(.tag)[start_token..end_token],
2939 .multiline_string_literal_line,
2940 ) != null;
2941}
2942
2943/// Assumes that start is the first byte past the previous token and
2944/// that end is the last byte before the next token.
2945fn renderComments(r: *Render, start: usize, end: usize) Error!bool {
2946 const tree = r.tree;
2947 const ais = r.ais;
2948
2949 var index: usize = start;
2950 while (mem.indexOf(u8, tree.source[index..end], "//")) |offset| {
2951 const comment_start = index + offset;
2952
2953 // If there is no newline, the comment ends with EOF
2954 const newline_index = mem.indexOfScalar(u8, tree.source[comment_start..end], '\n');
2955 const newline = if (newline_index) |i| comment_start + i else null;
2956
2957 const untrimmed_comment = tree.source[comment_start .. newline orelse tree.source.len];
2958 const trimmed_comment = mem.trimEnd(u8, untrimmed_comment, &std.ascii.whitespace);
2959
2960 // Don't leave any whitespace at the start of the file
2961 if (index != 0) {
2962 if (index == start and mem.containsAtLeast(u8, tree.source[index..comment_start], 2, "\n")) {
2963 // Leave up to one empty line before the first comment
2964 try ais.insertNewline();
2965 try ais.insertNewline();
2966 } else if (mem.indexOfScalar(u8, tree.source[index..comment_start], '\n') != null) {
2967 // Respect the newline directly before the comment.
2968 // Note: This allows an empty line between comments
2969 try ais.insertNewline();
2970 } else if (index == start) {
2971 // Otherwise if the first comment is on the same line as
2972 // the token before it, prefix it with a single space.
2973 try ais.writeByte(' ');
2974 }
2975 }
2976
2977 index = 1 + (newline orelse end - 1);
2978
2979 const comment_content = mem.trimStart(u8, trimmed_comment["//".len..], &std.ascii.whitespace);
2980 if (ais.disabled_offset != null and mem.eql(u8, comment_content, "zig fmt: on")) {
2981 // Write the source for which formatting was disabled directly
2982 // to the underlying writer, fixing up invalid whitespace.
2983 const disabled_source = tree.source[ais.disabled_offset.?..comment_start];
2984 try writeFixingWhitespace(ais.underlying_writer, disabled_source);
2985 // Write with the canonical single space.
2986 try ais.underlying_writer.writeAll("// zig fmt: on\n");
2987 ais.disabled_offset = null;
2988 } else if (ais.disabled_offset == null and mem.eql(u8, comment_content, "zig fmt: off")) {
2989 // Write with the canonical single space.
2990 try ais.writeAll("// zig fmt: off\n");
2991 ais.disabled_offset = index;
2992 } else {
2993 // Write the comment minus trailing whitespace.
2994 try ais.print("{s}\n", .{trimmed_comment});
2995 }
2996 }
2997
2998 if (index != start and mem.containsAtLeast(u8, tree.source[index - 1 .. end], 2, "\n")) {
2999 // Don't leave any whitespace at the end of the file
3000 if (end != tree.source.len) {
3001 try ais.insertNewline();
3002 }
3003 }
3004
3005 return index != start;
3006}
3007
3008fn renderExtraNewline(r: *Render, node: Ast.Node.Index) Error!void {
3009 return renderExtraNewlineToken(r, r.tree.firstToken(node));
3010}
3011
3012/// Check if there is an empty line immediately before the given token. If so, render it.
3013fn renderExtraNewlineToken(r: *Render, token_index: Ast.TokenIndex) Error!void {
3014 const tree = r.tree;
3015 const ais = r.ais;
3016 const token_start = tree.tokenStart(token_index);
3017 if (token_start == 0) return;
3018 const prev_token_end = if (token_index == 0)
3019 0
3020 else
3021 tree.tokenStart(token_index - 1) + tokenSliceForRender(tree, token_index - 1).len;
3022
3023 // If there is a immediately preceding comment or doc_comment,
3024 // skip it because required extra newline has already been rendered.
3025 if (mem.indexOf(u8, tree.source[prev_token_end..token_start], "//") != null) return;
3026 if (tree.isTokenPrecededByTags(token_index, &.{.doc_comment})) return;
3027
3028 // Iterate backwards to the end of the previous token, stopping if a
3029 // non-whitespace character is encountered or two newlines have been found.
3030 var i = token_start - 1;
3031 var newlines: u2 = 0;
3032 while (std.ascii.isWhitespace(tree.source[i])) : (i -= 1) {
3033 if (tree.source[i] == '\n') newlines += 1;
3034 if (newlines == 2) return ais.insertNewline();
3035 if (i == prev_token_end) break;
3036 }
3037}
3038
3039/// end_token is the token one past the last doc comment token. This function
3040/// searches backwards from there.
3041fn renderDocComments(r: *Render, end_token: Ast.TokenIndex) Error!void {
3042 const tree = r.tree;
3043 // Search backwards for the first doc comment.
3044 if (end_token == 0) return;
3045 var tok = end_token - 1;
3046 while (tree.tokenTag(tok) == .doc_comment) {
3047 if (tok == 0) break;
3048 tok -= 1;
3049 } else {
3050 tok += 1;
3051 }
3052 const first_tok = tok;
3053 if (first_tok == end_token) return;
3054
3055 if (first_tok != 0) {
3056 const prev_token_tag = tree.tokenTag(first_tok - 1);
3057
3058 // Prevent accidental use of `renderDocComments` for a function argument doc comment
3059 assert(prev_token_tag != .l_paren);
3060
3061 if (prev_token_tag != .l_brace) {
3062 try renderExtraNewlineToken(r, first_tok);
3063 }
3064 }
3065
3066 while (tree.tokenTag(tok) == .doc_comment) : (tok += 1) {
3067 try renderToken(r, tok, .newline);
3068 }
3069}
3070
3071/// start_token is first container doc comment token.
3072fn renderContainerDocComments(r: *Render, start_token: Ast.TokenIndex) Error!void {
3073 const tree = r.tree;
3074 var tok = start_token;
3075 while (tree.tokenTag(tok) == .container_doc_comment) : (tok += 1) {
3076 try renderToken(r, tok, .newline);
3077 }
3078 // Render extra newline if there is one between final container doc comment and
3079 // the next token. If the next token is a doc comment, that code path
3080 // will have its own logic to insert a newline.
3081 if (tree.tokenTag(tok) != .doc_comment) {
3082 try renderExtraNewlineToken(r, tok);
3083 }
3084}
3085
3086fn discardAllParams(r: *Render, fn_proto_node: Ast.Node.Index) Error!void {
3087 const tree = &r.tree;
3088 const ais = r.ais;
3089 var buf: [1]Ast.Node.Index = undefined;
3090 const fn_proto = tree.fullFnProto(&buf, fn_proto_node).?;
3091 var it = fn_proto.iterate(tree);
3092 while (it.next()) |param| {
3093 const name_ident = param.name_token.?;
3094 assert(tree.tokenTag(name_ident) == .identifier);
3095 try ais.writeAll("_ = ");
3096 try ais.writeAll(tokenSliceForRender(r.tree, name_ident));
3097 try ais.writeAll(";\n");
3098 }
3099}
3100
3101fn tokenSliceForRender(tree: Ast, token_index: Ast.TokenIndex) []const u8 {
3102 var ret = tree.tokenSlice(token_index);
3103 switch (tree.tokenTag(token_index)) {
3104 .container_doc_comment, .doc_comment => {
3105 ret = mem.trimEnd(u8, ret, &std.ascii.whitespace);
3106 },
3107 else => {},
3108 }
3109 return ret;
3110}
3111
3112fn hasSameLineComment(tree: Ast, token_index: Ast.TokenIndex) bool {
3113 const between_source = tree.source[tree.tokenStart(token_index)..tree.tokenStart(token_index + 1)];
3114 for (between_source) |byte| switch (byte) {
3115 '\n' => return false,
3116 '/' => return true,
3117 else => continue,
3118 };
3119 return false;
3120}
3121
3122/// Returns `true` if and only if there are any tokens or line comments between
3123/// start_token and end_token.
3124fn anythingBetween(tree: Ast, start_token: Ast.TokenIndex, end_token: Ast.TokenIndex) bool {
3125 if (start_token + 1 != end_token) return true;
3126 const between_source = tree.source[tree.tokenStart(start_token)..tree.tokenStart(start_token + 1)];
3127 for (between_source) |byte| switch (byte) {
3128 '/' => return true,
3129 else => continue,
3130 };
3131 return false;
3132}
3133
3134fn writeFixingWhitespace(bw: *std.io.BufferedWriter, slice: []const u8) Error!void {
3135 for (slice) |byte| switch (byte) {
3136 '\t' => try bw.splatByteAll(' ', indent_delta),
3137 '\r' => {},
3138 else => try bw.writeByte(byte),
3139 };
3140}
3141
3142fn nodeIsBlock(tag: Ast.Node.Tag) bool {
3143 return switch (tag) {
3144 .block,
3145 .block_semicolon,
3146 .block_two,
3147 .block_two_semicolon,
3148 => true,
3149 else => false,
3150 };
3151}
3152
3153fn nodeIsIfForWhileSwitch(tag: Ast.Node.Tag) bool {
3154 return switch (tag) {
3155 .@"if",
3156 .if_simple,
3157 .@"for",
3158 .for_simple,
3159 .@"while",
3160 .while_simple,
3161 .while_cont,
3162 .@"switch",
3163 .switch_comma,
3164 => true,
3165 else => false,
3166 };
3167}
3168
3169fn nodeCausesSliceOpSpace(tag: Ast.Node.Tag) bool {
3170 return switch (tag) {
3171 .@"catch",
3172 .add,
3173 .add_wrap,
3174 .array_cat,
3175 .array_mult,
3176 .assign,
3177 .assign_bit_and,
3178 .assign_bit_or,
3179 .assign_shl,
3180 .assign_shr,
3181 .assign_bit_xor,
3182 .assign_div,
3183 .assign_sub,
3184 .assign_sub_wrap,
3185 .assign_mod,
3186 .assign_add,
3187 .assign_add_wrap,
3188 .assign_mul,
3189 .assign_mul_wrap,
3190 .bang_equal,
3191 .bit_and,
3192 .bit_or,
3193 .shl,
3194 .shr,
3195 .bit_xor,
3196 .bool_and,
3197 .bool_or,
3198 .div,
3199 .equal_equal,
3200 .error_union,
3201 .greater_or_equal,
3202 .greater_than,
3203 .less_or_equal,
3204 .less_than,
3205 .merge_error_sets,
3206 .mod,
3207 .mul,
3208 .mul_wrap,
3209 .sub,
3210 .sub_wrap,
3211 .@"orelse",
3212 => true,
3213
3214 else => false,
3215 };
3216}
3217
3218// Returns the number of nodes in `exprs` that are on the same line as `rtoken`.
3219fn rowSize(tree: Ast, exprs: []const Ast.Node.Index, rtoken: Ast.TokenIndex) usize {
3220 const first_token = tree.firstToken(exprs[0]);
3221 if (tree.tokensOnSameLine(first_token, rtoken)) {
3222 const maybe_comma = rtoken - 1;
3223 if (tree.tokenTag(maybe_comma) == .comma)
3224 return 1;
3225 return exprs.len; // no newlines
3226 }
3227
3228 var count: usize = 1;
3229 for (exprs, 0..) |expr, i| {
3230 if (i + 1 < exprs.len) {
3231 const expr_last_token = tree.lastToken(expr) + 1;
3232 if (!tree.tokensOnSameLine(expr_last_token, tree.firstToken(exprs[i + 1]))) return count;
3233 count += 1;
3234 } else {
3235 return count;
3236 }
3237 }
3238 unreachable;
3239}
3240
3241/// Automatically inserts indentation of written data by keeping
3242/// track of the current indentation level
3243///
3244/// We introduce a new indentation scope with pushIndent/popIndent whenever
3245/// we potentially want to introduce an indent after the next newline.
3246///
3247/// Indentation should only ever increment by one from one line to the next,
3248/// no matter how many new indentation scopes are introduced. This is done by
3249/// only realizing the indentation from the most recent scope. As an example:
3250///
3251/// while (foo) if (bar)
3252/// f(x);
3253///
3254/// The body of `while` introduces a new indentation scope and the body of
3255/// `if` also introduces a new indentation scope. When the newline is seen,
3256/// only the indentation scope of the `if` is realized, and the `while` is
3257/// not.
3258///
3259/// As comments are rendered during space rendering, we need to keep track
3260/// of the appropriate indentation level for them with pushSpace/popSpace.
3261/// This should be done whenever a scope that ends in a .semicolon or a
3262/// .comma is introduced.
3263const AutoIndentingStream = struct {
3264 underlying_writer: *std.io.BufferedWriter,
3265
3266 /// Offset into the source at which formatting has been disabled with
3267 /// a `zig fmt: off` comment.
3268 ///
3269 /// If non-null, the AutoIndentingStream will not write any bytes
3270 /// to the underlying writer. It will however continue to track the
3271 /// indentation level.
3272 disabled_offset: ?usize = null,
3273
3274 indent_count: usize = 0,
3275 indent_delta: usize,
3276 indent_stack: std.ArrayList(StackElem),
3277 space_stack: std.ArrayList(SpaceElem),
3278 space_mode: ?usize = null,
3279 disable_indent_committing: usize = 0,
3280 current_line_empty: bool = true,
3281 /// the most recently applied indent
3282 applied_indent: usize = 0,
3283
3284 pub const IndentType = enum {
3285 normal,
3286 after_equals,
3287 binop,
3288 field_access,
3289 };
3290 const StackElem = struct {
3291 indent_type: IndentType,
3292 realized: bool,
3293 };
3294 const SpaceElem = struct {
3295 space: Space,
3296 indent_count: usize,
3297 };
3298
3299 pub fn init(gpa: Allocator, bw: *std.io.BufferedWriter, indent_delta_: usize) AutoIndentingStream {
3300 return .{
3301 .underlying_writer = bw,
3302 .indent_delta = indent_delta_,
3303 .indent_stack = .init(gpa),
3304 .space_stack = .init(gpa),
3305 };
3306 }
3307
3308 pub fn deinit(self: *AutoIndentingStream) void {
3309 self.indent_stack.deinit();
3310 self.space_stack.deinit();
3311 }
3312
3313 pub fn writeAll(ais: *AutoIndentingStream, bytes: []const u8) Error!void {
3314 if (bytes.len == 0) return;
3315 try ais.applyIndent();
3316 if (ais.disabled_offset == null) try ais.underlying_writer.writeAll(bytes);
3317 if (bytes[bytes.len - 1] == '\n') ais.resetLine();
3318 }
3319
3320 /// Assumes that if the printed data ends with a newline, it is directly
3321 /// contained in the format string.
3322 pub fn print(ais: *AutoIndentingStream, comptime format: []const u8, args: anytype) Error!void {
3323 try ais.applyIndent();
3324 if (ais.disabled_offset == null) try ais.underlying_writer.print(format, args);
3325 if (format[format.len - 1] == '\n') ais.resetLine();
3326 }
3327
3328 pub fn writeByte(ais: *AutoIndentingStream, byte: u8) Error!void {
3329 try ais.applyIndent();
3330 if (ais.disabled_offset == null) try ais.underlying_writer.writeByte(byte);
3331 assert(byte != '\n');
3332 }
3333
3334 pub fn splatByteAll(ais: *AutoIndentingStream, byte: u8, n: usize) Error!void {
3335 assert(byte != '\n');
3336 try ais.applyIndent();
3337 if (ais.disabled_offset == null) try ais.underlying_writer.splatByteAll(byte, n);
3338 }
3339
3340 // Change the indent delta without changing the final indentation level
3341 pub fn setIndentDelta(ais: *AutoIndentingStream, new_indent_delta: usize) void {
3342 if (ais.indent_delta == new_indent_delta) {
3343 return;
3344 } else if (ais.indent_delta > new_indent_delta) {
3345 assert(ais.indent_delta % new_indent_delta == 0);
3346 ais.indent_count = ais.indent_count * (ais.indent_delta / new_indent_delta);
3347 } else {
3348 // assert that the current indentation (in spaces) in a multiple of the new delta
3349 assert((ais.indent_count * ais.indent_delta) % new_indent_delta == 0);
3350 ais.indent_count = ais.indent_count / (new_indent_delta / ais.indent_delta);
3351 }
3352 ais.indent_delta = new_indent_delta;
3353 }
3354
3355 pub fn insertNewline(ais: *AutoIndentingStream) Error!void {
3356 if (ais.disabled_offset == null) try ais.underlying_writer.writeByte('\n');
3357 ais.resetLine();
3358 }
3359
3360 /// Insert a newline unless the current line is blank
3361 pub fn maybeInsertNewline(ais: *AutoIndentingStream) Error!void {
3362 if (!ais.current_line_empty)
3363 try ais.insertNewline();
3364 }
3365
3366 /// Push an indent that is automatically popped after being applied
3367 pub fn pushIndentOneShot(ais: *AutoIndentingStream) void {
3368 ais.indent_one_shot_count += 1;
3369 ais.pushIndent();
3370 }
3371
3372 /// Turns all one-shot indents into regular indents
3373 /// Returns number of indents that must now be manually popped
3374 pub fn lockOneShotIndent(ais: *AutoIndentingStream) usize {
3375 const locked_count = ais.indent_one_shot_count;
3376 ais.indent_one_shot_count = 0;
3377 return locked_count;
3378 }
3379
3380 /// Push an indent that should not take effect until the next line
3381 pub fn pushIndentNextLine(ais: *AutoIndentingStream) void {
3382 ais.indent_next_line += 1;
3383 ais.pushIndent();
3384 }
3385
3386 /// Checks to see if the most recent indentation exceeds the currently pushed indents
3387 pub fn isLineOverIndented(ais: *AutoIndentingStream) bool {
3388 if (ais.current_line_empty) return false;
3389 return ais.applied_indent > ais.currentIndent();
3390 }
3391
3392 fn resetLine(ais: *AutoIndentingStream) void {
3393 ais.current_line_empty = true;
3394
3395 if (ais.disable_indent_committing > 0) return;
3396
3397 if (ais.indent_stack.items.len > 0) {
3398 // By default, we realize the most recent indentation scope.
3399 var to_realize = ais.indent_stack.items.len - 1;
3400
3401 if (ais.indent_stack.items.len >= 2 and
3402 ais.indent_stack.items[to_realize - 1].indent_type == .after_equals and
3403 ais.indent_stack.items[to_realize - 1].realized and
3404 ais.indent_stack.items[to_realize].indent_type == .binop)
3405 {
3406 // If we are in a .binop scope and our direct parent is .after_equals, don't indent.
3407 // This ensures correct indentation in the below example:
3408 //
3409 // const foo =
3410 // (x >= 'a' and x <= 'z') or //<-- we are here
3411 // (x >= 'A' and x <= 'Z');
3412 //
3413 return;
3414 }
3415
3416 if (ais.indent_stack.items[to_realize].indent_type == .field_access) {
3417 // Only realize the top-most field_access in a chain.
3418 while (to_realize > 0 and ais.indent_stack.items[to_realize - 1].indent_type == .field_access)
3419 to_realize -= 1;
3420 }
3421
3422 if (ais.indent_stack.items[to_realize].realized) return;
3423 ais.indent_stack.items[to_realize].realized = true;
3424 ais.indent_count += 1;
3425 }
3426 }
3427
3428 /// Disables indentation level changes during the next newlines until re-enabled.
3429 pub fn disableIndentCommitting(ais: *AutoIndentingStream) void {
3430 ais.disable_indent_committing += 1;
3431 }
3432
3433 pub fn enableIndentCommitting(ais: *AutoIndentingStream) void {
3434 assert(ais.disable_indent_committing > 0);
3435 ais.disable_indent_committing -= 1;
3436 }
3437
3438 pub fn pushSpace(ais: *AutoIndentingStream, space: Space) !void {
3439 try ais.space_stack.append(.{ .space = space, .indent_count = ais.indent_count });
3440 }
3441
3442 pub fn popSpace(ais: *AutoIndentingStream) void {
3443 _ = ais.space_stack.pop();
3444 }
3445
3446 /// Sets current indentation level to be the same as that of the last pushSpace.
3447 pub fn enableSpaceMode(ais: *AutoIndentingStream, space: Space) void {
3448 if (ais.space_stack.items.len == 0) return;
3449 const curr = ais.space_stack.getLast();
3450 if (curr.space != space) return;
3451 ais.space_mode = curr.indent_count;
3452 }
3453
3454 pub fn disableSpaceMode(ais: *AutoIndentingStream) void {
3455 ais.space_mode = null;
3456 }
3457
3458 pub fn lastSpaceModeIndent(ais: *AutoIndentingStream) usize {
3459 if (ais.space_stack.items.len == 0) return 0;
3460 return ais.space_stack.getLast().indent_count * ais.indent_delta;
3461 }
3462
3463 /// Push default indentation
3464 /// Doesn't actually write any indentation.
3465 /// Just primes the stream to be able to write the correct indentation if it needs to.
3466 pub fn pushIndent(ais: *AutoIndentingStream, indent_type: IndentType) !void {
3467 try ais.indent_stack.append(.{ .indent_type = indent_type, .realized = false });
3468 }
3469
3470 /// Forces an indentation level to be realized.
3471 pub fn forcePushIndent(ais: *AutoIndentingStream, indent_type: IndentType) !void {
3472 try ais.indent_stack.append(.{ .indent_type = indent_type, .realized = true });
3473 ais.indent_count += 1;
3474 }
3475
3476 pub fn popIndent(ais: *AutoIndentingStream) void {
3477 if (ais.indent_stack.pop().?.realized) {
3478 assert(ais.indent_count > 0);
3479 ais.indent_count -= 1;
3480 }
3481 }
3482
3483 pub fn indentStackEmpty(ais: *AutoIndentingStream) bool {
3484 return ais.indent_stack.items.len == 0;
3485 }
3486
3487 /// Writes ' ' bytes if the current line is empty
3488 fn applyIndent(ais: *AutoIndentingStream) Error!void {
3489 const current_indent = ais.currentIndent();
3490 if (ais.current_line_empty and current_indent > 0) {
3491 if (ais.disabled_offset == null) {
3492 try ais.underlying_writer.splatByteAll(' ', current_indent);
3493 }
3494 ais.applied_indent = current_indent;
3495 }
3496 ais.current_line_empty = false;
3497 }
3498
3499 fn currentIndent(ais: *AutoIndentingStream) usize {
3500 const indent_count = ais.space_mode orelse ais.indent_count;
3501 return indent_count * ais.indent_delta;
3502 }
3503};
lib/std/zig/string_literal.zig+3-2
......@@ -360,8 +360,9 @@ pub fn parseAlloc(allocator: std.mem.Allocator, bytes: []const u8) ParseError![]
360360 var aw: std.io.AllocatingWriter = undefined;
361361 aw.init(allocator);
362362 defer aw.deinit();
363 // TODO try @errorCast(...)
364 const result = parseWrite(&aw.buffered_writer, bytes) catch |err| return @errorCast(err);
363 const result = parseWrite(&aw.buffered_writer, bytes) catch |err| switch (err) {
364 error.WriteFailed => return error.OutOfMemory,
365 };
365366 switch (result) {
366367 .success => return aw.toOwnedSlice(),
367368 .failure => return error.InvalidLiteral,
src/Sema.zig+8-8
......@@ -3032,7 +3032,7 @@ pub fn createTypeName(
30323032 aw.init(gpa);
30333033 defer aw.deinit();
30343034 const bw = &aw.buffered_writer;
3035 bw.print("{f}(", .{block.type_name_ctx.fmt(ip)}) catch |err| return @errorCast(err);
3035 bw.print("{f}(", .{block.type_name_ctx.fmt(ip)}) catch return error.OutOfMemory;
30363036
30373037 var arg_i: usize = 0;
30383038 for (fn_info.param_body) |zir_inst| switch (zir_tags[@intFromEnum(zir_inst)]) {
......@@ -3045,7 +3045,7 @@ pub fn createTypeName(
30453045 // result in a compile error.
30463046 const arg_val = try sema.resolveValue(arg) orelse break :func_strat; // fall through to anon strat
30473047
3048 if (arg_i != 0) bw.writeByte(',') catch |err| return @errorCast(err);
3048 if (arg_i != 0) bw.writeByte(',') catch return error.OutOfMemory;
30493049
30503050 // Limiting the depth here helps avoid type names getting too long, which
30513051 // in turn helps to avoid unreasonably long symbol names for namespaced
......@@ -3056,7 +3056,7 @@ pub fn createTypeName(
30563056 .pt = pt,
30573057 .opt_sema = sema,
30583058 .depth = 1,
3059 })}) catch |err| return @errorCast(err);
3059 })}) catch return error.OutOfMemory;
30603060
30613061 arg_i += 1;
30623062 continue;
......@@ -5920,19 +5920,19 @@ fn zirCompileLog(
59205920 const args = sema.code.refSlice(extra.end, extended.small);
59215921
59225922 for (args, 0..) |arg_ref, i| {
5923 if (i != 0) bw.writeAll(", ") catch |err| return @errorCast(err);
5923 if (i != 0) bw.writeAll(", ") catch return error.OutOfMemory;
59245924
59255925 const arg = try sema.resolveInst(arg_ref);
59265926 const arg_ty = sema.typeOf(arg);
59275927 if (try sema.resolveValueResolveLazy(arg)) |val| {
59285928 bw.print("@as({f}, {f})", .{
59295929 arg_ty.fmt(pt), val.fmtValueSema(pt, sema),
5930 }) catch |err| return @errorCast(err);
5930 }) catch return error.OutOfMemory;
59315931 } else {
5932 bw.print("@as({f}, [runtime value])", .{arg_ty.fmt(pt)}) catch |err| return @errorCast(err);
5932 bw.print("@as({f}, [runtime value])", .{arg_ty.fmt(pt)}) catch return error.OutOfMemory;
59335933 }
59345934 }
5935 try bw.print("\n", .{});
5935 bw.writeByte('\n') catch return error.OutOfMemory;
59365936
59375937 const line_data = try zcu.intern_pool.getOrPutString(gpa, pt.tid, aw.getWritten(), .no_embedded_nulls);
59385938
......@@ -37379,7 +37379,7 @@ fn notePathToComptimeAllocPtr(sema: *Sema, msg: *Zcu.ErrorMsg, src: LazySrcLoc,
3737937379 .lvalue,
3738037380 .{ .str = inter_name },
3738137381 20,
37382 ) catch |err| return @errorCast(err);
37382 ) catch return error.OutOfMemory;
3738337383
3738437384 switch (deriv_start) {
3738537385 .int, .nav_ptr => unreachable,
src/codegen/llvm.zig+2-2
......@@ -751,7 +751,7 @@ pub const Object = struct {
751751 const bw = object.builder.setModuleAsm(&aw);
752752 errdefer aw.deinit();
753753 for (object.pt.zcu.global_assembly.values()) |assembly| {
754 bw.print("{s}\n", .{assembly}) catch |err| return @errorCast(err);
754 bw.print("{s}\n", .{assembly}) catch return error.OutOfMemory;
755755 }
756756 try object.builder.finishModuleAsm(&aw);
757757 }
......@@ -2681,7 +2681,7 @@ pub const Object = struct {
26812681 var aw: std.io.AllocatingWriter = undefined;
26822682 aw.init(o.gpa);
26832683 defer aw.deinit();
2684 ty.print(&aw.buffered_writer, o.pt) catch |err| return @errorCast(err);
2684 ty.print(&aw.buffered_writer, o.pt) catch return error.OutOfMemory;
26852685 return aw.toOwnedSliceSentinel(0);
26862686 }
26872687
src/codegen/spirv.zig+1-1
......@@ -1262,7 +1262,7 @@ const NavGen = struct {
12621262 fn resolveTypeName(self: *NavGen, ty: Type) Allocator.Error![]const u8 {
12631263 var aw: std.io.AllocatingWriter = undefined;
12641264 aw.init(self.gpa);
1265 ty.print(&aw.buffered_writer, self.pt) catch |err| return @errorCast(err);
1265 ty.print(&aw.buffered_writer, self.pt) catch return error.OutOfMemory;
12661266 return aw.toOwnedSlice();
12671267 }
12681268
src/link/Dwarf.zig+21-21
......@@ -1482,8 +1482,8 @@ pub const WipNav = struct {
14821482 assert(wip_nav.func != .none);
14831483 if (wip_nav.dwarf.debug_frame.header.format == .none) return;
14841484 const loc_cfa: Cfa = .{ .advance_loc = loc };
1485 loc_cfa.write(wip_nav) catch |err| return @errorCast(err);
1486 cfa.write(wip_nav) catch |err| return @errorCast(err);
1485 try loc_cfa.write(wip_nav);
1486 try cfa.write(wip_nav);
14871487 }
14881488
14891489 pub const LocalVarTag = enum { arg, local_var };
......@@ -1541,7 +1541,7 @@ pub const WipNav = struct {
15411541
15421542 pub fn genVarArgsDebugInfo(wip_nav: *WipNav) UpdateError!void {
15431543 assert(wip_nav.func != .none);
1544 wip_nav.abbrevCode(.is_var_args) catch |err| return @errorCast(err);
1544 try wip_nav.abbrevCode(.is_var_args);
15451545 wip_nav.any_children = true;
15461546 }
15471547
......@@ -1560,8 +1560,8 @@ pub const WipNav = struct {
15601560 delta_line - header.line_base >= header.line_range)
15611561 remaining: {
15621562 assert(delta_line != 0);
1563 dlbw.writeByte(DW.LNS.advance_line) catch |err| return @errorCast(err);
1564 dlbw.writeLeb128(delta_line) catch |err| return @errorCast(err);
1563 try dlbw.writeByte(DW.LNS.advance_line);
1564 try dlbw.writeLeb128(delta_line);
15651565 break :remaining 0;
15661566 } else delta_line);
15671567
......@@ -1569,39 +1569,39 @@ pub const WipNav = struct {
15691569 header.maximum_operations_per_instruction + delta_op;
15701570 const max_op_advance: u9 = (std.math.maxInt(u8) - header.opcode_base) / header.line_range;
15711571 const remaining_op_advance: u8 = @intCast(if (op_advance >= 2 * max_op_advance) remaining: {
1572 dlbw.writeByte(DW.LNS.advance_pc) catch |err| return @errorCast(err);
1573 dlbw.writeLeb128(op_advance) catch |err| return @errorCast(err);
1572 try dlbw.writeByte(DW.LNS.advance_pc);
1573 try dlbw.writeLeb128(op_advance);
15741574 break :remaining 0;
15751575 } else if (op_advance >= max_op_advance) remaining: {
1576 dlbw.writeByte(DW.LNS.const_add_pc) catch |err| return @errorCast(err);
1576 try dlbw.writeByte(DW.LNS.const_add_pc);
15771577 break :remaining op_advance - max_op_advance;
15781578 } else op_advance);
15791579
1580 dlbw.writeByte(
1580 try dlbw.writeByte(
15811581 if (remaining_delta_line == 0 and remaining_op_advance == 0)
15821582 DW.LNS.copy
15831583 else
15841584 @intCast((remaining_delta_line - header.line_base) +
15851585 (header.line_range * remaining_op_advance) + header.opcode_base),
1586 ) catch |err| return @errorCast(err);
1586 );
15871587 }
15881588
15891589 pub fn setColumn(wip_nav: *WipNav, column: u32) Allocator.Error!void {
15901590 const dlbw = &wip_nav.debug_line.buffered_writer;
1591 dlbw.writeByte(DW.LNS.set_column) catch |err| return @errorCast(err);
1592 dlbw.writeLeb128(column + 1) catch |err| return @errorCast(err);
1591 try dlbw.writeByte(DW.LNS.set_column);
1592 try dlbw.writeLeb128(column + 1);
15931593 }
15941594
15951595 pub fn negateStmt(wip_nav: *WipNav) Allocator.Error!void {
1596 return @errorCast(wip_nav.debug_line.buffered_writer.writeByte(DW.LNS.negate_stmt));
1596 return wip_nav.debug_line.buffered_writer.writeByte(DW.LNS.negate_stmt);
15971597 }
15981598
15991599 pub fn setPrologueEnd(wip_nav: *WipNav) Allocator.Error!void {
1600 return @errorCast(wip_nav.debug_line.buffered_writer.writeByte(DW.LNS.set_prologue_end));
1600 return wip_nav.debug_line.buffered_writer.writeByte(DW.LNS.set_prologue_end);
16011601 }
16021602
16031603 pub fn setEpilogueBegin(wip_nav: *WipNav) Allocator.Error!void {
1604 return @errorCast(wip_nav.debug_line.buffered_writer.writeByte(DW.LNS.set_epilogue_begin));
1604 return wip_nav.debug_line.buffered_writer.writeByte(DW.LNS.set_epilogue_begin);
16051605 }
16061606
16071607 pub fn enterBlock(wip_nav: *WipNav, code_off: u64) UpdateError!void {
......@@ -2432,7 +2432,7 @@ pub fn initWipNav(
24322432 nav_index: InternPool.Nav.Index,
24332433 sym_index: u32,
24342434) error{ OutOfMemory, CodegenFail }!?WipNav {
2435 return dwarf.initWipNavInner(pt, nav_index, sym_index) catch |err| switch (@as(UpdateError, @errorCast(err))) {
2435 return dwarf.initWipNavInner(pt, nav_index, sym_index) catch |err| switch (err) {
24362436 error.OutOfMemory => return error.OutOfMemory,
24372437 else => |e| return pt.zcu.codegenFail(nav_index, "failed to init dwarf nav: {s}", .{@errorName(e)}),
24382438 };
......@@ -2445,7 +2445,7 @@ pub fn finishWipNavFunc(
24452445 code_size: u64,
24462446 wip_nav: *WipNav,
24472447) error{ OutOfMemory, CodegenFail }!void {
2448 return dwarf.finishWipNavFuncInner(pt, nav_index, code_size, wip_nav) catch |err| switch (@as(UpdateError, @errorCast(err))) {
2448 return dwarf.finishWipNavFuncInner(pt, nav_index, code_size, wip_nav) catch |err| switch (err) {
24492449 error.OutOfMemory => return error.OutOfMemory,
24502450 else => |e| return pt.zcu.codegenFail(nav_index, "failed to finish dwarf func nav: {s}", .{@errorName(e)}),
24512451 };
......@@ -2457,7 +2457,7 @@ pub fn finishWipNav(
24572457 nav_index: InternPool.Nav.Index,
24582458 wip_nav: *WipNav,
24592459) error{ OutOfMemory, CodegenFail }!void {
2460 return dwarf.finishWipNavInner(pt, nav_index, wip_nav) catch |err| switch (@as(UpdateError, @errorCast(err))) {
2460 return dwarf.finishWipNavInner(pt, nav_index, wip_nav) catch |err| switch (err) {
24612461 error.OutOfMemory => return error.OutOfMemory,
24622462 else => |e| return pt.zcu.codegenFail(nav_index, "failed to finish dwarf nav: {s}", .{@errorName(e)}),
24632463 };
......@@ -2468,7 +2468,7 @@ pub fn updateComptimeNav(
24682468 pt: Zcu.PerThread,
24692469 nav_index: InternPool.Nav.Index,
24702470) error{ OutOfMemory, CodegenFail }!void {
2471 return dwarf.updateComptimeNavInner(pt, nav_index) catch |err| switch (@as(UpdateError, @errorCast(err))) {
2471 return dwarf.updateComptimeNavInner(pt, nav_index) catch |err| switch (err) {
24722472 error.OutOfMemory => return error.OutOfMemory,
24732473 else => |e| return pt.zcu.codegenFail(nav_index, "failed to update dwarf comptime nav: {s}", .{@errorName(e)}),
24742474 };
......@@ -2479,14 +2479,14 @@ pub fn updateContainerType(
24792479 pt: Zcu.PerThread,
24802480 type_index: InternPool.Index,
24812481) error{ OutOfMemory, CodegenFail }!void {
2482 return dwarf.updateContainerType(pt, type_index) catch |err| switch (@as(UpdateError, @errorCast(err))) {
2482 return dwarf.updateContainerType(pt, type_index) catch |err| switch (err) {
24832483 error.OutOfMemory => return error.OutOfMemory,
24842484 else => |e| return pt.zcu.codegenFailType(type_index, "failed to update dwarf comptime nav: {s}", .{@errorName(e)}),
24852485 };
24862486}
24872487
24882488pub fn flushModule(dwarf: *Dwarf, pt: Zcu.PerThread) FlushError!void {
2489 return @errorCast(dwarf.flushModuleInner(pt));
2489 return dwarf.flushModuleInner(pt);
24902490}
24912491
24922492fn initWipNavInner(
src/link/Elf/Atom.zig+3-3
......@@ -674,7 +674,7 @@ pub fn resolveRelocsAlloc(self: Atom, elf_file: *Elf, code: []u8) RelocError!voi
674674 error.InvalidInstruction,
675675 error.CannotEncode,
676676 => has_reloc_errors = true,
677 else => |e| return @errorCast(e),
677 else => |e| return e,
678678 },
679679 .aarch64 => aarch64.resolveRelocAlloc(self, elf_file, rel, target, args, &it, &bw) catch |err| switch (err) {
680680 error.RelocFailure,
......@@ -682,13 +682,13 @@ pub fn resolveRelocsAlloc(self: Atom, elf_file: *Elf, code: []u8) RelocError!voi
682682 error.UnexpectedRemainder,
683683 error.DivisionByZero,
684684 => has_reloc_errors = true,
685 else => |e| return @errorCast(e),
685 else => |e| return e,
686686 },
687687 .riscv64 => riscv.resolveRelocAlloc(self, elf_file, rel, target, args, &it, &bw) catch |err| switch (err) {
688688 error.RelocFailure,
689689 error.RelaxFailure,
690690 => has_reloc_errors = true,
691 else => |e| return @errorCast(e),
691 else => |e| return e,
692692 },
693693 else => return error.UnsupportedCpuArch,
694694 }
src/link/MachO/Atom.zig+1-1
......@@ -595,7 +595,7 @@ pub fn resolveRelocs(self: Atom, macho_file: *MachO, buffer: []u8) !void {
595595 }
596596
597597 bw.end = std.math.cast(usize, rel_offset) orelse return error.Overflow;
598 self.resolveRelocInner(rel, subtractor, buffer, macho_file, &bw) catch |err| switch (@as(ResolveError, @errorCast(err))) {
598 self.resolveRelocInner(rel, subtractor, buffer, macho_file, &bw) catch |err| switch (err) {
599599 error.RelaxFail => {
600600 const target = switch (rel.tag) {
601601 .@"extern" => rel.getTargetSymbol(self, macho_file).getName(macho_file),
src/link/MachO/relocatable.zig+2-2
......@@ -67,7 +67,7 @@ pub fn flushObject(macho_file: *MachO, comp: *Compilation, module_obj_path: ?Pat
6767
6868 try writeSections(macho_file);
6969 sortRelocs(macho_file);
70 writeSectionsToFile(macho_file) catch |err| return @errorCast(err);
70 try writeSectionsToFile(macho_file);
7171
7272 // In order to please Apple ld (and possibly other MachO linkers in the wild),
7373 // we will now sanitize segment names of Zig-specific segments.
......@@ -131,7 +131,7 @@ pub fn flushStaticLib(macho_file: *MachO, comp: *Compilation, module_obj_path: ?
131131
132132 try writeSections(macho_file);
133133 sortRelocs(macho_file);
134 writeSectionsToFile(macho_file) catch |err| return @errorCast(err);
134 try writeSectionsToFile(macho_file);
135135
136136 // In order to please Apple ld (and possibly other MachO linkers in the wild),
137137 // we will now sanitize segment names of Zig-specific segments.
src/link/Plan9.zig+4-4
......@@ -665,11 +665,11 @@ pub fn flush(
665665 // connect the previous decl to the next
666666 const delta_line = @as(i32, @intCast(out.start_line)) - @as(i32, @intCast(linecount));
667667
668 changeLine(linecountinfo_bw, delta_line) catch |err| return @errorCast(err);
668 try changeLine(linecountinfo_bw, delta_line);
669669 // TODO change the pc too (maybe?)
670670
671671 // write out the actual info that was generated in codegen now
672 linecountinfo_bw.writeAll(out.lineinfo) catch |err| return @errorCast(err);
672 try linecountinfo_bw.writeAll(out.lineinfo);
673673 linecount = out.end_line;
674674 }
675675 foff += out.code.len;
......@@ -692,7 +692,7 @@ pub fn flush(
692692 }
693693 if (linecountinfo_aw.getWritten().len & 1 == 1) {
694694 // just a nop to make it even, the plan9 linker does this
695 linecountinfo_bw.writeByte(129) catch |err| return @errorCast(err);
695 try linecountinfo_bw.writeByte(129);
696696 }
697697 }
698698 const linecountinfo = linecountinfo_aw.getWritten();
......@@ -822,7 +822,7 @@ pub fn flush(
822822 var syms_aw: std.io.AllocatingWriter = undefined;
823823 syms_aw.init(gpa);
824824 defer syms_aw.deinit();
825 self.writeSyms(&syms_aw.buffered_writer) catch |err| return @errorCast(err);
825 try self.writeSyms(&syms_aw.buffered_writer);
826826 const syms = syms_aw.getWritten();
827827 assert(2 + self.atomCount() - self.externCount() == iovecs_i); // we didn't write all the decls
828828 iovecs[iovecs_i] = .{ .base = syms.ptr, .len = syms.len };
src/link/SpirV.zig+4-4
......@@ -207,7 +207,7 @@ pub fn flush(
207207 error_info.init(self.object.gpa);
208208 defer error_info.deinit();
209209
210 error_info.buffered_writer.writeAll("zig_errors:") catch |err| return @errorCast(err);
210 try error_info.buffered_writer.writeAll("zig_errors:");
211211 const ip = &self.base.comp.zcu.?.intern_pool;
212212 for (ip.global_error_set.getNamesFromMainThread()) |name| {
213213 // Errors can contain pretty much any character - to encode them in a string we must escape
......@@ -215,8 +215,8 @@ pub fn flush(
215215 // name if it contains no strange characters is nice for debugging. URI encoding fits the bill.
216216 // We're using : as separator, which is a reserved character.
217217
218 error_info.buffered_writer.writeByte(':') catch |err| return @errorCast(err);
219 std.Uri.Component.percentEncode(
218 try error_info.buffered_writer.writeByte(':');
219 try std.Uri.Component.percentEncode(
220220 &error_info.buffered_writer,
221221 name.toSlice(ip),
222222 struct {
......@@ -227,7 +227,7 @@ pub fn flush(
227227 };
228228 }
229229 }.isValidChar,
230 ) catch |err| return @errorCast(err);
230 );
231231 }
232232 try spv.sections.debug_strings.emit(gpa, .OpSourceExtension, .{
233233 .extension = error_info.getWritten(),