Skip to content

Ask rustc whether the emitted Rust compiles (#198, step 2) - #403

Merged
milyin merged 2 commits into
shape-coveragefrom
shape-matrix-compile
Aug 13, 2026
Merged

Ask rustc whether the emitted Rust compiles (#198, step 2)#403
milyin merged 2 commits into
shape-coveragefrom
shape-matrix-compile

Conversation

@milyin

@milyin milyin commented Aug 13, 2026

Copy link
Copy Markdown
Owner

Step 2 of #198 (in part), on the shape-coverage branch (#402).

Why

plan said the generator produced a file. That is a weaker claim than it looks: an emission can be well-formed, contain every substring a unit test looks for, and still not type-check — examples/emitcheck exists because that happened once with 41 of 41 tests green over it.

Every cell that produces Rust is now handed to rustc.

The state is a receipt, not a claim

Each cell is written to <id>.rs, the whole crate is checked in one pass, and each diagnostic is attributed back by the file rustc names. Nothing maps a cell to a fixture by hand — that mapping is exactly what let #175's regression test pass without ever creating the precondition it claimed to test.

Verified to discriminate, like the coverage gate in #400: the_compile_check_separates_good_from_bad puts one compiling and one non-compiling unit through the real path and requires them separated, with the failure attributed.

Two deliberate omissions from the committed report:

  • compiler messages — they vary by toolchain, and cargo test --all runs on both 1.85 and stable, so a message in the file would make the report disagree with itself between jobs. Failing cells print diagnostics on stderr.
  • caret dependency ranges — the check crate has its own lockfile, so jni/tracing/konst are pinned exactly. Otherwise an upstream release could move a cell with nothing in this repo having changed.

Exit: answers move

138 of 288 cells, in four classes and no others:

Transition n What it is
planrustc 120 the new evidence
planbad rust 10 the findings, below
8 Option<&T> / Vec<&T> in a field or payload
rejectedpanic 0 nothing moved between the refusal states

The 8 that became n/a were never legal Rust: the borrow rule only excused a spelling starting with &, so a borrow nested inside one was being measured against a struct that needs a lifetime parameter in order to exist.

Three harness defects, each of which had been producing a confident wrong answer

Turning the compiler on was mostly an audit of my own fixtures:

  1. the source crate was mounted as mod probe, beside a generated pub fn probe wrapper. Two different things sharing a name; the module is flat now.
  2. a returned borrow has no lifetime to elide from-> &Handle is not Rust at all. Every borrow-returning cell had reported plan for a fixture that could not have compiled. The fixture writes 'static now, which keeps the shape and adds no parameter.
  3. impl Display for ZError was being fed to the model, which correctly refuses an item kind the flat language does not have — failing 32 cells for a reason that had nothing to do with their shape. The model now sees the four item kinds a #[prebindgen] surface declares, the same filter emitcheck applies to its own source file.

Worth stating plainly: without the compiler, the first two were invisible and the third looked like a legitimate result.

What the 10 findings are

All confirmed in the generator's own output, not in fixture scaffolding (line numbers past the fixture module, checked by hand):

Kotlin/JNI

  • emits Cow<'static, str> unqualified into the consumer's scope — a consumer that has not imported Cow cannot compile the file (3 cells: param, field, payload);
  • mismatched types for a &mut T parameter, and for &mut MaybeUninit<T>.

C

  • emits flat::Option<Handle> — a std type qualified into the source module;
  • moves out of a value behind a raw pointer, for Option<data struct> and Option<sum> parameters;
  • calls the source function with one argument too many for a &[T] return;
  • builds a map/collect over a Vec<&T> return whose closure is a function item of the wrong signature.

I have not filed these as individual issues yet — they are cells in a committed report now, which is the point, and several look like they share a cause worth diagnosing together.

Cost

~11s added to a run, cached between runs; the check crate lives under target/ and is cleaned by cargo clean. It uses its own target directory so a nested cargo never queues behind the parent's lock.

Checks

cargo test -p shape-matrix (9 tests), cargo clippy --all-targets --all-features -- -D warnings, cargo fmt --check with CI's config, RUSTDOCFLAGS=-D warnings cargo doc, and examples/regen-check.sh clean with every other committed artifact byte-identical.

milyin added 2 commits August 13, 2026 23:35
`plan` said the generator produced a file. That is a weak claim: an emission can
be well-formed, contain every substring a unit test looks for, and still not
type-check — `examples/emitcheck` exists because that happened once with 41 of
41 tests green over it. Every cell that produced Rust is now compiled.

The state is a **receipt**, not a claim. Each cell is written to `<id>.rs`, the
whole crate is checked in one pass, and each diagnostic is attributed back by
the file rustc names. Nothing maps a cell to a fixture by hand — that mapping is
what let #175's test pass without creating its own precondition. Verified to
discriminate: `the_compile_check_separates_good_from_bad` feeds one compiling
and one non-compiling unit through the real path and requires them separated.

Compiler messages stay OUT of the committed report — they vary by toolchain, and
`cargo test --all` runs on both 1.85 and stable, so a message in the file would
make the report disagree with itself across jobs. Failing cells print their
diagnostics on stderr. The check crate's dependencies are pinned exactly for the
same reason: it has its own lockfile, so a caret range would let an upstream
release move a cell with nothing in this repo having changed.

**Exit: answers move.** 138 of 288 cells, in four classes:

* 120 `plan` -> `rustc` — the new evidence.
* 10 `plan` -> `bad rust` — emitted Rust that does not compile. See below.
* 8 -> n/a — `Option<&T>` and `Vec<&T>` in a field or payload. Those fixtures
  were never legal Rust; the borrow rule only excused a spelling *starting* with
  `&`, so a borrow nested inside one was being measured against a struct that
  needs a lifetime parameter to exist.
* no cell moved between `rejected` and `panic`.

Three harness defects found by turning the compiler on, each of which had been
producing a confident wrong answer:

* the source crate was mounted as `mod probe` beside a generated `pub fn probe`
  wrapper — two different things sharing a name, so it is `flat` now;
* a returned borrow has no lifetime to elide from, so `-> &Handle` is not Rust.
  Every borrow-returning cell had reported `plan` for a fixture that could not
  compile; the fixture writes `'static` now;
* `impl Display for ZError` was being fed to the *model*, which correctly refuses
  an item kind the flat language does not have — failing 32 cells for a reason
  unrelated to their shape. The model now sees the four item kinds a
  `#[prebindgen]` surface declares, the same filter `emitcheck` applies.

What the 10 findings are, all confirmed in the generator's own output rather
than in fixture scaffolding:

* JNI emits `Cow<'static, str>` **unqualified** into the consumer's scope, so a
  consumer that has not imported `Cow` cannot compile the file (3 cells);
* C emits `flat::Option<Handle>` — a std type qualified into the source module;
* C moves out of a value behind a raw pointer for `Option<data struct>` and
  `Option<sum>` parameters;
* C calls the source function with one argument too many for a `&[T]` return,
  and builds a `map`/`collect` over a `Vec<&T>` return whose closure is a
  function item of the wrong signature;
* JNI mismatches types for a `&mut T` parameter and for `&mut MaybeUninit<T>`.

Part of #198, tracked by #399.
Splits the receipts step in two: rustc accepts the emitted Rust (done), and the
rest of the toolchain plus the runtime states (not started).
@milyin
milyin merged commit 06c56cd into shape-coverage Aug 13, 2026
6 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant