Skip to content

Emit the Kotlin too, and ask cbindgen for the header (#198, step 2b) - #405

Merged
milyin merged 3 commits into
shape-coveragefrom
shape-matrix-header
Aug 13, 2026
Merged

Emit the Kotlin too, and ask cbindgen for the header (#198, step 2b)#405
milyin merged 3 commits into
shape-coveragefrom
shape-matrix-header

Conversation

@milyin

@milyin milyin commented Aug 13, 2026

Copy link
Copy Markdown
Owner

Step 2b of #198, on shape-coverage (#402). Two stages cells were passing without ever reaching.

JNI produced no Kotlin at all

The driver called write_rust and stopped. So a rustc cell had shown half of what the target emits — and not the half a Kotlin caller ever sees. The binding now writes its Kotlin too, and a binding whose Kotlin cannot be written (or that produces none) is a rejection like any other.

No cell moved. Every JNI cell that emits Rust also emits Kotlin, so this buys no new state today. It closes the hole where one would otherwise have hidden — a Kotlin-emission regression was previously invisible to every cell in the table.

C stopped at rustc, which is the wrong finish line

What a C consumer is given is a header, produced from the emitted Rust by cbindgen, and the two can disagree: a signature rustc is perfectly happy with can be one cbindgen skips, renders as an opaque it never defines, or cannot name. A cell whose wrapper is missing from the header is a cell no C program can call.

header is now the top C state, pinned to the same cbindgen version example-cbindgen uses — so this judges the header that example produces, not another tool's opinion of the same Rust.

The receipt is a declaration, not an Ok

cbindgen returns Ok for a header that declares nothing, so "it ran" would have been a state established by nothing. The receipt is that the wrapper is declared: the exported name at a word boundary followed by an open parenthesis, because cbindgen carries doc comments through and a receipt that can pass on prose is not a receipt.

the_header_stage_requires_a_declaration is the discrimination check. The negative control is the same Rust with extern "C" removed — still valid Rust, still parsed happily, still useless to a C caller:

extern fn: declared=true  detail=None
plain fn:  declared=false detail=Some("cbindgen produced a header that does not declare the wrapper")

Exit: answers move — one class, 45 cells

Transition n
rustcheader 45

Every C cell whose Rust compiles also yields a header declaring its wrapper. So this stage adds evidence, not findings — which is worth having on its own terms: it is the difference between "no C cell is known to be broken here" and "no C cell has been asked".

The ladders differ by target now

C runs one stage further than JNI, and the report says so rather than levelling to the shorter one — printing rustc for a C cell whose header is fine would throw away the stronger evidence.

Target header rustc bad rust rejected panic n/a
C 45 0 5 41 31 22
Kotlin/JNI 0 75 5 34 8 22

Left for 2c

The Kotlin is written but never compiled, so the JNI ladder still ends one stage short of C's; closing it needs kotlinc in CI. RuntimeExercised needs the JVM covertest and the C smoke tests, under the same receipt rule.

Checks

cargo test -p shape-matrix (10 tests), clippy -D warnings on all targets and features, 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 3 commits August 13, 2026 23:50
…#198, step 2b)

Two stages a cell was passing without ever reaching.

**JNI produced no Kotlin.** The driver called `write_rust` and stopped, so a
`rustc` cell had shown half of what the target emits — the half that is not what
a Kotlin caller ever sees. The binding now writes its Kotlin too, and a binding
whose Kotlin cannot be written, or that produces none at all, is a rejection like
any other. No cell moved: every JNI cell that emitted Rust also emits Kotlin, so
this buys no new state today and closes the hole where one would have hidden.

**C stopped at rustc, which is the wrong finish line.** What a C consumer gets is
a header, produced from the emitted Rust by cbindgen, and the two can disagree:
a signature rustc accepts can be one cbindgen skips or cannot name. `header` is
now the top C state, pinned to the same cbindgen version `example-cbindgen` uses
so this judges the header that example produces rather than another tool's
opinion of the same Rust.

The receipt is **not** "cbindgen returned `Ok`" — it returns `Ok` for a header
that declares nothing. It is that the wrapper is declared: the name followed by
an open parenthesis, since cbindgen carries doc comments through and a receipt
that passes on prose is not a receipt. `the_header_stage_requires_a_declaration`
is the discrimination check, with the negative control being the same Rust minus
`extern "C"` — still valid, still parsed happily, still useless to a C caller.

**Exit: answers move**, one class only: 45 cells `rustc` -> `header`. Every C
cell whose Rust compiles also yields a header declaring its wrapper, so the
stage adds evidence rather than findings. That is worth having anyway — it is
the difference between "no C cell is known to be broken here" and "no C cell has
been asked".

The ladders now differ by target, and the report says so rather than levelling
to the shorter one: C runs one stage further, JNI stops at rustc because this
crate does not run the Kotlin compiler.

Part of #198, tracked by #399.
The Kotlin is emitted but not compiled, so the JNI ladder still ends one stage
short of C's.
@milyin
milyin merged commit afe21fd 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