Skip to content

The declaration-policy axis: the same Rust, declared differently (#198, step 5) - #408

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

The declaration-policy axis: the same Rust, declared differently (#198, step 5)#408
milyin merged 2 commits into
shape-coveragefrom
shape-matrix-policy

Conversation

@milyin

@milyin milyin commented Aug 13, 2026

Copy link
Copy Markdown
Owner

Step 5 of #198 (the enumerable half), on shape-coverage (#402).

The choice that never varied

Every cell so far declared each type exactly one way — a struct as a value struct, a handle as a handle. But that is a decision a binding author makes, separate from the Rust, and while it never varies, whether it decides the answer is invisible.

Twelve curated cases. Not a product of shape × position × kind, which would quadruple the matrix to ask a question that is only interesting where a real binding has a real choice. Each row prints the varied answer beside the canonical one, because the difference is the point.

Three rows where the policy decides the answer

Case Canonical Declared as handle
Vec<Rec> parameter (JNI) rustc panic
Option<Rec> parameter (C) bad rust header
Mode return (JNI) rustc bad rust
  • Vec<Rec> as a parameter crosses when Rec is a value struct and is refused — by panic — when Rec is a handle. Same Rust, same position: the Vec-of-handles rule is about the declaration, and nothing in the value axis could have shown that.
  • Option<Rec> as a C parameter is the reverse. The canonical run emits Rust that does not compile (the move out of a raw pointer, already a finding from step 2), and declaring Rec as a handle avoids it — so that defect belongs to the by-value path, not to Option. The axis narrowed an existing finding for free.
  • A fieldless enum returned as an opaque handle is refused at compile time by a generated assertion: "opaque handle types must have alignment >= 2 (bit 0 is the closed tag)". Mode is byte-aligned, so the tagged-pointer representation cannot hold it.

The third one is a finding about this report, not the generator

That refusal is correct — a compile-time guard with a clear message beats undefined behaviour. But it lands as bad rust, exactly like genuinely broken output, because the state comes from whether rustc accepted the file and messages are deliberately kept out of the committed report (they vary by toolchain).

I did not paper over it with an error-code heuristic: basing a state on E0080 would make the report toolchain-sensitive, which is the one property that has kept it identical across 1.85 and stable. Instead the legend now says what bad rust covers, and points at #191 — a refusal arriving as a compile error rather than as a named rejection at declaration time is precisely that issue's subject. The run's stderr distinguishes the two.

Scope: why this is "the enumerable half"

The four kinds varied here are the JNI adapter's own closed vocabulary, which is what makes the axis enumerable at all. C is measured through the same four by the existing translation, so its rows are real — but its coverage is not: repr_c_struct, opaque_data_struct, callback and the rest have nothing to enumerate against until the C build API is reworked (#192). The branch map records that rather than implying the axis is complete.

Exit: answers move, additively

24 new cells; no pre-existing cell changed, verified per cell rather than by reading the diff.

Integrity test in the spirit of the others: a policy case must actually vary a declaration. A case whose override matched the canonical kind would print a row duplicating the one beside it — proving nothing while looking like evidence.

Checks

cargo test -p shape-matrix (17 tests), clippy -D warnings over all targets and features, cargo fmt --check with CI's config, RUSTDOCFLAGS=-D warnings cargo doc, examples/regen-check.sh clean.

milyin added 2 commits August 14, 2026 01:25
… step 5, JNI vocabulary)

Every cell so far declared each type exactly one way — a struct as a value
struct, a handle as a handle. That is a **choice a binding author makes**,
separate from the Rust, and while it never varies, whether it decides the answer
is invisible.

Twelve cases, curated rather than exhaustive: a full product of shape × position
× kind would quadruple the matrix to ask a question that is only interesting
where a real binding has a real choice. Each row prints the varied answer beside
the canonical one, because the difference is the point.

Three rows where the policy decides the answer:

* `Vec<Rec>` as a **parameter**: crosses when `Rec` is a value struct, and JNI
  refuses it — by panic — when `Rec` is a handle. Same Rust, same position; the
  Vec-of-handles rule is about the declaration, and nothing in the value axis
  could have shown that.
* `Option<Rec>` as a C parameter: the canonical run emits Rust that does not
  compile (the move out of a raw pointer, already a finding), and declaring
  `Rec` as a handle **avoids it** — so that defect belongs to the by-value
  path, not to `Option`.
* A fieldless enum returned as an opaque handle: refused at compile time by a
  generated assertion — *"opaque handle types must have alignment >= 2 (bit 0 is
  the closed tag)"*. `Mode` is byte-aligned, so the tagged-pointer
  representation cannot hold it.

That last one is a **correct refusal**, and it exposed a gap in this report's
vocabulary rather than in the generator: a deliberate compile-time rejection and
genuinely broken output both land as `bad rust`, since the states come from
whether rustc accepted the file and messages are deliberately kept out of the
committed report. The legend now says so, and points at #191 — a refusal
arriving as a compile error rather than a named rejection at declaration time is
exactly that issue's subject.

**Scope.** The four kinds varied here are the JNI adapter's own closed
vocabulary, which is what makes the axis enumerable at all. C is measured
through the same four via the existing translation, so its rows are real but its
*coverage* is not: `repr_c_struct`, `opaque_data_struct`, `callback` and the
rest have nothing to enumerate against until the C build API is reworked (#192).

**Exit: answers move**, additively: 24 new cells, no pre-existing cell changed.

Integrity test in the spirit of the others: a policy case must actually vary a
declaration. A case whose override matches the canonical kind would print a row
duplicating the one beside it — proving nothing while looking like evidence.

Part of #198, tracked by #399.
@milyin
milyin merged commit 890b5ac 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