diff --git a/NOW.md b/NOW.md index cf5979d4c..4d8a8afdb 100644 --- a/NOW.md +++ b/NOW.md @@ -2,6 +2,14 @@ Last updated: 2026-08-09 +## typecheck: warnings are PRINTED, and the unused-variable false positive is fixed (Refs #1948) + +- `typecheck` built every warning message and then dropped it: the OK branch printed only the total, so a warning was unactionable -- you could watch the number grow and never learn what it was. The messages were already in `result.errors`; they are printed now. Some are real correctness findings downgraded to warnings (a call to an undefined function, an argument type mismatch), so the silence actively hid defects +- Printing them immediately exposed a detector bug: a BARE bracket literal (`[a0, 99]`, no `[N]Type{...}` prefix) never becomes child nodes -- the parser captures the whole bracket body as element TEXT in `extra_size`. The unused-variable pass read only children, so every identifier used that way was reported unused. That is the idiomatic "bind elements to locals, then rebuild the array" pattern (health_monitoring::update_health_check, key_management::set_key_slot, cross_layer_optimizer/redundancy_management::set_slot4). `collect_reads` now scans the element text too -- it can only ADD reads, so it removes false warnings and can never introduce one +- tri-net corpus: unused-variable warnings 34 -> 14 (20 were false), total 788 -> 768; all 107 specs still typecheck clean +- FROZEN_HASH resealed + + ## gen-zig: narrowing unsigned cast lowers to @truncate (Refs #1948) - t27 `as` truncates a narrowing integer cast (Rust semantics), but gen-zig always emitted checked `@intCast` -> panics in safe builds when the value does not fit (`weighted_total as u32` on a 2^32 multiple) diff --git a/bootstrap/src/compiler.rs b/bootstrap/src/compiler.rs index 2b37b3765..1ad55821a 100644 --- a/bootstrap/src/compiler.rs +++ b/bootstrap/src/compiler.rs @@ -13782,6 +13782,33 @@ pub fn typecheck_ast(ast: &Node) -> TypeCheckResult { if node.kind == NodeKind::ExprIdentifier { reads.insert(node.name.clone()); } + // A BARE bracket literal (`[a0, 99]`, no `[N]Type{...}` prefix) + // never becomes child nodes: the parser captures the whole + // bracket body as element TEXT in extra_size. Reading only + // children therefore missed every identifier used that way and + // reported it as an unused variable -- a false positive on the + // idiomatic "bind the elements to locals, then rebuild the + // array" pattern (health_monitoring::update_health_check, + // key_management::set_key_slot, and others). Scan the text too. + // This can only ADD reads, so it removes false warnings and can + // never introduce one. + if node.kind == NodeKind::ExprArrayLiteral && !node.extra_size.is_empty() { + let mut ident = String::new(); + for ch in node.extra_size.chars() { + if ch.is_alphanumeric() || ch == '_' { + ident.push(ch); + } else { + if !ident.is_empty() && !ident.starts_with(|c: char| c.is_ascii_digit()) { + reads.insert(std::mem::take(&mut ident)); + } else { + ident.clear(); + } + } + } + if !ident.is_empty() && !ident.starts_with(|c: char| c.is_ascii_digit()) { + reads.insert(ident); + } + } for child in &node.children { collect_reads(child, reads); } diff --git a/bootstrap/src/main.rs b/bootstrap/src/main.rs index dcc3cc6ca..ab73a8efd 100644 --- a/bootstrap/src/main.rs +++ b/bootstrap/src/main.rs @@ -4624,6 +4624,16 @@ fn run_typecheck(input_path: &str, json: bool) -> anyhow::Result<()> { println!("{}", serde_json::to_string_pretty(&resp).unwrap()); } else if result.ok { println!("Typecheck OK (0 errors, {} warnings)", result.warnings); + // tri-net#375 pinned every spec's warning count, but the messages were + // built and then dropped on the floor: the OK branch printed only the + // total, so a warning was unactionable -- you could see the number grow + // and not what it was. They are already in result.errors; print them. + // Some of these are real correctness findings downgraded to warnings + // (a call to an undefined function, an argument type mismatch), so + // silence here actively hid defects. + for msg in &result.errors { + println!(" - {}", msg); + } } else { println!("Typecheck FAILED ({} errors, {} warnings):", result.error_count, result.warnings); for err in &result.errors { diff --git a/bootstrap/stage0/FROZEN_HASH b/bootstrap/stage0/FROZEN_HASH index cac93ae6b..f57d3ba2f 100644 --- a/bootstrap/stage0/FROZEN_HASH +++ b/bootstrap/stage0/FROZEN_HASH @@ -1 +1 @@ -6c30e43706ecea81acaab117b97615d989a071d7973b4e390a78f7a794f69bb5 +cd2822f290eb04ed9b6a6530357fea22dff6e09a4fb1ad7575ffc7d6ec744ff7 diff --git a/docs/NOW.md b/docs/NOW.md index 81b201846..a73a0d6ba 100644 --- a/docs/NOW.md +++ b/docs/NOW.md @@ -48,6 +48,14 @@ Last updated: 2026-08-09 Last updated: 2026-08-09 +## typecheck: warnings are PRINTED, and the unused-variable false positive is fixed (Refs #1948) + +- `typecheck` built every warning message and then dropped it: the OK branch printed only the total, so a warning was unactionable -- you could watch the number grow and never learn what it was. The messages were already in `result.errors`; they are printed now. Some are real correctness findings downgraded to warnings (a call to an undefined function, an argument type mismatch), so the silence actively hid defects +- Printing them immediately exposed a detector bug: a BARE bracket literal (`[a0, 99]`, no `[N]Type{...}` prefix) never becomes child nodes -- the parser captures the whole bracket body as element TEXT in `extra_size`. The unused-variable pass read only children, so every identifier used that way was reported unused. That is the idiomatic "bind elements to locals, then rebuild the array" pattern (health_monitoring::update_health_check, key_management::set_key_slot, cross_layer_optimizer/redundancy_management::set_slot4). `collect_reads` now scans the element text too -- it can only ADD reads, so it removes false warnings and can never introduce one +- tri-net corpus: unused-variable warnings 34 -> 14 (20 were false), total 788 -> 768; all 107 specs still typecheck clean +- FROZEN_HASH resealed + + ## numeric: GF-T registered in the catalog SSOT, all nine rungs (Refs #2001) - `specs/numeric/formats_catalog.t27` had **zero GF-T rows** -- the only `gft` id was `gfternary`, a different object (2-bit {-phi,0,+phi} alphabet, not a float with a balanced-ternary exponent field). Four rungs existed as specs with no row and no pack; five did not exist at all, while `zig-golden-float/specs/gft.tri` named this directory as its own source of truth