Commit 7ca53bdfaa

Veikka Tuominen <git@vexu.eu>
2021-02-17 21:11:26
translate-c: improve switch translation
1 parent 3717bed
Changed files (4)
src/translate_c/ast.zig
@@ -30,6 +30,7 @@ pub const Node = extern union {
         noreturn_type,
         @"anytype",
         @"continue",
+        @"break",
         /// pub usingnamespace @import("std").c.builtins;
         usingnamespace_builtins,
         // After this, the tag requires a payload.
@@ -48,9 +49,8 @@ pub const Node = extern union {
         @"switch",
         /// else => operand,
         switch_else,
-        /// lhs => rhs,
+        /// items => body,
         switch_prong,
-        @"break",
         break_val,
         @"return",
         field_access,
@@ -219,6 +219,7 @@ pub const Node = extern union {
                 .noreturn_type,
                 .@"anytype",
                 .@"continue",
+                .@"break",
                 => @compileError("Type Tag " ++ @tagName(t) ++ " has no payload"),
 
                 .std_mem_zeroes,
@@ -294,7 +295,6 @@ pub const Node = extern union {
                 .int_to_ptr,
                 .array_cat,
                 .ellipsis3,
-                .switch_prong,
                 .assign,
                 .align_cast,
                 .array_access,
@@ -312,8 +312,7 @@ pub const Node = extern union {
                 => Payload.Value,
                 .@"if" => Payload.If,
                 .@"while" => Payload.While,
-                .@"switch", .array_init => Payload.Switch,
-                .@"break" => Payload.Break,
+                .@"switch", .array_init,.switch_prong => Payload.Switch,
                 .break_val => Payload.BreakVal,
                 .call => Payload.Call,
                 .var_decl => Payload.VarDecl,
@@ -377,6 +376,37 @@ pub const Node = extern union {
         std.debug.assert(@enumToInt(payload.tag) >= Tag.no_payload_count);
         return .{ .ptr_otherwise = payload };
     }
+
+    pub fn isNoreturn(node: Node, break_counts: bool) bool {
+        switch (node.tag()) {
+            .block => {
+                const block_node = node.castTag(.block).?;
+                if (block_node.data.stmts.len == 0) return false;
+
+                const last = block_node.data.stmts[block_node.data.stmts.len - 1];
+                return last.isNoreturn(break_counts);
+            },
+            .@"switch" => {
+                const switch_node = node.castTag(.@"switch").?;
+
+                for (switch_node.data.cases) |case| {
+                    const body = if (case.castTag(.switch_else)) |some|
+                        some.data
+                    else if (case.castTag(.switch_prong)) |some|
+                        some.data.cond
+                    else unreachable;
+
+                    if (!body.isNoreturn(break_counts)) return false;
+                }
+                return true;
+            },
+            .@"return", .return_void => return true,
+            .break_val, .@"break" => if (break_counts) return true,
+            else => {},
+        }
+        return false;
+    }
+
 };
 
 pub const Payload = struct {
@@ -434,11 +464,6 @@ pub const Payload = struct {
         },
     };
 
-    pub const Break = struct {
-        base: Payload,
-        data: ?[]const u8,
-    };
-
     pub const BreakVal = struct {
         base: Payload,
         data: struct {
@@ -855,22 +880,14 @@ fn renderNode(c: *Context, node: Node) Allocator.Error!NodeIndex {
                 .rhs = undefined,
             },
         }),
-        .@"break" => {
-            const payload = node.castTag(.@"break").?.data;
-            const tok = try c.addToken(.keyword_break, "break");
-            const break_label = if (payload) |some| blk: {
-                _ = try c.addToken(.colon, ":");
-                break :blk try c.addIdentifier(some);
-            } else 0;
-            return c.addNode(.{
-                .tag = .@"break",
-                .main_token = tok,
-                .data = .{
-                    .lhs = break_label,
-                    .rhs = 0,
-                },
-            });
-        },
+        .@"break" => return c.addNode(.{
+            .tag = .@"break",
+            .main_token = try c.addToken(.keyword_break, "break"),
+            .data = .{
+                .lhs = 0,
+                .rhs = 0,
+            },
+        }),
         .break_val => {
             const payload = node.castTag(.break_val).?.data;
             const tok = try c.addToken(.keyword_break, "break");
@@ -1447,15 +1464,37 @@ fn renderNode(c: *Context, node: Node) Allocator.Error!NodeIndex {
         },
         .switch_prong => {
             const payload = node.castTag(.switch_prong).?.data;
-            const item = try renderNode(c, payload.lhs);
-            return c.addNode(.{
-                .tag = .switch_case_one,
-                .main_token = try c.addToken(.equal_angle_bracket_right, "=>"),
-                .data = .{
-                    .lhs = item,
-                    .rhs = try renderNode(c, payload.rhs),
-                },
-            });
+            var items = try c.gpa.alloc(NodeIndex, std.math.max(payload.cases.len, 1));
+            defer c.gpa.free(items);
+            items[0] = 0;
+            for (payload.cases) |item, i| {
+                if (i != 0) _ = try c.addToken(.comma, ",");
+                items[i] = try renderNode(c, item);
+            }
+            _ = try c.addToken(.r_brace, "}");
+            if (items.len < 2) {
+                return c.addNode(.{
+                    .tag = .switch_case_one,
+                    .main_token = try c.addToken(.equal_angle_bracket_right, "=>"),
+                    .data = .{
+                        .lhs = items[0],
+                        .rhs = try renderNode(c, payload.cond),
+                    },
+                });
+            } else {
+                const span = try c.listToSpan(items);
+                return c.addNode(.{
+                    .tag = .switch_case,
+                    .main_token = try c.addToken(.equal_angle_bracket_right, "=>"),
+                    .data = .{
+                        .lhs = try c.addExtra(NodeSubRange{
+                            .start = span.start,
+                            .end = span.end,
+                        }),
+                        .rhs = try renderNode(c, payload.cond),
+                    },
+                });
+            }
         },
         .opaque_literal => {
             const opaque_tok = try c.addToken(.keyword_opaque, "opaque");
@@ -1870,7 +1909,7 @@ fn addSemicolonIfNeeded(c: *Context, node: Node) !void {
 
 fn addSemicolonIfNotBlock(c: *Context, node: Node) !void {
     switch (node.tag()) {
-        .block, .empty_block, .block_single, => {},
+        .block, .empty_block, .block_single => {},
         .@"if" => {
             const payload = node.castTag(.@"if").?.data;
             if (payload.@"else") |some|
src/clang.zig
@@ -273,12 +273,12 @@ pub const CompoundAssignOperator = opaque {
 
 pub const CompoundStmt = opaque {
     pub const body_begin = ZigClangCompoundStmt_body_begin;
-    extern fn ZigClangCompoundStmt_body_begin(*const CompoundStmt) const_body_iterator;
+    extern fn ZigClangCompoundStmt_body_begin(*const CompoundStmt) ConstBodyIterator;
 
     pub const body_end = ZigClangCompoundStmt_body_end;
-    extern fn ZigClangCompoundStmt_body_end(*const CompoundStmt) const_body_iterator;
+    extern fn ZigClangCompoundStmt_body_end(*const CompoundStmt) ConstBodyIterator;
 
-    pub const const_body_iterator = [*]const *Stmt;
+    pub const ConstBodyIterator = [*]const *Stmt;
 };
 
 pub const ConditionalOperator = opaque {};
src/translate_c.zig
@@ -31,7 +31,6 @@ const Scope = struct {
     parent: ?*Scope,
 
     const Id = enum {
-        @"switch",
         block,
         root,
         condition,
@@ -39,17 +38,6 @@ const Scope = struct {
         do_loop,
     };
 
-    /// Represents an in-progress Node.Switch. This struct is stack-allocated.
-    /// When it is deinitialized, it produces an Node.Switch which is allocated
-    /// into the main arena.
-    const Switch = struct {
-        base: Scope,
-        pending_block: Block,
-        cases: std.ArrayList(Node),
-        switch_label: ?[]const u8,
-        default_label: ?[]const u8,
-    };
-
     /// Used for the scope of condition expressions, for example `if (cond)`.
     /// The block is lazily initialised because it is only needed for rare
     /// cases of comma operators being used.
@@ -230,7 +218,7 @@ const Scope = struct {
         return switch (scope.id) {
             .root => return name,
             .block => @fieldParentPtr(Block, "base", scope).getAlias(name),
-            .@"switch", .loop, .do_loop, .condition => scope.parent.?.getAlias(name),
+            .loop, .do_loop, .condition => scope.parent.?.getAlias(name),
         };
     }
 
@@ -238,7 +226,7 @@ const Scope = struct {
         return switch (scope.id) {
             .root => @fieldParentPtr(Root, "base", scope).contains(name),
             .block => @fieldParentPtr(Block, "base", scope).contains(name),
-            .@"switch", .loop, .do_loop, .condition => scope.parent.?.contains(name),
+            .loop, .do_loop, .condition => scope.parent.?.contains(name),
         };
     }
 
@@ -247,24 +235,12 @@ const Scope = struct {
         while (true) {
             switch (scope.id) {
                 .root => unreachable,
-                .@"switch" => return scope,
                 .loop, .do_loop => return scope,
                 else => scope = scope.parent.?,
             }
         }
     }
 
-    fn getSwitch(inner: *Scope) *Scope.Switch {
-        var scope = inner;
-        while (true) {
-            switch (scope.id) {
-                .root => unreachable,
-                .@"switch" => return @fieldParentPtr(Switch, "base", scope),
-                else => scope = scope.parent.?,
-            }
-        }
-    }
-
     /// Appends a node to the first block scope if inside a function, or to the root tree if not.
     fn appendNode(inner: *Scope, node: Node) !void {
         var scope = inner;
@@ -570,7 +546,7 @@ fn visitFnDecl(c: *Context, fn_decl: *const clang.FunctionDecl) Error!void {
     }
 
     const casted_body = @ptrCast(*const clang.CompoundStmt, body_stmt);
-    transCompoundStmtInline(c, &block_scope.base, casted_body, &block_scope) catch |err| switch (err) {
+    transCompoundStmtInline(c, casted_body, &block_scope) catch |err| switch (err) {
         error.OutOfMemory => |e| return e,
         error.UnsupportedTranslation,
         error.UnsupportedType,
@@ -583,24 +559,10 @@ fn visitFnDecl(c: *Context, fn_decl: *const clang.FunctionDecl) Error!void {
     };
     // add return statement if the function didn't have one
     blk: {
-        if (fn_ty.getNoReturnAttr()) break :blk;
-        if (isCVoid(return_qt)) break :blk;
-
-        if (block_scope.statements.items.len > 0) {
-            var last = block_scope.statements.items[block_scope.statements.items.len - 1];
-            while (true) {
-                switch (last.tag()) {
-                    .block => {
-                        const block = last.castTag(.block).?;
-                        if (block.data.stmts.len == 0) break;
-
-                        last = block.data.stmts[block.data.stmts.len - 1];
-                    },
-                    // no extra return needed
-                    .@"return", .return_void => break :blk,
-                    else => break,
-                }
-            }
+        const maybe_body = try block_scope.complete(c);
+        if (fn_ty.getNoReturnAttr() or isCVoid(return_qt) or maybe_body.isNoreturn(false)) {
+            proto_node.data.body = maybe_body;
+            break :blk;
         }
 
         const rhs = transZeroInitExpr(c, scope, fn_decl_loc, return_qt.getTypePtr()) catch |err| switch (err) {
@@ -616,9 +578,9 @@ fn visitFnDecl(c: *Context, fn_decl: *const clang.FunctionDecl) Error!void {
         };
         const ret = try Tag.@"return".create(c.arena, rhs);
         try block_scope.statements.append(ret);
+        proto_node.data.body = try block_scope.complete(c);
     }
 
-    proto_node.data.body = try block_scope.complete(c);
     return addTopLevelDecl(c, fn_name, Node.initPayload(&proto_node.base));
 }
 
@@ -1079,7 +1041,7 @@ fn transStmt(
             return Tag.empty_block.init();
         },
         .ContinueStmtClass => return Tag.@"continue".init(),
-        .BreakStmtClass => return transBreak(c, scope),
+        .BreakStmtClass => return Tag.@"break".init(),
         .ForStmtClass => return transForLoop(c, scope, @ptrCast(*const clang.ForStmt, stmt)),
         .FloatingLiteralClass => return transFloatingLiteral(c, scope, @ptrCast(*const clang.FloatingLiteral, stmt), result_used),
         .ConditionalOperatorClass => {
@@ -1089,8 +1051,9 @@ fn transStmt(
             return transBinaryConditionalOperator(c, scope, @ptrCast(*const clang.BinaryConditionalOperator, stmt), result_used);
         },
         .SwitchStmtClass => return transSwitch(c, scope, @ptrCast(*const clang.SwitchStmt, stmt)),
-        .CaseStmtClass => return transCase(c, scope, @ptrCast(*const clang.CaseStmt, stmt)),
-        .DefaultStmtClass => return transDefault(c, scope, @ptrCast(*const clang.DefaultStmt, stmt)),
+        .CaseStmtClass, .DefaultStmtClass => {
+            return fail(c, error.UnsupportedTranslation, stmt.getBeginLoc(), "TODO complex switch", .{});
+        },
         .ConstantExprClass => return transConstantExpr(c, scope, @ptrCast(*const clang.Expr, stmt), result_used),
         .PredefinedExprClass => return transPredefinedExpr(c, scope, @ptrCast(*const clang.PredefinedExpr, stmt), result_used),
         .CharacterLiteralClass => return transCharLiteral(c, scope, @ptrCast(*const clang.CharacterLiteral, stmt), result_used, .with_as),
@@ -1107,13 +1070,7 @@ fn transStmt(
             return maybeSuppressResult(c, scope, result_used, expr);
         },
         else => {
-            return fail(
-                c,
-                error.UnsupportedTranslation,
-                stmt.getBeginLoc(),
-                "TODO implement translation of stmt class {s}",
-                .{@tagName(sc)},
-            );
+            return fail(c, error.UnsupportedTranslation, stmt.getBeginLoc(), "TODO implement translation of stmt class {s}", .{@tagName(sc)});
         },
     }
 }
@@ -1255,14 +1212,13 @@ fn transBinaryOperator(
 
 fn transCompoundStmtInline(
     c: *Context,
-    parent_scope: *Scope,
     stmt: *const clang.CompoundStmt,
     block: *Scope.Block,
 ) TransError!void {
     var it = stmt.body_begin();
     const end_it = stmt.body_end();
     while (it != end_it) : (it += 1) {
-        const result = try transStmt(c, parent_scope, it[0], .unused);
+        const result = try transStmt(c, &block.base, it[0], .unused);
         if (result.tag() == .declaration) continue;
         try block.statements.append(result);
     }
@@ -1271,7 +1227,7 @@ fn transCompoundStmtInline(
 fn transCompoundStmt(c: *Context, scope: *Scope, stmt: *const clang.CompoundStmt) TransError!Node {
     var block_scope = try Scope.Block.init(c, scope, false);
     defer block_scope.deinit();
-    try transCompoundStmtInline(c, &block_scope.base, stmt, &block_scope);
+    try transCompoundStmtInline(c, stmt, &block_scope);
     return try block_scope.complete(c);
 }
 
@@ -2162,7 +2118,7 @@ fn transDoWhileLoop(
     defer cond_scope.deinit();
     const cond = try transBoolExpr(c, &cond_scope.base, @ptrCast(*const clang.Expr, stmt.getCond()), .used);
     const if_not_break = switch (cond.tag()) {
-        .false_literal => try Tag.@"break".create(c.arena, null),
+        .false_literal => Tag.@"break".init(),
         .true_literal => {
             const body_node = try transStmt(c, scope, stmt.getBody(), .unused);
             return Tag.while_true.create(c.arena, body_node);
@@ -2263,133 +2219,189 @@ fn transSwitch(
     };
     defer cond_scope.deinit();
     const switch_expr = try transExpr(c, &cond_scope.base, stmt.getCond(), .used);
-    const switch_node = try c.arena.create(ast.Payload.Switch);
-    switch_node.* = .{
-        .base = .{ .tag = .@"switch" },
-        .data = .{
-            .cond = switch_expr,
-            .cases = undefined, // set later
-        },
-    };
-
-    var switch_scope = Scope.Switch{
-        .base = .{
-            .id = .@"switch",
-            .parent = scope,
-        },
-        .cases = std.ArrayList(Node).init(c.gpa),
-        .pending_block = undefined,
-        .default_label = null,
-        .switch_label = null,
-    };
-    defer switch_scope.cases.deinit();
 
-    // tmp block that all statements will go before being picked up by a case or default
-    var block_scope = try Scope.Block.init(c, &switch_scope.base, false);
-    defer block_scope.deinit();
-
-    // Note that we do not defer a deinit here; the switch_scope.pending_block field
-    // has its own memory management. This resource is freed inside `transCase` and
-    // then the final pending_block is freed at the bottom of this function with
-    // pending_block.deinit().
-    switch_scope.pending_block = try Scope.Block.init(c, scope, false);
-    try switch_scope.pending_block.statements.append(Node.initPayload(&switch_node.base));
+    var cases = std.ArrayList(Node).init(c.gpa);
+    defer cases.deinit();
+    var has_default = false;
+
+    const body = stmt.getBody();
+    assert(body.getStmtClass() == .CompoundStmtClass);
+    const compound_stmt = @ptrCast(*const clang.CompoundStmt, body);
+    var it = compound_stmt.body_begin();
+    const end_it = compound_stmt.body_end();
+    // Iterate over switch body and collect all cases.
+    // Fallthrough is handled by duplicating statements.
+    while (it != end_it) : (it += 1) {
+        switch (it[0].getStmtClass()) {
+            .CaseStmtClass => {
+                var items = std.ArrayList(Node).init(c.gpa);
+                defer items.deinit();
+                const sub = try transCaseStmt(c, scope, it[0], &items);
+                const res = try transSwitchProngStmt(c, scope, sub, it, end_it);
+
+                if (items.items.len == 0) {
+                    has_default = true;
+                    const switch_else = try Tag.switch_else.create(c.arena, res);
+                    try cases.append(switch_else);
+                } else {
+                    const switch_prong = try Tag.switch_prong.create(c.arena, .{
+                        .cases = try c.arena.dupe(Node, items.items),
+                        .cond = res,
+                    });
+                    try cases.append(switch_prong);
+                }
+            },
+            .DefaultStmtClass => {
+                has_default = true;
+                const default_stmt = @ptrCast(*const clang.DefaultStmt, it[0]);
+
+                var sub = default_stmt.getSubStmt();
+                while (true) switch (sub.getStmtClass()) {
+                    .CaseStmtClass => sub = @ptrCast(*const clang.CaseStmt, sub).getSubStmt(),
+                    .DefaultStmtClass => sub = @ptrCast(*const clang.DefaultStmt, sub).getSubStmt(),
+                    else => break,
+                };
 
-    const last = try transStmt(c, &block_scope.base, stmt.getBody(), .unused);
+                const res = try transSwitchProngStmt(c, scope, sub, it, end_it);
 
-    // take all pending statements
-    const last_block_stmts = last.castTag(.block).?.data.stmts;
-    try switch_scope.pending_block.statements.ensureCapacity(
-        switch_scope.pending_block.statements.items.len + last_block_stmts.len,
-    );
-    for (last_block_stmts) |n| {
-        switch_scope.pending_block.statements.appendAssumeCapacity(n);
+                const switch_else = try Tag.switch_else.create(c.arena, res);
+                try cases.append(switch_else);
+            },
+            else => {}, // collected in transSwitchProngStmt
+        }
     }
 
-    if (switch_scope.default_label == null) {
-        switch_scope.switch_label = try block_scope.makeMangledName(c, "switch");
-    }
-    if (switch_scope.switch_label) |l| {
-        switch_scope.pending_block.label = l;
+    if (!has_default) {
+        const else_prong = try Tag.switch_else.create(c.arena, Tag.@"break".init());
+        try cases.append(else_prong);
     }
-    if (switch_scope.default_label == null) {
-        const else_prong = try Tag.switch_else.create(
-            c.arena,
-            try Tag.@"break".create(c.arena, switch_scope.switch_label.?),
-        );
-        try switch_scope.cases.append(else_prong);
-    }
-
-    switch_node.data.cases = try c.arena.dupe(Node, switch_scope.cases.items);
-    const result_node = try switch_scope.pending_block.complete(c);
-    switch_scope.pending_block.deinit();
-    return result_node;
-}
-
-fn transCase(
-    c: *Context,
-    scope: *Scope,
-    stmt: *const clang.CaseStmt,
-) TransError!Node {
-    const block_scope = try scope.findBlockScope(c);
-    const switch_scope = scope.getSwitch();
-    const label = try block_scope.makeMangledName(c, "case");
-
-    const expr = if (stmt.getRHS()) |rhs| blk: {
-        const lhs_node = try transExpr(c, scope, stmt.getLHS(), .used);
-        const rhs_node = try transExpr(c, scope, rhs, .used);
-
-        break :blk try Tag.ellipsis3.create(c.arena, .{ .lhs = lhs_node, .rhs = rhs_node });
-    } else
-        try transExpr(c, scope, stmt.getLHS(), .used);
 
-    const switch_prong = try Tag.switch_prong.create(c.arena, .{
-        .lhs = expr,
-        .rhs = try Tag.@"break".create(c.arena, label),
+    return Tag.@"switch".create(c.arena, .{
+        .cond = switch_expr,
+        .cases = try c.arena.dupe(Node, cases.items),
     });
-    try switch_scope.cases.append(switch_prong);
+}
 
-    switch_scope.pending_block.label = label;
+/// Collects all items for this case, returns the first statement after the labels.
+/// If items ends up empty, the prong should be translated as an else.
+fn transCaseStmt(c: *Context, scope: *Scope, stmt: *const clang.Stmt, items: *std.ArrayList(Node)) TransError!*const clang.Stmt {
+    var sub = stmt;
+    var seen_default = false;
+    while (true) {
+        switch (sub.getStmtClass()) {
+            .DefaultStmtClass => {
+                seen_default = true;
+                items.items.len = 0;
+                const default_stmt = @ptrCast(*const clang.DefaultStmt, sub);
+                sub = default_stmt.getSubStmt();
+            },
+            .CaseStmtClass => {
+                const case_stmt = @ptrCast(*const clang.CaseStmt, sub);
 
-    // take all pending statements
-    try switch_scope.pending_block.statements.appendSlice(block_scope.statements.items);
-    block_scope.statements.shrinkAndFree(0);
+                if (seen_default) {
+                    items.items.len = 0;
+                    sub = case_stmt.getSubStmt();
+                    continue;
+                }
 
-    const pending_node = try switch_scope.pending_block.complete(c);
-    switch_scope.pending_block.deinit();
-    switch_scope.pending_block = try Scope.Block.init(c, scope, false);
+                const expr = if (case_stmt.getRHS()) |rhs| blk: {
+                    const lhs_node = try transExprCoercing(c, scope, case_stmt.getLHS(), .used);
+                    const rhs_node = try transExprCoercing(c, scope, rhs, .used);
 
-    try switch_scope.pending_block.statements.append(pending_node);
+                    break :blk try Tag.ellipsis3.create(c.arena, .{ .lhs = lhs_node, .rhs = rhs_node });
+                } else
+                    try transExprCoercing(c, scope, case_stmt.getLHS(), .used);
 
-    return transStmt(c, scope, stmt.getSubStmt(), .unused);
+                try items.append(expr);
+                sub = case_stmt.getSubStmt();
+            },
+            else => return sub,
+        }
+    }
 }
 
-fn transDefault(
+/// Collects all statements seen by this case into a block.
+/// Avoids creating a block if the first statement is a break or return.
+fn transSwitchProngStmt(
     c: *Context,
     scope: *Scope,
-    stmt: *const clang.DefaultStmt,
+    stmt: *const clang.Stmt,
+    parent_it: clang.CompoundStmt.ConstBodyIterator,
+    parent_end_it: clang.CompoundStmt.ConstBodyIterator,
 ) TransError!Node {
-    const block_scope = try scope.findBlockScope(c);
-    const switch_scope = scope.getSwitch();
-    switch_scope.default_label = try block_scope.makeMangledName(c, "default");
-
-    const else_prong = try Tag.switch_else.create(
-        c.arena,
-        try Tag.@"break".create(c.arena, switch_scope.default_label.?),
-    );
-    try switch_scope.cases.append(else_prong);
-    switch_scope.pending_block.label = switch_scope.default_label.?;
-
-    // take all pending statements
-    try switch_scope.pending_block.statements.appendSlice(block_scope.statements.items);
-    block_scope.statements.shrinkAndFree(0);
+    switch (stmt.getStmtClass()) {
+        .BreakStmtClass => return Tag.empty_block.init(),
+        .ReturnStmtClass => return transStmt(c, scope, stmt, .unused),
+        .CaseStmtClass, .DefaultStmtClass => unreachable,
+        else => {
+            var block_scope = try Scope.Block.init(c, scope, false);
+            defer block_scope.deinit();
 
-    const pending_node = try switch_scope.pending_block.complete(c);
-    switch_scope.pending_block.deinit();
-    switch_scope.pending_block = try Scope.Block.init(c, scope, false);
-    try switch_scope.pending_block.statements.append(pending_node);
+            // we do not need to translate `stmt` since it is the first stmt of `parent_it`
+            try transSwitchProngStmtInline(c, &block_scope, parent_it, parent_end_it);
+            return try block_scope.complete(c);
+        },
+    }
+}
 
-    return transStmt(c, scope, stmt.getSubStmt(), .unused);
+/// Collects all statements seen by this case into a block.
+fn transSwitchProngStmtInline(
+    c: *Context,
+    block: *Scope.Block,
+    start_it: clang.CompoundStmt.ConstBodyIterator,
+    end_it: clang.CompoundStmt.ConstBodyIterator,
+) TransError!void {
+    var it = start_it;
+    while (it != end_it) : (it += 1) {
+        switch (it[0].getStmtClass()) {
+            .ReturnStmtClass => {
+                const result = try transStmt(c, &block.base, it[0], .unused);
+                try block.statements.append(result);
+                return;
+            },
+            .BreakStmtClass => return,
+            .CaseStmtClass => {
+                var sub = @ptrCast(*const clang.CaseStmt, it[0]).getSubStmt();
+                while (true) switch (sub.getStmtClass()) {
+                    .CaseStmtClass => sub = @ptrCast(*const clang.CaseStmt, sub).getSubStmt(),
+                    .DefaultStmtClass => sub = @ptrCast(*const clang.DefaultStmt, sub).getSubStmt(),
+                    else => break,
+                };
+                const result = try transStmt(c, &block.base, sub, .unused);
+                assert(result.tag() != .declaration);
+                try block.statements.append(result);
+            },
+            .DefaultStmtClass => {
+                var sub = @ptrCast(*const clang.DefaultStmt, it[0]).getSubStmt();
+                while (true) switch (sub.getStmtClass()) {
+                    .CaseStmtClass => sub = @ptrCast(*const clang.CaseStmt, sub).getSubStmt(),
+                    .DefaultStmtClass => sub = @ptrCast(*const clang.DefaultStmt, sub).getSubStmt(),
+                    else => break,
+                };
+                const result = try transStmt(c, &block.base, sub, .unused);
+                assert(result.tag() != .declaration);
+                try block.statements.append(result);
+            },
+            .CompoundStmtClass => {
+                const compound_stmt = @ptrCast(*const clang.CompoundStmt, it[0]);
+                var child_block = try Scope.Block.init(c, &block.base, false);
+                defer child_block.deinit();
+
+                try transCompoundStmtInline(c, compound_stmt, &child_block);
+                const result = try child_block.complete(c);
+                try block.statements.append(result);
+                if (result.isNoreturn(true)) {
+                    return;
+                }
+            },
+            else => {
+                const result = try transStmt(c, &block.base, it[0], .unused);
+                if (result.tag() == .declaration) continue;
+                try block.statements.append(result);
+            },
+        }
+    }
+    return;
 }
 
 fn transConstantExpr(c: *Context, scope: *Scope, expr: *const clang.Expr, used: ResultUsed) TransError!Node {
@@ -3025,19 +3037,6 @@ fn transCPtrCast(
     }
 }
 
-fn transBreak(c: *Context, scope: *Scope) TransError!Node {
-    const break_scope = scope.getBreakableScope();
-    const label_text: ?[]const u8 = if (break_scope.id == .@"switch") blk: {
-        const swtch = @fieldParentPtr(Scope.Switch, "base", break_scope);
-        const block_scope = try scope.findBlockScope(c);
-        swtch.switch_label = try block_scope.makeMangledName(c, "switch");
-        break :blk swtch.switch_label;
-    } else
-        null;
-
-    return Tag.@"break".create(c.arena, label_text);
-}
-
 fn transFloatingLiteral(c: *Context, scope: *Scope, stmt: *const clang.FloatingLiteral, used: ResultUsed) TransError!Node {
     // TODO use something more accurate
     var dbl = stmt.getValueAsApproximateDouble();
test/translate_c.zig
@@ -276,27 +276,10 @@ pub fn addCases(cases: *tests.TranslateCContext) void {
         \\    }
         \\}
     , &[_][]const u8{ // TODO properly translate this
-        \\pub export fn main() c_int {
-        \\    var i: c_int = 2;
-        \\    @"switch": {
-        \\        case_1: {
-        \\            case: {
-        \\                switch (i) {
-        \\                    @as(c_int, 0) => break :case,
-        \\                    @as(c_int, 2) => break :case_1,
-        \\                    else => break :@"switch",
-        \\                }
-        \\            }
-        \\        }
-        \\        {
-        \\            {
-        \\                i += @as(c_int, 2);
-        \\            }
-        \\            i += @as(c_int, 1);
-        \\        }
-        \\    }
-        \\    return 0;
-        \\}
+        \\source.h:5:13: warning: TODO complex switch
+        ,
+        \\source.h:1:5: warning: unable to translate function, demoted to extern
+        \\pub extern fn main() c_int;
     });
 
     cases.add("correct semicolon after infixop",
@@ -2013,34 +1996,47 @@ pub fn addCases(cases: *tests.TranslateCContext) void {
         \\        default:
         \\            res = 3 * i;
         \\            break;
+        \\            break;
         \\        case 4:
+        \\		case 5:
+        \\            res = 69;
+        \\        {
         \\            res = 5;
+        \\			  return;
+        \\        }
+        \\        case 6:
+        \\            res = 1;
+        \\			  return;
         \\    }
         \\}
     , &[_][]const u8{
         \\pub export fn switch_fn(arg_i: c_int) void {
         \\    var i = arg_i;
         \\    var res: c_int = 0;
-        \\    @"switch": {
-        \\        case_2: {
-        \\            default: {
-        \\                case_1: {
-        \\                    case: {
-        \\                        switch (i) {
-        \\                            @as(c_int, 0) => break :case,
-        \\                            @as(c_int, 1)...@as(c_int, 3) => break :case_1,
-        \\                            else => break :default,
-        \\                            @as(c_int, 4) => break :case_2,
-        \\                        }
-        \\                    }
-        \\                    res = 1;
-        \\                }
-        \\                res = 2;
-        \\            }
+        \\    switch (i) {
+        \\        @as(c_int, 0) => {
+        \\            res = 1;
+        \\            res = 2;
         \\            res = @as(c_int, 3) * i;
-        \\            break :@"switch";
-        \\        }
-        \\        res = 5;
+        \\        },
+        \\        @as(c_int, 1)...@as(c_int, 3) => {
+        \\            res = 2;
+        \\            res = @as(c_int, 3) * i;
+        \\        },
+        \\        else => {
+        \\            res = @as(c_int, 3) * i;
+        \\        },
+        \\        @as(c_int, 4), @as(c_int, 5) => {
+        \\            res = 69;
+        \\            {
+        \\                res = 5;
+        \\                return;
+        \\            }
+        \\        },
+        \\        @as(c_int, 6) => {
+        \\            res = 1;
+        \\            return;
+        \\        },
         \\    }
         \\}
     });