Skip to content

Per-constructor @unboxed is not compared across interfaces or coercions #9038

Closed

Description

@cknitt

Whether a constructor is @unboxed (untagged) is not compared across interfaces or in coercions:

module M: {
  type t = | @unboxed A(int) | B({x: int})
  let a: t
} = {
  type t = | A(int) | B({x: int})
  let a = A(3)
}
let n = switch M.a { | A(i) => i | B(_) => -1 }   // -1: the client expects 3, the implementation built {TAG: "A", _0: 3}

The reverse direction is accepted too. So is a coercion between the two representations:

type a = | @unboxed A(int) | B({x: int})
type b = | A(int) | B({x: int}) | C
let x: b = (A(3): a :> b)   // 3 is not a b: matching it gives C

Also, when the @unboxed configurations of two variants differ, the coercion error says the wrong variant is unboxed ("Variant c is unboxed" for the plain one).

Observed on master (d48d577). Cause:

  • Includecore.compare_variants compares tags of tagged constructors only, so a tagged and an untagged constructor agree.
  • The coercion check compares only the declared @as (Variant_coercion.variant_representation_matches).
  • Printtyp.print_variant_configuration_issue inverts "not".

Expected: an untagged constructor agrees only with an untagged one, in interfaces and coercions alike, and the error names the unboxed variant.

Found by a review of the untagged variant work (#8950).

Activity

  1. added theissue type on Oct 25, 2026
  2. added a commit that references this issue on Oct 25, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions