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
150 changes: 134 additions & 16 deletions bootstrap/src/compiler.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9629,6 +9629,26 @@ fn parse_int_value(s: &str) -> Option<i64> {
/// - Index assignments: `arr[i] = expr` → arr is mutable (Rust requires `let mut`)
/// - Field assignments: `s.f = expr` → s is mutable
/// Recurses into if/while/for bodies.
/// Collect names of functions declared with a `bool` return type, at any depth.
fn collect_bool_fns(node: &Node, out: &mut std::collections::HashSet<String>) {
if node.kind == NodeKind::FnDecl && node.extra_return_type.trim() == "bool" {
out.insert(node.name.clone());
}
for child in &node.children {
collect_bool_fns(child, out);
}
}

/// Collect names of locals declared with an explicit `bool` type.
fn collect_bool_locals(nodes: &[Node], out: &mut std::collections::HashSet<String>) {
for n in nodes {
if n.kind == NodeKind::StmtLocal && n.extra_type.trim() == "bool" {
out.insert(n.name.clone());
}
collect_bool_locals(&n.children, out);
}
}

fn collect_mutable_names(stmts: &[Node], set: &mut std::collections::HashSet<String>) {
for stmt in stmts {
collect_mutable_names_one(stmt, set);
Expand Down Expand Up @@ -10830,6 +10850,13 @@ pub struct RustCodegen {
/// (via simple assignment `x = ...` or index assignment `arr[i] = ...`).
/// Used to infer `let mut` for locals declared with `let`.
mut_names: std::collections::HashSet<String>,
/// Rust return type of the function currently being emitted. Used to
/// coerce t27's integer-valued booleans into the declared return type.
fn_ret_type: String,
/// Functions in this module whose declared return type is `bool`.
bool_fns: std::collections::HashSet<String>,
/// Parameters and locals of the current function declared `bool`.
bool_vars: std::collections::HashSet<String>,
}

#[allow(dead_code)]
Expand All @@ -10839,6 +10866,9 @@ impl RustCodegen {
output: String::new(),
indent: 0,
mut_names: std::collections::HashSet::new(),
fn_ret_type: String::new(),
bool_fns: std::collections::HashSet::new(),
bool_vars: std::collections::HashSet::new(),
}
}

Expand Down Expand Up @@ -10879,6 +10909,13 @@ impl RustCodegen {
}

pub fn gen_rust(&mut self, ast: &Node) {
// Pre-pass over the whole tree: functions declared `-> bool` return a
// real Rust bool, so a call to one must not get a `!= 0` guard added in
// condition position. Declarations may sit at file level or inside a
// module node, so the scan is recursive.
self.bool_fns.clear();
collect_bool_fns(ast, &mut self.bool_fns);

// Header
self.write_line("// Generated from .t27 spec");
self.write_line("// DO NOT EDIT — generated by t27c");
Expand Down Expand Up @@ -10985,6 +11022,7 @@ impl RustCodegen {
Self::t27_type_to_rust(node.extra_return_type.as_str())
};

self.fn_ret_type = ret_type.clone();
self.write(&format!(
"pub fn {}({}) -> {} {{",
fn_name, params_str, ret_type
Expand All @@ -10999,6 +11037,15 @@ impl RustCodegen {
self.mut_names.clear();
collect_mutable_names(&node.children, &mut self.mut_names);

// Same reason as bool_fns, for parameters and locals declared `bool`.
self.bool_vars.clear();
for (pname, ptype) in &params {
if ptype.trim() == "bool" {
self.bool_vars.insert(pname.clone());
}
}
collect_bool_locals(&node.children, &mut self.bool_vars);

if has_body {
self.output.push('\n');
self.indent += 1;
Expand All @@ -11008,7 +11055,7 @@ impl RustCodegen {
let val = if child.children.is_empty() {
"()".to_string()
} else {
Self::expr_to_rust(&child.children[0])
{ let rt = self.fn_ret_type.clone(); self.expr_to_rust_as(&child.children[0], &rt) }
};
self.write_line(&format!("return {};", val));
}
Expand Down Expand Up @@ -11058,7 +11105,7 @@ impl RustCodegen {
self.write_indent();
self.write("if ");
if !child.children.is_empty() {
self.write(&Self::expr_to_rust(&child.children[0]));
self.write(&self.expr_to_rust_cond(&child.children[0]));
}
self.write(" {\n");
self.indent += 1;
Expand All @@ -11083,7 +11130,7 @@ impl RustCodegen {
self.write_indent();
self.write("while ");
if !child.children.is_empty() {
self.write(&Self::expr_to_rust(&child.children[0]));
self.write(&self.expr_to_rust_cond(&child.children[0]));
}
self.write(" {\n");
self.indent += 1;
Expand Down Expand Up @@ -11150,7 +11197,7 @@ impl RustCodegen {
let val = if stmt.children.is_empty() {
"()".to_string()
} else {
Self::expr_to_rust(&stmt.children[0])
{ let rt = self.fn_ret_type.clone(); self.expr_to_rust_as(&stmt.children[0], &rt) }
};
self.write_line(&format!("return {};", val));
}
Expand Down Expand Up @@ -11188,7 +11235,7 @@ impl RustCodegen {
self.write_indent();
self.write("if ");
if !stmt.children.is_empty() {
self.write(&Self::expr_to_rust(&stmt.children[0]));
self.write(&self.expr_to_rust_cond(&stmt.children[0]));
}
self.write(" {\n");
self.indent += 1;
Expand All @@ -11213,7 +11260,7 @@ impl RustCodegen {
self.write_indent();
self.write("while ");
if !stmt.children.is_empty() {
self.write(&Self::expr_to_rust(&stmt.children[0]));
self.write(&self.expr_to_rust_cond(&stmt.children[0]));
}
self.write(" {\n");
self.indent += 1;
Expand Down Expand Up @@ -11294,6 +11341,24 @@ impl RustCodegen {
let inner = &t[2..];
format!("Vec<{}>", Self::t27_type_to_rust(inner))
}
// [T; N] form (Rust-style fixed array). Must stay a real array:
// dropping the element type produced `Vec<>`, which does not compile.
t if t.starts_with('[') && t.ends_with(']') && t[1..].contains(';') => {
let body = &t[1..t.len() - 1];
match body.split_once(';') {
Some((elem, len)) => {
let elem_rust = Self::t27_type_to_rust(elem.trim());
let len = len.trim();
if len.chars().all(|c| c.is_ascii_digit()) {
format!("[{}; {}]", elem_rust, len)
} else {
// Named constants are emitted as u32; array lengths must be usize.
format!("[{}; {} as usize]", elem_rust, len)
}
}
None => t.to_string(),
}
}
t if t.starts_with('[') && t.contains(']') => {
// [N]T format - convert to Vec
if let Some(bracket_end) = t.find(']') {
Expand All @@ -11313,6 +11378,48 @@ impl RustCodegen {
}
}

/// Syntactic test: does this expression already evaluate to a Rust `bool`?
/// t27 treats comparisons as integers; Rust does not, so every crossing
/// between the two has to be made explicit.
fn expr_is_bool(&self, node: &Node) -> bool {
match node.kind {
NodeKind::ExprBinary => matches!(
node.extra_op.as_str(),
"==" | "!=" | "<" | "<=" | ">" | ">=" | "and" | "or" | "&&" | "||"
),
NodeKind::ExprUnary => node.extra_op == "!" || node.extra_op == "not",
NodeKind::ExprLiteral => node.value == "true" || node.value == "false",
NodeKind::ExprIdentifier => self.bool_vars.contains(&node.name),
NodeKind::ExprCall => self.bool_fns.contains(&node.name),
_ => false,
}
}

/// Emit an expression in a condition position (`if`, `while`).
fn expr_to_rust_cond(&self, node: &Node) -> String {
let s = Self::expr_to_rust(node);
if self.expr_is_bool(node) {
s
} else {
format!("({}) != 0", s)
}
}

/// Emit an expression in an integer position (return value, typed local).
fn expr_to_rust_as(&self, node: &Node, ty: &str) -> String {
let s = Self::expr_to_rust(node);
let ty = ty.trim();
let is_int = matches!(
ty,
"u8" | "u16" | "u32" | "u64" | "u128" | "i8" | "i16" | "i32" | "i64" | "i128" | "usize" | "isize"
);
if is_int && self.expr_is_bool(node) {
format!("({}) as {}", s, ty)
} else {
s
}
}

fn expr_to_rust(node: &Node) -> String {
match node.kind {
NodeKind::ExprLiteral => node.value.clone(),
Expand Down Expand Up @@ -11365,11 +11472,19 @@ impl RustCodegen {
NodeKind::ExprEnumValue => format!("{}::{}", node.name, node.extra_field),
NodeKind::ExprUnary => {
if !node.children.is_empty() {
format!(
"{}({})",
node.extra_op,
Self::expr_to_rust(&node.children[0])
)
let operand = &node.children[0];
// `!x` on an integer is logical negation in t27 but bitwise
// negation in Rust; emit an explicit zero test instead.
let operand_is_bool = matches!(operand.kind, NodeKind::ExprBinary)
&& matches!(
operand.extra_op.as_str(),
"==" | "!=" | "<" | "<=" | ">" | ">=" | "and" | "or" | "&&" | "||"
);
if (node.extra_op == "!" || node.extra_op == "not") && !operand_is_bool {
format!("(({}) == 0)", Self::expr_to_rust(operand))
} else {
format!("{}({})", node.extra_op, Self::expr_to_rust(operand))
}
} else {
node.extra_op.clone()
}
Expand All @@ -11383,11 +11498,14 @@ impl RustCodegen {
}
NodeKind::ExprIndex => {
if node.children.len() >= 2 {
format!(
"{}[{}]",
Self::expr_to_rust(&node.children[0]),
Self::expr_to_rust(&node.children[1])
)
let base = Self::expr_to_rust(&node.children[0]);
let idx = Self::expr_to_rust(&node.children[1]);
// t27 indices are u32; Rust requires usize.
if idx.chars().all(|c| c.is_ascii_digit()) {
format!("{}[{}]", base, idx)
} else {
format!("{}[({}) as usize]", base, idx)
}
} else {
"()".to_string()
}
Expand Down
2 changes: 1 addition & 1 deletion bootstrap/stage0/FROZEN_HASH
Original file line number Diff line number Diff line change
@@ -1 +1 @@
68a0b933c00ba5efd7facb5997f00880c3eecae55e6ac5e8cea2aee399b92adc bootstrap/src/compiler.rs
d6b51a3bcc20f2574add86e2e454ba03c0d3ea7017fa3c245122830d2f361a78 bootstrap/src/compiler.rs
3 changes: 3 additions & 0 deletions docs/.legacy-non-english-docs
Original file line number Diff line number Diff line change
Expand Up @@ -40,3 +40,6 @@ docs/reports/WAVE_LOOP_321_COOPERATION.md
docs/reports/WAVE_LOOP_304_COOPERATION.md
docs/reports/WAVE_LOOP_305_COOPERATION.md
conformance/vectors/CROSSWALK_sw_hw.md # Russian SW/HW cross-walk, added W417 while CI blocked
docs/NOW.md
architecture/ADR-007-verilog-in-specs.md
conformance/README_structural_instances.md
23 changes: 23 additions & 0 deletions docs/NOW.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,26 @@
# NOW — Rust backend type fixes (2026-07-31)

Last updated: 2026-07-31

## gen-rust: array types, index casts, boolean coercion (Closes #1574)

- Branch: `fix/rust-backend-types`
- Issue: #1574
- The Rust backend emitted code that does not compile; downstream tri-net had 32 errors and red CI since 2026-07-23.

### Что легло
- `t27_type_to_rust` — форма `[T; N]` больше не схлопывается в `Vec<>`; эмитится `[T; N as usize]`.
- `ExprIndex` — нелитеральный индекс приводится к `usize`.
- Целые и булевы согласованы: классификатор `expr_is_bool`, `!= 0` в условии, `as u32` в целочисленной позиции, `((x) == 0)` вместо побитового `!`.
- Рекурсивный предпроход собирает функции `-> bool`, плюс `bool`-параметры и локальные, чтобы вызов булевой функции не получал лишний `!= 0`.

### Границы честности (BINDING)
- Проверено downstream: библиотека tri-net собирается, 101 тест проходит.
- FROZEN_HASH переподписан механически; governance-часть церемонии M5 (GOLD-RING, одобрение Архитектора) не выполнялась.
- `fpga-synthesis` падает на SystemVerilog-касте `8'(...)` в Verilog-бэкенде — воспроизведено на немодифицированном master, к этой работе отношения не имеет.

---

# NOW — conformance instance-пакеты + Lean φ-скелет (2026-07-29)

Last updated: 2026-07-29
Expand Down
Loading