julia-script / julia-script/silk
type-system: Add payload-carrying enum variants
Nobody has claimed this yet.
- 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
- The parser must accept named-field payloads after an enum variant name.
- 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. - Unit and payload variants must be constructors and patterns qualified through the enum
declaration, such asToken.EndandToken.Text { ... }. - A variant name must not resolve as a standalone source type.
- Two enum declarations must remain distinct nominal types even when their variant names
and payload shapes are identical. - Construction must validate missing, duplicate, unknown, inaccessible, and
type-incompatible payload fields. - A match over an enum must bind the selected variant's payload fields with the existing
move, shared-borrow, and exclusive-borrow match modes. - 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. - No implicit injection, widening, or conversion may occur between an enum and a
structural union, or between two distinct enum types. - 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. - 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. - Payload variants must inherit the discriminant rules established by issue #14.
Adding payloads must not create a public ABI or serialization promise. - 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
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- 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