Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
38 changes: 36 additions & 2 deletions bootstrap/src/compiler.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19845,6 +19845,13 @@ pub struct RustCodegen {
const_types: std::collections::HashMap<String, String>,
/// Declared Rust return type of every function in this module.
fn_ret_types: std::collections::HashMap<String, String>,
/// 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<String>,
}

#[allow(dead_code)]
Expand All @@ -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(),
}
}

Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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];
Expand All @@ -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()
}
Expand Down
2 changes: 1 addition & 1 deletion bootstrap/stage0/FROZEN_HASH
Original file line number Diff line number Diff line change
@@ -1 +1 @@
315fbe1df4f09eb5a5bd2ac8b9fd748537ccd0b5dc8f0528d602d324a4c48715 bootstrap/src/compiler.rs
cbbfac87dff32a7a8c0fee2331715453566d75f2e338e0c5ea76c089b96a8028 bootstrap/src/compiler.rs
25 changes: 25 additions & 0 deletions docs/NOW.md
Original file line number Diff line number Diff line change
@@ -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
Expand Down
Loading