diff --git a/.claude/skills/ci-gates/SKILL.md b/.claude/skills/ci-gates/SKILL.md index d33b4a678c..08622f7c3e 100644 --- a/.claude/skills/ci-gates/SKILL.md +++ b/.claude/skills/ci-gates/SKILL.md @@ -8705,3 +8705,45 @@ The general form: a build graph is a claim about coverage, and unlike a test list nothing prints it. `import` is the edge, the root file is the whole specification of what gets compiled, and it is nine lines long. +## 346. One rule, four positions, three treatments + +`types_compatible` rejects narrowing `F64 -> F32`, with a comment naming the +issue it closed. Where does that rejection actually apply? + +| position | compared? | verdict | +|---|---|---| +| assignment `x = d;` | yes | **error** | +| argument `p(d)` | yes | **warning** -- printed under a `Typecheck OK` header | +| declaration `var x: f32 = d;` | **no** | silent | +| return `-> f32 { return d; }` | **no** | silent | + +A soundness fix guarding one of four narrowing sites is a soundness fix in one +of four narrowing sites. **When you find a rule, enumerate the positions it +should hold in and check each one** -- the code will not tell you which ones it +forgot, because forgetting is silent by construction. + +Adding the declaration comparison at the argument's severity -- a warning -- +cost 18 warnings in 13 files across the whole corpus, and moved no ratchet. +I expected noise and got a work list. + +## 347. The check found the bug that made the check necessary + +Of those 18, two read `Str <- F64`: + +``` +var period_str : &str = "83.333"; +``` + +`infer_expr` returns `Str` when the literal's VALUE starts with a quote. The +parser marks the node `extra_kind: "string"` and the lexeme does not always +carry the quote, so a quoted string fell through to the float branch and any +string whose text parses as a number was typed as that number. `"hello"` was +fine -- it does not parse as a float, so it landed on `Unknown`, which is +compatible with everything and therefore silent. + +Two silences composed: the declaration position was never compared, and the +value that would have failed the comparison was mistyped. Neither was visible +alone. + +The fix reads the marker the parser already sets. `specs/pins/emitter_xdc.t27` +now typechecks -- 627 to 628 specs, zero regressions. diff --git a/.trinity/seals/EmitterXDC.json b/.trinity/seals/EmitterXDC.json index 18ef7dd029..a05db1efc4 100644 --- a/.trinity/seals/EmitterXDC.json +++ b/.trinity/seals/EmitterXDC.json @@ -1,6 +1,6 @@ { "gen_hash_c": "sha256:038f5e24b6ee91e6b41023f5c1fa171c07d9ff4e1e8ab71e5ed6b0e0b26da53b", - "gen_hash_rust": "sha256:6b2272a70de5f6ff5633ddb7fb2ba8000f021bb2e4f4209b487770b620fca3a0", + "gen_hash_rust": "sha256:d307b19f6a47782c3a0ac22059dda6c8430f350f47956afd9bca952d57d91763", "gen_hash_verilog": "sha256:cde8b3da09478086d0bbe7f87fd950c2366b36c21e7bfb8d0bfc9718e995d1fb", "gen_hash_zig": "sha256:3f16baa3c8bd8fb3b2af6ceb5f59238e3646c841eec55979d286760c47211656", "module": "EmitterXDC", diff --git a/.trinity/seals/pins_EmitterXDC.json b/.trinity/seals/pins_EmitterXDC.json index eb693f3c85..2ca4b28308 100644 --- a/.trinity/seals/pins_EmitterXDC.json +++ b/.trinity/seals/pins_EmitterXDC.json @@ -1,11 +1,11 @@ { "gen_hash_c": "sha256:038f5e24b6ee91e6b41023f5c1fa171c07d9ff4e1e8ab71e5ed6b0e0b26da53b", - "gen_hash_rust": "sha256:6b2272a70de5f6ff5633ddb7fb2ba8000f021bb2e4f4209b487770b620fca3a0", + "gen_hash_rust": "sha256:d307b19f6a47782c3a0ac22059dda6c8430f350f47956afd9bca952d57d91763", "gen_hash_verilog": "sha256:cde8b3da09478086d0bbe7f87fd950c2366b36c21e7bfb8d0bfc9718e995d1fb", "gen_hash_zig": "sha256:3f16baa3c8bd8fb3b2af6ceb5f59238e3646c841eec55979d286760c47211656", "module": "EmitterXDC", "ring": 12, - "sealed_at": "2026-08-29T04:11:27Z", + "sealed_at": "2026-08-29T22:04:32Z", "sealed_by": "t27c-bootstrap@0.2.0", "spec_hash": "sha256:d9954ca190ee3e748ef8df7ce32bd183096f7f97003651aede1ccabe5c1b0e19", "spec_path": "specs/pins/emitter_xdc.t27" diff --git a/bootstrap/src/compiler.rs b/bootstrap/src/compiler.rs index 38b6b2c593..ea73756511 100644 --- a/bootstrap/src/compiler.rs +++ b/bootstrap/src/compiler.rs @@ -21897,6 +21897,35 @@ fn check_stmt(node: &Node, symbols: &mut Vec, fns: &[FnEntry], resu for child in &node.children { check_expr(child, symbols, fns, result); } + // #920 rejects narrowing F64 -> F32 in an ASSIGNMENT and nowhere + // else. Measured: `x = d` is an error, `p(d)` is a warning, and a + // DECLARATION with an annotation was not compared at all -- + // `var x: f32 = d;` passed in silence. Three treatments of one + // rule, two of them silent. + // + // Reported at the same severity the argument position uses. Making + // it an error would be the consistent choice and would also fail + // specs that pass today; that is the owner's call, and a warning + // makes the hole countable without moving any ratchet. + if !node.extra_type.is_empty() && !node.children.is_empty() { + let declared = resolve_type_str(&node.extra_type); + let init = infer_expr(&node.children[0], symbols, fns); + if !types_compatible(&declared, &init) + && declared != TypeInfo::Unknown + && init != TypeInfo::Unknown + { + result.warnings += 1; + let line = if node.line > 0 { + format!(":{}", node.line) + } else { + String::new() + }; + result.errors.push(format!( + "warning: '{}' is declared {:?} and initialised from {:?}{}", + node.name, declared, init, line + )); + } + } // #920 bug 2: register the local in the CURRENT scope so subsequent // sibling statements can resolve it (was previously discarded). symbols.push(SymbolEntry { @@ -22110,7 +22139,14 @@ fn infer_expr(node: &Node, symbols: &[SymbolEntry], fns: &[FnEntry]) -> TypeInfo if node.value == "true" || node.value == "false" { return TypeInfo::Bool; } - if node.value.starts_with('"') { + // The parser marks a string literal with `extra_kind: "string"`. + // This tested the VALUE for a leading quote instead, and the lexeme + // does not always carry one -- so `var period_str : &str = "83.333";` + // fell through to the float branch below and the declaration was + // typed F64. Measured across the corpus: two such strings, both in + // specs/pins/emitter_xdc.t27, and neither was reported by anything + // because the declaration position was not compared at all. + if node.extra_kind == "string" || node.value.starts_with('"') { return TypeInfo::Str; } if node.value.parse::().is_ok() { diff --git a/bootstrap/stage0/FROZEN_HASH b/bootstrap/stage0/FROZEN_HASH index 4f63abe4b4..e6eabb7003 100644 --- a/bootstrap/stage0/FROZEN_HASH +++ b/bootstrap/stage0/FROZEN_HASH @@ -1 +1 @@ -f569da0c6c1415111f74b62680370272b7542ef1fe7f66c68267cbd71f2806df +472b902a8cef8b3f673c72e8e2735bad6eff4511b6869e35a7ae178090e26b8c diff --git a/docs/now/2026-08-30-one-rule-four-positions-three-treatments.md b/docs/now/2026-08-30-one-rule-four-positions-three-treatments.md new file mode 100644 index 0000000000..bd3eb3a56d --- /dev/null +++ b/docs/now/2026-08-30-one-rule-four-positions-three-treatments.md @@ -0,0 +1,8 @@ +# NOW -- One rule, four positions, three treatments (2026-08-30) + +## One rule, four positions, three treatments (Refs #2864) + +- #920 rejects narrowing F64 -> F32. Measured where the rule actually applies: ASSIGNMENT is an error, an ARGUMENT is a warning printed under a 'Typecheck OK' header, and a DECLARATION with an annotation was not compared at all. A return value is not compared either. +- Added the declaration comparison at the same severity the argument position uses -- a warning, so no ratchet can move. Cost across the corpus: 18 warnings in 13 files, not the noise I expected. +- Two of those were the sharp ones: 'var period_str : &str = "83.333";' typed F64. infer_expr tests the VALUE for a leading quote while the parser marks the node extra_kind=string, and the lexeme does not always carry the quote -- so a quoted string whose text parses as a float became a float. +- Fixed by reading the marker. specs/pins/emitter_xdc.t27 now typechecks: 627 -> 628 specs, zero regressions, bootstrap ratchet holds. 16 warnings remain -- 8 U64 <- F64, 7 F32 <- F64 -- countable debt rather than a silence. diff --git a/tools/specs_generate_baseline.txt b/tools/specs_generate_baseline.txt index c60602c354..cc68e9660e 100644 --- a/tools/specs_generate_baseline.txt +++ b/tools/specs_generate_baseline.txt @@ -83,4 +83,4 @@ specs/tri/collections/bitset.t27 | Error: Compile error: Expected LParen, got Kw specs/vm/jit_semantics.t27 | Error: Compile error: Expected LBrace, got Semicolon (';') at line 73:58 specs/vsa/packed_vsa.t27 | Error: Compile error: Expected LBrace, got Semicolon (';') at line 58:74 specs/vsa/sequence_hdc.t27 | Error: Compile error: Expected LBrace, got Semicolon (';') at line 96:88 -test_highlight.t27 | Error: Compile error: Unexpected top-level token: KwModule ('module') at line 4:5 +test_highlight.t27 | Error: Compile error: parse error at module level near line 21: Unexpected token in expression: Power ('**') at line 21:22