Skip to content

Commit 9c5dae6

Browse files
gHashTaggHashTag
andauthored
test: extend tests_compiler_rejects beyond casts (#1113)
Adds five tests grouped by empirically-observed outcome: drop-to-TODO (unclosed paren, malformed binop), hard compile error (unterminated module, value-less const), and a characterization of the known stray-identifier leak. Tests-only; pins the parser/codegen rejection contract so future regressions fail loudly. Closes #1110 Co-authored-by: gHashTag <admin@t27.ai>
1 parent b73f45f commit 9c5dae6

2 files changed

Lines changed: 116 additions & 0 deletions

File tree

bootstrap/src/compiler.rs

Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19759,6 +19759,114 @@ mod tests_compiler_rejects {
1975919759
v
1976019760
);
1976119761
}
19762+
19763+
// -----------------------------------------------------------------------
19764+
// Variant K: extend the contract beyond casts. The parser/codegen reacts to
19765+
// malformed input in three distinct, empirically-observed ways; each is
19766+
// pinned here so a future change that alters the behavior fails loudly.
19767+
//
19768+
// (a) DROP-TO-TODO -- an expression that fails mid-parse triggers
19769+
// statement-level recovery; the statement is dropped
19770+
// and the body is left `// TODO: implement`.
19771+
// (b) HARD ERROR -- a malformation the *module* parser cannot recover
19772+
// from (unterminated module, value-less const)
19773+
// aborts the whole compile with Err.
19774+
// (c) SILENT LEAK -- a stray token that still lexes as an identifier is
19775+
// accepted as a bare no-op statement and leaks into
19776+
// codegen. This is a KNOWN GAP, not a guarantee; the
19777+
// test characterizes the current behavior so the gap
19778+
// is visible and a future fix is detected.
19779+
// -----------------------------------------------------------------------
19780+
19781+
/// Helper: compile and return Ok(verilog) or the Err string, without
19782+
/// panicking, so module-level hard errors can be asserted directly.
19783+
fn try_emit(src: &str) -> Result<String, String> {
19784+
Compiler::compile_verilog(src)
19785+
}
19786+
19787+
// (a) Unclosed parenthesis in a return expression -> drop-to-TODO.
19788+
#[test]
19789+
fn rejects_unclosed_paren() {
19790+
let v = emit(
19791+
r#"module RejUnclosedParen {
19792+
pub fn f(x: u8) -> u8 {
19793+
return (x + 1
19794+
}
19795+
}"#,
19796+
);
19797+
assert_dropped(&v, "f", &[]);
19798+
}
19799+
19800+
// (a) Malformed binary expression (`x + * 2`) -> drop-to-TODO.
19801+
#[test]
19802+
fn rejects_malformed_binop() {
19803+
let v = emit(
19804+
r#"module RejBadBinop {
19805+
pub fn g(x: u8) -> u8 {
19806+
return x + * 2
19807+
}
19808+
}"#,
19809+
);
19810+
assert_dropped(&v, "g", &[]);
19811+
}
19812+
19813+
// (b) Unterminated module (missing closing brace) -> HARD compile error.
19814+
#[test]
19815+
fn rejects_unterminated_module() {
19816+
let r = try_emit(
19817+
r#"module RejUnterminated {
19818+
pub fn f(x: u8) -> u8 {
19819+
return x
19820+
}"#,
19821+
);
19822+
assert!(
19823+
r.is_err(),
19824+
"an unterminated module must fail to compile, got Ok:\n{:?}",
19825+
r
19826+
);
19827+
}
19828+
19829+
// (b) Const declaration with no value (`const W : u32 =`) -> HARD error.
19830+
#[test]
19831+
fn rejects_const_without_value() {
19832+
let r = try_emit(r#"module RejBadConst { pub const W : u32 = }"#);
19833+
assert!(
19834+
r.is_err(),
19835+
"a value-less const must fail to compile, got Ok:\n{:?}",
19836+
r
19837+
);
19838+
}
19839+
19840+
// (c) KNOWN GAP characterization: a stray token that still lexes as an
19841+
// identifier (`frobnicate`) is currently accepted as a bare no-op statement
19842+
// and LEAKS into codegen as `frobnicate;`, and the following `return` still
19843+
// lowers. This is NOT the desired contract -- it is pinned so the leak is
19844+
// visible and any future hardening of the parser is detected by this test
19845+
// failing (at which point it should be converted to an `assert_dropped`).
19846+
#[test]
19847+
fn characterizes_stray_ident_leak_known_gap() {
19848+
let v = emit(
19849+
r#"module GapStrayIdent {
19850+
pub fn k(x: u8) -> u8 {
19851+
frobnicate x
19852+
return x
19853+
}
19854+
}"#,
19855+
);
19856+
// Current (undesired) behavior: the stray identifier leaks as a no-op
19857+
// statement and the body is NOT dropped.
19858+
assert!(
19859+
v.contains("frobnicate;"),
19860+
"KNOWN GAP changed: stray ident no longer leaks as `frobnicate;`. \
19861+
If the parser now rejects it, convert this test to assert_dropped. Got:\n{}",
19862+
v
19863+
);
19864+
assert!(
19865+
!v.contains("// TODO: implement"),
19866+
"KNOWN GAP changed: body is now dropped; update this characterization. Got:\n{}",
19867+
v
19868+
);
19869+
}
1976219870
}
1976319871

1976419872
#[cfg(test)]

docs/NOW.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,14 @@
22

33
Last updated: 2026-06-14
44

5+
## compiler-negative-tests-ext -- extend the parser negative-tests gate beyond casts (Closes #1110)
6+
7+
- **WHERE**: `bootstrap/src/compiler.rs` (`#[cfg(test)] mod tests_compiler_rejects`: new helper `try_emit` + five tests appended after `accepts_valid_cast_as_control`).
8+
- **WHAT**: #1104 added the negative-tests gate but it only covered `as`-casts. This pass extends it with cases empirically grouped by the three distinct, observed reactions to malformed input. (a) DROP-TO-TODO: an unclosed paren in a return expr (`return (x + 1`) and a malformed binop (`x + * 2`) trigger statement-level recovery -- the statement is dropped and the body left `// TODO: implement` (asserted via `assert_dropped`). (b) HARD ERROR: an unterminated module (missing closing brace) and a value-less const (`const W : u32 =`) abort the whole compile with `Err` (asserted via the new `try_emit` helper + `.is_err()`). (c) KNOWN GAP characterization: a stray token that still lexes as an identifier (`frobnicate x`) currently LEAKS into codegen as `frobnicate;` and does NOT drop the body -- pinned as a characterization test, honestly documented as undesired current behavior, so the gap is visible and any future hardening of the parser is detected (at which point it is converted to an `assert_dropped`).
9+
- **Why** statement-level recovery is deliberate but makes parser rejections invisible, and a single cast-only gate under-specifies the contract; pinning all three observed outcome classes (including the honest gap) means a future change that lets malformed input leak into Verilog, or that silently changes recovery behavior, fails loudly. Tests-only, no production code change. Full suite passes with the five new tests (`tests_compiler_rejects` module: 10 passed / 0 failed), 0 regressions. L6 gf16 SSOT untouched; catalog stays 83; no gen/ edits; ASCII-only added lines; no quality claim added. Closes #1110.
10+
- **Anchor**: phi^2 + phi^-2 = 3
11+
12+
513
## seal-pre-push-hook -- advisory seal-staleness check at push time + make seal-check (Closes #1109)
614

715
- **WHERE**: new top-level `Makefile` and `scripts/install-git-hooks.sh` (the installed L4 pre-push hook).

0 commit comments

Comments
 (0)