aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorVeikka Tuominen <git@vexu.eu>2021-02-17 22:11:26 +0200
committerVeikka Tuominen <git@vexu.eu>2021-02-17 22:11:26 +0200
commit7ca53bdfaab59e61c38d0bedb6b16739904f7519 (patch)
treeb7678de14625868401c95001a77e697cb8fffa66 /src
parent3717bedb4e3198fe2ded167b41f9b0441e817b9c (diff)
downloadzig-7ca53bdfaab59e61c38d0bedb6b16739904f7519.tar.gz
zig-7ca53bdfaab59e61c38d0bedb6b16739904f7519.zip
translate-c: improve switch translation
Diffstat (limited to 'src')
-rw-r--r--src/clang.zig6
-rw-r--r--src/translate_c.zig359
-rw-r--r--src/translate_c/ast.zig111
3 files changed, 257 insertions, 219 deletions
diff --git a/src/clang.zig b/src/clang.zig
index fbb955205b..5adb858b90 100644
--- a/src/clang.zig
+++ b/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 {};
diff --git a/src/translate_c.zig b/src/translate_c.zig
index 5ac60bffae..47c601677e 100644
--- a/src/translate_c.zig
+++ b/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();
diff --git a/src/translate_c/ast.zig b/src/translate_c/ast.zig
index 25cbe1bf3f..2306d1c36f 100644
--- a/src/translate_c/ast.zig
+++ b/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|