fix(issue-0057): all-diverging match arms no longer fail LLVM verification
A match (`if subject == { case ... }`) whose arms all diverge (each
`return`s / `raise`s) failed LLVM verification with a `void` phi plus
"Terminator found in the middle of a basic block". Two causes in lowerMatch:
- The value-arm path did `lowerBlockValue(arm.body) orelse constInt(0, …)`,
emitting the fallback `const` into a block the body had ALREADY terminated
(a diverging arm), so `currentBlockHasTerminator()` then saw the const (not
the `ret`) and emitted a `br merge` after the terminator. Fix: materialize
the fallback value + branch only when the block hasn't terminated.
- A fully-diverging match infers `result_type == .noreturn` yet still built a
value-merge phi. Fix: `has_value_merge` excludes `.noreturn`, so such a
match builds no phi; its arms terminate and the merge block is unreachable.
Also: inferMatchResultType now skips `.noreturn` arms (a diverging arm doesn't
decide the result type) and reports `.noreturn` only when EVERY arm diverges —
so a mixed match (some arms yield values, some diverge) infers the value type.
This unblocks ERR E1.5's `catch` match-body form (`x catch e == { case .A:
return …; else: raise e; }`), which desugars to an all-diverging match.
Regression: examples/225-match-diverging-arms.sx (all-diverging + mixed,
exit 134). Gates: zig build, zig build test, 263/263 examples.
This commit is contained in:
@@ -3722,7 +3722,11 @@ pub const Lowering = struct {
|
||||
}
|
||||
const is_value = if (is_type_match) self.force_block_value else (self.force_block_value or (inferred_result != .void and inferred_result != .unresolved));
|
||||
const result_type: TypeId = if (is_value) inferred_result else .void;
|
||||
const merge_params: []const TypeId = if (is_value and result_type != .void) &.{result_type} else &.{};
|
||||
// A fully-diverging match (`result_type == .noreturn` — every arm
|
||||
// `return`s / `raise`s / etc.) produces no value, so it builds no
|
||||
// merge phi; its arms terminate and the merge block is unreachable.
|
||||
const has_value_merge = is_value and result_type != .void and result_type != .noreturn;
|
||||
const merge_params: []const TypeId = if (has_value_merge) &.{result_type} else &.{};
|
||||
const merge_bb = self.freshBlockWithParams("match.merge", merge_params);
|
||||
|
||||
// Build arm blocks
|
||||
@@ -3918,16 +3922,20 @@ pub const Lowering = struct {
|
||||
self.current_match_tags = arm_tag_values.items[i];
|
||||
}
|
||||
|
||||
if (is_value and result_type != .void) {
|
||||
var v = self.lowerBlockValue(arm.body) orelse if (result_type == .string or !result_type.isBuiltin())
|
||||
self.builder.constUndef(result_type)
|
||||
else
|
||||
self.builder.constInt(0, result_type);
|
||||
if (has_value_merge) {
|
||||
const maybe_v = self.lowerBlockValue(arm.body);
|
||||
self.current_match_tags = saved_match_tags;
|
||||
self.scope = old_scope;
|
||||
arm_scope.deinit();
|
||||
// Only materialize a value + branch to the merge when the arm
|
||||
// body did NOT diverge. A diverging arm (e.g. `return x`) has
|
||||
// already terminated its block; emitting the fallback const
|
||||
// here would land AFTER the terminator (the issue-0057 bug).
|
||||
if (!self.currentBlockHasTerminator()) {
|
||||
// Coerce arm value to match result type
|
||||
var v = maybe_v orelse if (result_type == .string or !result_type.isBuiltin())
|
||||
self.builder.constUndef(result_type)
|
||||
else
|
||||
self.builder.constInt(0, result_type);
|
||||
const v_ty = self.builder.getRefType(v);
|
||||
v = self.coerceToType(v, v_ty, result_type);
|
||||
self.builder.br(merge_bb, &.{v});
|
||||
@@ -3954,7 +3962,7 @@ pub const Lowering = struct {
|
||||
if (is_type_match) {
|
||||
// For type-category matches, unrecognized tags should skip to merge
|
||||
// (e.g., optional types not covered by any_to_string categories)
|
||||
if (is_value and result_type != .void) {
|
||||
if (has_value_merge) {
|
||||
const default_val = self.builder.constUndef(result_type);
|
||||
self.builder.br(merge_bb, &.{default_val});
|
||||
} else {
|
||||
@@ -3976,7 +3984,7 @@ pub const Lowering = struct {
|
||||
};
|
||||
if (is_exhaustive) {
|
||||
self.builder.emitUnreachable();
|
||||
} else if (is_value and result_type != .void) {
|
||||
} else if (has_value_merge) {
|
||||
const default_val = self.builder.constUndef(result_type);
|
||||
self.builder.br(merge_bb, &.{default_val});
|
||||
} else {
|
||||
@@ -3987,7 +3995,7 @@ pub const Lowering = struct {
|
||||
}
|
||||
|
||||
self.builder.switchToBlock(merge_bb);
|
||||
if (is_value and result_type != .void) {
|
||||
if (has_value_merge) {
|
||||
return self.builder.blockParam(merge_bb, 0, result_type);
|
||||
}
|
||||
return self.builder.constInt(0, .void);
|
||||
@@ -10803,6 +10811,7 @@ pub const Lowering = struct {
|
||||
// were null arms, the result is ?T (optional).
|
||||
var has_null = false;
|
||||
var saw_unresolved = false;
|
||||
var saw_noreturn = false;
|
||||
for (me.arms) |arm| {
|
||||
const last_node = if (arm.body.data == .block) blk: {
|
||||
if (arm.body.data.block.stmts.len > 0) {
|
||||
@@ -10821,6 +10830,14 @@ pub const Lowering = struct {
|
||||
// enum literal) doesn't decide — keep looking; the caller falls back
|
||||
// to the contextual target type if none of the arms resolve.
|
||||
const arm_ty = self.inferExprType(last_node);
|
||||
// A diverging arm (`noreturn` — `return` / `raise` / `break` /
|
||||
// `continue`) doesn't produce a value, so it doesn't decide the
|
||||
// result type; keep looking. The match is `noreturn` only if EVERY
|
||||
// arm diverges (handled after the loop).
|
||||
if (arm_ty == .noreturn) {
|
||||
saw_noreturn = true;
|
||||
continue;
|
||||
}
|
||||
if (arm_ty == .unresolved) {
|
||||
saw_unresolved = true;
|
||||
continue;
|
||||
@@ -10830,7 +10847,9 @@ pub const Lowering = struct {
|
||||
}
|
||||
return arm_ty;
|
||||
}
|
||||
return if (saw_unresolved) .unresolved else .void;
|
||||
if (saw_unresolved) return .unresolved;
|
||||
if (saw_noreturn) return .noreturn; // all arms diverge
|
||||
return .void;
|
||||
}
|
||||
|
||||
fn isTypeCategoryMatch(me: *const ast.MatchExpr) bool {
|
||||
|
||||
Reference in New Issue
Block a user