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
42 changes: 42 additions & 0 deletions .claude/skills/ci-gates/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
2 changes: 1 addition & 1 deletion .trinity/seals/EmitterXDC.json
Original file line number Diff line number Diff line change
@@ -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",
Expand Down
4 changes: 2 additions & 2 deletions .trinity/seals/pins_EmitterXDC.json
Original file line number Diff line number Diff line change
@@ -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"
Expand Down
38 changes: 37 additions & 1 deletion bootstrap/src/compiler.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21897,6 +21897,35 @@ fn check_stmt(node: &Node, symbols: &mut Vec<SymbolEntry>, 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 {
Expand Down Expand Up @@ -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::<i64>().is_ok() {
Expand Down
2 changes: 1 addition & 1 deletion bootstrap/stage0/FROZEN_HASH
Original file line number Diff line number Diff line change
@@ -1 +1 @@
f569da0c6c1415111f74b62680370272b7542ef1fe7f66c68267cbd71f2806df
472b902a8cef8b3f673c72e8e2735bad6eff4511b6869e35a7ae178090e26b8c
Original file line number Diff line number Diff line change
@@ -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.
2 changes: 1 addition & 1 deletion tools/specs_generate_baseline.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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
Loading