julia-script / julia-script/silk

type-system: Add payload-carrying enum variants

Open
#15 1 comment 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

backend blocked diagnostics enhancement lsp memory new feature P3 parser spec-change syntax type-system
Dominant language
TypeScript
Stars
48
Forks
0
Avg merge
4h 49m
Merged PRs (30d)
213

Description

Context

Silk already has runtime-tagged structural unions. A type such as A | B is a finite,
closed set of independently declared nominal members. The compiler owns its discriminant,
payload layout, contextual injection, widening, exhaustive matching, move behavior, and
active-member cleanup.

This ticket adds a different type-system tool: a nominal enum whose unit and payload
variants belong to one declaration. The new capability is nominal family identity and
declaration-scoped constructors, not runtime tagging.

Today, code that needs one closed conceptual family must declare a struct for every case
and repeat their structural union. The filesystem implementation has a concrete example:

struct DirectoryPresent {}
struct DirectoryMissing {}
struct DirectoryWrongType {}
struct DirectoryStatFailure { error: FileError }

fn classifyDirectory(
  outcome: Success<FileInfo | DirectoryInfo> | Failure<FileError>
) -> DirectoryPresent | DirectoryMissing | DirectoryWrongType | DirectoryStatFailure

Those four structs exist only as cases of one classification result. They are not useful
as independently reusable types.

The standard library's current Option<T> representation is not evidence that ordinary
Silk can name a structural union: semantic analysis currently normalizes the
silk/option.Option spelling specially. Generic enum parameters remain outside this
ticket, so migrating Option, Result, or Vector is not an acceptance criterion.

Why a structural union is not enough

Structural unions intentionally preserve the identity and independent usefulness of their
members:

  • one nominal member may participate in several unions;
  • an immediate context may inject a member or widen a smaller union;
  • two equivalent member sets normalize to the same structural type.

A nominal enum makes the opposite guarantees:

  • each variant is constructed and matched through its enum;
  • a variant is not a standalone source type;
  • two enum declarations remain distinct even when their variant names and payload shapes
    are identical;
  • no contextual injection, widening, or implicit conversion crosses the enum boundary.

A transparent type alias would remove repeated spelling but would not provide these
nominal guarantees. Wrapping a structural union in a struct provides an outer nominal
identity, but still requires independently declared member structs and an extra
destructuring layer.

Both structural unions and nominal enums therefore remain useful.

Current behavior

A user writes one struct for each case and joins them with |:

struct TextToken {
  start: usize
  end: usize
}

struct NumberToken {
  value: u64
}

struct EndToken {}

fn width(token: TextToken | NumberToken | EndToken) -> usize {
  return match token {
    TextToken { start, end } => end - start
    NumberToken { value } => counted(8)
    EndToken {} => counted(0)
  }
}

This works when the member types are meaningful independently. It is ceremony when the
members exist only as cases of one result, and it cannot express a distinct nominal
family over those cases.

Proposed form

pub enum Token {
  Text { start: usize, end: usize }
  Number { value: u64 }
  End
}

pub fn width(token: Token) -> usize {
  return match token {
    Token.Text { start, end } => end - start
    Token.Number { value } => counted(8)
    Token.End => counted(0)
  }
}

A repository-backed non-generic use can replace the filesystem helper structs:

enum DirectoryClassification {
  Present
  Missing
  WrongType
  StatFailure { error: FileError }
}

Requirements

  1. The parser must accept named-field payloads after an enum variant name.
  2. A payload field list must follow the same field-name, duplicate-name, type, visibility,
    and recovery rules as a struct field list unless this specification says otherwise.
  3. Unit and payload variants must be constructors and patterns qualified through the enum
    declaration, such as Token.End and Token.Text { ... }.
  4. A variant name must not resolve as a standalone source type.
  5. Two enum declarations must remain distinct nominal types even when their variant names
    and payload shapes are identical.
  6. Construction must validate missing, duplicate, unknown, inaccessible, and
    type-incompatible payload fields.
  7. A match over an enum must bind the selected variant's payload fields with the existing
    move, shared-borrow, and exclusive-borrow match modes.
  8. Exhaustiveness must use the enum declaration's complete variant set. Guards,
    universal patterns, duplicate arms, and unreachable arms must follow the existing
    exhaustive-matching rules.
  9. No implicit injection, widening, or conversion may occur between an enum and a
    structural union, or between two distinct enum types.
  10. Moving, borrowing, copying, and dropping an enum must operate on exactly its active
    variant. Cleanup must release the active payload exactly once after success, typed
    failure, defect, and interruption paths represented by the existing ownership model.
  11. Layout planning must reuse the existing compiler-owned union representation rules:
    one deterministic discriminant, payload storage sized and aligned for the largest
    variant, deterministic padding, and one fixed calling shape used by evaluation,
    native LLVM, and direct Wasm.
  12. Payload variants must inherit the discriminant rules established by issue #14.
    Adding payloads must not create a public ABI or serialization promise.
  13. Semantic analysis and lowering must operate from declaration identity and must not
    recognize a particular enum or variant by spelling, module path, or standard-library
    origin.

Out of scope

  • Changing structural-union identity, injection, widening, matching, or layout.
  • Generic parameters on an enum. A follow-up must design them before Option<T>,
    Result<A, E>, or generic storage families can migrate.
  • Transparent type aliases.
  • Methods on an enum type.
  • Automatic or implicit conversion between an enum and a structural union.
  • Stable C, FFI, serialization, or source-observable layout.
  • Migrating existing standard-library types in this ticket.

Implementation note

The backend representation can reuse structural-union planning, but the frontend
representation cannot treat a nominal enum as a structural member set. Name resolution,
construction, type identity, matching, diagnostics, semantic facts, HIR, ownership, and
tooling must preserve the enclosing enum declaration and variant identity.

Unit variants from issue #14 and payload variants from this ticket are one enum construct.
A unit variant is the zero-field case; this ticket must extend the same constructor,
pattern, exhaustiveness, and discriminant model rather than introduce a parallel tagged
type.

Dependencies

  • Issue #14 must establish enum declarations, qualified unit variants, variant patterns,
    nominal identity, discriminant rules, and exhaustiveness.

Acceptance criteria

  • The parser accepts unit and named-payload variants in one lossless enum declaration.
  • Construction accepts a complete payload and diagnoses missing, duplicate, unknown,
    inaccessible, and mismatched fields.
  • A semantic test proves that a variant name alone is not a type.
  • A semantic test proves that two same-shaped enum declarations are distinct.
  • A match test binds one payload through move, shared-borrow, and exclusive-borrow modes.
  • An omitted variant produces the existing uncovered-member diagnostic.
  • Duplicate, guarded, universal, and unreachable variant arms follow the existing
    coverage rules.
  • An equivalent structural union does not implicitly convert to or from the enum.
  • Layout tests prove deterministic discriminant, payload offset, maximum size and
    alignment, padding, and calling-shape parity across evaluator, LLVM, and Wasm.
  • Ownership tests prove that a move-only or Drop-bearing field is released exactly
    once only for the active variant.
  • Navigation, hover, completion, occurrences, and semantic encoding retain both enum
    and variant declaration identity.
  • The relevant syntax, semantic-type, exhaustive-matching, ownership, target-layout,
    HIR, MIR, evaluation, backend, and analysis-facade OpenSpecs are amended.

Contributor guide

No contributing guide indexed for this repository

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start by reading issue #14 and the existing structural-union implementation, then trace the syntax, semantic-type, matching, ownership, HIR, MIR, evaluator, LLVM, Wasm, and analysis-facade areas named here. Confirm the declaration and variant identities remain distinct from structural unions and that the listed semantic, layout, ownership, tooling, and OpenSpec acceptance criteria are covered.

Written by the indexing model from the issue text.

Assessment

Tech stack
typescript
Domain
compilers
Issue type
Feature
Difficulty
5/5
Estimated time
Over a week
Activity status
Quiet
Clarity
Needs clarification
Newbie friendliness
25/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.