Zig 0.17.0-dev (Split by item)

This is an example of documentation generated by ZigDoc, an alternative to Zig's built-in Auto Doc feature. See also examples in other modes/formats. The project being documented here (as the example) is the Zig library itself.

renderNode

ast.renderNode
fn renderNode(c: *Context, node: Node) Allocator.Error!NodeIndex

File

Code

fn renderNode(c: *Context, node: Node) Allocator.Error!NodeIndex {
    switch (node.tag()) {
        .declaration => unreachable,
        .warning => unreachable,
        .discard => {
            const payload = node.castTag(.discard).?.data;
            std.debug.assert(!payload.should_skip);

            const lhs = try c.addNode(.{
                .tag = .identifier,
                .main_token = try c.addToken(.identifier, "_"),
                .data = undefined,
            });
            const main_token = try c.addToken(.equal, "=");
            if (payload.value.tag() == .identifier) {
                // Render as `_ = &foo;` to avoid tripping "pointless discard" and "local variable never mutated" errors.
                var addr_of_pl: Payload.UnOp = .{
                    .base = .{ .tag = .address_of },
                    .data = payload.value,
                };
                const addr_of: Node = .{ .ptr_otherwise = &addr_of_pl.base };
                return try c.addNode(.{
                    .tag = .assign,
                    .main_token = main_token,
                    .data = .{ .node_and_node = .{
                        lhs, try renderNode(c, addr_of),
                    } },
                });
            } else {
                return try c.addNode(.{
                    .tag = .assign,
                    .main_token = main_token,
                    .data = .{ .node_and_node = .{
                        lhs, try renderNode(c, payload.value),
                    } },
                });
            }
        },
        .std_mem_zeroes => {
            const payload = node.castTag(.std_mem_zeroes).?.data;
            const import_node = try renderStdImport(c, &.{ "mem", "zeroes" });
            return renderCall(c, import_node, &.{payload});
        },
        .std_mem_zeroinit => {
            const payload = node.castTag(.std_mem_zeroinit).?.data;
            const import_node = try renderStdImport(c, &.{ "mem", "zeroInit" });
            return renderCall(c, import_node, &.{ payload.lhs, payload.rhs });
        },
        .vector => {
            const payload = node.castTag(.vector).?.data;
            return renderBuiltinCall(c, "@Vector", &.{ payload.lhs, payload.rhs });
        },
        .call => {
            const payload = node.castTag(.call).?.data;
            const lhs = try renderNodeGrouped(c, payload.lhs);
            return renderCall(c, lhs, payload.args);
        },
        .null_literal => return c.addNode(.{
            .tag = .identifier,
            .main_token = try c.addToken(.identifier, "null"),
            .data = undefined,
        }),
        .undefined_literal => return c.addNode(.{
            .tag = .identifier,
            .main_token = try c.addToken(.identifier, "undefined"),
            .data = undefined,
        }),
        .true_literal => return c.addNode(.{
            .tag = .identifier,
            .main_token = try c.addToken(.identifier, "true"),
            .data = undefined,
        }),
        .false_literal => return c.addNode(.{
            .tag = .identifier,
            .main_token = try c.addToken(.identifier, "false"),
            .data = undefined,
        }),
        .zero_literal => return c.addNode(.{
            .tag = .number_literal,
            .main_token = try c.addToken(.number_literal, "0"),
            .data = undefined,
        }),
        .one_literal => return c.addNode(.{
            .tag = .number_literal,
            .main_token = try c.addToken(.number_literal, "1"),
            .data = undefined,
        }),
        .@"unreachable" => return c.addNode(.{
            .tag = .unreachable_literal,
            .main_token = try c.addToken(.keyword_unreachable, "unreachable"),
            .data = undefined,
        }),
        .void_type => return c.addNode(.{
            .tag = .identifier,
            .main_token = try c.addToken(.identifier, "void"),
            .data = undefined,
        }),
        .noreturn_type => return c.addNode(.{
            .tag = .identifier,
            .main_token = try c.addToken(.identifier, "noreturn"),
            .data = undefined,
        }),
        .@"continue" => return c.addNode(.{
            .tag = .@"continue",
            .main_token = try c.addToken(.keyword_continue, "continue"),
            .data = .{ .opt_token_and_opt_node = .{
                .none, .none,
            } },
        }),
        .return_void => return c.addNode(.{
            .tag = .@"return",
            .main_token = try c.addToken(.keyword_return, "return"),
            .data = .{ .opt_node = .none },
        }),
        .@"break" => return c.addNode(.{
            .tag = .@"break",
            .main_token = try c.addToken(.keyword_break, "break"),
            .data = .{ .opt_token_and_opt_node = .{
                .none, .none,
            } },
        }),
        .break_val => {
            const payload = node.castTag(.break_val).?.data;
            const tok = try c.addToken(.keyword_break, "break");
            const break_label = if (payload.label) |some| blk: {
                _ = try c.addToken(.colon, ":");
                break :blk try c.addIdentifier(some);
            } else 0;
            return c.addNode(.{
                .tag = .@"break",
                .main_token = tok,
                .data = .{ .opt_token_and_opt_node = .{
                    .fromToken(break_label), (try renderNode(c, payload.val)).toOptional(),
                } },
            });
        },
        .@"return" => {
            const payload = node.castTag(.@"return").?.data;
            return c.addNode(.{
                .tag = .@"return",
                .main_token = try c.addToken(.keyword_return, "return"),
                .data = .{ .opt_node = (try renderNode(c, payload)).toOptional() },
            });
        },
        .@"comptime" => {
            const payload = node.castTag(.@"comptime").?.data;
            return c.addNode(.{
                .tag = .@"comptime",
                .main_token = try c.addToken(.keyword_comptime, "comptime"),
                .data = .{
                    .node = try renderNode(c, payload),
                },
            });
        },
        .@"defer" => {
            const payload = node.castTag(.@"defer").?.data;
            return c.addNode(.{
                .tag = .@"defer",
                .main_token = try c.addToken(.keyword_defer, "defer"),
                .data = .{
                    .node = try renderNode(c, payload),
                },
            });
        },
        .asm_simple => {
            const payload = node.castTag(.asm_simple).?.data;
            const asm_token = try c.addToken(.keyword_asm, "asm");
            _ = try c.addToken(.l_paren, "(");
            return c.addNode(.{
                .tag = .asm_simple,
                .main_token = asm_token,
                .data = .{ .node_and_token = .{
                    try renderNode(c, payload),
                    try c.addToken(.r_paren, ")"),
                } },
            });
        },
        .type => {
            const payload = node.castTag(.type).?.data;
            return c.addNode(.{
                .tag = .identifier,
                .main_token = try c.addToken(.identifier, payload),
                .data = undefined,
            });
        },
        .identifier => {
            const payload = node.castTag(.identifier).?.data;
            return c.addNode(.{
                .tag = .identifier,
                .main_token = try c.addIdentifier(payload),
                .data = undefined,
            });
        },
        .float_literal => {
            const payload = node.castTag(.float_literal).?.data;
            return c.addNode(.{
                .tag = .number_literal,
                .main_token = try c.addToken(.number_literal, payload),
                .data = undefined,
            });
        },
        .integer_literal => {
            const payload = node.castTag(.integer_literal).?.data;
            return c.addNode(.{
                .tag = .number_literal,
                .main_token = try c.addToken(.number_literal, payload),
                .data = undefined,
            });
        },
        .string_literal => {
            const payload = node.castTag(.string_literal).?.data;
            return c.addNode(.{
                .tag = .string_literal,
                .main_token = try c.addToken(.string_literal, payload),
                .data = undefined,
            });
        },
        .char_literal => {
            const payload = node.castTag(.char_literal).?.data;
            return c.addNode(.{
                .tag = .char_literal,
                .main_token = try c.addToken(.char_literal, payload),
                .data = undefined,
            });
        },
        .enum_literal => {
            const payload = node.castTag(.enum_literal).?.data;
            _ = try c.addToken(.period, ".");
            return c.addNode(.{
                .tag = .enum_literal,
                .main_token = try c.addToken(.identifier, payload),
                .data = undefined,
            });
        },
        .string_slice => {
            const payload = node.castTag(.string_slice).?.data;

            const string = try renderNode(c, payload.string);
            const l_bracket = try c.addToken(.l_bracket, "[");
            const start = try c.addNode(.{
                .tag = .number_literal,
                .main_token = try c.addToken(.number_literal, "0"),
                .data = undefined,
            });
            _ = try c.addToken(.ellipsis2, "..");
            const end = try c.addNode(.{
                .tag = .number_literal,
                .main_token = try c.addTokenFmt(.number_literal, "{d}", .{payload.end}),
                .data = undefined,
            });
            _ = try c.addToken(.r_bracket, "]");

            return c.addNode(.{
                .tag = .slice,
                .main_token = l_bracket,
                .data = .{ .node_and_extra = .{
                    string,
                    try c.addExtra(std.zig.Ast.Node.Slice{
                        .start = start,
                        .end = end,
                    }),
                } },
            });
        },
        .fail_decl => {
            const payload = node.castTag(.fail_decl).?.data;
            // pub const name = (if (true))? @compileError(msg);
            if (!payload.local) _ = try c.addToken(.keyword_pub, "pub");
            const const_tok = try c.addToken(.keyword_const, "const");
            _ = try c.addIdentifier(payload.actual);
            _ = try c.addToken(.equal, "=");

            var if_tok: TokenIndex = undefined;
            var true_node: NodeIndex = undefined;
            if (payload.local) {
                if_tok = try c.addToken(.keyword_if, "if");
                _ = try c.addToken(.l_paren, "(");
                true_node = try c.addNode(.{
                    .tag = .identifier,
                    .main_token = try c.addToken(.identifier, "true"),
                    .data = undefined,
                });
                _ = try c.addToken(.r_paren, ")");
            }

            const compile_error_tok = try c.addToken(.builtin, "@compileError");
            _ = try c.addToken(.l_paren, "(");
            const err_msg_tok = try c.addTokenFmt(.string_literal, "\"{f}\"", .{std.zig.fmtString(payload.mangled)});
            const err_msg = try c.addNode(.{
                .tag = .string_literal,
                .main_token = err_msg_tok,
                .data = undefined,
            });
            _ = try c.addToken(.r_paren, ")");
            const compile_error = try c.addNode(.{
                .tag = .builtin_call_two,
                .main_token = compile_error_tok,
                .data = .{ .opt_node_and_opt_node = .{
                    err_msg.toOptional(), .none,
                } },
            });
            _ = try c.addToken(.semicolon, ";");

            return c.addNode(.{
                .tag = .simple_var_decl,
                .main_token = const_tok,
                .data = .{
                    .opt_node_and_opt_node = .{
                        .none, // Type expression
                        if (payload.local) // Init expression
                            (try c.addNode(.{
                                .tag = .if_simple,
                                .main_token = if_tok,
                                .data = .{ .node_and_node = .{
                                    true_node, compile_error,
                                } },
                            })).toOptional()
                        else
                            compile_error.toOptional(),
                    },
                },
            });
        },
        .pub_var_simple, .var_simple => {
            const payload = @as(*Payload.SimpleVarDecl, @alignCast(@fieldParentPtr("base", node.ptr_otherwise))).data;
            if (node.tag() == .pub_var_simple) _ = try c.addToken(.keyword_pub, "pub");
            const const_tok = try c.addToken(.keyword_const, "const");
            _ = try c.addIdentifier(payload.name);
            _ = try c.addToken(.equal, "=");

            const init = try renderNode(c, payload.init);
            _ = try c.addToken(.semicolon, ";");

            return c.addNode(.{
                .tag = .simple_var_decl,
                .main_token = const_tok,
                .data = .{
                    .opt_node_and_opt_node = .{
                        .none, // Type expression
                        init.toOptional(), // Init expression
                    },
                },
            });
        },
        .wrapped_local => {
            const payload = node.castTag(.wrapped_local).?.data;

            const const_tok = try c.addToken(.keyword_const, "const");
            _ = try c.addIdentifier(payload.name);
            _ = try c.addToken(.equal, "=");

            const kind_tok = try c.addToken(.keyword_struct, "struct");
            _ = try c.addToken(.l_brace, "{");

            const container_def = try c.addNode(.{
                .tag = .container_decl_two_trailing,
                .main_token = kind_tok,
                .data = .{ .opt_node_and_opt_node = .{
                    (try renderNode(c, payload.init)).toOptional(), .none,
                } },
            });
            _ = try c.addToken(.r_brace, "}");
            _ = try c.addToken(.semicolon, ";");

            return c.addNode(.{
                .tag = .simple_var_decl,
                .main_token = const_tok,
                .data = .{
                    .opt_node_and_opt_node = .{
                        .none, // Type expression
                        container_def.toOptional(), // Init expression
                    },
                },
            });
        },
        .mut_str => {
            const payload = node.castTag(.mut_str).?.data;

            const var_tok = try c.addToken(.keyword_var, "var");
            _ = try c.addIdentifier(payload.name);
            _ = try c.addToken(.equal, "=");

            const deref = try c.addNode(.{
                .tag = .deref,
                .data = .{
                    .node = try renderNodeGrouped(c, payload.init),
                },
                .main_token = try c.addToken(.period_asterisk, ".*"),
            });
            _ = try c.addToken(.semicolon, ";");

            return c.addNode(.{
                .tag = .simple_var_decl,
                .main_token = var_tok,
                .data = .{
                    .opt_node_and_opt_node = .{
                        .none, // Type expression
                        deref.toOptional(), // Init expression
                    },
                },
            });
        },
        .var_decl => return renderVar(c, node),
        .arg_redecl, .alias => {
            const payload = @as(*Payload.ArgRedecl, @alignCast(@fieldParentPtr("base", node.ptr_otherwise))).data;
            if (node.tag() == .alias) _ = try c.addToken(.keyword_pub, "pub");
            const mut_tok = if (node.tag() == .alias)
                try c.addToken(.keyword_const, "const")
            else
                try c.addToken(.keyword_var, "var");
            _ = try c.addIdentifier(payload.actual);
            _ = try c.addToken(.equal, "=");

            const init = try c.addNode(.{
                .tag = .identifier,
                .main_token = try c.addIdentifier(payload.mangled),
                .data = undefined,
            });
            _ = try c.addToken(.semicolon, ";");

            return c.addNode(.{
                .tag = .simple_var_decl,
                .main_token = mut_tok,
                .data = .{
                    .opt_node_and_opt_node = .{
                        .none, // Type expression
                        init.toOptional(), // Init expression
                    },
                },
            });
        },
        .int_cast => {
            const payload = node.castTag(.int_cast).?.data;
            return renderBuiltinCall(c, "@intCast", &.{payload});
        },
        .const_cast => {
            const payload = node.castTag(.const_cast).?.data;
            return renderBuiltinCall(c, "@constCast", &.{payload});
        },
        .volatile_cast => {
            const payload = node.castTag(.volatile_cast).?.data;
            return renderBuiltinCall(c, "@volatileCast", &.{payload});
        },
        .div_trunc => {
            const payload = node.castTag(.div_trunc).?.data;
            return renderBuiltinCall(c, "@divTrunc", &.{ payload.lhs, payload.rhs });
        },
        .int_from_bool => {
            const payload = node.castTag(.int_from_bool).?.data;
            return renderBuiltinCall(c, "@intFromBool", &.{payload});
        },
        .as => {
            const payload = node.castTag(.as).?.data;
            return renderBuiltinCall(c, "@as", &.{ payload.lhs, payload.rhs });
        },
        .truncate => {
            const payload = node.castTag(.truncate).?.data;
            return renderBuiltinCall(c, "@truncate", &.{payload});
        },
        .bit_cast => {
            const payload = node.castTag(.bit_cast).?.data;
            return renderBuiltinCall(c, "@bitCast", &.{payload});
        },
        .float_cast => {
            const payload = node.castTag(.float_cast).?.data;
            return renderBuiltinCall(c, "@floatCast", &.{payload});
        },
        .int_from_float => {
            const payload = node.castTag(.int_from_float).?.data;
            return renderBuiltinCall(c, "@intFromFloat", &.{payload});
        },
        .float_from_int => {
            const payload = node.castTag(.float_from_int).?.data;
            return renderBuiltinCall(c, "@floatFromInt", &.{payload});
        },
        .ptr_from_int => {
            const payload = node.castTag(.ptr_from_int).?.data;
            return renderBuiltinCall(c, "@ptrFromInt", &.{payload});
        },
        .int_from_ptr => {
            const payload = node.castTag(.int_from_ptr).?.data;
            return renderBuiltinCall(c, "@intFromPtr", &.{payload});
        },
        .align_cast => {
            const payload = node.castTag(.align_cast).?.data;
            return renderBuiltinCall(c, "@alignCast", &.{payload});
        },
        .ptr_cast => {
            const payload = node.castTag(.ptr_cast).?.data;
            return renderBuiltinCall(c, "@ptrCast", &.{payload});
        },
        .div_exact => {
            const payload = node.castTag(.div_exact).?.data;
            return renderBuiltinCall(c, "@divExact", &.{ payload.lhs, payload.rhs });
        },
        .offset_of => {
            const payload = node.castTag(.offset_of).?.data;
            return renderBuiltinCall(c, "@offsetOf", &.{ payload.lhs, payload.rhs });
        },
        .sizeof => {
            const payload = node.castTag(.sizeof).?.data;
            return renderBuiltinCall(c, "@sizeOf", &.{payload});
        },
        .shuffle => {
            const payload = node.castTag(.shuffle).?.data;
            return renderBuiltinCall(c, "@shuffle", &.{
                payload.element_type,
                payload.a,
                payload.b,
                payload.mask_vector,
            });
        },
        .builtin_extern => {
            const payload = node.castTag(.builtin_extern).?.data;

            var info_inits: [1]Payload.ContainerInitDot.Initializer = .{
                .{ .name = "name", .value = payload.name },
            };
            var info_payload: Payload.ContainerInitDot = .{
                .base = .{ .tag = .container_init_dot },
                .data = &info_inits,
            };

            return renderBuiltinCall(c, "@extern", &.{
                payload.type,
                .{ .ptr_otherwise = &info_payload.base },
            });
        },
        .helper_call => {
            const payload = node.castTag(.helper_call).?.data;
            const helpers_tok = try c.addNode(.{
                .tag = .identifier,
                .main_token = try c.addIdentifier("__helpers"),
                .data = undefined,
            });
            const func = try renderFieldAccess(c, helpers_tok, payload.name);
            return renderCall(c, func, payload.args);
        },
        .helper_ref => {
            const payload = node.castTag(.helper_ref).?.data;
            const helpers_tok = try c.addNode(.{
                .tag = .identifier,
                .main_token = try c.addIdentifier("__helpers"),
                .data = undefined,
            });
            return renderFieldAccess(c, helpers_tok, payload);
        },
        .alignof => {
            const payload = node.castTag(.alignof).?.data;
            return renderBuiltinCall(c, "@alignOf", &.{payload});
        },
        .typeof => {
            const payload = node.castTag(.typeof).?.data;
            return renderBuiltinCall(c, "@TypeOf", &.{payload});
        },
        .typeinfo => {
            const payload = node.castTag(.typeinfo).?.data;
            return renderBuiltinCall(c, "@typeInfo", &.{payload});
        },
        .byte_swap => {
            const payload = node.castTag(.byte_swap).?.data;
            return renderBuiltinCall(c, "@byteSwap", &.{payload});
        },
        .ceil => {
            const payload = node.castTag(.ceil).?.data;
            return renderBuiltinCall(c, "@ceil", &.{payload});
        },
        .cos => {
            const payload = node.castTag(.cos).?.data;
            return renderBuiltinCall(c, "@cos", &.{payload});
        },
        .sin => {
            const payload = node.castTag(.sin).?.data;
            return renderBuiltinCall(c, "@sin", &.{payload});
        },
        .exp => {
            const payload = node.castTag(.exp).?.data;
            return renderBuiltinCall(c, "@exp", &.{payload});
        },
        .exp2 => {
            const payload = node.castTag(.exp2).?.data;
            return renderBuiltinCall(c, "@exp2", &.{payload});
        },
        .exp10 => {
            const payload = node.castTag(.exp10).?.data;
            return renderBuiltinCall(c, "@exp10", &.{payload});
        },
        .abs => {
            const payload = node.castTag(.abs).?.data;
            return renderBuiltinCall(c, "@abs", &.{payload});
        },
        .log => {
            const payload = node.castTag(.log).?.data;
            return renderBuiltinCall(c, "@log", &.{payload});
        },
        .log2 => {
            const payload = node.castTag(.log2).?.data;
            return renderBuiltinCall(c, "@log2", &.{payload});
        },
        .log10 => {
            const payload = node.castTag(.log10).?.data;
            return renderBuiltinCall(c, "@log10", &.{payload});
        },
        .round => {
            const payload = node.castTag(.round).?.data;
            return renderBuiltinCall(c, "@round", &.{payload});
        },
        .sqrt => {
            const payload = node.castTag(.sqrt).?.data;
            return renderBuiltinCall(c, "@sqrt", &.{payload});
        },
        .trunc => {
            const payload = node.castTag(.trunc).?.data;
            return renderBuiltinCall(c, "@trunc", &.{payload});
        },
        .floor => {
            const payload = node.castTag(.floor).?.data;
            return renderBuiltinCall(c, "@floor", &.{payload});
        },
        .negate => return renderPrefixOp(c, node, .negation, .minus, "-"),
        .negate_wrap => return renderPrefixOp(c, node, .negation_wrap, .minus_percent, "-%"),
        .bit_not => return renderPrefixOp(c, node, .bit_not, .tilde, "~"),
        .not => return renderPrefixOp(c, node, .bool_not, .bang, "!"),
        .optional_type => return renderPrefixOp(c, node, .optional_type, .question_mark, "?"),
        .address_of => {
            const payload = node.castTag(.address_of).?.data;

            const ampersand = try c.addToken(.ampersand, "&");
            const base = try renderNodeGrouped(c, payload);
            return c.addNode(.{
                .tag = .address_of,
                .main_token = ampersand,
                .data = .{
                    .node = base,
                },
            });
        },
        .deref => {
            const payload = node.castTag(.deref).?.data;
            const operand = try renderNodeGrouped(c, payload);
            const deref_tok = try c.addToken(.period_asterisk, ".*");
            return c.addNode(.{
                .tag = .deref,
                .main_token = deref_tok,
                .data = .{
                    .node = operand,
                },
            });
        },
        .unwrap => {
            const payload = node.castTag(.unwrap).?.data;
            const operand = try renderNodeGrouped(c, payload);
            const period = try c.addToken(.period, ".");
            const question_mark = try c.addToken(.question_mark, "?");
            return c.addNode(.{
                .tag = .unwrap_optional,
                .main_token = period,
                .data = .{ .node_and_token = .{
                    operand, question_mark,
                } },
            });
        },
        .c_pointer, .single_pointer => {
            const payload = @as(*Payload.Pointer, @alignCast(@fieldParentPtr("base", node.ptr_otherwise))).data;

            const main_token = if (node.tag() == .single_pointer)
                try c.addToken(.asterisk, "*")
            else blk: {
                const res = try c.addToken(.l_bracket, "[");
                _ = try c.addToken(.asterisk, "*");
                _ = try c.addIdentifier("c");
                _ = try c.addToken(.r_bracket, "]");
                break :blk res;
            };
            if (payload.is_const) _ = try c.addToken(.keyword_const, "const");
            if (payload.is_volatile) _ = try c.addToken(.keyword_volatile, "volatile");
            if (payload.is_allowzero) _ = try c.addToken(.keyword_allowzero, "allowzero");
            const elem_type = try renderNodeGrouped(c, payload.elem_type);

            return c.addNode(.{
                .tag = .ptr_type_aligned,
                .main_token = main_token,
                .data = .{
                    .opt_node_and_node = .{
                        .none, // Align node
                        elem_type,
                    },
                },
            });
        },
        .add => return renderBinOpGrouped(c, node, .add, .plus, "+"),
        .add_assign => return renderBinOp(c, node, .assign_add, .plus_equal, "+="),
        .add_wrap => return renderBinOpGrouped(c, node, .add_wrap, .plus_percent, "+%"),
        .add_wrap_assign => return renderBinOp(c, node, .assign_add_wrap, .plus_percent_equal, "+%="),
        .sub => return renderBinOpGrouped(c, node, .sub, .minus, "-"),
        .sub_assign => return renderBinOp(c, node, .assign_sub, .minus_equal, "-="),
        .sub_wrap => return renderBinOpGrouped(c, node, .sub_wrap, .minus_percent, "-%"),
        .sub_wrap_assign => return renderBinOp(c, node, .assign_sub_wrap, .minus_percent_equal, "-%="),
        .mul => return renderBinOpGrouped(c, node, .mul, .asterisk, "*"),
        .mul_assign => return renderBinOp(c, node, .assign_mul, .asterisk_equal, "*="),
        .mul_wrap => return renderBinOpGrouped(c, node, .mul_wrap, .asterisk_percent, "*%"),
        .mul_wrap_assign => return renderBinOp(c, node, .assign_mul_wrap, .asterisk_percent_equal, "*%="),
        .div => return renderBinOpGrouped(c, node, .div, .slash, "/"),
        .div_assign => return renderBinOp(c, node, .assign_div, .slash_equal, "/="),
        .shl => return renderBinOpGrouped(c, node, .shl, .angle_bracket_angle_bracket_left, "<<"),
        .shl_assign => return renderBinOp(c, node, .assign_shl, .angle_bracket_angle_bracket_left_equal, "<<="),
        .shr => return renderBinOpGrouped(c, node, .shr, .angle_bracket_angle_bracket_right, ">>"),
        .shr_assign => return renderBinOp(c, node, .assign_shr, .angle_bracket_angle_bracket_right_equal, ">>="),
        .mod => return renderBinOpGrouped(c, node, .mod, .percent, "%"),
        .mod_assign => return renderBinOp(c, node, .assign_mod, .percent_equal, "%="),
        .@"and" => return renderBinOpGrouped(c, node, .bool_and, .keyword_and, "and"),
        .@"or" => return renderBinOpGrouped(c, node, .bool_or, .keyword_or, "or"),
        .less_than => return renderBinOpGrouped(c, node, .less_than, .angle_bracket_left, "<"),
        .less_than_equal => return renderBinOpGrouped(c, node, .less_or_equal, .angle_bracket_left_equal, "<="),
        .greater_than => return renderBinOpGrouped(c, node, .greater_than, .angle_bracket_right, ">="),
        .greater_than_equal => return renderBinOpGrouped(c, node, .greater_or_equal, .angle_bracket_right_equal, ">="),
        .equal => return renderBinOpGrouped(c, node, .equal_equal, .equal_equal, "=="),
        .not_equal => return renderBinOpGrouped(c, node, .bang_equal, .bang_equal, "!="),
        .bit_and => return renderBinOpGrouped(c, node, .bit_and, .ampersand, "&"),
        .bit_and_assign => return renderBinOp(c, node, .assign_bit_and, .ampersand_equal, "&="),
        .bit_or => return renderBinOpGrouped(c, node, .bit_or, .pipe, "|"),
        .bit_or_assign => return renderBinOp(c, node, .assign_bit_or, .pipe_equal, "|="),
        .bit_xor => return renderBinOpGrouped(c, node, .bit_xor, .caret, "^"),
        .bit_xor_assign => return renderBinOp(c, node, .assign_bit_xor, .caret_equal, "^="),
        .array_cat => return renderBinOp(c, node, .array_cat, .plus_plus, "++"),
        .ellipsis3 => return renderBinOpGrouped(c, node, .switch_range, .ellipsis3, "..."),
        .assign => return renderBinOp(c, node, .assign, .equal, "="),
        .empty_block => {
            const l_brace = try c.addToken(.l_brace, "{");
            _ = try c.addToken(.r_brace, "}");
            return c.addNode(.{
                .tag = .block_two,
                .main_token = l_brace,
                .data = .{ .opt_node_and_opt_node = .{
                    .none, .none,
                } },
            });
        },
        .block_single => {
            const payload = node.castTag(.block_single).?.data;
            const l_brace = try c.addToken(.l_brace, "{");

            const stmt = (try renderNodeOpt(c, payload)) orelse {
                _ = try c.addToken(.r_brace, "}");
                return c.addNode(.{
                    .tag = .block_two,
                    .main_token = l_brace,
                    .data = .{ .opt_node_and_opt_node = .{
                        .none, .none,
                    } },
                });
            };
            try addSemicolonIfNeeded(c, payload);

            _ = try c.addToken(.r_brace, "}");
            return c.addNode(.{
                .tag = .block_two_semicolon,
                .main_token = l_brace,
                .data = .{ .opt_node_and_opt_node = .{
                    stmt.toOptional(), .none,
                } },
            });
        },
        .block => {
            const payload = node.castTag(.block).?.data;
            if (payload.label) |some| {
                _ = try c.addIdentifier(some);
                _ = try c.addToken(.colon, ":");
            }
            const l_brace = try c.addToken(.l_brace, "{");

            var stmts: std.ArrayList(NodeIndex) = .empty;
            defer stmts.deinit(c.gpa);
            for (payload.stmts) |stmt| {
                const res = (try renderNodeOpt(c, stmt)) orelse continue;
                try addSemicolonIfNeeded(c, stmt);
                try stmts.append(c.gpa, res);
            }
            const span = try c.listToSpan(stmts.items);
            _ = try c.addToken(.r_brace, "}");

            const semicolon = c.tokens.items(.tag)[c.tokens.len - 2] == .semicolon;
            return c.addNode(.{
                .tag = if (semicolon) .block_semicolon else .block,
                .main_token = l_brace,
                .data = .{ .extra_range = span },
            });
        },
        .func => return renderFunc(c, node),
        .pub_inline_fn => return renderMacroFunc(c, node),
        .@"while" => {
            const payload = node.castTag(.@"while").?.data;
            const while_tok = try c.addToken(.keyword_while, "while");
            _ = try c.addToken(.l_paren, "(");
            const cond = try renderNode(c, payload.cond);
            _ = try c.addToken(.r_paren, ")");

            const cont_expr_opt = if (payload.cont_expr) |some| blk: {
                _ = try c.addToken(.colon, ":");
                _ = try c.addToken(.l_paren, "(");
                const res = try renderNode(c, some);
                _ = try c.addToken(.r_paren, ")");
                break :blk res;
            } else null;
            const body = try renderNode(c, payload.body);

            if (cont_expr_opt) |cont_expr| {
                return c.addNode(.{
                    .tag = .while_cont,
                    .main_token = while_tok,
                    .data = .{ .node_and_extra = .{
                        cond,
                        try c.addExtra(std.zig.Ast.Node.WhileCont{
                            .cont_expr = cont_expr,
                            .then_expr = body,
                        }),
                    } },
                });
            } else {
                return c.addNode(.{
                    .tag = .while_simple,
                    .main_token = while_tok,
                    .data = .{ .node_and_node = .{
                        cond, body,
                    } },
                });
            }
        },
        .while_true => {
            const payload = node.castTag(.while_true).?.data;
            const while_tok = try c.addToken(.keyword_while, "while");
            _ = try c.addToken(.l_paren, "(");
            const cond = try c.addNode(.{
                .tag = .identifier,
                .main_token = try c.addToken(.identifier, "true"),
                .data = undefined,
            });
            _ = try c.addToken(.r_paren, ")");
            const body = try renderNode(c, payload);

            return c.addNode(.{
                .tag = .while_simple,
                .main_token = while_tok,
                .data = .{ .node_and_node = .{
                    cond, body,
                } },
            });
        },
        .@"if" => {
            const payload = node.castTag(.@"if").?.data;
            const if_tok = try c.addToken(.keyword_if, "if");
            _ = try c.addToken(.l_paren, "(");
            const cond = try renderNode(c, payload.cond);
            _ = try c.addToken(.r_paren, ")");

            const then_expr = try renderNode(c, payload.then);
            const else_node = payload.@"else" orelse return c.addNode(.{
                .tag = .if_simple,
                .main_token = if_tok,
                .data = .{ .node_and_node = .{
                    cond, then_expr,
                } },
            });
            _ = try c.addToken(.keyword_else, "else");
            const else_expr = try renderNode(c, else_node);

            return c.addNode(.{
                .tag = .@"if",
                .main_token = if_tok,
                .data = .{ .node_and_extra = .{
                    cond,
                    try c.addExtra(std.zig.Ast.Node.If{
                        .then_expr = then_expr,
                        .else_expr = else_expr,
                    }),
                } },
            });
        },
        .if_not_break => {
            const payload = node.castTag(.if_not_break).?.data;
            const if_tok = try c.addToken(.keyword_if, "if");
            _ = try c.addToken(.l_paren, "(");
            const cond = try c.addNode(.{
                .tag = .bool_not,
                .main_token = try c.addToken(.bang, "!"),
                .data = .{
                    .node = try renderNodeGrouped(c, payload),
                },
            });
            _ = try c.addToken(.r_paren, ")");
            const then_expr = try c.addNode(.{
                .tag = .@"break",
                .main_token = try c.addToken(.keyword_break, "break"),
                .data = .{ .opt_token_and_opt_node = .{
                    .none, .none,
                } },
            });

            return c.addNode(.{
                .tag = .if_simple,
                .main_token = if_tok,
                .data = .{ .node_and_node = .{
                    cond, then_expr,
                } },
            });
        },
        .@"switch" => {
            const payload = node.castTag(.@"switch").?.data;
            const switch_tok = try c.addToken(.keyword_switch, "switch");
            _ = try c.addToken(.l_paren, "(");
            const cond = try renderNode(c, payload.cond);
            _ = try c.addToken(.r_paren, ")");

            _ = try c.addToken(.l_brace, "{");
            var cases = try c.gpa.alloc(NodeIndex, payload.cases.len);
            defer c.gpa.free(cases);
            for (payload.cases, 0..) |case, i| {
                cases[i] = try renderNode(c, case);
                _ = try c.addToken(.comma, ",");
            }
            const span = try c.listToSpan(cases);
            _ = try c.addToken(.r_brace, "}");
            return c.addNode(.{
                .tag = .switch_comma,
                .main_token = switch_tok,
                .data = .{ .node_and_extra = .{
                    cond,
                    try c.addExtra(NodeSubRange{
                        .start = span.start,
                        .end = span.end,
                    }),
                } },
            });
        },
        .switch_else => {
            const payload = node.castTag(.switch_else).?.data;
            _ = try c.addToken(.keyword_else, "else");
            return c.addNode(.{
                .tag = .switch_case_one,
                .main_token = try c.addToken(.equal_angle_bracket_right, "=>"),
                .data = .{ .opt_node_and_node = .{
                    .none, try renderNode(c, payload),
                } },
            });
        },
        .switch_prong => {
            const payload = node.castTag(.switch_prong).?.data;
            var items = try c.gpa.alloc(NodeIndex, payload.cases.len);
            defer c.gpa.free(items);

            for (payload.cases, 0..) |item, i| {
                if (i != 0) _ = try c.addToken(.comma, ",");
                items[i] = try renderNode(c, item);
            }
            _ = try c.addToken(.r_brace, "}");
            if (items.len < 2) {
                return c.addNode(.{
                    .tag = .switch_case_one,
                    .main_token = try c.addToken(.equal_angle_bracket_right, "=>"),
                    .data = .{ .opt_node_and_node = .{
                        if (payload.cases.len == 1) items[0].toOptional() else .none,
                        try renderNode(c, payload.cond),
                    } },
                });
            } else {
                return c.addNode(.{
                    .tag = .switch_case,
                    .main_token = try c.addToken(.equal_angle_bracket_right, "=>"),
                    .data = .{ .extra_and_node = .{
                        try c.addExtra(try c.listToSpan(items)),
                        try renderNode(c, payload.cond),
                    } },
                });
            }
        },
        .opaque_literal => {
            const opaque_tok = try c.addToken(.keyword_opaque, "opaque");
            _ = try c.addToken(.l_brace, "{");
            _ = try c.addToken(.r_brace, "}");

            return c.addNode(.{
                .tag = .container_decl_two,
                .main_token = opaque_tok,
                .data = .{ .opt_node_and_opt_node = .{
                    .none, .none,
                } },
            });
        },
        .array_access => {
            const payload = node.castTag(.array_access).?.data;
            const lhs = try renderNodeGrouped(c, payload.lhs);
            const l_bracket = try c.addToken(.l_bracket, "[");
            const index_expr = try renderNode(c, payload.rhs);
            _ = try c.addToken(.r_bracket, "]");
            return c.addNode(.{
                .tag = .array_access,
                .main_token = l_bracket,
                .data = .{ .node_and_node = .{
                    lhs, index_expr,
                } },
            });
        },
        .array_type => {
            const payload = node.castTag(.array_type).?.data;
            return renderArrayType(c, payload.len, payload.elem_type);
        },
        .null_sentinel_array_type => {
            const payload = node.castTag(.null_sentinel_array_type).?.data;
            return renderNullSentinelArrayType(c, payload.len, payload.elem_type);
        },
        .array_filler => {
            const payload = node.castTag(.array_filler).?.data;

            const as_tok = try c.addToken(.builtin, "@as");
            _ = try c.addToken(.l_paren, "(");
            const type_node = try renderArrayType(c, payload.count, payload.type);
            _ = try c.addToken(.comma, ",");
            const splat_node = try renderBuiltinCall(c, "@splat", &.{payload.filler});
            _ = try c.addToken(.r_paren, ")");

            return c.addNode(.{
                .tag = .builtin_call_two,
                .main_token = as_tok,
                .data = .{ .opt_node_and_opt_node = .{
                    .fromOptional(type_node), .fromOptional(splat_node),
                } },
            });
        },
        .empty_array => {
            const payload = node.castTag(.empty_array).?.data;

            const type_expr = try renderNode(c, payload);
            return renderArrayInit(c, type_expr, &.{});
        },
        .array_init => {
            const payload = node.castTag(.array_init).?.data;
            const type_expr = try renderNode(c, payload.cond);
            return renderArrayInit(c, type_expr, payload.cases);
        },
        .vector_zero_init => {
            const payload = node.castTag(.vector_zero_init).?.data;
            return renderBuiltinCall(c, "@splat", &.{payload});
        },
        .field_access => {
            const payload = node.castTag(.field_access).?.data;
            const lhs = try renderNodeGrouped(c, payload.lhs);
            return renderFieldAccess(c, lhs, payload.field_name);
        },
        .field_builtin => {
            const payload = node.castTag(.field_builtin).?.data;
            return renderBuiltinCall(c, "@field", &.{ payload.lhs, payload.rhs });
        },
        .@"struct", .@"union", .@"opaque" => return renderContainer(c, node),
        .enum_constant => {
            const payload = node.castTag(.enum_constant).?.data;

            if (payload.is_public) _ = try c.addToken(.keyword_pub, "pub");
            const const_tok = try c.addToken(.keyword_const, "const");
            _ = try c.addIdentifier(payload.name);

            const type_node_opt = if (payload.type) |enum_const_type| blk: {
                _ = try c.addToken(.colon, ":");
                break :blk try renderNode(c, enum_const_type);
            } else null;

            _ = try c.addToken(.equal, "=");

            const init_node = try renderNode(c, payload.value);
            _ = try c.addToken(.semicolon, ";");

            return c.addNode(.{
                .tag = .simple_var_decl,
                .main_token = const_tok,
                .data = .{ .opt_node_and_opt_node = .{
                    .fromOptional(type_node_opt),
                    init_node.toOptional(),
                } },
            });
        },
        .tuple => {
            const payload = node.castTag(.tuple).?.data;
            _ = try c.addToken(.period, ".");
            const l_brace = try c.addToken(.l_brace, "{");
            var inits = try c.gpa.alloc(NodeIndex, payload.len);
            defer c.gpa.free(inits);

            for (payload, 0..) |init, i| {
                if (i != 0) _ = try c.addToken(.comma, ",");
                inits[i] = try renderNode(c, init);
            }
            _ = try c.addToken(.r_brace, "}");
            if (payload.len < 3) {
                return c.addNode(.{
                    .tag = .array_init_dot_two,
                    .main_token = l_brace,
                    .data = .{ .opt_node_and_opt_node = .{
                        if (inits.len >= 1) inits[0].toOptional() else .none,
                        if (inits.len >= 2) inits[1].toOptional() else .none,
                    } },
                });
            } else {
                return c.addNode(.{
                    .tag = .array_init_dot,
                    .main_token = l_brace,
                    .data = .{ .extra_range = try c.listToSpan(inits) },
                });
            }
        },
        .container_init_dot => {
            const payload = node.castTag(.container_init_dot).?.data;
            _ = try c.addToken(.period, ".");
            const l_brace = try c.addToken(.l_brace, "{");
            var inits = try c.gpa.alloc(NodeIndex, payload.len);
            defer c.gpa.free(inits);

            for (payload, 0..) |init, i| {
                _ = try c.addToken(.period, ".");
                _ = try c.addIdentifier(init.name);
                _ = try c.addToken(.equal, "=");
                inits[i] = try renderNode(c, init.value);
                _ = try c.addToken(.comma, ",");
            }
            _ = try c.addToken(.r_brace, "}");

            if (payload.len < 3) {
                return c.addNode(.{
                    .tag = .struct_init_dot_two_comma,
                    .main_token = l_brace,
                    .data = .{ .opt_node_and_opt_node = .{
                        if (inits.len >= 1) inits[0].toOptional() else .none,
                        if (inits.len >= 2) inits[1].toOptional() else .none,
                    } },
                });
            } else {
                return c.addNode(.{
                    .tag = .struct_init_dot_comma,
                    .main_token = l_brace,
                    .data = .{ .extra_range = try c.listToSpan(inits) },
                });
            }
        },
        .container_init => {
            const payload = node.castTag(.container_init).?.data;
            const lhs = try renderNode(c, payload.lhs);

            const l_brace = try c.addToken(.l_brace, "{");
            var inits = try c.gpa.alloc(NodeIndex, payload.inits.len);
            defer c.gpa.free(inits);

            for (payload.inits, 0..) |init, i| {
                _ = try c.addToken(.period, ".");
                _ = try c.addIdentifier(init.name);
                _ = try c.addToken(.equal, "=");
                inits[i] = try renderNode(c, init.value);
                _ = try c.addToken(.comma, ",");
            }
            _ = try c.addToken(.r_brace, "}");

            switch (inits.len) {
                0 => return c.addNode(.{
                    .tag = .struct_init_one,
                    .main_token = l_brace,
                    .data = .{ .node_and_opt_node = .{
                        lhs, .none,
                    } },
                }),
                1 => return c.addNode(.{
                    .tag = .struct_init_one_comma,
                    .main_token = l_brace,
                    .data = .{ .node_and_opt_node = .{
                        lhs, inits[0].toOptional(),
                    } },
                }),
                else => return c.addNode(.{
                    .tag = .struct_init_comma,
                    .main_token = l_brace,
                    .data = .{ .node_and_extra = .{
                        lhs,
                        try c.addExtra(try c.listToSpan(inits)),
                    } },
                }),
            }
        },
        .static_assert => {
            const payload = node.castTag(.static_assert).?.data;
            const comptime_tok = try c.addToken(.keyword_comptime, "comptime");
            const l_brace = try c.addToken(.l_brace, "{");

            const if_tok = try c.addToken(.keyword_if, "if");
            _ = try c.addToken(.l_paren, "(");
            const cond = try c.addNode(.{
                .tag = .bool_not,
                .main_token = try c.addToken(.bang, "!"),
                .data = .{
                    .node = try renderNodeGrouped(c, payload.lhs),
                },
            });
            _ = try c.addToken(.r_paren, ")");

            const compile_error_tok = try c.addToken(.builtin, "@compileError");
            _ = try c.addToken(.l_paren, "(");
            const err_msg = try renderNode(c, payload.rhs);
            _ = try c.addToken(.r_paren, ")");
            const compile_error = try c.addNode(.{
                .tag = .builtin_call_two,
                .main_token = compile_error_tok,
                .data = .{ .opt_node_and_opt_node = .{
                    err_msg.toOptional(), .none,
                } },
            });

            const if_node = try c.addNode(.{
                .tag = .if_simple,
                .main_token = if_tok,
                .data = .{ .node_and_node = .{
                    cond, compile_error,
                } },
            });
            _ = try c.addToken(.semicolon, ";");
            _ = try c.addToken(.r_brace, "}");
            const block_node = try c.addNode(.{
                .tag = .block_two_semicolon,
                .main_token = l_brace,
                .data = .{ .opt_node_and_opt_node = .{
                    if_node.toOptional(), .none,
                } },
            });

            return c.addNode(.{
                .tag = .@"comptime",
                .main_token = comptime_tok,
                .data = .{
                    .node = block_node,
                },
            });
        },
        .@"anytype" => unreachable, // Handled in renderParams
        .root_ref => {
            const payload = node.castTag(.root_ref).?.data;
            const root_tok = try c.addNode(.{
                .tag = .identifier,
                .main_token = try c.addIdentifier("__root"),
                .data = undefined,
            });
            return renderFieldAccess(c, root_tok, payload);
        },
    }
}