Discarding first inline else capture on tagged union switch when using labeled switch fails to compile
Zig Version
0.15.2
Steps to Reproduce and Observed Behavior
Sorry for the bulky issue name :< The bug is just kinda that specific.
const TaggedUnion = union(enum) {
a: u32,
b: i32,
c: void,
};
// In some function...
const tu: TaggedUnion = .{ .a = 15 };
sw: switch (tu) {
inline else => |_, tag| {
// Code body doesn't matter, but presumably you're using tag somewhere.
_ = tag;
continue :sw next; // Any continue value still fails
},
}
The code above will fail to compile with no sensible error (only returning error code 1), despite the fact that this should be valid as far as I can tell.
Replacing inline else => |_, tag| with inline else => |value, tag| and then discarding value using _ = value compiles just fine.
I don't personally see any use cases where this would actually matter, but it definitely shouldn't just crash without explanation like it does.
Expected Behavior
The given code should compile, or if it's not valid for some reason, it should return an actual compiler error instead of simply crashing the compiler.
Same issue as #24789
This is indeed a compiler bug your code should be valid :)
error trace etc
Compiler crash context:
Analyzing 'build/repro.zig'
%15 = dbg_stmt(2, 5)
%16 = block_comptime(reason=type, {
%17 = decl_val("TaggedUnion") token_offset:307:15 to :307:26
%18 = break_inline(%16, %17)
}) node_offset:307:15 to :307:26
%19 = alloc(%16) node_offset:307:5 to :307:41
%20 = opt_eu_base_ptr_init(%19) node_offset:307:29 to :307:41
%21 = struct_init_field_ptr(%20, "a") node_offset:307:37 to :307:39
%22 = int(15)
%23 = store_node(%21, %22) node_offset:307:37 to :307:39
%24 = validate_ptr_struct_init({
%21 = struct_init_field_ptr(%20, "a") node_offset:307:37 to :307:39
}) node_offset:307:29 to :307:41
%25 = make_ptr_const(%19) node_offset:307:5 to :307:41
%26 = dbg_var_ptr(%25, "tu")
%27 = dbg_stmt(3, 5)
%28 = load(%25) node_offset:308:17 to :308:19
%29 = typeof(%28) node_offset:308:17 to :308:19
%30 = dbg_stmt(3, 17)
> %31 = switch_block(%28, tag_capture=%32,
by_val inline else => {
%33 = dbg_var_val(%32, "tag")
%34 = dbg_stmt(7, 13)
%35 = ensure_result_non_error(%32) node_offset:312:17 to :312:20
%36 = dbg_stmt(8, 13)
%37 = validate_struct_init_result_ty(%29) node_offset:313:26 to :313:40
%38 = struct_init_field_type(%29, a) node_offset:313:34 to :313:38
%39 = struct_init([%38, %14]) node_offset:313:26 to :313:40
%40 = restore_err_ret_index_unconditional(%31) node_offset:313:13 to :313:40
%41 = switch_continue(%31, %39)
%42 = break(%31, @void_value)
}) node_offset:308:5 to :308:7
%43 = ensure_result_used(%31) node_offset:308:5 to :308:7
%44 = restore_err_ret_index_unconditional(.none) node_offset:306:1 to :306:10
%45 = ret_implicit(@void_value) token_offset:316:1 to :316:1
For full context, use the command
build/stage4-master/bin/zig ast-check -t build/repro.zig
thread 278351901 panic: reached unreachable code
zig/src/InternPool.zig:10549:17: 0x107212a4b in childType (zig)
else => unreachable,
^
zig/src/Type.zig:1946:42: 0x106fa09e7 in childTypeIp (zig)
return Type.fromInterned(ip.childType(ty.toIntern()));
^
zig/src/Type.zig:1942:23: 0x106e7e4e3 in childType (zig)
return childTypeIp(ty, &zcu.intern_pool);
^
zig/src/Sema.zig:10586:55: 0x108b0b803 in analyzeTagCapture (zig)
const alloc_child = alloc_ty.childType(zcu);
^
zig/src/Sema.zig:10540:54: 0x108b12a33 in analyzeProngRuntime (zig)
const tag_ref = try spa.analyzeTagCapture(case_block, capture_src, inline_case_capture);
[...]
😔 i swear i searched for an existing issue, dangit
well good to know i'm not crazy!
Can you please explain the compiler's error trace? I'm not familiar enough with the language to understand it. I'm new to the zig world,, hello!
Sure! First off, to even get this kind of error trace you need a debug build of the compiler, further details are in the build from source section of the README.
Everything below 'Compiler crash context:' (all of the % stuff) is ZIR (zig intermediate representation), it's basically zig code that's already been parsed and turned into a series of instructions, but has not yet been semantically analyzed (basically type resolution/checking and everything comptime related). For more details on how ZIR works and how it's semantically analyzed have a look at this great post by mlugg.
The little > points out where something went wrong. In this case it's pointed at a switch_block instruction, so the compiler crashed while trying to semantically analyze a switch statement.
From there on it's just a normal stack trace. I shortened it a bit because the rest isn't really relevant, the issue is already known anyway. What's happening is that analyzeTagCapture is accessing an undefined value (see #24792 for more details):
fn analyzeTagCapture(
spa: SwitchProngAnalysis,
block: *Block,
capture_src: LazySrcLoc,
inline_case_capture: Air.Inst.Ref,
) CompileError!Air.Inst.Ref {
const sema = spa.sema;
const pt = sema.pt;
const zcu = pt.zcu;
const operand_ty = switch (spa.operand) {
.simple => |s| sema.typeOf(s.by_val),
.loop => |l| ty: {
const alloc_ty = sema.typeOf(l.operand_alloc); // <- operand_alloc is undefined at this point, so alloc_ty is also a garbage value
const alloc_child = alloc_ty.childType(zcu); // <- the crash happens here, might as well have happened a line earlier
if (l.operand_is_ref) break :ty alloc_child.childType(zcu);
break :ty alloc_child;
},
};
[...]
https://github.com/ziglang/zig/blob/3c647ca6bb987c496652fd6bf279ef2625f41821/src/Sema.zig#L10562-L10579
Welcome to the zig world! If you have more questions I'd recommend joining a zig community