diff --git a/crates/levcs-store/tests/d0_contract.rs b/crates/levcs-store/tests/d0_contract.rs index 4407ac5..46d4a95 100644 --- a/crates/levcs-store/tests/d0_contract.rs +++ b/crates/levcs-store/tests/d0_contract.rs @@ -315,9 +315,12 @@ const FORBIDDEN: &[&str] = &[ /// ``` /// /// the first column-zero `}` is *`shipping_code`'s*, so every line of it was -/// skipped. A semicolon-terminated item therefore exempts only itself, and any -/// third shape — including an item header rustfmt has split across lines — is a -/// scanner error rather than a guess. +/// skipped. A semicolon-terminated item therefore exempts only itself. Of the +/// braced shapes, this crate currently needs only `mod` and `impl`, so those are +/// the only two accepted: merely ending a line in `{` is not enough, because a +/// semicolon-terminated `static` or `const` can begin a block initializer there +/// and close with `};`. Any other shape — including an item header rustfmt has +/// split across lines — is a scanner error rather than a guess. fn scan_for_unfunnelled_calls(text: &str) -> Result, String> { #[derive(PartialEq)] enum Exempt { @@ -361,7 +364,9 @@ fn scan_for_unfunnelled_calls(text: &str) -> Result, String index + 1 )); }; - if item.ends_with('{') { + let item = item.trim_start(); + let recognized_braced_item = item.starts_with("mod ") || item.starts_with("impl "); + if item.ends_with('{') && recognized_braced_item { exempt = Exempt::UntilUnindentedBrace; index = head + 1; continue; @@ -493,6 +498,29 @@ fn ship() { "a `#[cfg(test)] use ...;` must not exempt the function that follows it" ); + // A semicolon-terminated item can *start* with a line ending in `{`. It is + // not a braced item: the closure belongs to the initializer, and the item + // closes with `});`. Treating the first `{` as the item's delimiter latches + // the exemption through `ship`, exactly like the single-line `use` defect + // above. `static` is not one of the two braced shapes this crate needs, so + // the scanner refuses to guess where it ends. + let block_initializer = "\ +#[cfg(test)] +static HOOK: LazyLock<()> = LazyLock::new(|| { + setup(); +}); + +fn ship() { + std::fs::write(p, b\"\"); +} +"; + let reason = scan_for_unfunnelled_calls(block_initializer) + .expect_err("a block initializer must not be mistaken for a braced test item"); + assert!( + reason.contains("cannot bound") && reason.contains("static HOOK"), + "the scanner must name the unsupported item rather than latch its exemption: {reason}" + ); + // Stacked attributes and doc comments between the trigger and the item. let stacked = "\ #[cfg(test)] diff --git a/doc/instance-throughput-rewrite-plan.md b/doc/instance-throughput-rewrite-plan.md index 14e5d19..4defcc5 100644 --- a/doc/instance-throughput-rewrite-plan.md +++ b/doc/instance-throughput-rewrite-plan.md @@ -1284,13 +1284,16 @@ and the failure is the signal that the documented assumption changed. §3.1 reco the "directory holding only `LOCK` is empty" special case that startup state 1 depends on. **The funnel guard's exemption could still latch, and the fix is a scanner that refuses to guess.** -Bounding a `#[cfg(test)]` exemption at the next column-zero `}` is right for a braced item and wrong -for every other shape: after `#[cfg(test)] use crate::test_support;` the first such brace belongs to -the *next* function, so all of it was skipped. The scanner now reads the shape of the attributed -item — braced items are exempt to their closing brace, semicolon-terminated items exempt only -themselves, and any third shape (including an item header rustfmt split across lines) **fails the -guard** rather than being assumed safe. A shape the scanner cannot bound is not a shape it may -treat as harmless. +Bounding a `#[cfg(test)]` exemption at the next column-zero `}` is right for a recognized braced +item and wrong for every other shape: after `#[cfg(test)] use crate::test_support;` the first such +brace belongs to the *next* function, so all of it was skipped. A first repair still accepted +anything whose first item line ended in `{`; that was the same latch in another spelling, because a +semicolon-terminated `static` or `const` can begin a block initializer there and close with `};`. +The scanner now accepts only the two braced top-level shapes the crate actually uses (`mod` and +`impl`), exempts a one-line semicolon-terminated item only for that line, and **fails the guard** on +every other shape (including a block initializer or an item header rustfmt split across lines) +rather than assuming it is safe. A shape the scanner cannot bound is not a shape it may treat as +harmless. It is also now a function over `&str` with synthetic tests, which is the load-bearing half. Mutating real sources only ever probes the shapes those sources happen to contain: no file in the crate has a