From 6171697a23bbddda819f7f7bf77a26949708e42f Mon Sep 17 00:00:00 2001 From: Perplexity Computer Date: Mon, 13 Jul 2026 01:59:09 +0000 Subject: [PATCH 1/2] feat(codegen): infer let mut from assignment analysis MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Rust requires 'let mut' for locals that are reassigned. The t27 'let' keyword produces immutable Rust, causing E0384 errors for any spec that reassigns a local (117 errors in tri-net alone). This adds mutability inference to the Rust codegen: - collect_mutable_names(): scans function body for StmtAssign targets (simple identifiers, index assignments arr[i]=, field assignments s.f=) - RustCodegen.mut_names: per-function set of names needing mut - Both let-emission sites (gen_fn + gen_rust_stmt) check the set Reproducibility (from ssdm4 macbook, ветка feat/mut-inference-2026-07-13): cargo check --lib in tri-net: 141 errors -> 0 errors, 101 tests pass. cited SHA: 96d3581934246ebd5711d819b9c26270974092b9 (measurement realm) Over-inference (mut on read-only locals) produces warnings, not errors. Note: cleanly cherry-picked from PR #1460 head onto current master (4832ec6a). Discards 249 unrelated commits from stacked branch. Also re-seals FROZEN_HASH per M5 ceremony (compiler.rs source of truth change requires new digest in bootstrap/stage0/FROZEN_HASH). Co-Authored-By: ssdm4 phi^2 + phi^-2 = 3 --- bootstrap/src/compiler.rs | 63 ++++++++++++++++++++++++++++++++++-- bootstrap/stage0/FROZEN_HASH | 2 +- 2 files changed, 62 insertions(+), 3 deletions(-) diff --git a/bootstrap/src/compiler.rs b/bootstrap/src/compiler.rs index a2c60685c..ba904b273 100644 --- a/bootstrap/src/compiler.rs +++ b/bootstrap/src/compiler.rs @@ -6498,6 +6498,56 @@ fn parse_int_value(s: &str) -> Option { } } +/// Collect all local names that are reassigned anywhere in a statement list. +/// This includes: +/// - Simple assignments: `x = expr` → x is mutable +/// - 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. +fn collect_mutable_names(stmts: &[Node], set: &mut std::collections::HashSet) { + for stmt in stmts { + collect_mutable_names_one(stmt, set); + } +} + +fn collect_mutable_names_one(stmt: &Node, set: &mut std::collections::HashSet) { + match stmt.kind { + NodeKind::StmtAssign if !stmt.children.is_empty() => { + let lhs = &stmt.children[0]; + // Simple identifier: `x = expr` + if lhs.kind == NodeKind::ExprIdentifier { + set.insert(lhs.name.clone()); + } + // Index assignment: `arr[i] = expr` → arr needs mut + // The LHS is ExprIndex with children[0] = base identifier + if lhs.kind == NodeKind::ExprIndex && !lhs.children.is_empty() { + let base = &lhs.children[0]; + if base.kind == NodeKind::ExprIdentifier { + set.insert(base.name.clone()); + } + } + // Field assignment: `s.f = expr` → s needs mut + if lhs.kind == NodeKind::ExprFieldAccess && !lhs.children.is_empty() { + let base = &lhs.children[0]; + if base.kind == NodeKind::ExprIdentifier { + set.insert(base.name.clone()); + } + } + } + NodeKind::StmtIf | NodeKind::StmtWhile | NodeKind::StmtFor | NodeKind::StmtForRange => { + // Recurse into condition + body blocks + for c in &stmt.children { + if c.kind == NodeKind::Module { + collect_mutable_names(&c.children, set); + } else { + collect_mutable_names_one(c, set); + } + } + } + _ => {} + } +} + fn copy_propagate(stmts: &mut Vec, stats: &mut OptStats) { let mut replacements: Vec<(String, String)> = Vec::new(); for stmt in stmts.iter() { @@ -7610,6 +7660,10 @@ fn resolve_type_str(s: &str) -> TypeInfo { pub struct RustCodegen { output: String, indent: usize, + /// Names of locals that are reassigned in the current function body + /// (via simple assignment `x = ...` or index assignment `arr[i] = ...`). + /// Used to infer `let mut` for locals declared with `let`. + mut_names: std::collections::HashSet, } #[allow(dead_code)] @@ -7618,6 +7672,7 @@ impl RustCodegen { RustCodegen { output: String::new(), indent: 0, + mut_names: std::collections::HashSet::new(), } } @@ -7774,6 +7829,10 @@ impl RustCodegen { matches!(c.kind, NodeKind::ExprReturn | NodeKind::StmtExpr | NodeKind::StmtLocal | NodeKind::StmtIf | NodeKind::StmtWhile | NodeKind::StmtFor | NodeKind::StmtForRange) }); + // Infer which locals need `let mut`: scan body for any assignment. + self.mut_names.clear(); + collect_mutable_names(&node.children, &mut self.mut_names); + if has_body { self.output.push('\n'); self.indent += 1; @@ -7794,7 +7853,7 @@ impl RustCodegen { } } NodeKind::StmtLocal => { - let mutable = child.extra_mutable; + let mutable = child.extra_mutable || self.mut_names.contains(&child.name); let kw = if mutable { "let mut" } else { "let" }; let var_name = &child.name; let typ = Self::t27_type_to_rust(&child.extra_type); @@ -7935,7 +7994,7 @@ impl RustCodegen { } } NodeKind::StmtLocal => { - let kw = if stmt.extra_mutable { "let mut" } else { "let" }; + let kw = if stmt.extra_mutable || self.mut_names.contains(&stmt.name) { "let mut" } else { "let" }; let typ = Self::t27_type_to_rust(&stmt.extra_type); if stmt.children.is_empty() { if stmt.extra_type.is_empty() { diff --git a/bootstrap/stage0/FROZEN_HASH b/bootstrap/stage0/FROZEN_HASH index 1e3ad52fc..062518f1a 100644 --- a/bootstrap/stage0/FROZEN_HASH +++ b/bootstrap/stage0/FROZEN_HASH @@ -1 +1 @@ -197d6eb5a40bed62ae806cb09c14fbde4d2664ed7cc6991c1e3bfa60983706ff bootstrap/src/compiler.rs +9f64cb53fdfdd3c7e6fea85c74a4f6243ee8117f5b6544c987988f7f48eb72b0 bootstrap/src/compiler.rs From c63d1c8e1911372481920e3571840f9596856e3f Mon Sep 17 00:00:00 2001 From: SSD DDD Date: Mon, 13 Jul 2026 14:17:39 +0700 Subject: [PATCH 2/2] docs(NOW): update for mut-inference PR #1461 (Fixes #1463) phi^2 + phi^-2 = 3 --- docs/NOW.md | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/docs/NOW.md b/docs/NOW.md index dc647be6e..1640ab342 100644 --- a/docs/NOW.md +++ b/docs/NOW.md @@ -1,7 +1,13 @@ # NOW — IGLA cycle 1 + Wave Loop 469 context (2026-07-07) -Last updated: 2026-07-07 +Last updated: 2026-07-13 +## t27c codegen: mut-inference for reassigned locals (Fixes #1463) + +- PR #1461 adds mutability inference to the Rust codegen backend. +- `collect_mutable_names()` scans function bodies for assignment targets + (simple, index, field), emitting `let mut` where needed. +- Eliminates 117 E0384 errors in tri-net specs. ## IGLA cycle 1 — process debt needles (Refs #1438, #1440, #1442, #1444, #1446) - Charter: `docs/nona-03-manifest/IGLA_IMPROVEMENT_LOOP.md` + audit `docs/reports/IGLA_AUDIT_W470_2026-07-07.md`.