Commit 9a5a1013a8

Andrew Kelley <andrew@ziglang.org>
2020-08-15 00:27:48
std.zig.ast: extract out Node.LabeledBlock from Node.Block
This is part of an ongoing effort to reduce size of in-memory AST. This enum flattening pattern is widespread throughout the self-hosted compiler. This is a API breaking change for consumers of the self-hosted parser.
1 parent c12a262
Changed files (6)
lib/std/zig/ast.zig
@@ -526,6 +526,7 @@ pub const Node = struct {
         Comptime,
         Nosuspend,
         Block,
+        LabeledBlock,
 
         // Misc
         DocComment,
@@ -654,6 +655,7 @@ pub const Node = struct {
                 .Comptime => Comptime,
                 .Nosuspend => Nosuspend,
                 .Block => Block,
+                .LabeledBlock => LabeledBlock,
                 .DocComment => DocComment,
                 .SwitchCase => SwitchCase,
                 .SwitchElse => SwitchElse,
@@ -666,6 +668,13 @@ pub const Node = struct {
                 .FieldInitializer => FieldInitializer,
             };
         }
+
+        pub fn isBlock(tag: Tag) bool {
+            return switch (tag) {
+                .Block, .LabeledBlock => true,
+                else => false,
+            };
+        }
     };
 
     /// Prefer `castTag` to this.
@@ -729,6 +738,7 @@ pub const Node = struct {
                 .Root,
                 .ContainerField,
                 .Block,
+                .LabeledBlock,
                 .Payload,
                 .PointerPayload,
                 .PointerIndexPayload,
@@ -739,6 +749,7 @@ pub const Node = struct {
                 .DocComment,
                 .TestDecl,
                 => return false,
+
                 .While => {
                     const while_node = @fieldParentPtr(While, "base", n);
                     if (while_node.@"else") |@"else"| {
@@ -746,7 +757,7 @@ pub const Node = struct {
                         continue;
                     }
 
-                    return while_node.body.tag != .Block;
+                    return !while_node.body.tag.isBlock();
                 },
                 .For => {
                     const for_node = @fieldParentPtr(For, "base", n);
@@ -755,7 +766,7 @@ pub const Node = struct {
                         continue;
                     }
 
-                    return for_node.body.tag != .Block;
+                    return !for_node.body.tag.isBlock();
                 },
                 .If => {
                     const if_node = @fieldParentPtr(If, "base", n);
@@ -764,7 +775,7 @@ pub const Node = struct {
                         continue;
                     }
 
-                    return if_node.body.tag != .Block;
+                    return !if_node.body.tag.isBlock();
                 },
                 .Else => {
                     const else_node = @fieldParentPtr(Else, "base", n);
@@ -773,29 +784,40 @@ pub const Node = struct {
                 },
                 .Defer => {
                     const defer_node = @fieldParentPtr(Defer, "base", n);
-                    return defer_node.expr.tag != .Block;
+                    return !defer_node.expr.tag.isBlock();
                 },
                 .Comptime => {
                     const comptime_node = @fieldParentPtr(Comptime, "base", n);
-                    return comptime_node.expr.tag != .Block;
+                    return !comptime_node.expr.tag.isBlock();
                 },
                 .Suspend => {
                     const suspend_node = @fieldParentPtr(Suspend, "base", n);
                     if (suspend_node.body) |body| {
-                        return body.tag != .Block;
+                        return !body.tag.isBlock();
                     }
 
                     return true;
                 },
                 .Nosuspend => {
                     const nosuspend_node = @fieldParentPtr(Nosuspend, "base", n);
-                    return nosuspend_node.expr.tag != .Block;
+                    return !nosuspend_node.expr.tag.isBlock();
                 },
                 else => return true,
             }
         }
     }
 
+    /// Asserts the node is a Block or LabeledBlock and returns the statements slice.
+    pub fn blockStatements(base: *Node) []*Node {
+        if (base.castTag(.Block)) |block| {
+            return block.statements();
+        } else if (base.castTag(.LabeledBlock)) |labeled_block| {
+            return labeled_block.statements();
+        } else {
+            unreachable;
+        }
+    }
+
     pub fn dump(self: *Node, indent: usize) void {
         {
             var i: usize = 0;
@@ -1460,7 +1482,6 @@ pub const Node = struct {
         statements_len: NodeIndex,
         lbrace: TokenIndex,
         rbrace: TokenIndex,
-        label: ?TokenIndex,
 
         /// After this the caller must initialize the statements list.
         pub fn alloc(allocator: *mem.Allocator, statements_len: NodeIndex) !*Block {
@@ -1483,10 +1504,6 @@ pub const Node = struct {
         }
 
         pub fn firstToken(self: *const Block) TokenIndex {
-            if (self.label) |label| {
-                return label;
-            }
-
             return self.lbrace;
         }
 
@@ -1509,6 +1526,57 @@ pub const Node = struct {
         }
     };
 
+    /// The statements of the block follow LabeledBlock directly in memory.
+    pub const LabeledBlock = struct {
+        base: Node = Node{ .tag = .LabeledBlock },
+        statements_len: NodeIndex,
+        lbrace: TokenIndex,
+        rbrace: TokenIndex,
+        label: TokenIndex,
+
+        /// After this the caller must initialize the statements list.
+        pub fn alloc(allocator: *mem.Allocator, statements_len: NodeIndex) !*LabeledBlock {
+            const bytes = try allocator.alignedAlloc(u8, @alignOf(LabeledBlock), sizeInBytes(statements_len));
+            return @ptrCast(*LabeledBlock, bytes.ptr);
+        }
+
+        pub fn free(self: *LabeledBlock, allocator: *mem.Allocator) void {
+            const bytes = @ptrCast([*]u8, self)[0..sizeInBytes(self.statements_len)];
+            allocator.free(bytes);
+        }
+
+        pub fn iterate(self: *const LabeledBlock, index: usize) ?*Node {
+            var i = index;
+
+            if (i < self.statements_len) return self.statementsConst()[i];
+            i -= self.statements_len;
+
+            return null;
+        }
+
+        pub fn firstToken(self: *const LabeledBlock) TokenIndex {
+            return self.label;
+        }
+
+        pub fn lastToken(self: *const LabeledBlock) TokenIndex {
+            return self.rbrace;
+        }
+
+        pub fn statements(self: *LabeledBlock) []*Node {
+            const decls_start = @ptrCast([*]u8, self) + @sizeOf(LabeledBlock);
+            return @ptrCast([*]*Node, decls_start)[0..self.statements_len];
+        }
+
+        pub fn statementsConst(self: *const LabeledBlock) []const *Node {
+            const decls_start = @ptrCast([*]const u8, self) + @sizeOf(LabeledBlock);
+            return @ptrCast([*]const *Node, decls_start)[0..self.statements_len];
+        }
+
+        fn sizeInBytes(statements_len: NodeIndex) usize {
+            return @sizeOf(LabeledBlock) + @sizeOf(*Node) * @as(usize, statements_len);
+        }
+    };
+
     pub const Defer = struct {
         base: Node = Node{ .tag = .Defer },
         defer_token: TokenIndex,
lib/std/zig/parse.zig
@@ -364,9 +364,10 @@ const Parser = struct {
         const name_node = try p.expectNode(parseStringLiteralSingle, .{
             .ExpectedStringLiteral = .{ .token = p.tok_i },
         });
-        const block_node = try p.expectNode(parseBlock, .{
-            .ExpectedLBrace = .{ .token = p.tok_i },
-        });
+        const block_node = (try p.parseBlock(null)) orelse {
+            try p.errors.append(p.gpa, .{ .ExpectedLBrace = .{ .token = p.tok_i } });
+            return error.ParseError;
+        };
 
         const test_node = try p.arena.allocator.create(Node.TestDecl);
         test_node.* = .{
@@ -540,12 +541,14 @@ const Parser = struct {
                 if (p.eatToken(.Semicolon)) |_| {
                     break :blk null;
                 }
-                break :blk try p.expectNodeRecoverable(parseBlock, .{
+                const body_block = (try p.parseBlock(null)) orelse {
                     // Since parseBlock only return error.ParseError on
                     // a missing '}' we can assume this function was
                     // supposed to end here.
-                    .ExpectedSemiOrLBrace = .{ .token = p.tok_i },
-                });
+                    try p.errors.append(p.gpa, .{ .ExpectedSemiOrLBrace = .{ .token = p.tok_i } });
+                    break :blk null;
+                };
+                break :blk body_block;
             },
             .as_type => null,
         };
@@ -823,10 +826,7 @@ const Parser = struct {
         var colon: TokenIndex = undefined;
         const label_token = p.parseBlockLabel(&colon);
 
-        if (try p.parseBlock()) |node| {
-            node.cast(Node.Block).?.label = label_token;
-            return node;
-        }
+        if (try p.parseBlock(label_token)) |node| return node;
 
         if (try p.parseLoopStatement()) |node| {
             if (node.cast(Node.For)) |for_node| {
@@ -1003,14 +1003,13 @@ const Parser = struct {
     fn parseBlockExpr(p: *Parser) Error!?*Node {
         var colon: TokenIndex = undefined;
         const label_token = p.parseBlockLabel(&colon);
-        const block_node = (try p.parseBlock()) orelse {
+        const block_node = (try p.parseBlock(label_token)) orelse {
             if (label_token) |label| {
                 p.putBackToken(label + 1); // ":"
                 p.putBackToken(label); // IDENTIFIER
             }
             return null;
         };
-        block_node.cast(Node.Block).?.label = label_token;
         return block_node;
     }
 
@@ -1177,7 +1176,7 @@ const Parser = struct {
             p.putBackToken(token); // IDENTIFIER
         }
 
-        if (try p.parseBlock()) |node| return node;
+        if (try p.parseBlock(null)) |node| return node;
         if (try p.parseCurlySuffixExpr()) |node| return node;
 
         return null;
@@ -1189,7 +1188,7 @@ const Parser = struct {
     }
 
     /// Block <- LBRACE Statement* RBRACE
-    fn parseBlock(p: *Parser) !?*Node {
+    fn parseBlock(p: *Parser, label_token: ?TokenIndex) !?*Node {
         const lbrace = p.eatToken(.LBrace) orelse return null;
 
         var statements = std.ArrayList(*Node).init(p.gpa);
@@ -1211,16 +1210,26 @@ const Parser = struct {
 
         const statements_len = @intCast(NodeIndex, statements.items.len);
 
-        const block_node = try Node.Block.alloc(&p.arena.allocator, statements_len);
-        block_node.* = .{
-            .label = null,
-            .lbrace = lbrace,
-            .statements_len = statements_len,
-            .rbrace = rbrace,
-        };
-        std.mem.copy(*Node, block_node.statements(), statements.items);
-
-        return &block_node.base;
+        if (label_token) |label| {
+            const block_node = try Node.LabeledBlock.alloc(&p.arena.allocator, statements_len);
+            block_node.* = .{
+                .label = label,
+                .lbrace = lbrace,
+                .statements_len = statements_len,
+                .rbrace = rbrace,
+            };
+            std.mem.copy(*Node, block_node.statements(), statements.items);
+            return &block_node.base;
+        } else {
+            const block_node = try Node.Block.alloc(&p.arena.allocator, statements_len);
+            block_node.* = .{
+                .lbrace = lbrace,
+                .statements_len = statements_len,
+                .rbrace = rbrace,
+            };
+            std.mem.copy(*Node, block_node.statements(), statements.items);
+            return &block_node.base;
+        }
     }
 
     /// LoopExpr <- KEYWORD_inline? (ForExpr / WhileExpr)
@@ -1658,11 +1667,8 @@ const Parser = struct {
         var colon: TokenIndex = undefined;
         const label = p.parseBlockLabel(&colon);
 
-        if (label) |token| {
-            if (try p.parseBlock()) |node| {
-                node.cast(Node.Block).?.label = token;
-                return node;
-            }
+        if (label) |label_token| {
+            if (try p.parseBlock(label_token)) |node| return node;
         }
 
         if (try p.parseLoopTypeExpr()) |node| {
@@ -3440,6 +3446,7 @@ const Parser = struct {
         }
     }
 
+    /// TODO Delete this function. I don't like the inversion of control.
     fn expectNode(
         p: *Parser,
         parseFn: NodeParseFn,
@@ -3449,6 +3456,7 @@ const Parser = struct {
         return (try p.expectNodeRecoverable(parseFn, err)) orelse return error.ParseError;
     }
 
+    /// TODO Delete this function. I don't like the inversion of control.
     fn expectNodeRecoverable(
         p: *Parser,
         parseFn: NodeParseFn,
lib/std/zig/render.zig
@@ -392,28 +392,50 @@ fn renderExpression(
             return renderToken(tree, stream, any_type.token, indent, start_col, space);
         },
 
-        .Block => {
-            const block = @fieldParentPtr(ast.Node.Block, "base", base);
+        .Block, .LabeledBlock => {
+            const block: struct {
+                label: ?ast.TokenIndex,
+                statements: []*ast.Node,
+                lbrace: ast.TokenIndex,
+                rbrace: ast.TokenIndex,
+            } = b: {
+                if (base.castTag(.Block)) |block| {
+                    break :b .{
+                        .label = null,
+                        .statements = block.statements(),
+                        .lbrace = block.lbrace,
+                        .rbrace = block.rbrace,
+                    };
+                } else if (base.castTag(.LabeledBlock)) |block| {
+                    break :b .{
+                        .label = block.label,
+                        .statements = block.statements(),
+                        .lbrace = block.lbrace,
+                        .rbrace = block.rbrace,
+                    };
+                } else {
+                    unreachable;
+                }
+            };
 
             if (block.label) |label| {
                 try renderToken(tree, stream, label, indent, start_col, Space.None);
                 try renderToken(tree, stream, tree.nextToken(label), indent, start_col, Space.Space);
             }
 
-            if (block.statements_len == 0) {
+            if (block.statements.len == 0) {
                 try renderToken(tree, stream, block.lbrace, indent + indent_delta, start_col, Space.None);
                 return renderToken(tree, stream, block.rbrace, indent, start_col, space);
             } else {
                 const block_indent = indent + indent_delta;
                 try renderToken(tree, stream, block.lbrace, block_indent, start_col, Space.Newline);
 
-                const block_statements = block.statements();
-                for (block_statements) |statement, i| {
+                for (block.statements) |statement, i| {
                     try stream.writeByteNTimes(' ', block_indent);
                     try renderStatement(allocator, stream, tree, block_indent, start_col, statement);
 
-                    if (i + 1 < block_statements.len) {
-                        try renderExtraNewline(tree, stream, start_col, block_statements[i + 1]);
+                    if (i + 1 < block.statements.len) {
+                        try renderExtraNewline(tree, stream, start_col, block.statements[i + 1]);
                     }
                 }
 
@@ -1841,7 +1863,7 @@ fn renderExpression(
 
             const rparen = tree.nextToken(for_node.array_expr.lastToken());
 
-            const body_is_block = for_node.body.tag == .Block;
+            const body_is_block = for_node.body.tag.isBlock();
             const src_one_line_to_body = !body_is_block and tree.tokensOnSameLine(rparen, for_node.body.firstToken());
             const body_on_same_line = body_is_block or src_one_line_to_body;
 
@@ -2578,6 +2600,7 @@ fn renderDocCommentsToken(
 fn nodeIsBlock(base: *const ast.Node) bool {
     return switch (base.tag) {
         .Block,
+        .LabeledBlock,
         .If,
         .For,
         .While,
src-self-hosted/astgen.zig
@@ -107,31 +107,46 @@ pub fn expr(mod: *Module, scope: *Scope, rl: ResultLoc, node: *ast.Node) InnerEr
         .NullLiteral => return rlWrap(mod, scope, rl, try nullLiteral(mod, scope, node.castTag(.NullLiteral).?)),
         .OptionalType => return rlWrap(mod, scope, rl, try optionalType(mod, scope, node.castTag(.OptionalType).?)),
         .UnwrapOptional => return unwrapOptional(mod, scope, rl, node.castTag(.UnwrapOptional).?),
-        .Block => return blockExpr(mod, scope, rl, node.castTag(.Block).?),
+        .Block => return rlWrapVoid(mod, scope, rl, node, try blockExpr(mod, scope, node.castTag(.Block).?)),
+        .LabeledBlock => return labeledBlockExpr(mod, scope, rl, node.castTag(.LabeledBlock).?),
         else => return mod.failNode(scope, node, "TODO implement astgen.Expr for {}", .{@tagName(node.tag)}),
     }
 }
 
-pub fn blockExpr(
+pub fn blockExpr(mod: *Module, parent_scope: *Scope, block_node: *ast.Node.Block) InnerError!void {
+    const tracy = trace(@src());
+    defer tracy.end();
+
+    try blockExprStmts(mod, parent_scope, &block_node.base, block_node.statements());
+}
+
+fn labeledBlockExpr(
     mod: *Module,
     parent_scope: *Scope,
     rl: ResultLoc,
-    block_node: *ast.Node.Block,
+    block_node: *ast.Node.LabeledBlock,
 ) InnerError!*zir.Inst {
     const tracy = trace(@src());
     defer tracy.end();
 
-    if (block_node.label) |label| {
-        return mod.failTok(parent_scope, label, "TODO implement labeled blocks", .{});
+    const statements = block_node.statements();
+
+    if (statements.len == 0) {
+        // Hot path for `{}`.
+        return rlWrapVoid(mod, parent_scope, rl, &block_node.base, {});
     }
 
-    var block_arena = std.heap.ArenaAllocator.init(mod.gpa);
-    defer block_arena.deinit();
+    return mod.failNode(parent_scope, &block_node.base, "TODO implement labeled blocks", .{});
+}
 
+fn blockExprStmts(mod: *Module, parent_scope: *Scope, node: *ast.Node, statements: []*ast.Node) !void {
     const tree = parent_scope.tree();
 
+    var block_arena = std.heap.ArenaAllocator.init(mod.gpa);
+    defer block_arena.deinit();
+
     var scope = parent_scope;
-    for (block_node.statements()) |statement| {
+    for (statements) |statement| {
         const src = tree.token_locs[statement.firstToken()].start;
         _ = try addZIRNoOp(mod, scope, src, .dbg_stmt);
         switch (statement.tag) {
@@ -162,12 +177,6 @@ pub fn blockExpr(
             },
         }
     }
-
-    const src = tree.token_locs[block_node.firstToken()].start;
-    return addZIRInstConst(mod, parent_scope, src, .{
-        .ty = Type.initTag(.void),
-        .val = Value.initTag(.void_value),
-    });
 }
 
 fn varDecl(
@@ -1184,6 +1193,7 @@ fn nodeMayNeedMemoryLocation(start_node: *ast.Node) bool {
             .Slice,
             .Deref,
             .ArrayAccess,
+            .Block,
             => return false,
 
             // Forward the question to a sub-expression.
@@ -1210,11 +1220,11 @@ fn nodeMayNeedMemoryLocation(start_node: *ast.Node) bool {
             .Switch,
             .Call,
             .BuiltinCall, // TODO some of these can return false
+            .LabeledBlock,
             => return true,
 
             // Depending on AST properties, they may need memory locations.
             .If => return node.castTag(.If).?.@"else" != null,
-            .Block => return node.castTag(.Block).?.label != null,
         }
     }
 }
src-self-hosted/Module.zig
@@ -1343,7 +1343,7 @@ fn astGenAndAnalyzeDecl(self: *Module, decl: *Decl) !bool {
 
                 const body_block = body_node.cast(ast.Node.Block).?;
 
-                _ = try astgen.blockExpr(self, params_scope, .none, body_block);
+                try astgen.blockExpr(self, params_scope, body_block);
 
                 if (gen_scope.instructions.items.len == 0 or
                     !gen_scope.instructions.items[gen_scope.instructions.items.len - 1].tag.isNoReturn())
src-self-hosted/translate_c.zig
@@ -118,19 +118,31 @@ const Scope = struct {
             self.* = undefined;
         }
 
-        fn complete(self: *Block, c: *Context) !*ast.Node.Block {
+        fn complete(self: *Block, c: *Context) !*ast.Node {
             // We reserve 1 extra statement if the parent is a Loop. This is in case of
             // do while, we want to put `if (cond) break;` at the end.
             const alloc_len = self.statements.items.len + @boolToInt(self.base.parent.?.id == .Loop);
-            const node = try ast.Node.Block.alloc(c.arena, alloc_len);
-            node.* = .{
-                .statements_len = self.statements.items.len,
-                .lbrace = self.lbrace,
-                .rbrace = try appendToken(c, .RBrace, "}"),
-                .label = self.label,
-            };
-            mem.copy(*ast.Node, node.statements(), self.statements.items);
-            return node;
+            const rbrace = try appendToken(c, .RBrace, "}");
+            if (self.label) |label| {
+                const node = try ast.Node.LabeledBlock.alloc(c.arena, alloc_len);
+                node.* = .{
+                    .statements_len = self.statements.items.len,
+                    .lbrace = self.lbrace,
+                    .rbrace = rbrace,
+                    .label = label,
+                };
+                mem.copy(*ast.Node, node.statements(), self.statements.items);
+                return &node.base;
+            } else {
+                const node = try ast.Node.Block.alloc(c.arena, alloc_len);
+                node.* = .{
+                    .statements_len = self.statements.items.len,
+                    .lbrace = self.lbrace,
+                    .rbrace = rbrace,
+                };
+                mem.copy(*ast.Node, node.statements(), self.statements.items);
+                return &node.base;
+            }
         }
 
         /// Given the desired name, return a name that does not shadow anything from outer scopes.
@@ -320,15 +332,9 @@ pub const Context = struct {
         return node;
     }
 
-    fn createBlock(c: *Context, label: ?[]const u8, statements_len: ast.NodeIndex) !*ast.Node.Block {
-        const label_node = if (label) |l| blk: {
-            const ll = try appendIdentifier(c, l);
-            _ = try appendToken(c, .Colon, ":");
-            break :blk ll;
-        } else null;
+    fn createBlock(c: *Context, statements_len: ast.NodeIndex) !*ast.Node.Block {
         const block_node = try ast.Node.Block.alloc(c.arena, statements_len);
         block_node.* = .{
-            .label = label_node,
             .lbrace = try appendToken(c, .LBrace, "{"),
             .statements_len = statements_len,
             .rbrace = undefined,
@@ -640,8 +646,8 @@ fn visitFnDecl(c: *Context, fn_decl: *const ZigClangFunctionDecl) Error!void {
             var last = block_scope.statements.items[block_scope.statements.items.len - 1];
             while (true) {
                 switch (last.tag) {
-                    .Block => {
-                        const stmts = last.castTag(.Block).?.statements();
+                    .Block, .LabeledBlock => {
+                        const stmts = last.blockStatements();
                         if (stmts.len == 0) break;
 
                         last = stmts[stmts.len - 1];
@@ -669,7 +675,7 @@ fn visitFnDecl(c: *Context, fn_decl: *const ZigClangFunctionDecl) Error!void {
     }
 
     const body_node = try block_scope.complete(rp.c);
-    proto_node.setTrailer("body_node", &body_node.base);
+    proto_node.setTrailer("body_node", body_node);
     return addTopLevelDecl(c, fn_name, &proto_node.base);
 }
 
@@ -1275,7 +1281,7 @@ fn transStmt(
         .WhileStmtClass => return transWhileLoop(rp, scope, @ptrCast(*const ZigClangWhileStmt, stmt)),
         .DoStmtClass => return transDoWhileLoop(rp, scope, @ptrCast(*const ZigClangDoStmt, stmt)),
         .NullStmtClass => {
-            const block = try rp.c.createBlock(null, 0);
+            const block = try rp.c.createBlock(0);
             block.rbrace = try appendToken(rp.c, .RBrace, "}");
             return &block.base;
         },
@@ -1356,7 +1362,7 @@ fn transBinaryOperator(
                 const grouped_expr = try rp.c.arena.create(ast.Node.GroupedExpression);
                 grouped_expr.* = .{
                     .lparen = lparen,
-                    .expr = &block_node.base,
+                    .expr = block_node,
                     .rparen = rparen,
                 };
                 return maybeSuppressResult(rp, scope, result_used, &grouped_expr.base);
@@ -1521,8 +1527,7 @@ fn transCompoundStmt(rp: RestorePoint, scope: *Scope, stmt: *const ZigClangCompo
     var block_scope = try Scope.Block.init(rp.c, scope, false);
     defer block_scope.deinit();
     try transCompoundStmtInline(rp, &block_scope.base, stmt, &block_scope);
-    const node = try block_scope.complete(rp.c);
-    return &node.base;
+    return try block_scope.complete(rp.c);
 }
 
 fn transCStyleCastExprClass(
@@ -2589,7 +2594,7 @@ fn transDoWhileLoop(
         // zig:   if (!cond) break;
         // zig: }
         const node = try transStmt(rp, &loop_scope, ZigClangDoStmt_getBody(stmt), .unused, .r_value);
-        break :blk node.cast(ast.Node.Block).?;
+        break :blk node.castTag(.Block).?;
     } else blk: {
         // the C statement is without a block, so we need to create a block to contain it.
         // c: do
@@ -2600,7 +2605,7 @@ fn transDoWhileLoop(
         // zig:   if (!cond) break;
         // zig: }
         new = true;
-        const block = try rp.c.createBlock(null, 2);
+        const block = try rp.c.createBlock(2);
         block.statements_len = 1; // over-allocated so we can add another below
         block.statements()[0] = try transStmt(rp, &loop_scope, ZigClangDoStmt_getBody(stmt), .unused, .r_value);
         break :blk block;
@@ -2659,8 +2664,7 @@ fn transForLoop(
     while_node.body = try transStmt(rp, &loop_scope, ZigClangForStmt_getBody(stmt), .unused, .r_value);
     if (block_scope) |*bs| {
         try bs.statements.append(&while_node.base);
-        const node = try bs.complete(rp.c);
-        return &node.base;
+        return try bs.complete(rp.c);
     } else {
         _ = try appendToken(rp.c, .Semicolon, ";");
         return &while_node.base;
@@ -2768,7 +2772,7 @@ fn transSwitch(
 
     const result_node = try switch_scope.pending_block.complete(rp.c);
     switch_scope.pending_block.deinit();
-    return &result_node.base;
+    return result_node;
 }
 
 fn transCase(
@@ -2820,7 +2824,7 @@ fn transCase(
     switch_scope.pending_block.deinit();
     switch_scope.pending_block = try Scope.Block.init(rp.c, scope, false);
 
-    try switch_scope.pending_block.statements.append(&pending_node.base);
+    try switch_scope.pending_block.statements.append(pending_node);
 
     return transStmt(rp, scope, ZigClangCaseStmt_getSubStmt(stmt), .unused, .r_value);
 }
@@ -2857,7 +2861,7 @@ fn transDefault(
     const pending_node = try switch_scope.pending_block.complete(rp.c);
     switch_scope.pending_block.deinit();
     switch_scope.pending_block = try Scope.Block.init(rp.c, scope, false);
-    try switch_scope.pending_block.statements.append(&pending_node.base);
+    try switch_scope.pending_block.statements.append(pending_node);
 
     return transStmt(rp, scope, ZigClangDefaultStmt_getSubStmt(stmt), .unused, .r_value);
 }
@@ -2972,7 +2976,7 @@ fn transStmtExpr(rp: RestorePoint, scope: *Scope, stmt: *const ZigClangStmtExpr,
     const grouped_expr = try rp.c.arena.create(ast.Node.GroupedExpression);
     grouped_expr.* = .{
         .lparen = lparen,
-        .expr = &block_node.base,
+        .expr = block_node,
         .rparen = rparen,
     };
     return maybeSuppressResult(rp, scope, used, &grouped_expr.base);
@@ -3304,7 +3308,7 @@ fn transCreatePreCrement(
     const grouped_expr = try rp.c.arena.create(ast.Node.GroupedExpression);
     grouped_expr.* = .{
         .lparen = try appendToken(rp.c, .LParen, "("),
-        .expr = &block_node.base,
+        .expr = block_node,
         .rparen = try appendToken(rp.c, .RParen, ")"),
     };
     return &grouped_expr.base;
@@ -3398,7 +3402,7 @@ fn transCreatePostCrement(
     const grouped_expr = try rp.c.arena.create(ast.Node.GroupedExpression);
     grouped_expr.* = .{
         .lparen = try appendToken(rp.c, .LParen, "("),
-        .expr = &block_node.base,
+        .expr = block_node,
         .rparen = try appendToken(rp.c, .RParen, ")"),
     };
     return &grouped_expr.base;
@@ -3589,7 +3593,7 @@ fn transCreateCompoundAssign(
     const grouped_expr = try rp.c.arena.create(ast.Node.GroupedExpression);
     grouped_expr.* = .{
         .lparen = try appendToken(rp.c, .LParen, "("),
-        .expr = &block_node.base,
+        .expr = block_node,
         .rparen = try appendToken(rp.c, .RParen, ")"),
     };
     return &grouped_expr.base;
@@ -3748,7 +3752,7 @@ fn transBinaryConditionalOperator(rp: RestorePoint, scope: *Scope, stmt: *const
     const grouped_expr = try rp.c.arena.create(ast.Node.GroupedExpression);
     grouped_expr.* = .{
         .lparen = lparen,
-        .expr = &block_node.base,
+        .expr = block_node,
         .rparen = try appendToken(rp.c, .RParen, ")"),
     };
     return maybeSuppressResult(rp, scope, used, &grouped_expr.base);
@@ -4191,7 +4195,7 @@ fn transCreateNodeAssign(
     const block_node = try block_scope.complete(rp.c);
     // semicolon must immediately follow rbrace because it is the last token in a block
     _ = try appendToken(rp.c, .Semicolon, ";");
-    return &block_node.base;
+    return block_node;
 }
 
 fn transCreateNodeFieldAccess(c: *Context, container: *ast.Node, field_name: []const u8) !*ast.Node {
@@ -4484,7 +4488,6 @@ fn transCreateNodeMacroFn(c: *Context, name: []const u8, ref: *ast.Node, proto_a
 
     const block = try ast.Node.Block.alloc(c.arena, 1);
     block.* = .{
-        .label = null,
         .lbrace = block_lbrace,
         .statements_len = 1,
         .rbrace = try appendToken(c, .RBrace, "}"),
@@ -5475,9 +5478,9 @@ fn transMacroFnDefine(c: *Context, m: *MacroCtx) ParseError!void {
     if (last != .Eof and last != .Nl)
         return m.fail(c, "unable to translate C expr: unexpected token .{}", .{@tagName(last)});
     _ = try appendToken(c, .Semicolon, ";");
-    const type_of_arg = if (expr.tag != .Block) expr else blk: {
-        const blk = @fieldParentPtr(ast.Node.Block, "base", expr);
-        const blk_last = blk.statements()[blk.statements_len - 1];
+    const type_of_arg = if (!expr.tag.isBlock()) expr else blk: {
+        const stmts = expr.blockStatements();
+        const blk_last = stmts[stmts.len - 1];
         const br = blk_last.cast(ast.Node.ControlFlowExpression).?;
         break :blk br.getRHS().?;
     };
@@ -5500,7 +5503,7 @@ fn transMacroFnDefine(c: *Context, m: *MacroCtx) ParseError!void {
         .visib_token = pub_tok,
         .extern_export_inline_token = inline_tok,
         .name_token = name_tok,
-        .body_node = &block_node.base,
+        .body_node = block_node,
     });
     mem.copy(ast.Node.FnProto.ParamDecl, fn_proto.params(), fn_params.items);
 
@@ -5555,8 +5558,7 @@ fn parseCExpr(c: *Context, m: *MacroCtx, scope: *Scope) ParseError!*ast.Node {
 
             const break_node = try transCreateNodeBreak(c, block_scope.label, last);
             try block_scope.statements.append(&break_node.base);
-            const block_node = try block_scope.complete(c);
-            return &block_node.base;
+            return try block_scope.complete(c);
         },
         else => {
             m.i -= 1;