Skip to content

CEP-0001: Concrete Type Unions

Draft

Draft proposal for future concrete value unions. V1 still supports | only for enum unions and error-set unions.

Summary

Catalyst should eventually support closed concrete type unions over ordinary concrete value types.

Example

Possible future shape:

const Fact = ImplementsFact | SatisfiesFact | TypeKindFact | DynSafeContractFact

A value of any member type should coerce into the union when an expected type supplies the union:

const fact: Fact = SatisfiesFact{
  .subject = T,
  .shape = Shape,
  .scope = scope,
}

This proposal captures the ergonomic direction. It does not change V1, where | remains limited to closed enum unions and error-set unions.

Motivation

Some Catalyst metadata needs to return heterogeneous but closed sets of values. Predicate reflection facts are the motivating example:

const Fact = ImplementsFact | SatisfiesFact | TypeKindFact | DynSafeContractFact

Without concrete type unions, V1 must model this either as separate fact arrays or as one struct with optional fields whose validity depends on a tag. Separate arrays are V1-compatible, but they lose the ergonomic shape of a single deterministic list. Nullable tag-dependent fields are compact, but they make invalid states easy to represent.

Concrete type unions provide the intended shape: one closed value type, implicit member injection, and no nullable fields that depend on a separate kind tag.

Proposed Direction

A | B over concrete value types creates a closed concrete union type when every operand is a concrete value type accepted by the union rules.

If future constraint unions use the same | spelling, this proposal must either include their normalization and narrowing rules explicitly or split them into a separate CEP before acceptance.

Member values coerce into the union in expected-type contexts:

fn record(fact: Fact) void

record(TypeKindFact{ .subject = T, .kind = .sized })

The union stores exactly one member value at a time. It is not a constraint, interface, structural type, or dynamic dispatch surface.

Direct field or method access through a concrete union is not available without narrowing. A union value does not expose the intersection of member fields by default.

Dependencies

Concrete type unions depend on a narrowing story. The likely dependencies are:

  • pattern matching over closed unions;
  • an is operator or equivalent type test for union members;
  • reflection for union member types;
  • deterministic diagnostics for ambiguous or unreachable narrowing.

V1 pattern matching and general runtime narrowing are deferred, so this proposal is not a V1 feature.

Representation Direction

Concrete type unions should have a predictable closed representation:

  • a discriminant identifying the active member;
  • payload storage large and aligned enough for any member;
  • no heap allocation implied by the union itself;
  • copyability only when every member is copyable;
  • cleanup by active member when any member has resource cleanup.

The first accepted version should not promise C ABI layout. External representation, @repr controls, and ABI guarantees can be designed later.

Open Questions

  • Are union operands restricted to sized concrete types in the first version?
  • Are duplicate member types and aliases normalized by semantic type identity?
  • How does A | B | C normalize and display in diagnostics?
  • What source syntax narrows a concrete union value?
  • Should member-to-union coercion be implicit only in expected-type contexts?
  • Do constraint unions share this syntax, or do they need a separate accepted model?
  • How should equality, ordering, and hashing conformances be derived or rejected?
  • How does resource cleanup interact with partially initialized union payloads?

V1 Compatibility

V1 remains unchanged:

  • | composes only closed enum types/enum-union types and closed error sets/error-set unions.
  • General A | B type unions are rejected outside those families.
  • Predicate reflection should use V1-compatible metadata shapes until this proposal is accepted.