From 7ef8a302f11de7f684c838e200f952665e3e1300 Mon Sep 17 00:00:00 2001 From: Vasilev Dmitrii Date: Sun, 9 Aug 2026 14:18:42 +0700 Subject: [PATCH] fix(typecheck): print warning messages, and fix the unused-variable false positive Two coupled changes: make warnings visible, then fix what visibility immediately exposed. 1. typecheck built every warning message and then dropped it on the floor. 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 sitting in result.errors; print them. This matters because some of these are real correctness findings downgraded to warnings: a call to an UNDEFINED function, and an argument type mismatch. The silence actively hid defects. 2. With the texts visible, 719 of tri-net's 788 warnings turned out to be one category and 34 another -- and 20 of those 34 were FALSE. A bare bracket literal ([a0, 99], with 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 walked children only, so every identifier used that way was reported unused. That is the idiomatic "bind the elements to locals, then rebuild the array" pattern, e.g. let a0 = arr[0]; let a1 = arr[1]; if (idx == 0) { return [99, a1]; } return [a0, 99]; which reported both a0 and a1 unused. collect_reads now scans the element text as well. 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, and all 107 specs still typecheck clean. t27 suite 1537 passed; the single failure (bitnet_layer) is the known environmental iverilog failure, present on master before this change. FROZEN_HASH resealed. Refs #1948 --- NOW.md | 8 ++++++++ bootstrap/src/compiler.rs | 27 +++++++++++++++++++++++++++ bootstrap/src/main.rs | 10 ++++++++++ bootstrap/stage0/FROZEN_HASH | 2 +- docs/NOW.md | 8 ++++++++ 5 files changed, 54 insertions(+), 1 deletion(-) 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