Commit 6f3b93e2e8

Isaac Freund <ifreund@ifreund.xyz>
2021-02-05 13:15:42
zig fmt: struct and anon array initialization
1 parent 3e960cf
Changed files (4)
lib/std/zig/ast.zig
@@ -212,8 +212,6 @@ pub const Tree = struct {
             .Try,
             .Await,
             .OptionalType,
-            .ArrayInitDotTwo,
-            .ArrayInitDot,
             .Switch,
             .IfSimple,
             .If,
@@ -250,9 +248,12 @@ pub const Tree = struct {
             .FnProto,
             => return main_tokens[n],
 
+            .ArrayInitDot,
+            .ArrayInitDotTwo,
+            .ArrayInitDotTwoComma,
+            .StructInitDot,
             .StructInitDotTwo,
             .StructInitDotTwoComma,
-            .StructInitDot,
             => return main_tokens[n] - 1,
 
             .Catch,
@@ -304,6 +305,7 @@ pub const Tree = struct {
             .ArrayInitOne,
             .ArrayInit,
             .StructInitOne,
+            .StructInit,
             .CallOne,
             .Call,
             .SwitchCaseOne,
@@ -367,7 +369,6 @@ pub const Tree = struct {
             .PtrTypeSentinel => unreachable, // TODO
             .PtrType => unreachable, // TODO
             .SliceType => unreachable, // TODO
-            .StructInit => unreachable, // TODO
             .SwitchCaseMulti => unreachable, // TODO
             .WhileSimple => unreachable, // TODO
             .WhileCont => unreachable, // TODO
@@ -506,6 +507,7 @@ pub const Tree = struct {
                 n = datas[n].rhs;
             },
 
+            .ArrayInitDotTwo,
             .BuiltinCallTwo,
             .BlockTwo,
             .StructInitDotTwo,
@@ -519,7 +521,9 @@ pub const Tree = struct {
                     return main_tokens[n] + end_offset;
                 }
             },
-            .StructInitDotTwoComma => {
+            .ArrayInitDotTwoComma,
+            .StructInitDotTwoComma,
+            => {
                 end_offset += 2; // for the comma + rbrace
                 if (datas[n].rhs != 0) {
                     n = datas[n].rhs;
@@ -590,13 +594,16 @@ pub const Tree = struct {
             // require recursion due to the optional comma followed by rbrace.
             // TODO follow the pattern set by StructInitDotTwoComma which will allow
             // lastToken to work for all of these.
+            .ArrayInit => unreachable,
+            .ArrayInitOne => unreachable,
+            .ArrayInitDot => unreachable,
+            .StructInit => unreachable,
+            .StructInitOne => unreachable,
             .StructInitDot => unreachable,
             .ContainerFieldInit => unreachable,
             .ContainerFieldAlign => unreachable,
             .ContainerField => unreachable,
 
-            .ArrayInitDotTwo => unreachable, // TODO
-            .ArrayInitDot => unreachable, // TODO
             .Switch => unreachable, // TODO
             .If => unreachable, // TODO
             .Continue => unreachable, // TODO
@@ -606,9 +613,6 @@ pub const Tree = struct {
             .Asm => unreachable, // TODO
             .SliceOpen => unreachable, // TODO
             .Slice => unreachable, // TODO
-            .ArrayInitOne => unreachable, // TODO
-            .ArrayInit => unreachable, // TODO
-            .StructInitOne => unreachable, // TODO
             .SwitchCaseOne => unreachable, // TODO
             .SwitchRange => unreachable, // TODO
             .FnDecl => unreachable, // TODO
@@ -618,7 +622,6 @@ pub const Tree = struct {
             .PtrTypeSentinel => unreachable, // TODO
             .PtrType => unreachable, // TODO
             .SliceType => unreachable, // TODO
-            .StructInit => unreachable, // TODO
             .SwitchCaseMulti => unreachable, // TODO
             .WhileCont => unreachable, // TODO
             .While => unreachable, // TODO
@@ -863,6 +866,65 @@ pub const Tree = struct {
         });
     }
 
+    pub fn arrayInitOne(tree: Tree, buffer: *[1]Node.Index, node: Node.Index) Full.ArrayInit {
+        assert(tree.nodes.items(.tag)[node] == .ArrayInitOne);
+        const data = tree.nodes.items(.data)[node];
+        buffer[0] = data.rhs;
+        const elements = if (data.rhs == 0) buffer[0..0] else buffer[0..1];
+        return .{
+            .ast = .{
+                .lbrace = tree.nodes.items(.main_token)[node],
+                .elements = elements,
+                .type_expr = data.lhs,
+            },
+        };
+    }
+
+    pub fn arrayInitDotTwo(tree: Tree, buffer: *[2]Node.Index, node: Node.Index) Full.ArrayInit {
+        assert(tree.nodes.items(.tag)[node] == .ArrayInitDotTwo or
+            tree.nodes.items(.tag)[node] == .ArrayInitDotTwoComma);
+        const data = tree.nodes.items(.data)[node];
+        buffer.* = .{ data.lhs, data.rhs };
+        const elements = if (data.rhs != 0)
+            buffer[0..2]
+        else if (data.lhs != 0)
+            buffer[0..1]
+        else
+            buffer[0..0];
+        return .{
+            .ast = .{
+                .lbrace = tree.nodes.items(.main_token)[node],
+                .elements = elements,
+                .type_expr = 0,
+            },
+        };
+    }
+
+    pub fn arrayInitDot(tree: Tree, node: Node.Index) Full.ArrayInit {
+        assert(tree.nodes.items(.tag)[node] == .ArrayInitDot);
+        const data = tree.nodes.items(.data)[node];
+        return .{
+            .ast = .{
+                .lbrace = tree.nodes.items(.main_token)[node],
+                .elements = tree.extra_data[data.lhs..data.rhs],
+                .type_expr = 0,
+            },
+        };
+    }
+
+    pub fn arrayInit(tree: Tree, node: Node.Index) Full.ArrayInit {
+        assert(tree.nodes.items(.tag)[node] == .ArrayInit);
+        const data = tree.nodes.items(.data)[node];
+        const elem_range = tree.extraData(data.rhs, Node.SubRange);
+        return .{
+            .ast = .{
+                .lbrace = tree.nodes.items(.main_token)[node],
+                .elements = tree.extra_data[elem_range.start..elem_range.end],
+                .type_expr = data.lhs,
+            },
+        };
+    }
+
     fn fullVarDecl(tree: Tree, info: Full.VarDecl.Ast) Full.VarDecl {
         const token_tags = tree.tokens.items(.tag);
         var result: Full.VarDecl = .{
@@ -1015,6 +1077,16 @@ pub const Full = struct {
             type_expr: Node.Index,
         };
     };
+
+    pub const ArrayInit = struct {
+        ast: Ast,
+
+        pub const Ast = struct {
+            lbrace: TokenIndex,
+            elements: []const Node.Index,
+            type_expr: Node.Index,
+        };
+    };
 };
 
 pub const Error = union(enum) {
@@ -1421,6 +1493,9 @@ pub const Node = struct {
         ArrayInitOne,
         /// `.{lhs, rhs}`. lhs and rhs can be omitted.
         ArrayInitDotTwo,
+        /// Same as `ArrayInitDotTwo` except there is known to be a trailing comma
+        /// before the final rbrace.
+        ArrayInitDotTwoComma,
         /// `.{a, b}`. `sub_list[lhs..rhs]`.
         ArrayInitDot,
         /// `lhs{a, b}`. `sub_range_list[rhs]`. lhs can be omitted which means `.{a, b}`.
lib/std/zig/parse.zig
@@ -2536,7 +2536,7 @@ const Parser = struct {
                     const comma_one = p.eatToken(.Comma);
                     if (p.eatToken(.RBrace)) |_| {
                         return p.addNode(.{
-                            .tag = .ArrayInitDotTwo,
+                            .tag = if (comma_one != null) .ArrayInitDotTwoComma else .ArrayInitDotTwo,
                             .main_token = lbrace,
                             .data = .{
                                 .lhs = elem_init_one,
@@ -2553,7 +2553,7 @@ const Parser = struct {
                     const comma_two = p.eatToken(.Comma);
                     if (p.eatToken(.RBrace)) |_| {
                         return p.addNode(.{
-                            .tag = .ArrayInitDotTwo,
+                            .tag = if (comma_one != null) .ArrayInitDotTwoComma else .ArrayInitDotTwo,
                             .main_token = lbrace,
                             .data = .{
                                 .lhs = elem_init_one,
@@ -2576,7 +2576,10 @@ const Parser = struct {
                         if (next == 0) break;
                         try init_list.append(next);
                         switch (p.token_tags[p.nextToken()]) {
-                            .Comma => continue,
+                            .Comma => {
+                                if (p.eatToken(.RBrace)) |_| break;
+                                continue;
+                            },
                             .RBrace => break,
                             .Colon, .RParen, .RBracket => {
                                 p.tok_i -= 1;
lib/std/zig/parser_test.zig
@@ -336,24 +336,160 @@ test "zig fmt: nosuspend block" {
 //        \\
 //    );
 //}
-//
-//test "zig fmt: anon struct literal syntax" {
-//    try testCanonical(
-//        \\const x = .{
-//        \\    .a = b,
-//        \\    .c = d,
-//        \\};
-//        \\
-//    );
-//}
-//
-//test "zig fmt: anon list literal syntax" {
-//    try testCanonical(
-//        \\const x = .{ a, b, c };
-//        \\
-//    );
-//}
-//
+
+test "zig fmt: anon struct literal 1 element" {
+    try testCanonical(
+        \\const x = .{ .a = b };
+        \\
+    );
+}
+
+test "zig fmt: anon struct literal 1 element comma" {
+    try testCanonical(
+        \\const x = .{
+        \\    .a = b,
+        \\};
+        \\
+    );
+}
+
+test "zig fmt: anon struct literal 2 element" {
+    try testCanonical(
+        \\const x = .{ .a = b, .c = d };
+        \\
+    );
+}
+
+test "zig fmt: anon struct literal 2 element comma" {
+    try testCanonical(
+        \\const x = .{
+        \\    .a = b,
+        \\    .c = d,
+        \\};
+        \\
+    );
+}
+
+test "zig fmt: anon struct literal 3 element" {
+    try testCanonical(
+        \\const x = .{ .a = b, .c = d, .e = f };
+        \\
+    );
+}
+
+test "zig fmt: anon struct literal 3 element comma" {
+    try testCanonical(
+        \\const x = .{
+        \\    .a = b,
+        \\    .c = d,
+        \\    .e = f,
+        \\};
+        \\
+    );
+}
+
+test "zig fmt: struct literal 1 element" {
+    try testCanonical(
+        \\const x = X{ .a = b };
+        \\
+    );
+}
+
+test "zig fmt: struct literal 1 element comma" {
+    try testCanonical(
+        \\const x = X{
+        \\    .a = b,
+        \\};
+        \\
+    );
+}
+
+test "zig fmt: struct literal 2 element" {
+    try testCanonical(
+        \\const x = X{ .a = b, .c = d };
+        \\
+    );
+}
+
+test "zig fmt: struct literal 2 element comma" {
+    try testCanonical(
+        \\const x = X{
+        \\    .a = b,
+        \\    .c = d,
+        \\};
+        \\
+    );
+}
+
+test "zig fmt: struct literal 3 element" {
+    try testCanonical(
+        \\const x = X{ .a = b, .c = d, .e = f };
+        \\
+    );
+}
+
+test "zig fmt: struct literal 3 element comma" {
+    try testCanonical(
+        \\const x = X{
+        \\    .a = b,
+        \\    .c = d,
+        \\    .e = f,
+        \\};
+        \\
+    );
+}
+
+test "zig fmt: anon list literal 1 element" {
+    try testCanonical(
+        \\const x = .{a};
+        \\
+    );
+}
+
+test "zig fmt: anon list literal 1 element comma" {
+    try testCanonical(
+        \\const x = .{
+        \\    a,
+        \\};
+        \\
+    );
+}
+
+test "zig fmt: anon list literal 2 element" {
+    try testCanonical(
+        \\const x = .{ a, b };
+        \\
+    );
+}
+
+test "zig fmt: anon list literal 2 element comma" {
+    try testCanonical(
+        \\const x = .{
+        \\    a,
+        \\    b,
+        \\};
+        \\
+    );
+}
+
+test "zig fmt: anon list literal 3 element" {
+    try testCanonical(
+        \\const x = .{ a, b, c };
+        \\
+    );
+}
+
+test "zig fmt: anon list literal 3 element comma" {
+    try testCanonical(
+        \\const x = .{
+        \\    a,
+        \\    b,
+        \\    c,
+        \\};
+        \\
+    );
+}
+
 //test "zig fmt: async function" {
 //    try testCanonical(
 //        \\pub const Server = struct {
lib/std/zig/render.zig
@@ -503,221 +503,16 @@ fn renderExpression(ais: *Ais, tree: ast.Tree, node: ast.Node.Index, space: Spac
         //    return renderExpression(ais, tree, slice_type.rhs, space);
         //},
 
-        .ArrayInitOne => unreachable, // TODO
-        .ArrayInitDotTwo => unreachable, // TODO
-        .ArrayInitDot => unreachable, // TODO
-        .ArrayInit => unreachable, // TODO
-        //.ArrayInitializer, .ArrayInitializerDot => {
-        //    var rtoken: ast.TokenIndex = undefined;
-        //    var exprs: []ast.Node.Index = undefined;
-        //    const lhs: union(enum) { dot: ast.TokenIndex, node: ast.Node.Index } = switch (base.tag) {
-        //        .ArrayInitializerDot => blk: {
-        //            const casted = @fieldParentPtr(ast.Node.ArrayInitializerDot, "base", base);
-        //            rtoken = casted.rtoken;
-        //            exprs = casted.list();
-        //            break :blk .{ .dot = casted.dot };
-        //        },
-        //        .ArrayInitializer => blk: {
-        //            const casted = @fieldParentPtr(ast.Node.ArrayInitializer, "base", base);
-        //            rtoken = casted.rtoken;
-        //            exprs = casted.list();
-        //            break :blk .{ .node = casted.lhs };
-        //        },
-        //        else => unreachable,
-        //    };
-
-        //    const lbrace = switch (lhs) {
-        //        .dot => |dot| tree.nextToken(dot),
-        //        .node => |node| tree.nextToken(node.lastToken()),
-        //    };
-
-        //    switch (lhs) {
-        //        .dot => |dot| try renderToken(ais, tree, dot, Space.None),
-        //        .node => |node| try renderExpression(ais, tree, node, Space.None),
-        //    }
-
-        //    if (exprs.len == 0) {
-        //        try renderToken(ais, tree, lbrace, Space.None);
-        //        return renderToken(ais, tree, rtoken, space);
-        //    }
-
-        //    if (exprs.len == 1 and exprs[0].tag != .MultilineStringLiteral and tree.token_tags[exprs[0].*.lastToken() + 1] == .RBrace) {
-        //        const expr = exprs[0];
-
-        //        try renderToken(ais, tree, lbrace, Space.None);
-        //        try renderExpression(ais, tree, expr, Space.None);
-        //        return renderToken(ais, tree, rtoken, space);
-        //    }
-
-        //    // scan to find row size
-        //    if (rowSize(tree, exprs, rtoken) != null) {
-        //        {
-        //            ais.pushIndentNextLine();
-        //            defer ais.popIndent();
-        //            try renderToken(ais, tree, lbrace, Space.Newline);
-
-        //            var expr_index: usize = 0;
-        //            while (rowSize(tree, exprs[expr_index..], rtoken)) |row_size| {
-        //                const row_exprs = exprs[expr_index..];
-        //                // A place to store the width of each expression and its column's maximum
-        //                var widths = try allocator.alloc(usize, row_exprs.len + row_size);
-        //                defer allocator.free(widths);
-        //                mem.set(usize, widths, 0);
-
-        //                var expr_newlines = try allocator.alloc(bool, row_exprs.len);
-        //                defer allocator.free(expr_newlines);
-        //                mem.set(bool, expr_newlines, false);
-
-        //                var expr_widths = widths[0 .. widths.len - row_size];
-        //                var column_widths = widths[widths.len - row_size ..];
-
-        //                // Find next row with trailing comment (if any) to end the current section
-        //                var section_end = sec_end: {
-        //                    var this_line_first_expr: usize = 0;
-        //                    var this_line_size = rowSize(tree, row_exprs, rtoken);
-        //                    for (row_exprs) |expr, i| {
-        //                        // Ignore comment on first line of this section
-        //                        if (i == 0 or tree.tokensOnSameLine(row_exprs[0].firstToken(), expr.lastToken())) continue;
-        //                        // Track start of line containing comment
-        //                        if (!tree.tokensOnSameLine(row_exprs[this_line_first_expr].firstToken(), expr.lastToken())) {
-        //                            this_line_first_expr = i;
-        //                            this_line_size = rowSize(tree, row_exprs[this_line_first_expr..], rtoken);
-        //                        }
-
-        //                        const maybe_comma = expr.lastToken() + 1;
-        //                        const maybe_comment = expr.lastToken() + 2;
-        //                        if (maybe_comment < tree.token_tags.len) {
-        //                            if (tree.token_tags[maybe_comma] == .Comma and
-        //                                tree.token_tags[maybe_comment] == .LineComment and
-        //                                tree.tokensOnSameLine(expr.lastToken(), maybe_comment))
-        //                            {
-        //                                var comment_token_loc = tree.token_locs[maybe_comment];
-        //                                const comment_is_empty = mem.trimRight(u8, tree.tokenSliceLoc(comment_token_loc), " ").len == 2;
-        //                                if (!comment_is_empty) {
-        //                                    // Found row ending in comment
-        //                                    break :sec_end i - this_line_size.? + 1;
-        //                                }
-        //                            }
-        //                        }
-        //                    }
-        //                    break :sec_end row_exprs.len;
-        //                };
-        //                expr_index += section_end;
-
-        //                const section_exprs = row_exprs[0..section_end];
-
-        //                // Null stream for counting the printed length of each expression
-        //                var line_find_stream = std.io.findByteWriter('\n', std.io.null_writer);
-        //                var counting_stream = std.io.countingWriter(line_find_stream.writer());
-        //                var auto_indenting_stream = std.io.autoIndentingStream(indent_delta, counting_stream.writer());
-
-        //                // Calculate size of columns in current section
-        //                var column_counter: usize = 0;
-        //                var single_line = true;
-        //                for (section_exprs) |expr, i| {
-        //                    if (i + 1 < section_exprs.len) {
-        //                        counting_stream.bytes_written = 0;
-        //                        line_find_stream.byte_found = false;
-        //                        try renderExpression(allocator, &auto_indenting_stream, tree, expr, Space.None);
-        //                        const width = @intCast(usize, counting_stream.bytes_written);
-        //                        expr_widths[i] = width;
-        //                        expr_newlines[i] = line_find_stream.byte_found;
-
-        //                        if (!line_find_stream.byte_found) {
-        //                            const column = column_counter % row_size;
-        //                            column_widths[column] = std.math.max(column_widths[column], width);
-
-        //                            const expr_last_token = expr.*.lastToken() + 1;
-        //                            const next_expr = section_exprs[i + 1];
-        //                            const loc = tree.tokenLocation(tree.token_locs[expr_last_token].start, next_expr.*.firstToken());
-
-        //                            column_counter += 1;
-
-        //                            if (loc.line != 0) single_line = false;
-        //                        } else {
-        //                            single_line = false;
-        //                            column_counter = 0;
-        //                        }
-        //                    } else {
-        //                        counting_stream.bytes_written = 0;
-        //                        try renderExpression(allocator, &auto_indenting_stream, tree, expr, Space.None);
-        //                        const width = @intCast(usize, counting_stream.bytes_written);
-        //                        expr_widths[i] = width;
-        //                        expr_newlines[i] = line_find_stream.byte_found;
-
-        //                        if (!line_find_stream.byte_found) {
-        //                            const column = column_counter % row_size;
-        //                            column_widths[column] = std.math.max(column_widths[column], width);
-        //                        }
-        //                        break;
-        //                    }
-        //                }
-
-        //                // Render exprs in current section
-        //                column_counter = 0;
-        //                var last_col_index: usize = row_size - 1;
-        //                for (section_exprs) |expr, i| {
-        //                    if (i + 1 < section_exprs.len) {
-        //                        const next_expr = section_exprs[i + 1];
-        //                        try renderExpression(ais, tree, expr, Space.None);
-
-        //                        const comma = tree.nextToken(expr.*.lastToken());
-
-        //                        if (column_counter != last_col_index) {
-        //                            if (!expr_newlines[i] and !expr_newlines[i + 1]) {
-        //                                // Neither the current or next expression is multiline
-        //                                try renderToken(ais, tree, comma, Space.Space); // ,
-        //                                assert(column_widths[column_counter % row_size] >= expr_widths[i]);
-        //                                const padding = column_widths[column_counter % row_size] - expr_widths[i];
-        //                                try ais.writer().writeByteNTimes(' ', padding);
-
-        //                                column_counter += 1;
-        //                                continue;
-        //                            }
-        //                        }
-        //                        if (single_line and row_size != 1) {
-        //                            try renderToken(ais, tree, comma, Space.Space); // ,
-        //                            continue;
-        //                        }
-
-        //                        column_counter = 0;
-        //                        try renderToken(ais, tree, comma, Space.Newline); // ,
-        //                        try renderExtraNewline(ais, tree, next_expr);
-        //                    } else {
-        //                        const maybe_comma = tree.nextToken(expr.*.lastToken());
-        //                        if (tree.token_tags[maybe_comma] == .Comma) {
-        //                            try renderExpression(ais, tree, expr, Space.None); // ,
-        //                            try renderToken(ais, tree, maybe_comma, Space.Newline); // ,
-        //                        } else {
-        //                            try renderExpression(ais, tree, expr, Space.Comma); // ,
-        //                        }
-        //                    }
-        //                }
-
-        //                if (expr_index == exprs.len) {
-        //                    break;
-        //                }
-        //            }
-        //        }
-
-        //        return renderToken(ais, tree, rtoken, space);
-        //    }
-
-        //    // Single line
-        //    try renderToken(ais, tree, lbrace, Space.Space);
-        //    for (exprs) |expr, i| {
-        //        if (i + 1 < exprs.len) {
-        //            const next_expr = exprs[i + 1];
-        //            try renderExpression(ais, tree, expr, Space.None);
-        //            const comma = tree.nextToken(expr.*.lastToken());
-        //            try renderToken(ais, tree, comma, Space.Space); // ,
-        //        } else {
-        //            try renderExpression(ais, tree, expr, Space.Space);
-        //        }
-        //    }
-
-        //    return renderToken(ais, tree, rtoken, space);
-        //},
+        .ArrayInitOne => {
+            var elements: [1]ast.Node.Index = undefined;
+            return renderArrayInit(ais, tree, tree.arrayInitOne(&elements, node), space);
+        },
+        .ArrayInitDotTwo, .ArrayInitDotTwoComma => {
+            var elements: [2]ast.Node.Index = undefined;
+            return renderArrayInit(ais, tree, tree.arrayInitDotTwo(&elements, node), space);
+        },
+        .ArrayInitDot => return renderArrayInit(ais, tree, tree.arrayInitDot(node), space),
+        .ArrayInit => return renderArrayInit(ais, tree, tree.arrayInit(node), space),
 
         .StructInitOne => {
             var fields: [1]ast.Node.Index = undefined;
@@ -2158,6 +1953,52 @@ fn renderStructInit(
     }
 }
 
+fn renderArrayInit(
+    ais: *Ais,
+    tree: ast.Tree,
+    array_init: ast.Full.ArrayInit,
+    space: Space,
+) Error!void {
+    const token_tags = tree.tokens.items(.tag);
+    if (array_init.ast.type_expr == 0) {
+        try renderToken(ais, tree, array_init.ast.lbrace - 1, .None); // .
+    } else {
+        try renderExpression(ais, tree, array_init.ast.type_expr, .None); // T
+    }
+    if (array_init.ast.elements.len == 0) {
+        try renderToken(ais, tree, array_init.ast.lbrace, .None); // lbrace
+        return renderToken(ais, tree, array_init.ast.lbrace + 1, space); // rbrace
+    }
+    const last_elem = array_init.ast.elements[array_init.ast.elements.len - 1];
+    const last_elem_token = tree.lastToken(last_elem);
+    if (token_tags[last_elem_token + 1] == .Comma) {
+        // Render one element per line.
+        ais.pushIndent();
+        try renderToken(ais, tree, array_init.ast.lbrace, .Newline);
+
+        try renderExpression(ais, tree, array_init.ast.elements[0], .Comma);
+        for (array_init.ast.elements[1..]) |elem| {
+            try renderExpressionNewlined(ais, tree, elem, .Comma);
+        }
+
+        ais.popIndent();
+        return renderToken(ais, tree, last_elem_token + 2, space); // rbrace
+    } else {
+        // Render all on one line, no trailing comma.
+        if (array_init.ast.elements.len == 1) {
+            // If there is only one element, we don't use spaces
+            try renderToken(ais, tree, array_init.ast.lbrace, .None);
+            try renderExpression(ais, tree, array_init.ast.elements[0], .None);
+        } else {
+            try renderToken(ais, tree, array_init.ast.lbrace, .Space);
+            for (array_init.ast.elements) |elem| {
+                try renderExpression(ais, tree, elem, .CommaSpace);
+            }
+        }
+        return renderToken(ais, tree, last_elem_token + 1, space); // rbrace
+    }
+}
+
 /// 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);