diff --git a/bootstrap/src/compiler.rs b/bootstrap/src/compiler.rs index a8d70ac15..9a7a9d571 100644 --- a/bootstrap/src/compiler.rs +++ b/bootstrap/src/compiler.rs @@ -19845,6 +19845,13 @@ pub struct RustCodegen { const_types: std::collections::HashMap, /// Declared Rust return type of every function in this module. fn_ret_types: std::collections::HashMap, + /// Names of enums declared in this module. + /// + /// t27 writes an enum member as `Verdict.escalate`, which is Zig's + /// spelling and what the parser produces. Rust spells it + /// `Verdict::escalate`. Without knowing which identifiers name enums the + /// emitter cannot tell that access apart from a struct field. + enum_names: std::collections::HashSet, } #[allow(dead_code)] @@ -19860,6 +19867,7 @@ impl RustCodegen { var_types: std::collections::HashMap::new(), const_types: std::collections::HashMap::new(), fn_ret_types: std::collections::HashMap::new(), + enum_names: std::collections::HashSet::new(), } } @@ -20006,6 +20014,9 @@ impl RustCodegen { } fn gen_enum(&mut self, node: &Node) { + // Recorded before the body is written, so a member referenced inside + // the same module resolves however the declarations are ordered. + self.enum_names.insert(node.name.clone()); self.write_line("#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)]"); self.write_line(&format!("pub enum {} {{", node.name)); self.indent += 1; @@ -20725,7 +20736,23 @@ impl RustCodegen { .collect(); format!("{} {{ {} }}", node.name, fields.join(", ")) } - NodeKind::ExprEnumValue => format!("{}::{}", node.name, node.extra_field), + NodeKind::ExprEnumValue => { + if node.extra_field.is_empty() { + // Shorthand `.variant`. t27 takes the enum from context the + // way Zig does; Rust has no such rule, so the emitter must + // supply it. The context available here is the declared + // return type of the function being emitted - exactly the + // case the shorthand is written for. Without it the variant + // was printed on the LEFT of the separator: `escalate::`. + if self.enum_names.contains(&self.fn_ret_type) { + format!("{}::{}", self.fn_ret_type, node.name) + } else { + node.name.clone() + } + } else { + format!("{}::{}", node.name, node.extra_field) + } + } NodeKind::ExprUnary => { if !node.children.is_empty() { let operand = &node.children[0]; @@ -20748,7 +20775,14 @@ impl RustCodegen { } NodeKind::ExprFieldAccess => { if !node.children.is_empty() { - format!("{}.{}", self.expr_to_rust(&node.children[0]), node.name) + let base = self.expr_to_rust(&node.children[0]); + // An enum member is a path in Rust and a field access in + // t27's Zig-shaped syntax. Only the base tells them apart. + if self.enum_names.contains(&base) { + format!("{}::{}", base, node.name) + } else { + format!("{}.{}", base, node.name) + } } else { node.name.clone() } diff --git a/bootstrap/stage0/FROZEN_HASH b/bootstrap/stage0/FROZEN_HASH index b4b2e4a72..f3c5ae07e 100644 --- a/bootstrap/stage0/FROZEN_HASH +++ b/bootstrap/stage0/FROZEN_HASH @@ -1 +1 @@ -315fbe1df4f09eb5a5bd2ac8b9fd748537ccd0b5dc8f0528d602d324a4c48715 bootstrap/src/compiler.rs +cbbfac87dff32a7a8c0fee2331715453566d75f2e338e0c5ea76c089b96a8028 bootstrap/src/compiler.rs diff --git a/docs/NOW.md b/docs/NOW.md index 542421f9a..4ca00556b 100644 --- a/docs/NOW.md +++ b/docs/NOW.md @@ -1,3 +1,28 @@ +# NOW -- an enum member is a path, not a field access (2026-08-20) + +Last updated: 2026-08-20 + +## fix(rust-emitter): enum paths and the shorthand's missing type + +Working on the trios side, moving the supervisor's decision core out of Swift +into a `.t27` ring. Generating it exposed two gaps in the Rust backend, both in +how an enum member is spelled, and together they made every enum-returning +function ungeneratable: + +- `Verdict.escalate` emitted as `Verdict.escalate`; Rust needs `::`. +- The shorthand `.escalate` emitted as `escalate::` -- the variant on the left + of the separator. + +Both fixed in `RustCodegen`. The seal moved because `compiler.rs` changed; +new digest recorded per FROZEN.md ยง5. + +Not touched: the FPGA stands, `fpga/`, or anything on the wave branch. Three +further gaps are recorded in the commit message and left alone -- `switch` as a +function body, `;` comments inside an enum body, and `pub module` not being +picked up from a `//`-commented file. + +--- + # NOW -- the props read with -formal too (2026-08-20) Last updated: 2026-08-20