🐛 Bug
//# publish
module 0x42::test {
struct Inner has copy, drop { x: u64 }
struct Outer has copy, drop { inner: Inner }
fun if_else_wrapping_select(cond: bool, o1: Outer, o2: Outer): u64 {
(if (cond) { o1.inner } else { o2.inner }).x
}
}
Compiling this gives:
bug: bytecode verification failed with unexpected status code `STLOC_TYPE_MISMATCH_ERROR`:
Error message: none
┌─ .../test/sources/test3.move:7:22
│
7 │ (if (cond) { o1.inner } else { o2.inner }).x
│ ^^^^^^^^
│
Analysis from Claude Code
bytecode_generator.rs:
When gen_select processes EXPR.field, it calls gen_auto_ref_arg(EXPR, Immutable) which enters reference mode and delegates to gen_arg. If EXPR is a Block or IfElse (not a direct Select, Temporary, or LocalVar), gen_arg falls to the _ => case (line ~1375), allocates a value-typed temporary via self.get_node_type(id), and calls self.generate(vec![temp], exp) — still in reference mode.
generate for Block (line ~450) and IfElse (lines ~494-498) propagates to inner expressions without resetting reference mode. When an inner Select (e.g. o.inner) is reached, gen_select runs with:
target_type = Inner (value, not reference)
self.reference_mode() = true (leaked)
This makes need_read_ref = !(false || true) = false, so BorrowField writes a &Inner reference directly into the value-typed Inner temporary — a STLOC_TYPE_MISMATCH_ERROR at bytecode verification.
Triggering expressions: { o.inner }.x and (if (cond) { o1.inner } else { o2.inner }).x.
🐛 Bug
Compiling this gives:
Analysis from Claude Code
bytecode_generator.rs:When
gen_selectprocessesEXPR.field, it callsgen_auto_ref_arg(EXPR, Immutable)which enters reference mode and delegates togen_arg. IfEXPRis aBlockorIfElse(not a directSelect,Temporary, orLocalVar),gen_argfalls to the_ =>case (line ~1375), allocates a value-typed temporary viaself.get_node_type(id), and callsself.generate(vec![temp], exp)— still in reference mode.generateforBlock(line ~450) andIfElse(lines ~494-498) propagates to inner expressions without resetting reference mode. When an innerSelect(e.g.o.inner) is reached,gen_selectruns with:target_type = Inner(value, not reference)self.reference_mode() = true(leaked)This makes
need_read_ref = !(false || true) = false, soBorrowFieldwrites a&Innerreference directly into the value-typedInnertemporary — aSTLOC_TYPE_MISMATCH_ERRORat bytecode verification.Triggering expressions:
{ o.inner }.xand(if (cond) { o1.inner } else { o2.inner }).x.