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
63 changes: 61 additions & 2 deletions bootstrap/src/compiler.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6498,6 +6498,56 @@ fn parse_int_value(s: &str) -> Option<i64> {
}
}

/// 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<String>) {
for stmt in stmts {
collect_mutable_names_one(stmt, set);
}
}

fn collect_mutable_names_one(stmt: &Node, set: &mut std::collections::HashSet<String>) {
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<Node>, stats: &mut OptStats) {
let mut replacements: Vec<(String, String)> = Vec::new();
for stmt in stmts.iter() {
Expand Down Expand Up @@ -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<String>,
}

#[allow(dead_code)]
Expand All @@ -7618,6 +7672,7 @@ impl RustCodegen {
RustCodegen {
output: String::new(),
indent: 0,
mut_names: std::collections::HashSet::new(),
}
}

Expand Down Expand Up @@ -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;
Expand All @@ -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);
Expand Down Expand Up @@ -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() {
Expand Down
2 changes: 1 addition & 1 deletion bootstrap/stage0/FROZEN_HASH
Original file line number Diff line number Diff line change
@@ -1 +1 @@
197d6eb5a40bed62ae806cb09c14fbde4d2664ed7cc6991c1e3bfa60983706ff bootstrap/src/compiler.rs
9f64cb53fdfdd3c7e6fea85c74a4f6243ee8117f5b6544c987988f7f48eb72b0 bootstrap/src/compiler.rs
8 changes: 7 additions & 1 deletion docs/NOW.md
Original file line number Diff line number Diff line change
@@ -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`.
Expand Down
Loading