From d3266a65ca7c0ecadd32ad65a892a3f4c132e5f4 Mon Sep 17 00:00:00 2001 From: Brian Searls Date: Sat, 25 Apr 2026 22:24:03 +0000 Subject: [PATCH 1/2] fix(v3): fail closed on missing Go variant parent --- src/v3/compiler/src/emit.rs | 42 +++++++++++++++++++------ src/v3/compiler/src/emit/rust_target.rs | 4 +++ 2 files changed, 37 insertions(+), 9 deletions(-) diff --git a/src/v3/compiler/src/emit.rs b/src/v3/compiler/src/emit.rs index 5647bc2273b..5cfa3032500 100644 --- a/src/v3/compiler/src/emit.rs +++ b/src/v3/compiler/src/emit.rs @@ -1453,15 +1453,10 @@ impl<'a> Ctx<'a> { }); } }; - let variant_name = variant_parent_info(self.dag, variant_id) - .map(|(_, variant_name)| variant_name) - .unwrap_or_else(|| { - self.dag - .declaration(variant_id) - .name - .clone() - .unwrap_or_else(|| "UnknownVariant".to_string()) - }); + let Some((_parent_name, variant_name)) = variant_parent_info(self.dag, variant_id) + else { + return Err(EmitError::VariantParentNotFound { variant_id }); + }; let mut arm_locals = locals.clone(); if let Some(binding) = &path.binding { let elide = matches!( @@ -3300,6 +3295,35 @@ mod tests { assert!(!rendered.contains("payload := v.inner;"), "got: {rendered}"); } + #[test] + fn go_sum_branch_fails_when_variant_parent_is_missing() { + let mut dag = compile_to_dag( + "type Choice = Left | Right\nfn pick(choice: Choice) -> Int = match choice { Left => 1, Right => 2 }\n", + "missing_variant_parent.v3", + ) + .expect("compiles"); + let right_variant = dag + .declaration_by_name("Right") + .expect("Right variant declaration") + .id; + let choice_decl = dag.declaration_by_name("Choice").expect("Choice decl").id; + let TypeConnective::Disj { variants } = &mut dag.declaration_mut(choice_decl).connective + else { + panic!("Choice should lower to a Disj"); + }; + variants.retain(|variant| variant.ty != right_variant); + + let err = emit_module(&dag, EmitTarget::Go).expect_err("missing parent must fail closed"); + assert!( + matches!( + err, + EmitDispatchError::Core(EmitError::VariantParentNotFound { variant_id }) + if variant_id == right_variant + ), + "expected VariantParentNotFound for Right, got {err:?}" + ); + } + #[test] fn behavior_result_port_returns_the_behavior_output_port() { let mut dag = Dag::new(); diff --git a/src/v3/compiler/src/emit/rust_target.rs b/src/v3/compiler/src/emit/rust_target.rs index 1e77b0a9d49..f1d278214b2 100644 --- a/src/v3/compiler/src/emit/rust_target.rs +++ b/src/v3/compiler/src/emit/rust_target.rs @@ -163,6 +163,10 @@ pub enum EmitError { /// inference — either inference didn't run or the scrutinee's /// Disj has no matching variant. UnresolvedBranchPattern { variant_name: String }, + /// A Branch arm resolved to a variant declaration that no longer + /// has a Disj parent. Emit must not fabricate a plausible target + /// identifier when the substrate cannot identify the parent enum. + VariantParentNotFound { variant_id: DeclarationId }, /// A Branch's scrutinee resolved to a Disj that isn't the v3 /// `Classical` (Bool) sum — PR-B only emits boolean branches. /// Carries the scrutinee's resolved variant ids so callers can From ce19f43b8a1dfa6de3b8d1b8a190a2c80079fdb8 Mon Sep 17 00:00:00 2001 From: Brian Searls Date: Sat, 25 Apr 2026 19:41:02 -0400 Subject: [PATCH 2/2] =?UTF-8?q?fix(v3):=20B1=20test=20=E2=80=94=20find=20v?= =?UTF-8?q?ariant=20by=20label=20in=20parent=20Disj,=20not=20declaration?= =?UTF-8?q?=5Fby=5Fname?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The variant declaration's .name field doesn't match the label string; look up via the parent Disj's variants list instead. --- src/v3/compiler/src/emit.rs | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/src/v3/compiler/src/emit.rs b/src/v3/compiler/src/emit.rs index 5cfa3032500..4bcd12e1653 100644 --- a/src/v3/compiler/src/emit.rs +++ b/src/v3/compiler/src/emit.rs @@ -3302,11 +3302,17 @@ mod tests { "missing_variant_parent.v3", ) .expect("compiles"); - let right_variant = dag - .declaration_by_name("Right") - .expect("Right variant declaration") - .id; let choice_decl = dag.declaration_by_name("Choice").expect("Choice decl").id; + let right_variant = match &dag.declaration(choice_decl).connective { + TypeConnective::Disj { variants } => { + variants + .iter() + .find(|v| v.label == "Right") + .expect("Right variant in Choice") + .ty + } + _ => panic!("Choice should lower to a Disj"), + }; let TypeConnective::Disj { variants } = &mut dag.declaration_mut(choice_decl).connective else { panic!("Choice should lower to a Disj");