From d869133a9fb96fb27a07c1e42c12c5270eae5940 Mon Sep 17 00:00:00 2001 From: Isaac Freund Date: Mon, 8 Feb 2021 13:38:24 +0100 Subject: [PATCH] zig fmt: implement switches --- lib/std/zig/ast.zig | 101 ++++++++++++-- lib/std/zig/parse.zig | 5 +- lib/std/zig/parser_test.zig | 144 ++++++++++---------- lib/std/zig/render.zig | 257 ++++++++++++------------------------ 4 files changed, 247 insertions(+), 260 deletions(-) diff --git a/lib/std/zig/ast.zig b/lib/std/zig/ast.zig index 2d27de575a..9b2d8f9d54 100644 --- a/lib/std/zig/ast.zig +++ b/lib/std/zig/ast.zig @@ -213,6 +213,7 @@ pub const Tree = struct { .Await, .OptionalType, .Switch, + .SwitchComma, .IfSimple, .If, .Suspend, @@ -313,7 +314,6 @@ pub const Tree = struct { .StructInit, .CallOne, .Call, - .SwitchCaseOne, .SwitchRange, .FnDecl, .ErrorUnion, @@ -406,7 +406,19 @@ pub const Tree = struct { }; }, - .SwitchCaseMulti => unreachable, // TODO + .SwitchCaseOne => { + if (datas[n].lhs == 0) { + return main_tokens[n] - 1; // else token + } else { + n = datas[n].lhs; + } + }, + .SwitchCase => { + const extra = tree.extraData(datas[n].lhs, Node.SubRange); + assert(extra.end - extra.start > 0); + n = extra.start; + }, + .WhileSimple => unreachable, // TODO .WhileCont => unreachable, // TODO .While => unreachable, // TODO @@ -494,6 +506,8 @@ pub const Tree = struct { .PtrTypeSentinel, .PtrType, .PtrTypeBitRange, + .SwitchCaseOne, + .SwitchCase, => n = datas[n].rhs, .FieldAccess, @@ -532,6 +546,16 @@ pub const Tree = struct { } n = tree.extra_data[params.end - 1]; // last parameter }, + .Switch => { + const cases = tree.extraData(datas[n].rhs, Node.SubRange); + if (cases.end - cases.start == 0) { + end_offset += 3; // rparen, lbrace, rbrace + n = datas[n].lhs; // condition expression + } else { + end_offset += 1; // for the rbrace + n = tree.extra_data[cases.end - 1]; // last case + } + }, .ContainerDeclArg => { const members = tree.extraData(datas[n].rhs, Node.SubRange); if (members.end - members.start == 0) { @@ -542,7 +566,9 @@ pub const Tree = struct { n = tree.extra_data[members.end - 1]; // last parameter } }, - .ContainerDeclArgComma => { + .ContainerDeclArgComma, + .SwitchComma, + => { const members = tree.extraData(datas[n].rhs, Node.SubRange); assert(members.end - members.start > 0); end_offset += 2; // for the comma + rbrace @@ -737,16 +763,13 @@ pub const Tree = struct { .TaggedUnionEnumTag => unreachable, // TODO .TaggedUnionEnumTagComma => unreachable, // TODO - .Switch => unreachable, // TODO .If => unreachable, // TODO .Continue => unreachable, // TODO .AsmSimple => unreachable, // TODO .Asm => unreachable, // TODO - .SwitchCaseOne => unreachable, // TODO .SwitchRange => unreachable, // TODO .ArrayType => unreachable, // TODO .ArrayTypeSentinel => unreachable, // TODO - .SwitchCaseMulti => unreachable, // TODO .WhileCont => unreachable, // TODO .While => unreachable, // TODO .ForSimple => unreachable, // TODO @@ -1202,7 +1225,8 @@ pub const Tree = struct { } pub fn containerDeclArg(tree: Tree, node: Node.Index) Full.ContainerDecl { - assert(tree.nodes.items(.tag)[node] == .ContainerDeclArg); + assert(tree.nodes.items(.tag)[node] == .ContainerDeclArg or + tree.nodes.items(.tag)[node] == .ContainerDeclArgComma); const data = tree.nodes.items(.data)[node]; const members_range = tree.extraData(data.rhs, Node.SubRange); return tree.fullContainerDecl(.{ @@ -1214,7 +1238,8 @@ pub const Tree = struct { } pub fn taggedUnionTwo(tree: Tree, buffer: *[2]Node.Index, node: Node.Index) Full.ContainerDecl { - assert(tree.nodes.items(.tag)[node] == .TaggedUnionTwo); + assert(tree.nodes.items(.tag)[node] == .TaggedUnionTwo or + tree.nodes.items(.tag)[node] == .TaggedUnionTwoComma); const data = tree.nodes.items(.data)[node]; buffer.* = .{ data.lhs, data.rhs }; const members = if (data.rhs != 0) @@ -1233,7 +1258,8 @@ pub const Tree = struct { } pub fn taggedUnion(tree: Tree, node: Node.Index) Full.ContainerDecl { - assert(tree.nodes.items(.tag)[node] == .TaggedUnion); + assert(tree.nodes.items(.tag)[node] == .TaggedUnion or + tree.nodes.items(.tag)[node] == .TaggedUnionComma); const data = tree.nodes.items(.data)[node]; const main_token = tree.nodes.items(.main_token)[node]; return tree.fullContainerDecl(.{ @@ -1245,7 +1271,8 @@ pub const Tree = struct { } pub fn taggedUnionEnumTag(tree: Tree, node: Node.Index) Full.ContainerDecl { - assert(tree.nodes.items(.tag)[node] == .TaggedUnionEnumTag); + assert(tree.nodes.items(.tag)[node] == .TaggedUnionEnumTag or + tree.nodes.items(.tag)[node] == .TaggedUnionEnumTagComma); const data = tree.nodes.items(.data)[node]; const members_range = tree.extraData(data.rhs, Node.SubRange); const main_token = tree.nodes.items(.main_token)[node]; @@ -1257,6 +1284,25 @@ pub const Tree = struct { }); } + pub fn switchCaseOne(tree: Tree, node: Node.Index) Full.SwitchCase { + const data = &tree.nodes.items(.data)[node]; + return tree.fullSwitchCase(.{ + .values = if (data.lhs == 0) &.{} else @ptrCast([*]Node.Index, &data.lhs)[0..1], + .arrow_token = tree.nodes.items(.main_token)[node], + .target_expr = data.rhs, + }); + } + + pub fn switchCase(tree: Tree, node: Node.Index) Full.SwitchCase { + const data = tree.nodes.items(.data)[node]; + const extra = tree.extraData(data.lhs, Node.SubRange); + return tree.fullSwitchCase(.{ + .values = tree.extra_data[extra.start..extra.end], + .arrow_token = tree.nodes.items(.main_token)[node], + .target_expr = data.rhs, + }); + } + fn fullVarDecl(tree: Tree, info: Full.VarDecl.Ast) Full.VarDecl { const token_tags = tree.tokens.items(.tag); var result: Full.VarDecl = .{ @@ -1407,6 +1453,18 @@ pub const Tree = struct { } return result; } + + fn fullSwitchCase(tree: Tree, info: Full.SwitchCase.Ast) Full.SwitchCase { + const token_tags = tree.tokens.items(.tag); + var result: Full.SwitchCase = .{ + .ast = info, + .payload_token = null, + }; + if (token_tags[info.arrow_token + 1] == .Pipe) { + result.payload_token = info.arrow_token + 2; + } + return result; + } }; /// Fully assembled AST node information. @@ -1552,6 +1610,20 @@ pub const Full = struct { arg: Node.Index, }; }; + + pub const SwitchCase = struct { + /// Points to the first token after the `|`. Will either be an identifier or + /// a `*` (with an identifier immediately after it). + payload_token: ?TokenIndex, + ast: Ast, + + pub const Ast = struct { + /// If empty, this is an else case + values: []const Node.Index, + arrow_token: TokenIndex, + target_expr: Node.Index, + }; + }; }; pub const Error = union(enum) { @@ -1996,13 +2068,16 @@ pub const Node = struct { /// `lhs(a, b, c)`. `sub_range_list[rhs]`. /// main_token is the `(`. Call, - /// `switch(lhs) {}`. `sub_range_list[rhs]`. + /// `switch(lhs) {}`. `SubRange[rhs]`. Switch, + /// Same as Switch except there is known to be a trailing comma + /// before the final rbrace + SwitchComma, /// `lhs => rhs`. If lhs is omitted it means `else`. /// main_token is the `=>` SwitchCaseOne, - /// `a, b, c => rhs`. `sub_range_list[lhs]`. - SwitchCaseMulti, + /// `a, b, c => rhs`. `SubRange[lhs]`. + SwitchCase, /// `lhs...rhs`. SwitchRange, /// `while (lhs) rhs`. diff --git a/lib/std/zig/parse.zig b/lib/std/zig/parse.zig index 96d9a0dea6..aa70634c47 100644 --- a/lib/std/zig/parse.zig +++ b/lib/std/zig/parse.zig @@ -2887,10 +2887,11 @@ const Parser = struct { _ = try p.expectToken(.RParen); _ = try p.expectToken(.LBrace); const cases = try p.parseSwitchProngList(); + const trailing_comma = p.token_tags[p.tok_i - 1] == .Comma; _ = try p.expectToken(.RBrace); return p.addNode(.{ - .tag = .Switch, + .tag = if (trailing_comma) .SwitchComma else .Switch, .main_token = switch_token, .data = .{ .lhs = expr_node, @@ -3208,7 +3209,7 @@ const Parser = struct { const arrow_token = try p.expectToken(.EqualAngleBracketRight); _ = try p.parsePtrPayload(); return p.addNode(.{ - .tag = .SwitchCaseMulti, + .tag = .SwitchCase, .main_token = arrow_token, .data = .{ .lhs = try p.addExtra(Node.SubRange{ diff --git a/lib/std/zig/parser_test.zig b/lib/std/zig/parser_test.zig index 7338122afc..983190b152 100644 --- a/lib/std/zig/parser_test.zig +++ b/lib/std/zig/parser_test.zig @@ -1671,32 +1671,32 @@ test "zig fmt: block in slice expression" { // \\ // ); //} -// -//test "zig fmt: switch cases trailing comma" { -// try testTransform( -// \\fn switch_cases(x: i32) void { -// \\ switch (x) { -// \\ 1,2,3 => {}, -// \\ 4,5, => {}, -// \\ 6... 8, => {}, -// \\ else => {}, -// \\ } -// \\} -// , -// \\fn switch_cases(x: i32) void { -// \\ switch (x) { -// \\ 1, 2, 3 => {}, -// \\ 4, -// \\ 5, -// \\ => {}, -// \\ 6...8 => {}, -// \\ else => {}, -// \\ } -// \\} -// \\ -// ); -//} -// + +test "zig fmt: switch cases trailing comma" { + try testTransform( + \\test "switch cases trailing comma"{ + \\ switch (x) { + \\ 1,2,3 => {}, + \\ 4,5, => {}, + \\ 6... 8, => {}, + \\ else => {}, + \\ } + \\} + , + \\test "switch cases trailing comma" { + \\ switch (x) { + \\ 1, 2, 3 => {}, + \\ 4, + \\ 5, + \\ => {}, + \\ 6...8 => {}, + \\ else => {}, + \\ } + \\} + \\ + ); +} + //test "zig fmt: slice align" { // try testCanonical( // \\const A = struct { @@ -1996,16 +1996,16 @@ test "zig fmt: ptr deref operator and unwrap optional operator" { // \\ // ); //} -// -//test "zig fmt: switch with empty body" { -// try testCanonical( -// \\test "" { -// \\ foo() catch |err| switch (err) {}; -// \\} -// \\ -// ); -//} -// + +test "zig fmt: switch with empty body" { + try testCanonical( + \\test "" { + \\ foo() catch |err| switch (err) {}; + \\} + \\ + ); +} + //test "zig fmt: line comments in struct initializer" { // try testCanonical( // \\fn foo() void { @@ -2725,42 +2725,42 @@ test "zig fmt: blocks" { ); } -//test "zig fmt: switch" { -// try testCanonical( -// \\test "switch" { -// \\ switch (0) { -// \\ 0 => {}, -// \\ 1 => unreachable, -// \\ 2, 3 => {}, -// \\ 4...7 => {}, -// \\ 1 + 4 * 3 + 22 => {}, -// \\ else => { -// \\ const a = 1; -// \\ const b = a; -// \\ }, -// \\ } -// \\ -// \\ const res = switch (0) { -// \\ 0 => 0, -// \\ 1 => 2, -// \\ 1 => a = 4, -// \\ else => 4, -// \\ }; -// \\ -// \\ const Union = union(enum) { -// \\ Int: i64, -// \\ Float: f64, -// \\ }; -// \\ -// \\ switch (u) { -// \\ Union.Int => |int| {}, -// \\ Union.Float => |*float| unreachable, -// \\ } -// \\} -// \\ -// ); -//} -// +test "zig fmt: switch" { + try testCanonical( + \\test "switch" { + \\ switch (0) { + \\ 0 => {}, + \\ 1 => unreachable, + \\ 2, 3 => {}, + \\ 4...7 => {}, + \\ 1 + 4 * 3 + 22 => {}, + \\ else => { + \\ const a = 1; + \\ const b = a; + \\ }, + \\ } + \\ + \\ const res = switch (0) { + \\ 0 => 0, + \\ 1 => 2, + \\ 1 => a = 4, + \\ else => 4, + \\ }; + \\ + \\ const Union = union(enum) { + \\ Int: i64, + \\ Float: f64, + \\ }; + \\ + \\ switch (u) { + \\ Union.Int => |int| {}, + \\ Union.Float => |*float| unreachable, + \\ } + \\} + \\ + ); +} + //test "zig fmt: while" { // try testCanonical( // \\test "while" { diff --git a/lib/std/zig/render.zig b/lib/std/zig/render.zig index f67c95d355..bd9b73e55d 100644 --- a/lib/std/zig/render.zig +++ b/lib/std/zig/render.zig @@ -207,19 +207,18 @@ fn renderExpression(ais: *Ais, tree: ast.Tree, node: ast.Node.Index, space: Spac => { const 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); + return renderBlock(ais, tree, node, statements[0..0], space); } else if (datas[node].rhs == 0) { - return renderBlock(ais, tree, main_tokens[node], statements[0..1], space); + return renderBlock(ais, tree, node, statements[0..1], space); } else { - return renderBlock(ais, tree, main_tokens[node], statements[0..2], space); + return renderBlock(ais, tree, node, statements[0..2], space); } }, .Block, .BlockSemicolon, => { - const lbrace = main_tokens[node]; const statements = tree.extra_data[datas[node].lhs..datas[node].rhs]; - return renderBlock(ais, tree, main_tokens[node], statements, space); + return renderBlock(ais, tree, node, statements, space); }, .ErrDefer => { @@ -615,81 +614,6 @@ fn renderExpression(ais: *Ais, tree: ast.Tree, node: ast.Node.Index, space: Spac try renderToken(ais, tree, rbrace, space); } }, - //.ErrorSetDecl => { - // const err_set_decl = @fieldParentPtr(ast.Node.ErrorSetDecl, "base", base); - - // const lbrace = tree.nextToken(err_set_decl.error_token); - - // if (err_set_decl.decls_len == 0) { - // try renderToken(ais, tree, err_set_decl.error_token, Space.None); - // try renderToken(ais, tree, lbrace, Space.None); - // return renderToken(ais, tree, err_set_decl.rbrace_token, space); - // } - - // if (err_set_decl.decls_len == 1) blk: { - // const node = err_set_decl.decls()[0]; - - // // if there are any doc comments or same line comments - // // don't try to put it all on one line - // if (node.cast(ast.Node.ErrorTag)) |tag| { - // if (tag.doc_comments != null) break :blk; - // } else { - // break :blk; - // } - - // try renderToken(ais, tree, err_set_decl.error_token, Space.None); // error - // try renderToken(ais, tree, lbrace, Space.None); // lbrace - // try renderExpression(ais, tree, node, Space.None); - // return renderToken(ais, tree, err_set_decl.rbrace_token, space); // rbrace - // } - - // try renderToken(ais, tree, err_set_decl.error_token, Space.None); // error - - // const src_has_trailing_comma = blk: { - // const maybe_comma = tree.prevToken(err_set_decl.rbrace_token); - // break :blk tree.token_tags[maybe_comma] == .Comma; - // }; - - // if (src_has_trailing_comma) { - // { - // ais.pushIndent(); - // defer ais.popIndent(); - - // try renderToken(ais, tree, lbrace, Space.Newline); // lbrace - // const decls = err_set_decl.decls(); - // for (decls) |node, i| { - // if (i + 1 < decls.len) { - // try renderExpression(ais, tree, node, Space.None); - // try renderToken(ais, tree, tree.nextToken(node.lastToken()), Space.Newline); // , - - // try renderExtraNewline(ais, tree, decls[i + 1]); - // } else { - // try renderExpression(ais, tree, node, Space.Comma); - // } - // } - // } - - // return renderToken(ais, tree, err_set_decl.rbrace_token, space); // rbrace - // } else { - // try renderToken(ais, tree, lbrace, Space.Space); // lbrace - - // const decls = err_set_decl.decls(); - // for (decls) |node, i| { - // if (i + 1 < decls.len) { - // try renderExpression(ais, tree, node, Space.None); - - // const comma_token = tree.nextToken(node.lastToken()); - // assert(tree.token_tags[comma_token] == .Comma); - // try renderToken(ais, tree, comma_token, Space.Space); // , - // try renderExtraNewline(ais, tree, decls[i + 1]); - // } else { - // try renderExpression(ais, tree, node, Space.Space); - // } - // } - - // return renderToken(ais, tree, err_set_decl.rbrace_token, space); // rbrace - // } - //}, .BuiltinCallTwo, .BuiltinCallTwoComma => { if (datas[node].lhs == 0) { @@ -732,92 +656,38 @@ fn renderExpression(ais: *Ais, tree: ast.Tree, node: ast.Node.Index, space: Spac // } //}, - .Switch => unreachable, // TODO - //.Switch => { - // const switch_node = @fieldParentPtr(ast.Node.Switch, "base", base); + .Switch, + .SwitchComma, + => { + const switch_token = main_tokens[node]; + const condition = datas[node].lhs; + const extra = tree.extraData(datas[node].rhs, ast.Node.SubRange); + const cases = tree.extra_data[extra.start..extra.end]; + const rparen = tree.lastToken(condition) + 1; - // try renderToken(ais, tree, switch_node.switch_token, Space.Space); // switch - // try renderToken(ais, tree, tree.nextToken(switch_node.switch_token), Space.None); // ( + try renderToken(ais, tree, switch_token, .Space); // switch keyword + try renderToken(ais, tree, switch_token + 1, .None); // lparen + try renderExpression(ais, tree, condition, .None); // condtion expression + try renderToken(ais, tree, rparen, .Space); // rparen - // const rparen = tree.nextToken(switch_node.expr.lastToken()); - // const lbrace = tree.nextToken(rparen); + if (cases.len == 0) { + try renderToken(ais, tree, rparen + 1, .None); // lbrace + try renderToken(ais, tree, rparen + 2, space); // rbrace + } else { + try renderToken(ais, tree, rparen + 1, .Newline); // lbrace + ais.pushIndent(); + try renderExpression(ais, tree, cases[0], .Comma); + for (cases[1..]) |case| { + try renderExtraNewline(ais, tree, case); + try renderExpression(ais, tree, case, .Comma); + } + ais.popIndent(); + try renderToken(ais, tree, tree.lastToken(node), space); // rbrace + } + }, - // if (switch_node.cases_len == 0) { - // try renderExpression(ais, tree, switch_node.expr, Space.None); - // try renderToken(ais, tree, rparen, Space.Space); // ) - // try renderToken(ais, tree, lbrace, Space.None); // lbrace - // return renderToken(ais, tree, switch_node.rbrace, space); // rbrace - // } - - // try renderExpression(ais, tree, switch_node.expr, Space.None); - // try renderToken(ais, tree, rparen, Space.Space); // ) - - // { - // ais.pushIndentNextLine(); - // defer ais.popIndent(); - // try renderToken(ais, tree, lbrace, Space.Newline); // lbrace - - // const cases = switch_node.cases(); - // for (cases) |node, i| { - // try renderExpression(ais, tree, node, Space.Comma); - - // if (i + 1 < cases.len) { - // try renderExtraNewline(ais, tree, cases[i + 1]); - // } - // } - // } - - // return renderToken(ais, tree, switch_node.rbrace, space); // rbrace - //}, - - .SwitchCaseOne => unreachable, // TODO - .SwitchCaseMulti => unreachable, // TODO - //.SwitchCase => { - // const switch_case = @fieldParentPtr(ast.Node.SwitchCase, "base", base); - - // assert(switch_case.items_len != 0); - // const src_has_trailing_comma = blk: { - // const last_node = switch_case.items()[switch_case.items_len - 1]; - // const maybe_comma = tree.nextToken(last_node.lastToken()); - // break :blk tree.token_tags[maybe_comma] == .Comma; - // }; - - // if (switch_case.items_len == 1 or !src_has_trailing_comma) { - // const items = switch_case.items(); - // for (items) |node, i| { - // if (i + 1 < items.len) { - // try renderExpression(ais, tree, node, Space.None); - - // const comma_token = tree.nextToken(node.lastToken()); - // try renderToken(ais, tree, comma_token, Space.Space); // , - // try renderExtraNewline(ais, tree, items[i + 1]); - // } else { - // try renderExpression(ais, tree, node, Space.Space); - // } - // } - // } else { - // const items = switch_case.items(); - // for (items) |node, i| { - // if (i + 1 < items.len) { - // try renderExpression(ais, tree, node, Space.None); - - // const comma_token = tree.nextToken(node.lastToken()); - // try renderToken(ais, tree, comma_token, Space.Newline); // , - // try renderExtraNewline(ais, tree, items[i + 1]); - // } else { - // try renderExpression(ais, tree, node, Space.Comma); - // } - // } - // } - - // try renderToken(ais, tree, switch_case.arrow_token, Space.Space); // => - - // if (switch_case.payload) |payload| { - // try renderExpression(ais, tree, payload, Space.Space); - // } - - // return renderExpression(ais, tree, switch_case.expr, space); - //}, + .SwitchCaseOne => try renderSwitchCase(ais, tree, tree.switchCaseOne(node), space), + .SwitchCase => try renderSwitchCase(ais, tree, tree.switchCase(node), space), .WhileSimple => unreachable, // TODO .WhileCont => unreachable, // TODO @@ -1745,16 +1615,64 @@ fn renderFnProto(ais: *Ais, tree: ast.Tree, fn_proto: ast.Full.FnProto, space: S return renderExpression(ais, tree, fn_proto.ast.return_type, space); } +fn renderSwitchCase( + ais: *Ais, + tree: ast.Tree, + switch_case: ast.Full.SwitchCase, + space: Space, +) Error!void { + const token_tags = tree.tokens.items(.tag); + const trailing_comma = token_tags[switch_case.ast.arrow_token - 1] == .Comma; + + // Render everything before the arrow + if (switch_case.ast.values.len == 0) { + try renderToken(ais, tree, switch_case.ast.arrow_token - 1, .Space); // else keyword + } else if (switch_case.ast.values.len == 1) { + // render on one line and drop the trailing comma if any + try renderExpression(ais, tree, switch_case.ast.values[0], .Space); + } else if (trailing_comma) { + // Render each value on a new line + try renderExpression(ais, tree, switch_case.ast.values[0], .Comma); + for (switch_case.ast.values[1..]) |value_expr| { + try renderExtraNewline(ais, tree, value_expr); + try renderExpression(ais, tree, value_expr, .Comma); + } + } else { + // Render on one line + for (switch_case.ast.values) |value_expr| { + try renderExpression(ais, tree, value_expr, .CommaSpace); + } + } + + // Render the arrow and everything after it + try renderToken(ais, tree, switch_case.ast.arrow_token, .Space); + + if (switch_case.payload_token) |payload_token| { + try renderToken(ais, tree, payload_token - 1, .None); // pipe + if (token_tags[payload_token] == .Asterisk) { + try renderToken(ais, tree, payload_token, .None); // asterisk + try renderToken(ais, tree, payload_token + 1, .None); // identifier + try renderToken(ais, tree, payload_token + 2, .Space); // pipe + } else { + try renderToken(ais, tree, payload_token, .None); // identifier + try renderToken(ais, tree, payload_token + 1, .Space); // pipe + } + } + + try renderExpression(ais, tree, switch_case.ast.target_expr, space); +} + fn renderBlock( ais: *Ais, tree: ast.Tree, - lbrace: ast.TokenIndex, + block_node: ast.Node.Index, statements: []const ast.Node.Index, space: Space, ) Error!void { const token_tags = tree.tokens.items(.tag); const node_tags = tree.nodes.items(.tag); const nodes_data = tree.nodes.items(.data); + const lbrace = tree.nodes.items(.main_token)[block_node]; if (token_tags[lbrace - 1] == .Colon and token_tags[lbrace - 2] == .Identifier) @@ -1783,15 +1701,8 @@ fn renderBlock( } } 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, tree.lastToken(block_node), space); // rbrace } // TODO: handle comments between fields