Semantic Diff

#9040 Make @unboxed apply to a whole variant only

2 commits

5689a30531da7ab9700bd4464ec735f637312dae..pr-9040

cf19d26e4 · cknitt + Claude · Oct 25, 2026, 08:00 UTC

Make @unboxed apply to a whole variant only

Why

@unboxed on a single constructor made that constructor untagged and left the others tagged. Such mixed variants missed the checks of untagged ones (#9020) and interface and coercion checks (#9038), and their matches crashed or picked the wrong case. Public code uses the attribute on every payload constructor or on the only one, which the type-level @unboxed says, but no code mixes tagged and untagged cases.

@unboxed on a constructor is an error that says to write it on the type. The layout of a variant is untagged exactly when its type is @unboxed, so check_invariant no longer considers tagged and untagged blocks together, the type's @unboxed is no longer copied onto constructors, and process_untagged, a twin of has_untagged, is gone.

addedDiagnostic @unboxed on a constructor

type t = | @unboxed A(int) | B({x: int}) | C
diagnostic
Before
After
none; `A(3)` is `3` and `B` is `{TAG: "B", x: 1}`@unboxed applies to a whole variant: write it on the type, as in `@unboxed type t = ...`, instead of on constructor A.

addedDiagnostic @unboxed on a single-field inline record constructor

type t = | @unboxed A({x: int})
diagnostic
Before
After
none; `A({x: 1})` is `{x: 1}`@unboxed applies to a whole variant: write it on the type, as in `@unboxed type t = ...`, instead of on constructor A. For a constructor whose payload is an inline record with one field, `@unboxed type t = A({...})` makes the value that field. To keep the record object, give the record type a name: `@unboxed type t = A(r)`.
6d9792a5f · cknitt + Claude · Oct 25, 2026, 08:38 UTC

Name the unboxed variant correctly in the coercion error

The error for a coercion between variants with different @unboxed configurations said that the wrong one was unboxed.

modifiedDiagnostic unboxed configurations

@unboxed type x = One(bool) | Twotype y = One(bool) | Two
diagnostic
Before
After
- Variant x is not unboxed.- Variant y is unboxed.- Variant x is unboxed.- Variant y is not unboxed.