Commit 8e46d06650

Andrew Kelley <andrew@ziglang.org>
2021-02-05 00:38:29
zig fmt: implement fn protos and defers
1 parent 4428acf
Changed files (4)
lib/std/zig/ast.zig
@@ -244,9 +244,12 @@ pub const Tree = struct {
             .AnyType,
             .Comptime,
             .Nosuspend,
-            .Block,
             .AsmSimple,
             .Asm,
+            .FnProtoSimple,
+            .FnProtoMulti,
+            .FnProtoOne,
+            .FnProto,
             => return main_tokens[n],
 
             .Catch,
@@ -303,6 +306,7 @@ pub const Tree = struct {
             .SwitchCaseOne,
             .SwitchRange,
             .FnDecl,
+            .ErrorUnion,
             => n = datas[n].lhs,
 
             .ContainerFieldInit,
@@ -342,6 +346,18 @@ pub const Tree = struct {
                 return i;
             },
 
+            .Block,
+            .BlockTwo,
+            => {
+                // Look for a label.
+                const lbrace = main_tokens[n];
+                if (token_tags[lbrace - 1] == .Colon) {
+                    return lbrace - 2;
+                } else {
+                    return lbrace;
+                }
+            },
+
             .ArrayType => unreachable, // TODO
             .ArrayTypeSentinel => unreachable, // TODO
             .PtrTypeAligned => unreachable, // TODO
@@ -355,10 +371,6 @@ pub const Tree = struct {
             .While => unreachable, // TODO
             .ForSimple => unreachable, // TODO
             .For => unreachable, // TODO
-            .FnProtoSimple => unreachable, // TODO
-            .FnProtoSimpleMulti => unreachable, // TODO
-            .FnProtoOne => unreachable, // TODO
-            .FnProto => unreachable, // TODO
             .ContainerDecl => unreachable, // TODO
             .ContainerDeclArg => unreachable, // TODO
             .TaggedUnion => unreachable, // TODO
@@ -366,7 +378,6 @@ pub const Tree = struct {
             .AsmOutput => unreachable, // TODO
             .AsmInput => unreachable, // TODO
             .ErrorValue => unreachable, // TODO
-            .ErrorUnion => unreachable, // TODO
         };
     }
 
@@ -468,13 +479,20 @@ pub const Tree = struct {
             .Call,
             .BuiltinCall,
             => {
-                end_offset += 1; // for the `)`
+                end_offset += 1; // for the rparen
                 const params = tree.extraData(datas[n].rhs, Node.SubRange);
                 if (params.end - params.start == 0) {
                     return main_tokens[n] + end_offset;
                 }
                 n = tree.extra_data[params.end - 1]; // last parameter
             },
+            .Block => {
+                end_offset += 1; // for the rbrace
+                if (datas[n].rhs - datas[n].lhs == 0) {
+                    return main_tokens[n] + end_offset;
+                }
+                n = tree.extra_data[datas[n].rhs - 1]; // last statement
+            },
             .CallOne,
             .ArrayAccess,
             => {
@@ -485,8 +503,8 @@ pub const Tree = struct {
                 n = datas[n].rhs;
             },
 
-            .BuiltinCallTwo => {
-                end_offset += 1; // for the rparen
+            .BuiltinCallTwo, .BlockTwo => {
+                end_offset += 1; // for the rparen/rbrace
                 if (datas[n].rhs == 0) {
                     if (datas[n].lhs == 0) {
                         return main_tokens[n] + end_offset;
@@ -511,7 +529,6 @@ pub const Tree = struct {
             .Continue => unreachable, // TODO
             .EnumLiteral => unreachable, // TODO
             .ErrorSetDecl => unreachable, // TODO
-            .Block => unreachable, // TODO
             .AsmSimple => unreachable, // TODO
             .Asm => unreachable, // TODO
             .SliceOpen => unreachable, // TODO
@@ -539,7 +556,7 @@ pub const Tree = struct {
             .ForSimple => unreachable, // TODO
             .For => unreachable, // TODO
             .FnProtoSimple => unreachable, // TODO
-            .FnProtoSimpleMulti => unreachable, // TODO
+            .FnProtoMulti => unreachable, // TODO
             .FnProtoOne => unreachable, // TODO
             .FnProto => unreachable, // TODO
             .ContainerDecl => unreachable, // TODO
@@ -665,6 +682,67 @@ pub const Tree = struct {
         });
     }
 
+    pub fn fnProtoSimple(tree: Tree, buffer: *[1]Node.Index, node: Node.Index) Full.FnProto {
+        assert(tree.nodes.items(.tag)[node] == .FnProtoSimple);
+        const data = tree.nodes.items(.data)[node];
+        buffer[0] = data.lhs;
+        const params = if (data.lhs == 0) buffer[0..0] else buffer[0..1];
+        return tree.fullFnProto(.{
+            .fn_token = tree.nodes.items(.main_token)[node],
+            .return_type = data.rhs,
+            .params = params,
+            .align_expr = 0,
+            .section_expr = 0,
+            .callconv_expr = 0,
+        });
+    }
+
+    pub fn fnProtoMulti(tree: Tree, node: Node.Index) Full.FnProto {
+        assert(tree.nodes.items(.tag)[node] == .FnProtoMulti);
+        const data = tree.nodes.items(.data)[node];
+        const params_range = tree.extraData(data.lhs, Node.SubRange);
+        const params = tree.extra_data[params_range.start..params_range.end];
+        return tree.fullFnProto(.{
+            .fn_token = tree.nodes.items(.main_token)[node],
+            .return_type = data.rhs,
+            .params = params,
+            .align_expr = 0,
+            .section_expr = 0,
+            .callconv_expr = 0,
+        });
+    }
+
+    pub fn fnProtoOne(tree: Tree, buffer: *[1]Node.Index, node: Node.Index) Full.FnProto {
+        assert(tree.nodes.items(.tag)[node] == .FnProtoOne);
+        const data = tree.nodes.items(.data)[node];
+        const extra = tree.extraData(data.lhs, Node.FnProtoOne);
+        buffer[0] = extra.param;
+        const params = if (extra.param == 0) buffer[0..0] else buffer[0..1];
+        return tree.fullFnProto(.{
+            .fn_token = tree.nodes.items(.main_token)[node],
+            .return_type = data.rhs,
+            .params = params,
+            .align_expr = extra.align_expr,
+            .section_expr = extra.section_expr,
+            .callconv_expr = extra.callconv_expr,
+        });
+    }
+
+    pub fn fnProto(tree: Tree, node: Node.Index) Full.FnProto {
+        assert(tree.nodes.items(.tag)[node] == .FnProto);
+        const data = tree.nodes.items(.data)[node];
+        const extra = tree.extraData(data.lhs, Node.FnProto);
+        const params = tree.extra_data[extra.params_start..extra.params_end];
+        return tree.fullFnProto(.{
+            .fn_token = tree.nodes.items(.main_token)[node],
+            .return_type = data.rhs,
+            .params = params,
+            .align_expr = extra.align_expr,
+            .section_expr = extra.section_expr,
+            .callconv_expr = extra.callconv_expr,
+        });
+    }
+
     fn fullVarDecl(tree: Tree, info: Full.VarDecl.Ast) Full.VarDecl {
         const token_tags = tree.tokens.items(.tag);
         var result: Full.VarDecl = .{
@@ -728,6 +806,14 @@ pub const Tree = struct {
         }
         return result;
     }
+
+    fn fullFnProto(tree: Tree, info: Full.FnProto.Ast) Full.FnProto {
+        const token_tags = tree.tokens.items(.tag);
+        var result: Full.FnProto = .{
+            .ast = info,
+        };
+        return result;
+    }
 };
 
 /// Fully assembled AST node information.
@@ -778,6 +864,19 @@ pub const Full = struct {
             align_expr: Node.Index,
         };
     };
+
+    pub const FnProto = struct {
+        ast: Ast,
+
+        pub const Ast = struct {
+            fn_token: TokenIndex,
+            return_type: Node.Index,
+            params: []const Node.Index,
+            align_expr: Node.Index,
+            section_expr: Node.Index,
+            callconv_expr: Node.Index,
+        };
+    };
 };
 
 pub const Error = union(enum) {
@@ -1247,7 +1346,7 @@ pub const Node = struct {
         FnProtoSimple,
         /// `fn(a: b, c: d) rhs`. `sub_range_list[lhs]`.
         /// anytype and ... parameters are omitted from the AST tree.
-        FnProtoSimpleMulti,
+        FnProtoMulti,
         /// `fn(a: b) rhs linksection(e) callconv(f)`. lhs is index into extra_data.
         /// zero or one parameters.
         /// anytype and ... parameters are omitted from the AST tree.
@@ -1321,6 +1420,9 @@ pub const Node = struct {
         Comptime,
         /// `nosuspend lhs`. rhs unused.
         Nosuspend,
+        /// `{lhs; rhs;}`. rhs or lhs can be omitted.
+        /// main_token points at the `{`.
+        BlockTwo,
         /// `{}`. `sub_list[lhs..rhs]`.
         /// main_token points at the `{`.
         Block,
lib/std/zig/parse.zig
@@ -541,7 +541,7 @@ const Parser = struct {
                 .multi => |list| {
                     const span = try p.listToSpan(list);
                     return p.addNode(.{
-                        .tag = .FnProtoSimpleMulti,
+                        .tag = .FnProtoMulti,
                         .main_token = fn_token,
                         .data = .{
                             .lhs = try p.addExtra(Node.SubRange{
@@ -810,6 +810,22 @@ const Parser = struct {
         return statement;
     }
 
+    /// If a parse error occurs, reports an error, but then finds the next statement
+    /// and returns that one instead. If a parse error occurs but there is no following
+    /// statement, returns 0.
+    fn expectStatementRecoverable(p: *Parser) error{OutOfMemory}!Node.Index {
+        while (true) {
+            return p.expectStatement() catch |err| switch (err) {
+                error.OutOfMemory => return error.OutOfMemory,
+                error.ParseError => {
+                    p.findNextStmt(); // Try to skip to the next statement.
+                    if (p.token_tags[p.tok_i] == .RBrace) return null_node;
+                    continue;
+                },
+            };
+        }
+    }
+
     /// IfStatement
     ///     <- IfPrefix BlockExpr ( KEYWORD_else Payload? Statement )?
     ///      / IfPrefix AssignExpr ( SEMICOLON / KEYWORD_else Payload? Statement )
@@ -1859,25 +1875,53 @@ const Parser = struct {
     fn parseBlock(p: *Parser) !Node.Index {
         const lbrace = p.eatToken(.LBrace) orelse return null_node;
 
+        if (p.eatToken(.RBrace)) |_| {
+            return p.addNode(.{
+                .tag = .BlockTwo,
+                .main_token = lbrace,
+                .data = .{
+                    .lhs = 0,
+                    .rhs = 0,
+                },
+            });
+        }
+
+        const stmt_one = try p.expectStatementRecoverable();
+        if (p.eatToken(.RBrace)) |_| {
+            return p.addNode(.{
+                .tag = .BlockTwo,
+                .main_token = lbrace,
+                .data = .{
+                    .lhs = stmt_one,
+                    .rhs = 0,
+                },
+            });
+        }
+        const stmt_two = try p.expectStatementRecoverable();
+        if (p.eatToken(.RBrace)) |_| {
+            return p.addNode(.{
+                .tag = .BlockTwo,
+                .main_token = lbrace,
+                .data = .{
+                    .lhs = stmt_one,
+                    .rhs = stmt_two,
+                },
+            });
+        }
+
         var statements = std.ArrayList(Node.Index).init(p.gpa);
         defer statements.deinit();
 
+        try statements.appendSlice(&[_]Node.Index{ stmt_one, stmt_two });
+
         while (true) {
-            const statement = (p.parseStatement() catch |err| switch (err) {
-                error.OutOfMemory => return error.OutOfMemory,
-                error.ParseError => {
-                    // try to skip to the next statement
-                    p.findNextStmt();
-                    continue;
-                },
-            });
+            const statement = try p.expectStatementRecoverable();
             if (statement == 0) break;
             try statements.append(statement);
+            if (p.token_tags[p.tok_i] == .RBrace) break;
         }
-
-        const rbrace = try p.expectToken(.RBrace);
+        _ = try p.expectToken(.RBrace);
         const statements_span = try p.listToSpan(statements.items);
-
         return p.addNode(.{
             .tag = .Block,
             .main_token = lbrace,
lib/std/zig/parser_test.zig
@@ -100,44 +100,44 @@ test "zig fmt: top-level fields" {
     );
 }
 
-//test "zig fmt: decl between fields" {
-//    try testError(
-//        \\const S = struct {
-//        \\    const foo = 2;
-//        \\    const bar = 2;
-//        \\    const baz = 2;
-//        \\    a: usize,
-//        \\    const foo1 = 2;
-//        \\    const bar1 = 2;
-//        \\    const baz1 = 2;
-//        \\    b: usize,
-//        \\};
-//    , &[_]Error{
-//        .DeclBetweenFields,
-//    });
-//}
-//
-//test "zig fmt: eof after missing comma" {
-//    try testError(
-//        \\foo()
-//    , &[_]Error{
-//        .ExpectedToken,
-//    });
-//}
-//
-//test "zig fmt: errdefer with payload" {
-//    try testCanonical(
-//        \\pub fn main() anyerror!void {
-//        \\    errdefer |a| x += 1;
-//        \\    errdefer |a| {}
-//        \\    errdefer |a| {
-//        \\        x += 1;
-//        \\    }
-//        \\}
-//        \\
-//    );
-//}
-//
+test "zig fmt: decl between fields" {
+    try testError(
+        \\const S = struct {
+        \\    const foo = 2;
+        \\    const bar = 2;
+        \\    const baz = 2;
+        \\    a: usize,
+        \\    const foo1 = 2;
+        \\    const bar1 = 2;
+        \\    const baz1 = 2;
+        \\    b: usize,
+        \\};
+    , &[_]Error{
+        .DeclBetweenFields,
+    });
+}
+
+test "zig fmt: eof after missing comma" {
+    try testError(
+        \\foo()
+    , &[_]Error{
+        .ExpectedToken,
+    });
+}
+
+test "zig fmt: errdefer with payload" {
+    try testCanonical(
+        \\pub fn main() anyerror!void {
+        \\    errdefer |a| x += 1;
+        \\    errdefer |a| {}
+        \\    errdefer |a| {
+        \\        x += 1;
+        \\    }
+        \\}
+        \\
+    );
+}
+
 //test "zig fmt: nosuspend block" {
 //    try testCanonical(
 //        \\pub fn main() anyerror!void {
lib/std/zig/render.zig
@@ -77,22 +77,11 @@ fn renderExtraNewline(ais: *Ais, tree: ast.Tree, node: ast.Node.Index) Error!voi
 }
 
 fn renderExtraNewlineToken(ais: *Ais, tree: ast.Tree, first_token: ast.TokenIndex) Error!void {
-    @panic("TODO implement renderExtraNewlineToken");
-    //var prev_token = first_token;
-    //if (prev_token == 0) return;
-    //const token_tags = tree.tokens.items(.tag);
-    //var newline_threshold: usize = 2;
-    //while (token_tags[prev_token - 1] == .DocComment) {
-    //    if (tree.tokenLocation(tree.token_locs[prev_token - 1].end, prev_token).line == 1) {
-    //        newline_threshold += 1;
-    //    }
-    //    prev_token -= 1;
-    //}
-    //const prev_token_end = tree.token_locs[prev_token - 1].end;
-    //const loc = tree.tokenLocation(prev_token_end, first_token);
-    //if (loc.line >= newline_threshold) {
-    //    try ais.insertNewline();
-    //}
+    if (first_token == 0) return;
+    const token_starts = tree.tokens.items(.start);
+    if (tree.tokenLocation(token_starts[first_token - 1], first_token).line >= 2) {
+        return ais.insertNewline();
+    }
 }
 
 fn renderContainerDecl(ais: *Ais, tree: ast.Tree, decl: ast.Node.Index, space: Space) Error!void {
@@ -101,24 +90,43 @@ fn renderContainerDecl(ais: *Ais, tree: ast.Tree, decl: ast.Node.Index, space: S
     const datas = tree.nodes.items(.data);
     try renderDocComments(ais, tree, tree.firstToken(decl));
     switch (tree.nodes.items(.tag)[decl]) {
-        .FnProtoSimple => unreachable, // TODO
-        .FnProtoSimpleMulti => unreachable, // TODO
-        .FnProtoOne => unreachable, // TODO
-        .FnDecl => unreachable, // TODO
-        .FnProto => unreachable, // TODO
-        //    .FnProto => {
-        //        const fn_proto = @fieldParentPtr(ast.Node.FnProto, "base", decl);
-
-        //        try renderDocComments(ais, tree, fn_proto, fn_proto.getDocComments());
-
-        //        if (fn_proto.getBodyNode()) |body_node| {
-        //            try renderExpression(ais, tree, decl, .Space);
-        //            try renderExpression(ais, tree, body_node, space);
-        //        } else {
-        //            try renderExpression(ais, tree, decl, .None);
-        //            try renderToken(ais, tree, tree.nextToken(decl.lastToken()), space);
-        //        }
-        //    },
+        .FnDecl => {
+            // Some examples:
+            // pub extern "foo" fn ...
+            // export fn ...
+            const fn_proto = datas[decl].lhs;
+            const fn_token = main_tokens[fn_proto];
+            // Go back to the first token we should render here.
+            var i = fn_token;
+            while (i > 0) {
+                i -= 1;
+                switch (token_tags[i]) {
+                    .Keyword_extern,
+                    .Keyword_export,
+                    .Keyword_pub,
+                    .StringLiteral,
+                    => continue,
+
+                    else => {
+                        i += 1;
+                        break;
+                    },
+                }
+            }
+            while (i < fn_token) : (i += 1) {
+                try renderToken(ais, tree, i, .Space);
+            }
+            try renderExpression(ais, tree, fn_proto, .Space);
+            return renderExpression(ais, tree, datas[decl].rhs, space);
+        },
+        .FnProtoSimple,
+        .FnProtoMulti,
+        .FnProtoOne,
+        .FnProto,
+        => {
+            try renderExpression(ais, tree, decl, .None);
+            try renderToken(ais, tree, tree.lastToken(decl) + 1, space); // semicolon
+        },
 
         .UsingNamespace => unreachable, // TODO
         //    .Use => {
@@ -186,90 +194,48 @@ fn renderExpression(ais: *Ais, tree: ast.Tree, node: ast.Node.Index, space: Spac
         //    }
         //    return renderToken(ais, tree, any_type.token, space);
         //},
+        .BlockTwo => {
+            var statements = [2]ast.Node.Index{ datas[node].lhs, datas[node].rhs };
+            if (datas[node].lhs == 0) {
+                return renderBlock(ais, tree, main_tokens[node], statements[0..0], space);
+            } else if (datas[node].rhs == 0) {
+                return renderBlock(ais, tree, main_tokens[node], statements[0..1], space);
+            } else {
+                return renderBlock(ais, tree, main_tokens[node], statements[0..2], space);
+            }
+        },
         .Block => {
             const lbrace = main_tokens[node];
-            if (token_tags[lbrace - 1] == .Colon and
-                token_tags[lbrace - 2] == .Identifier)
-            {
-                try renderToken(ais, tree, lbrace - 2, .None);
-                try renderToken(ais, tree, lbrace - 1, .Space);
-            }
-            const nodes_data = tree.nodes.items(.data);
-            const statements = tree.extra_data[nodes_data[node].lhs..nodes_data[node].rhs];
+            const statements = tree.extra_data[datas[node].lhs..datas[node].rhs];
+            return renderBlock(ais, tree, main_tokens[node], statements, space);
+        },
 
-            if (statements.len == 0) {
-                ais.pushIndentNextLine();
-                try renderToken(ais, tree, lbrace, .None);
-                ais.popIndent();
-                const rbrace = lbrace + 1;
-                return renderToken(ais, tree, rbrace, space);
-            } else {
-                ais.pushIndentNextLine();
-
-                try renderToken(ais, tree, lbrace, .Newline);
-
-                for (statements) |stmt, i| {
-                    switch (node_tags[stmt]) {
-                        .GlobalVarDecl => try renderVarDecl(ais, tree, tree.globalVarDecl(stmt)),
-                        .LocalVarDecl => try renderVarDecl(ais, tree, tree.localVarDecl(stmt)),
-                        .SimpleVarDecl => try renderVarDecl(ais, tree, tree.simpleVarDecl(stmt)),
-                        .AlignedVarDecl => try renderVarDecl(ais, tree, tree.alignedVarDecl(stmt)),
-                        else => {
-                            const semicolon = tree.lastToken(stmt) + 1;
-                            if (token_tags[semicolon] == .Semicolon) {
-                                try renderExpression(ais, tree, stmt, .None);
-                                try renderToken(ais, tree, semicolon, .Newline);
-                            } else {
-                                try renderExpression(ais, tree, stmt, .Newline);
-                            }
-                        },
-                    }
+        .ErrDefer => {
+            const defer_token = main_tokens[node];
+            const payload_token = datas[node].lhs;
+            const expr = datas[node].rhs;
 
-                    if (i + 1 < statements.len) {
-                        try renderExtraNewline(ais, tree, statements[i + 1]);
-                    }
-                }
-                ais.popIndent();
-                // The rbrace could be +1 or +2 from the last token of the last
-                // statement in the block because lastToken() does not count semicolons.
-                const maybe_rbrace = tree.lastToken(statements[statements.len - 1]) + 1;
-                if (token_tags[maybe_rbrace] == .RBrace) {
-                    return renderToken(ais, tree, maybe_rbrace, space);
-                } else {
-                    assert(token_tags[maybe_rbrace + 1] == .RBrace);
-                    return renderToken(ais, tree, maybe_rbrace + 1, space);
-                }
+            try renderToken(ais, tree, defer_token, .Space);
+            if (payload_token != 0) {
+                try renderToken(ais, tree, payload_token - 1, .None); // |
+                try renderToken(ais, tree, payload_token, .None); // identifier
+                try renderToken(ais, tree, payload_token + 1, .Space); // |
             }
+            return renderExpression(ais, tree, expr, space);
         },
 
-        .Defer => unreachable, // TODO
-        .ErrDefer => unreachable, // TODO
-        //.Defer => {
-        //    const defer_node = @fieldParentPtr(ast.Node.Defer, "base", base);
-
-        //    try renderToken(ais, tree, defer_node.defer_token, Space.Space);
-        //    if (defer_node.payload) |payload| {
-        //        try renderExpression(ais, tree, payload, Space.Space);
-        //    }
-        //    return renderExpression(ais, tree, defer_node.expr, space);
-        //},
-        .Comptime => {
+        .Defer => {
+            const defer_token = main_tokens[node];
+            const expr = datas[node].rhs;
+            try renderToken(ais, tree, defer_token, .Space);
+            return renderExpression(ais, tree, expr, space);
+        },
+        .Comptime, .Nosuspend => {
             const comptime_token = main_tokens[node];
             const block = datas[node].lhs;
             try renderToken(ais, tree, comptime_token, .Space);
             return renderExpression(ais, tree, block, space);
         },
-        .Nosuspend => unreachable, // TODO
-        //.Nosuspend => {
-        //    const nosuspend_node = @fieldParentPtr(ast.Node.Nosuspend, "base", base);
-        //    if (mem.eql(u8, tree.tokenSlice(nosuspend_node.nosuspend_token), "noasync")) {
-        //        // TODO: remove this
-        //        try ais.writer().writeAll("nosuspend ");
-        //    } else {
-        //        try renderToken(ais, tree, nosuspend_node.nosuspend_token, Space.Space);
-        //    }
-        //    return renderExpression(ais, tree, nosuspend_node.expr, space);
-        //},
 
         .Suspend => unreachable, // TODO
         //.Suspend => {
@@ -1274,140 +1240,16 @@ fn renderExpression(ais: *Ais, tree: ast.Tree, node: ast.Node.Index, space: Spac
             return renderBuiltinCall(ais, tree, main_tokens[node], params, space);
         },
 
-        .FnProtoSimple => unreachable, // TODO
-        .FnProtoSimpleMulti => unreachable, // TODO
-        .FnProtoOne => unreachable, // TODO
-        .FnProto => unreachable, // TODO
-        //.FnProto => {
-        //    const fn_proto = @fieldParentPtr(ast.Node.FnProto, "base", base);
-
-        //    if (fn_proto.getVisibToken()) |visib_token_index| {
-        //        const visib_token = tree.token_tags[visib_token_index];
-        //        assert(visib_token == .Keyword_pub or visib_token == .Keyword_export);
-
-        //        try renderToken(ais, tree, visib_token_index, Space.Space); // pub
-        //    }
-
-        //    if (fn_proto.getExternExportInlineToken()) |extern_export_inline_token| {
-        //        if (fn_proto.getIsExternPrototype() == null)
-        //            try renderToken(ais, tree, extern_export_inline_token, Space.Space); // extern/export/inline
-        //    }
-
-        //    if (fn_proto.getLibName()) |lib_name| {
-        //        try renderExpression(ais, tree, lib_name, Space.Space);
-        //    }
-
-        //    const lparen = if (fn_proto.getNameToken()) |name_token| blk: {
-        //        try renderToken(ais, tree, fn_proto.fn_token, Space.Space); // fn
-        //        try renderToken(ais, tree, name_token, Space.None); // name
-        //        break :blk tree.nextToken(name_token);
-        //    } else blk: {
-        //        try renderToken(ais, tree, fn_proto.fn_token, Space.Space); // fn
-        //        break :blk tree.nextToken(fn_proto.fn_token);
-        //    };
-        //    assert(tree.token_tags[lparen] == .LParen);
-
-        //    const rparen = tree.prevToken(
-        //        // the first token for the annotation expressions is the left
-        //        // parenthesis, hence the need for two prevToken
-        //        if (fn_proto.getAlignExpr()) |align_expr|
-        //            tree.prevToken(tree.prevToken(align_expr.firstToken()))
-        //        else if (fn_proto.getSectionExpr()) |section_expr|
-        //            tree.prevToken(tree.prevToken(section_expr.firstToken()))
-        //        else if (fn_proto.getCallconvExpr()) |callconv_expr|
-        //            tree.prevToken(tree.prevToken(callconv_expr.firstToken()))
-        //        else switch (fn_proto.return_type) {
-        //            .Explicit => |node| node.firstToken(),
-        //            .InferErrorSet => |node| tree.prevToken(node.firstToken()),
-        //            .Invalid => unreachable,
-        //        },
-        //    );
-        //    assert(tree.token_tags[rparen] == .RParen);
-
-        //    const src_params_trailing_comma = blk: {
-        //        const maybe_comma = tree.token_tags[rparen - 1];
-        //        break :blk maybe_comma == .Comma or maybe_comma == .LineComment;
-        //    };
-
-        //    if (!src_params_trailing_comma) {
-        //        try renderToken(ais, tree, lparen, Space.None); // (
-
-        //        // render all on one line, no trailing comma
-        //        for (fn_proto.params()) |param_decl, i| {
-        //            try renderParamDecl(allocator, ais, tree, param_decl, Space.None);
-
-        //            if (i + 1 < fn_proto.params_len or fn_proto.getVarArgsToken() != null) {
-        //                const comma = tree.nextToken(param_decl.lastToken());
-        //                try renderToken(ais, tree, comma, Space.Space); // ,
-        //            }
-        //        }
-        //        if (fn_proto.getVarArgsToken()) |var_args_token| {
-        //            try renderToken(ais, tree, var_args_token, Space.None);
-        //        }
-        //    } else {
-        //        // one param per line
-        //        ais.pushIndent();
-        //        defer ais.popIndent();
-        //        try renderToken(ais, tree, lparen, Space.Newline); // (
-
-        //        for (fn_proto.params()) |param_decl| {
-        //            try renderParamDecl(allocator, ais, tree, param_decl, Space.Comma);
-        //        }
-        //        if (fn_proto.getVarArgsToken()) |var_args_token| {
-        //            try renderToken(ais, tree, var_args_token, Space.Comma);
-        //        }
-        //    }
-
-        //    try renderToken(ais, tree, rparen, Space.Space); // )
-
-        //    if (fn_proto.getAlignExpr()) |align_expr| {
-        //        const align_rparen = tree.nextToken(align_expr.lastToken());
-        //        const align_lparen = tree.prevToken(align_expr.firstToken());
-        //        const align_kw = tree.prevToken(align_lparen);
-
-        //        try renderToken(ais, tree, align_kw, Space.None); // align
-        //        try renderToken(ais, tree, align_lparen, Space.None); // (
-        //        try renderExpression(ais, tree, align_expr, Space.None);
-        //        try renderToken(ais, tree, align_rparen, Space.Space); // )
-        //    }
-
-        //    if (fn_proto.getSectionExpr()) |section_expr| {
-        //        const section_rparen = tree.nextToken(section_expr.lastToken());
-        //        const section_lparen = tree.prevToken(section_expr.firstToken());
-        //        const section_kw = tree.prevToken(section_lparen);
-
-        //        try renderToken(ais, tree, section_kw, Space.None); // section
-        //        try renderToken(ais, tree, section_lparen, Space.None); // (
-        //        try renderExpression(ais, tree, section_expr, Space.None);
-        //        try renderToken(ais, tree, section_rparen, Space.Space); // )
-        //    }
-
-        //    if (fn_proto.getCallconvExpr()) |callconv_expr| {
-        //        const callconv_rparen = tree.nextToken(callconv_expr.lastToken());
-        //        const callconv_lparen = tree.prevToken(callconv_expr.firstToken());
-        //        const callconv_kw = tree.prevToken(callconv_lparen);
-
-        //        try renderToken(ais, tree, callconv_kw, Space.None); // callconv
-        //        try renderToken(ais, tree, callconv_lparen, Space.None); // (
-        //        try renderExpression(ais, tree, callconv_expr, Space.None);
-        //        try renderToken(ais, tree, callconv_rparen, Space.Space); // )
-        //    } else if (fn_proto.getIsExternPrototype() != null) {
-        //        try ais.writer().writeAll("callconv(.C) ");
-        //    } else if (fn_proto.getIsAsync() != null) {
-        //        try ais.writer().writeAll("callconv(.Async) ");
-        //    }
-
-        //    switch (fn_proto.return_type) {
-        //        .Explicit => |node| {
-        //            return renderExpression(ais, tree, node, space);
-        //        },
-        //        .InferErrorSet => |node| {
-        //            try renderToken(ais, tree, tree.prevToken(node.firstToken()), Space.None); // !
-        //            return renderExpression(ais, tree, node, space);
-        //        },
-        //        .Invalid => unreachable,
-        //    }
-        //},
+        .FnProtoSimple => {
+            var params: [1]ast.Node.Index = undefined;
+            return renderFnProto(ais, tree, tree.fnProtoSimple(&params, node), space);
+        },
+        .FnProtoMulti => return renderFnProto(ais, tree, tree.fnProtoMulti(node), space),
+        .FnProtoOne => {
+            var params: [1]ast.Node.Index = undefined;
+            return renderFnProto(ais, tree, tree.fnProtoOne(&params, node), space);
+        },
+        .FnProto => return renderFnProto(ais, tree, tree.fnProto(node), space),
 
         .AnyFrameType => unreachable, // TODO
         //.AnyFrameType => {
@@ -2130,30 +1972,6 @@ fn renderContainerField(
     return renderExpressionComma(ais, tree, field.ast.value_expr, space); // value
 }
 
-fn renderParamDecl(
-    allocator: *mem.Allocator,
-    ais: *Ais,
-    tree: ast.Tree,
-    param_decl: ast.Node.FnProto.ParamDecl,
-    space: Space,
-) Error!void {
-    try renderDocComments(ais, tree, param_decl, param_decl.doc_comments);
-
-    if (param_decl.comptime_token) |comptime_token| {
-        try renderToken(ais, tree, comptime_token, Space.Space);
-    }
-    if (param_decl.noalias_token) |noalias_token| {
-        try renderToken(ais, tree, noalias_token, Space.Space);
-    }
-    if (param_decl.name_token) |name_token| {
-        try renderToken(ais, tree, name_token, Space.None);
-        try renderToken(ais, tree, tree.nextToken(name_token), Space.Space); // :
-    }
-    switch (param_decl.param_type) {
-        .any_type, .type_expr => |node| try renderExpression(ais, tree, node, space),
-    }
-}
-
 fn renderBuiltinCall(
     ais: *Ais,
     tree: ast.Tree,
@@ -2200,6 +2018,239 @@ fn renderBuiltinCall(
     }
 }
 
+fn renderFnProto(ais: *Ais, tree: ast.Tree, fn_proto: ast.Full.FnProto, space: Space) Error!void {
+    const token_tags = tree.tokens.items(.tag);
+
+    const after_fn_token = fn_proto.ast.fn_token + 1;
+    const lparen = if (token_tags[after_fn_token] == .Identifier) blk: {
+        try renderToken(ais, tree, fn_proto.ast.fn_token, .Space); // fn
+        try renderToken(ais, tree, after_fn_token, .None); // name
+        break :blk after_fn_token + 1;
+    } else blk: {
+        try renderToken(ais, tree, fn_proto.ast.fn_token, .Space); // fn
+        break :blk fn_proto.ast.fn_token + 1;
+    };
+    assert(token_tags[lparen] == .LParen);
+
+    const maybe_bang = tree.firstToken(fn_proto.ast.return_type) - 1;
+    const rparen = blk: {
+        // The first token for the annotation expressions is the left
+        // parenthesis, hence the need for two previous tokens.
+        if (fn_proto.ast.align_expr != 0) {
+            break :blk tree.firstToken(fn_proto.ast.align_expr) - 3;
+        }
+        if (fn_proto.ast.section_expr != 0) {
+            break :blk tree.firstToken(fn_proto.ast.section_expr) - 3;
+        }
+        if (fn_proto.ast.callconv_expr != 0) {
+            break :blk tree.firstToken(fn_proto.ast.callconv_expr) - 3;
+        }
+        if (token_tags[maybe_bang] == .Bang) {
+            break :blk maybe_bang - 1;
+        }
+        break :blk maybe_bang;
+    };
+    assert(token_tags[rparen] == .RParen);
+
+    // The params list is a sparse set that does *not* include anytype or ... parameters.
+
+    if (token_tags[rparen - 1] != .Comma) {
+        // Render all on one line, no trailing comma.
+        try renderToken(ais, tree, lparen, .None); // (
+
+        var param_i: usize = 0;
+        var last_param_token = lparen;
+        while (true) {
+            last_param_token += 1;
+            switch (token_tags[last_param_token]) {
+                .DocComment => {
+                    try renderToken(ais, tree, last_param_token, .Newline);
+                    continue;
+                },
+                .Ellipsis3 => {
+                    try renderToken(ais, tree, last_param_token, .None); // ...
+                    break;
+                },
+                .Keyword_noalias, .Keyword_comptime => {
+                    try renderToken(ais, tree, last_param_token, .Space);
+                    last_param_token += 1;
+                },
+                .Identifier => {},
+                .Keyword_anytype => {
+                    try renderToken(ais, tree, last_param_token, .None); // anytype
+                    continue;
+                },
+                .RParen => break,
+                .Comma => {
+                    try renderToken(ais, tree, last_param_token, .Space); // ,
+                    last_param_token += 1;
+                },
+                else => unreachable,
+            }
+            if (token_tags[last_param_token] == .Identifier) {
+                try renderToken(ais, tree, last_param_token, .None); // name
+                last_param_token += 1;
+                try renderToken(ais, tree, last_param_token, .Space); // :
+                last_param_token += 1;
+            }
+            if (token_tags[last_param_token] == .Keyword_anytype) {
+                try renderToken(ais, tree, last_param_token, .None); // anytype
+                continue;
+            }
+            const param = fn_proto.ast.params[param_i];
+            param_i += 1;
+            try renderExpression(ais, tree, param, .None);
+            last_param_token = tree.lastToken(param) + 1;
+        }
+    } else {
+        // One param per line.
+        ais.pushIndent();
+        try renderToken(ais, tree, lparen, .Newline); // (
+
+        var param_i: usize = 0;
+        var last_param_token = lparen;
+        while (true) {
+            last_param_token += 1;
+            switch (token_tags[last_param_token]) {
+                .DocComment => {
+                    try renderToken(ais, tree, last_param_token, .Newline);
+                    continue;
+                },
+                .Ellipsis3 => {
+                    try renderToken(ais, tree, last_param_token, .Comma); // ...
+                    break;
+                },
+                .Keyword_noalias, .Keyword_comptime => {
+                    try renderToken(ais, tree, last_param_token, .Space);
+                    last_param_token += 1;
+                },
+                .Identifier => {},
+                .Keyword_anytype => {
+                    try renderToken(ais, tree, last_param_token, .Comma); // anytype
+                    continue;
+                },
+                .RParen => break,
+                else => unreachable,
+            }
+            if (token_tags[last_param_token] == .Identifier) {
+                try renderToken(ais, tree, last_param_token, .None); // name
+                last_param_token += 1;
+                try renderToken(ais, tree, last_param_token, .Space); // :
+                last_param_token += 1;
+            }
+            if (token_tags[last_param_token] == .Keyword_anytype) {
+                try renderToken(ais, tree, last_param_token, .Comma); // anytype
+                continue;
+            }
+            const param = fn_proto.ast.params[param_i];
+            param_i += 1;
+            try renderExpression(ais, tree, param, .Comma);
+            last_param_token = tree.lastToken(param) + 2;
+        }
+        ais.popIndent();
+    }
+
+    try renderToken(ais, tree, rparen, .Space); // )
+
+    if (fn_proto.ast.align_expr != 0) {
+        const align_lparen = tree.firstToken(fn_proto.ast.align_expr) - 1;
+        const align_rparen = tree.lastToken(fn_proto.ast.align_expr) + 1;
+
+        try renderToken(ais, tree, align_lparen - 1, .None); // align
+        try renderToken(ais, tree, align_lparen, .None); // (
+        try renderExpression(ais, tree, fn_proto.ast.align_expr, .None);
+        try renderToken(ais, tree, align_rparen, .Space); // )
+    }
+
+    if (fn_proto.ast.section_expr != 0) {
+        const section_lparen = tree.firstToken(fn_proto.ast.section_expr) - 1;
+        const section_rparen = tree.lastToken(fn_proto.ast.section_expr) + 1;
+
+        try renderToken(ais, tree, section_lparen - 1, .None); // section
+        try renderToken(ais, tree, section_lparen, .None); // (
+        try renderExpression(ais, tree, fn_proto.ast.section_expr, .None);
+        try renderToken(ais, tree, section_rparen, .Space); // )
+    }
+
+    if (fn_proto.ast.callconv_expr != 0) {
+        const callconv_lparen = tree.firstToken(fn_proto.ast.callconv_expr) - 1;
+        const callconv_rparen = tree.lastToken(fn_proto.ast.callconv_expr) + 1;
+
+        try renderToken(ais, tree, callconv_lparen - 1, .None); // callconv
+        try renderToken(ais, tree, callconv_lparen, .None); // (
+        try renderExpression(ais, tree, fn_proto.ast.callconv_expr, .None);
+        try renderToken(ais, tree, callconv_rparen, .Space); // )
+    }
+
+    if (token_tags[maybe_bang] == .Bang) {
+        try renderToken(ais, tree, maybe_bang, .None); // !
+    }
+    return renderExpression(ais, tree, fn_proto.ast.return_type, space);
+}
+
+fn renderBlock(
+    ais: *Ais,
+    tree: ast.Tree,
+    lbrace: ast.TokenIndex,
+    statements: []const ast.Node.Index,
+    space: Space,
+) Error!void {
+    const token_tags = tree.tokens.items(.tag);
+    const node_tags = tree.nodes.items(.tag);
+
+    if (token_tags[lbrace - 1] == .Colon and
+        token_tags[lbrace - 2] == .Identifier)
+    {
+        try renderToken(ais, tree, lbrace - 2, .None);
+        try renderToken(ais, tree, lbrace - 1, .Space);
+    }
+    const nodes_data = tree.nodes.items(.data);
+
+    if (statements.len == 0) {
+        ais.pushIndentNextLine();
+        try renderToken(ais, tree, lbrace, .None);
+        ais.popIndent();
+        const rbrace = lbrace + 1;
+        return renderToken(ais, tree, rbrace, space);
+    } else {
+        ais.pushIndentNextLine();
+
+        try renderToken(ais, tree, lbrace, .Newline);
+
+        for (statements) |stmt, i| {
+            switch (node_tags[stmt]) {
+                .GlobalVarDecl => try renderVarDecl(ais, tree, tree.globalVarDecl(stmt)),
+                .LocalVarDecl => try renderVarDecl(ais, tree, tree.localVarDecl(stmt)),
+                .SimpleVarDecl => try renderVarDecl(ais, tree, tree.simpleVarDecl(stmt)),
+                .AlignedVarDecl => try renderVarDecl(ais, tree, tree.alignedVarDecl(stmt)),
+                else => {
+                    const semicolon = tree.lastToken(stmt) + 1;
+                    if (token_tags[semicolon] == .Semicolon) {
+                        try renderExpression(ais, tree, stmt, .None);
+                        try renderToken(ais, tree, semicolon, .Newline);
+                    } else {
+                        try renderExpression(ais, tree, stmt, .Newline);
+                    }
+                },
+            }
+
+            if (i + 1 < statements.len) {
+                try renderExtraNewline(ais, tree, statements[i + 1]);
+            }
+        }
+        ais.popIndent();
+        // The rbrace could be +1 or +2 from the last token of the last
+        // statement in the block because lastToken() does not count semicolons.
+        const maybe_rbrace = tree.lastToken(statements[statements.len - 1]) + 1;
+        if (token_tags[maybe_rbrace] == .RBrace) {
+            return renderToken(ais, tree, maybe_rbrace, space);
+        } else {
+            assert(token_tags[maybe_rbrace + 1] == .RBrace);
+            return renderToken(ais, tree, maybe_rbrace + 1, space);
+        }
+    }
+}
+
 /// Render an expression, and the comma that follows it, if it is present in the source.
 fn renderExpressionComma(ais: *Ais, tree: ast.Tree, node: ast.Node.Index, space: Space) Error!void {
     const token_tags = tree.tokens.items(.tag);