authorgravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2020-05-20 16:13:02-04:00
committergravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2020-05-20 16:13:02-04:00
log64c149ca19cc299485693acbda325eee51d37f28
tree4722fb13d235122b1e69d78bc19a868c7c52609a
parentb1bcdc96ca4451be95caa00fe71aafb578611e34

fields and decls: ArrayList appending, memcpy to ast arena

This makes fields and decl ast nodes part of the Root and ContainerDecl AST nodes. Surprisingly, it's a performance regression from using a singly-linked list for these nodes: throughput: 76.5 MiB/s => 69.4 MiB/s However it has much better memory usage: maxrss: 392 KB => 77 KB It's also better API for consumers of the parser, since it is a flat list in memory.

3 files changed, 137 insertions(+), 86 deletions(-)

lib/std/zig/ast.zig+75-31
...@@ -6,6 +6,7 @@ const mem = std.mem;...@@ -6,6 +6,7 @@ const mem = std.mem;
6const Token = std.zig.Token;6const Token = std.zig.Token;
77
8pub const TokenIndex = usize;8pub const TokenIndex = usize;
9pub const NodeIndex = usize;
910
10pub const Tree = struct {11pub const Tree = struct {
11 /// Reference to externally-owned data.12 /// Reference to externally-owned data.
...@@ -616,40 +617,62 @@ pub const Node = struct {...@@ -616,40 +617,62 @@ pub const Node = struct {
616 }617 }
617 }618 }
618619
620 /// The decls data follows this struct in memory as an array of Node pointers.
619 pub const Root = struct {621 pub const Root = struct {
620 base: Node = Node{ .id = .Root },622 base: Node = Node{ .id = .Root },
621 decls: DeclList,
622 eof_token: TokenIndex,623 eof_token: TokenIndex,
624 decls_len: NodeIndex,
625
626 /// After this the caller must initialize the decls list.
627 pub fn create(allocator: *mem.Allocator, decls_len: NodeIndex, eof_token: TokenIndex) !*Root {
628 const bytes = try allocator.alignedAlloc(u8, @alignOf(Root), sizeInBytes(decls_len));
629 const self = @ptrCast(*Root, bytes.ptr);
630 self.* = .{
631 .eof_token = eof_token,
632 .decls_len = decls_len,
633 };
634 return self;
635 }
623636
624 pub const DeclList = LinkedList(*Node);637 pub fn destroy(self: *Decl, allocator: *mem.Allocator) void {
638 const bytes = @ptrCast([*]u8, self)[0..sizeInBytes(self.decls_len)];
639 allocator.free(bytes);
640 }
625641
626 pub fn iterate(self: *const Root) Node.Iterator {642 pub fn iterate(self: *const Root) Node.Iterator {
627 return .{ .parent_node = &self.base, .index = 0, .node = self.decls.first };643 return .{ .parent_node = &self.base, .index = 0, .node = null };
628 }644 }
629645
630 pub fn iterateNext(self: *const Root, it: *Node.Iterator) ?*Node {646 pub fn iterateNext(self: *const Root, it: *Node.Iterator) ?*Node {
631 const decl = it.node orelse return null;647 var i = it.index;
632 it.node = decl.next;648 it.index += 1;
633 return decl.data;649
650 if (i < self.decls_len) return self.declsConst()[i];
651 return null;
652 }
653
654 pub fn decls(self: *Root) []*Node {
655 const decls_start = @ptrCast([*]u8, self) + @sizeOf(Root);
656 return @ptrCast([*]*Node, decls_start)[0..self.decls_len];
657 }
658
659 pub fn declsConst(self: *const Root) []const *Node {
660 const decls_start = @ptrCast([*]const u8, self) + @sizeOf(Root);
661 return @ptrCast([*]const *Node, decls_start)[0..self.decls_len];
634 }662 }
635663
636 pub fn firstToken(self: *const Root) TokenIndex {664 pub fn firstToken(self: *const Root) TokenIndex {
637 if (self.decls.first) |first| {665 if (self.decls_len == 0) return self.eof_token;
638 return first.data.firstToken();666 return self.declsConst()[0].firstToken();
639 } else {
640 return self.eof_token;
641 }
642 }667 }
643668
644 pub fn lastToken(self: *const Root) TokenIndex {669 pub fn lastToken(self: *const Root) TokenIndex {
645 if (self.decls.first) |first| {670 if (self.decls_len == 0) return self.eof_token;
646 var node = first;671 return self.declsConst()[self.decls_len - 1].lastToken();
647 while (true) {672 }
648 node = node.next orelse return node.data.lastToken();673
649 }674 fn sizeInBytes(decls_len: NodeIndex) usize {
650 } else {675 return @sizeOf(Root) + @sizeOf(*Node) * @as(usize, decls_len);
651 return self.eof_token;
652 }
653 }676 }
654 };677 };
655678
...@@ -777,14 +800,12 @@ pub const Node = struct {...@@ -777,14 +800,12 @@ pub const Node = struct {
777800
778 pub const ContainerDecl = struct {801 pub const ContainerDecl = struct {
779 base: Node = Node{ .id = .ContainerDecl },802 base: Node = Node{ .id = .ContainerDecl },
780 layout_token: ?TokenIndex,
781 kind_token: TokenIndex,803 kind_token: TokenIndex,
782 init_arg_expr: InitArg,804 layout_token: ?TokenIndex,
783 fields_and_decls: DeclList,
784 lbrace_token: TokenIndex,805 lbrace_token: TokenIndex,
785 rbrace_token: TokenIndex,806 rbrace_token: TokenIndex,
786807 fields_and_decls_len: NodeIndex,
787 pub const DeclList = Root.DeclList;808 init_arg_expr: InitArg,
788809
789 pub const InitArg = union(enum) {810 pub const InitArg = union(enum) {
790 None,811 None,
...@@ -792,8 +813,19 @@ pub const Node = struct {...@@ -792,8 +813,19 @@ pub const Node = struct {
792 Type: *Node,813 Type: *Node,
793 };814 };
794815
816 /// After this the caller must initialize the fields_and_decls list.
817 pub fn alloc(allocator: *mem.Allocator, fields_and_decls_len: NodeIndex) !*ContainerDecl {
818 const bytes = try allocator.alignedAlloc(u8, @alignOf(ContainerDecl), sizeInBytes(fields_and_decls_len));
819 return @ptrCast(*ContainerDecl, bytes.ptr);
820 }
821
822 pub fn free(self: *Decl, allocator: *mem.Allocator) void {
823 const bytes = @ptrCast([*]u8, self)[0..sizeInBytes(self.fields_and_decls_len)];
824 allocator.free(bytes);
825 }
826
795 pub fn iterate(self: *const ContainerDecl) Node.Iterator {827 pub fn iterate(self: *const ContainerDecl) Node.Iterator {
796 return .{ .parent_node = &self.base, .index = 0, .node = self.fields_and_decls.first };828 return .{ .parent_node = &self.base, .index = 0, .node = null };
797 }829 }
798830
799 pub fn iterateNext(self: *const ContainerDecl, it: *Node.Iterator) ?*Node {831 pub fn iterateNext(self: *const ContainerDecl, it: *Node.Iterator) ?*Node {
...@@ -808,10 +840,8 @@ pub const Node = struct {...@@ -808,10 +840,8 @@ pub const Node = struct {
808 .None, .Enum => {},840 .None, .Enum => {},
809 }841 }
810842
811 if (it.node) |child| {843 if (i < self.fields_and_decls_len) return self.fieldsAndDeclsConst()[i];
812 it.node = child.next;844 i -= self.fields_and_decls_len;
813 return child.data;
814 }
815845
816 return null;846 return null;
817 }847 }
...@@ -826,6 +856,20 @@ pub const Node = struct {...@@ -826,6 +856,20 @@ pub const Node = struct {
826 pub fn lastToken(self: *const ContainerDecl) TokenIndex {856 pub fn lastToken(self: *const ContainerDecl) TokenIndex {
827 return self.rbrace_token;857 return self.rbrace_token;
828 }858 }
859
860 pub fn fieldsAndDecls(self: *ContainerDecl) []*Node {
861 const decls_start = @ptrCast([*]u8, self) + @sizeOf(ContainerDecl);
862 return @ptrCast([*]*Node, decls_start)[0..self.fields_and_decls_len];
863 }
864
865 pub fn fieldsAndDeclsConst(self: *const ContainerDecl) []const *Node {
866 const decls_start = @ptrCast([*]const u8, self) + @sizeOf(ContainerDecl);
867 return @ptrCast([*]const *Node, decls_start)[0..self.fields_and_decls_len];
868 }
869
870 fn sizeInBytes(fields_and_decls_len: NodeIndex) usize {
871 return @sizeOf(ContainerDecl) + @sizeOf(*Node) * @as(usize, fields_and_decls_len);
872 }
829 };873 };
830874
831 pub const ContainerField = struct {875 pub const ContainerField = struct {
...@@ -1116,7 +1160,7 @@ pub const Node = struct {...@@ -1116,7 +1160,7 @@ pub const Node = struct {
1116 statements: StatementList,1160 statements: StatementList,
1117 rbrace: TokenIndex,1161 rbrace: TokenIndex,
11181162
1119 pub const StatementList = Root.DeclList;1163 pub const StatementList = LinkedList(*Node);
11201164
1121 pub fn iterate(self: *const Block) Node.Iterator {1165 pub fn iterate(self: *const Block) Node.Iterator {
1122 return .{ .parent_node = &self.base, .index = 0, .node = self.statements.first };1166 return .{ .parent_node = &self.base, .index = 0, .node = self.statements.first };
...@@ -2615,7 +2659,7 @@ pub const Node = struct {...@@ -2615,7 +2659,7 @@ pub const Node = struct {
2615test "iterate" {2659test "iterate" {
2616 var root = Node.Root{2660 var root = Node.Root{
2617 .base = Node{ .id = Node.Id.Root },2661 .base = Node{ .id = Node.Id.Root },
2618 .decls = Node.Root.DeclList.init(std.testing.allocator),2662 .decls_len = 0,
2619 .eof_token = 0,2663 .eof_token = 0,
2620 };2664 };
2621 var base = &root.base;2665 var base = &root.base;
lib/std/zig/parse.zig+41-31
...@@ -63,16 +63,20 @@ const Parser = struct {...@@ -63,16 +63,20 @@ const Parser = struct {
6363
64 /// Root <- skip ContainerMembers eof64 /// Root <- skip ContainerMembers eof
65 fn parseRoot(p: *Parser) Allocator.Error!*Node.Root {65 fn parseRoot(p: *Parser) Allocator.Error!*Node.Root {
66 const node = try p.arena.allocator.create(Node.Root);66 const decls = try parseContainerMembers(p, true);
67 node.* = .{67 defer p.gpa.free(decls);
68 .decls = try parseContainerMembers(p, true),68
69 // parseContainerMembers will try to skip as much69 // parseContainerMembers will try to skip as much
70 // invalid tokens as it can so this can only be the EOF70 // invalid tokens as it can so this can only be the EOF
71 .eof_token = p.eatToken(.Eof).?,71 const eof_token = p.eatToken(.Eof).?;
72 };72
73 const node = try Node.Root.create(&p.arena.allocator, decls.len, eof_token);
74 std.mem.copy(*ast.Node, node.decls(), decls);
75
73 return node;76 return node;
74 }77 }
7578
79 /// Helper function for appending elements to a singly linked list.
76 fn llpush(80 fn llpush(
77 p: *Parser,81 p: *Parser,
78 comptime T: type,82 comptime T: type,
...@@ -92,9 +96,9 @@ const Parser = struct {...@@ -92,9 +96,9 @@ const Parser = struct {
92 /// / ContainerField COMMA ContainerMembers96 /// / ContainerField COMMA ContainerMembers
93 /// / ContainerField97 /// / ContainerField
94 /// /98 /// /
95 fn parseContainerMembers(p: *Parser, top_level: bool) !Node.Root.DeclList {99 fn parseContainerMembers(p: *Parser, top_level: bool) ![]*ast.Node {
96 var list = Node.Root.DeclList{};100 var list = std.ArrayList(*ast.Node).init(p.gpa);
97 var list_it = &list.first;101 defer list.deinit();
98102
99 var field_state: union(enum) {103 var field_state: union(enum) {
100 /// no fields have been seen104 /// no fields have been seen
...@@ -110,7 +114,7 @@ const Parser = struct {...@@ -110,7 +114,7 @@ const Parser = struct {
110114
111 while (true) {115 while (true) {
112 if (try p.parseContainerDocComments()) |node| {116 if (try p.parseContainerDocComments()) |node| {
113 list_it = try p.llpush(*Node, list_it, node);117 try list.append(node);
114 continue;118 continue;
115 }119 }
116120
...@@ -127,7 +131,7 @@ const Parser = struct {...@@ -127,7 +131,7 @@ const Parser = struct {
127 field_state = .{ .end = node.firstToken() };131 field_state = .{ .end = node.firstToken() };
128 }132 }
129 node.cast(Node.TestDecl).?.doc_comments = doc_comments;133 node.cast(Node.TestDecl).?.doc_comments = doc_comments;
130 list_it = try p.llpush(*Node, list_it, node);134 try list.append(node);
131 continue;135 continue;
132 }136 }
133137
...@@ -142,7 +146,7 @@ const Parser = struct {...@@ -142,7 +146,7 @@ const Parser = struct {
142 field_state = .{ .end = node.firstToken() };146 field_state = .{ .end = node.firstToken() };
143 }147 }
144 node.cast(Node.Comptime).?.doc_comments = doc_comments;148 node.cast(Node.Comptime).?.doc_comments = doc_comments;
145 list_it = try p.llpush(*Node, list_it, node);149 try list.append(node);
146 continue;150 continue;
147 }151 }
148152
...@@ -173,7 +177,7 @@ const Parser = struct {...@@ -173,7 +177,7 @@ const Parser = struct {
173 },177 },
174 else => unreachable,178 else => unreachable,
175 }179 }
176 list_it = try p.llpush(*Node, list_it, node);180 try list.append(node);
177 if (try p.parseAppendedDocComment(node.lastToken())) |appended_comment| {181 if (try p.parseAppendedDocComment(node.lastToken())) |appended_comment| {
178 switch (node.id) {182 switch (node.id) {
179 .FnProto => {},183 .FnProto => {},
...@@ -215,7 +219,7 @@ const Parser = struct {...@@ -215,7 +219,7 @@ const Parser = struct {
215219
216 const field = node.cast(Node.ContainerField).?;220 const field = node.cast(Node.ContainerField).?;
217 field.doc_comments = doc_comments;221 field.doc_comments = doc_comments;
218 list_it = try p.llpush(*Node, list_it, node);222 try list.append(node);
219 const comma = p.eatToken(.Comma) orelse {223 const comma = p.eatToken(.Comma) orelse {
220 // try to continue parsing224 // try to continue parsing
221 const index = p.tok_i;225 const index = p.tok_i;
...@@ -275,7 +279,7 @@ const Parser = struct {...@@ -275,7 +279,7 @@ const Parser = struct {
275 }279 }
276 }280 }
277281
278 return list;282 return list.toOwnedSlice();
279 }283 }
280284
281 /// Attempts to find next container member by searching for certain tokens285 /// Attempts to find next container member by searching for certain tokens
...@@ -2778,24 +2782,36 @@ const Parser = struct {...@@ -2778,24 +2782,36 @@ const Parser = struct {
27782782
2779 /// ContainerDeclAuto <- ContainerDeclType LBRACE ContainerMembers RBRACE2783 /// ContainerDeclAuto <- ContainerDeclType LBRACE ContainerMembers RBRACE
2780 fn parseContainerDeclAuto(p: *Parser) !?*Node {2784 fn parseContainerDeclAuto(p: *Parser) !?*Node {
2781 const node = (try p.parseContainerDeclType()) orelse return null;2785 const container_decl_type = (try p.parseContainerDeclType()) orelse return null;
2782 const lbrace = try p.expectToken(.LBrace);2786 const lbrace = try p.expectToken(.LBrace);
2783 const members = try p.parseContainerMembers(false);2787 const members = try p.parseContainerMembers(false);
2788 defer p.gpa.free(members);
2784 const rbrace = try p.expectToken(.RBrace);2789 const rbrace = try p.expectToken(.RBrace);
27852790
2786 const decl_type = node.cast(Node.ContainerDecl).?;2791 const node = try Node.ContainerDecl.alloc(&p.arena.allocator, members.len);
2787 decl_type.fields_and_decls = members;2792 node.* = .{
2788 decl_type.lbrace_token = lbrace;2793 .layout_token = null,
2789 decl_type.rbrace_token = rbrace;2794 .kind_token = container_decl_type.kind_token,
27902795 .init_arg_expr = container_decl_type.init_arg_expr,
2791 return node;2796 .fields_and_decls_len = members.len,
2797 .lbrace_token = lbrace,
2798 .rbrace_token = rbrace,
2799 };
2800 std.mem.copy(*ast.Node, node.fieldsAndDecls(), members);
2801 return &node.base;
2792 }2802 }
27932803
2804 /// Holds temporary data until we are ready to construct the full ContainerDecl AST node.
2805 const ContainerDeclType = struct {
2806 kind_token: TokenIndex,
2807 init_arg_expr: ast.Node.ContainerDecl.InitArg,
2808 };
2809
2794 /// ContainerDeclType2810 /// ContainerDeclType
2795 /// <- KEYWORD_struct2811 /// <- KEYWORD_struct
2796 /// / KEYWORD_enum (LPAREN Expr RPAREN)?2812 /// / KEYWORD_enum (LPAREN Expr RPAREN)?
2797 /// / KEYWORD_union (LPAREN (KEYWORD_enum (LPAREN Expr RPAREN)? / Expr) RPAREN)?2813 /// / KEYWORD_union (LPAREN (KEYWORD_enum (LPAREN Expr RPAREN)? / Expr) RPAREN)?
2798 fn parseContainerDeclType(p: *Parser) !?*Node {2814 fn parseContainerDeclType(p: *Parser) !?ContainerDeclType {
2799 const kind_token = p.nextToken();2815 const kind_token = p.nextToken();
28002816
2801 const init_arg_expr = switch (kind_token.ptr.id) {2817 const init_arg_expr = switch (kind_token.ptr.id) {
...@@ -2838,16 +2854,10 @@ const Parser = struct {...@@ -2838,16 +2854,10 @@ const Parser = struct {
2838 },2854 },
2839 };2855 };
28402856
2841 const node = try p.arena.allocator.create(Node.ContainerDecl);2857 return ContainerDeclType{
2842 node.* = .{
2843 .layout_token = null,
2844 .kind_token = kind_token.index,2858 .kind_token = kind_token.index,
2845 .init_arg_expr = init_arg_expr,2859 .init_arg_expr = init_arg_expr,
2846 .fields_and_decls = undefined, // set by caller
2847 .lbrace_token = undefined, // set by caller
2848 .rbrace_token = undefined, // set by caller
2849 };2860 };
2850 return &node.base;
2851 }2861 }
28522862
2853 /// ByteAlign <- KEYWORD_align LPAREN Expr RPAREN2863 /// ByteAlign <- KEYWORD_align LPAREN Expr RPAREN
lib/std/zig/render.zig+21-24
...@@ -79,9 +79,10 @@ fn renderRoot(...@@ -79,9 +79,10 @@ fn renderRoot(
79 }79 }
8080
81 var start_col: usize = 0;81 var start_col: usize = 0;
82 var it = tree.root_node.decls.first orelse return;82 var decl_i: ast.NodeIndex = 0;
83 const root_decls = tree.root_node.decls();
83 while (true) {84 while (true) {
84 var decl = it.data;85 var decl = root_decls[decl_i];
8586
86 // This loop does the following:87 // This loop does the following:
87 //88 //
...@@ -130,14 +131,15 @@ fn renderRoot(...@@ -130,14 +131,15 @@ fn renderRoot(
130 token_index = decl.firstToken();131 token_index = decl.firstToken();
131132
132 while (!fmt_active) {133 while (!fmt_active) {
133 it = it.next orelse {134 decl_i += 1;
135 if (decl_i >= root_decls.len) {
134 // If there's no next reformatted `decl`, just copy the136 // If there's no next reformatted `decl`, just copy the
135 // remaining input tokens and bail out.137 // remaining input tokens and bail out.
136 const start = tree.tokens[copy_start_token_index].start;138 const start = tree.tokens[copy_start_token_index].start;
137 try copyFixingWhitespace(stream, tree.source[start..]);139 try copyFixingWhitespace(stream, tree.source[start..]);
138 return;140 return;
139 };141 }
140 decl = it.data;142 decl = root_decls[decl_i];
141 var decl_first_token_index = decl.firstToken();143 var decl_first_token_index = decl.firstToken();
142144
143 while (token_index < decl_first_token_index) : (token_index += 1) {145 while (token_index < decl_first_token_index) : (token_index += 1) {
...@@ -178,8 +180,9 @@ fn renderRoot(...@@ -178,8 +180,9 @@ fn renderRoot(
178 }180 }
179181
180 try renderTopLevelDecl(allocator, stream, tree, 0, &start_col, decl);182 try renderTopLevelDecl(allocator, stream, tree, 0, &start_col, decl);
181 it = it.next orelse return;183 decl_i += 1;
182 try renderExtraNewline(tree, stream, &start_col, it.data);184 if (decl_i >= root_decls.len) return;
185 try renderExtraNewline(tree, stream, &start_col, root_decls[decl_i]);
183 }186 }
184}187}
185188
...@@ -1189,7 +1192,7 @@ fn renderExpression(...@@ -1189,7 +1192,7 @@ fn renderExpression(
1189 },1192 },
1190 }1193 }
11911194
1192 if (container_decl.fields_and_decls.first == null) {1195 if (container_decl.fields_and_decls_len == 0) {
1193 try renderToken(tree, stream, container_decl.lbrace_token, indent + indent_delta, start_col, Space.None); // {1196 try renderToken(tree, stream, container_decl.lbrace_token, indent + indent_delta, start_col, Space.None); // {
1194 return renderToken(tree, stream, container_decl.rbrace_token, indent, start_col, space); // }1197 return renderToken(tree, stream, container_decl.rbrace_token, indent, start_col, space); // }
1195 }1198 }
...@@ -1203,18 +1206,18 @@ fn renderExpression(...@@ -1203,18 +1206,18 @@ fn renderExpression(
1203 break :blk tree.tokens[maybe_comma].id == .Comma;1206 break :blk tree.tokens[maybe_comma].id == .Comma;
1204 };1207 };
12051208
1209 const fields_and_decls = container_decl.fieldsAndDecls();
1210
1206 // Check if the first declaration and the { are on the same line1211 // Check if the first declaration and the { are on the same line
1207 const src_has_newline = !tree.tokensOnSameLine(1212 const src_has_newline = !tree.tokensOnSameLine(
1208 container_decl.lbrace_token,1213 container_decl.lbrace_token,
1209 container_decl.fields_and_decls.first.?.data.firstToken(),1214 fields_and_decls[0].firstToken(),
1210 );1215 );
12111216
1212 // We can only print all the elements in-line if all the1217 // We can only print all the elements in-line if all the
1213 // declarations inside are fields1218 // declarations inside are fields
1214 const src_has_only_fields = blk: {1219 const src_has_only_fields = blk: {
1215 var it = container_decl.fields_and_decls.first;1220 for (fields_and_decls) |decl| {
1216 while (it) |decl_node| : (it = decl_node.next) {
1217 const decl = decl_node.data;
1218 if (decl.id != .ContainerField) break :blk false;1221 if (decl.id != .ContainerField) break :blk false;
1219 }1222 }
1220 break :blk true;1223 break :blk true;
...@@ -1225,14 +1228,12 @@ fn renderExpression(...@@ -1225,14 +1228,12 @@ fn renderExpression(
1225 const new_indent = indent + indent_delta;1228 const new_indent = indent + indent_delta;
1226 try renderToken(tree, stream, container_decl.lbrace_token, new_indent, start_col, .Newline); // {1229 try renderToken(tree, stream, container_decl.lbrace_token, new_indent, start_col, .Newline); // {
12271230
1228 var it = container_decl.fields_and_decls.first;1231 for (fields_and_decls) |decl, i| {
1229 while (it) |decl_node| : (it = decl_node.next) {
1230 const decl = decl_node.data;
1231 try stream.writeByteNTimes(' ', new_indent);1232 try stream.writeByteNTimes(' ', new_indent);
1232 try renderContainerDecl(allocator, stream, tree, new_indent, start_col, decl, .Newline);1233 try renderContainerDecl(allocator, stream, tree, new_indent, start_col, decl, .Newline);
12331234
1234 if (decl_node.next) |next_decl| {1235 if (i + 1 < fields_and_decls.len) {
1235 try renderExtraNewline(tree, stream, start_col, next_decl.data);1236 try renderExtraNewline(tree, stream, start_col, fields_and_decls[i + 1]);
1236 }1237 }
1237 }1238 }
12381239
...@@ -1245,10 +1246,8 @@ fn renderExpression(...@@ -1245,10 +1246,8 @@ fn renderExpression(
1245 const new_indent = indent + indent_delta;1246 const new_indent = indent + indent_delta;
1246 try stream.writeByteNTimes(' ', new_indent);1247 try stream.writeByteNTimes(' ', new_indent);
12471248
1248 var it = container_decl.fields_and_decls.first;1249 for (fields_and_decls) |decl, i| {
1249 while (it) |decl_node| : (it = decl_node.next) {1250 const space_after_decl: Space = if (i + 1 >= fields_and_decls.len) .Newline else .Space;
1250 const decl = decl_node.data;
1251 const space_after_decl: Space = if (decl_node.next == null) .Newline else .Space;
1252 try renderContainerDecl(allocator, stream, tree, new_indent, start_col, decl, space_after_decl);1251 try renderContainerDecl(allocator, stream, tree, new_indent, start_col, decl, space_after_decl);
1253 }1252 }
12541253
...@@ -1257,9 +1256,7 @@ fn renderExpression(...@@ -1257,9 +1256,7 @@ fn renderExpression(
1257 // All the declarations on the same line1256 // All the declarations on the same line
1258 try renderToken(tree, stream, container_decl.lbrace_token, indent, start_col, .Space); // {1257 try renderToken(tree, stream, container_decl.lbrace_token, indent, start_col, .Space); // {
12591258
1260 var it = container_decl.fields_and_decls.first;1259 for (fields_and_decls) |decl| {
1261 while (it) |decl_node| : (it = decl_node.next) {
1262 const decl = decl_node.data;
1263 try renderContainerDecl(allocator, stream, tree, indent, start_col, decl, .Space);1260 try renderContainerDecl(allocator, stream, tree, indent, start_col, decl, .Space);
1264 }1261 }
1265 }1262 }