Skip to content
Joshua Reynolds edited this page Oct 7, 2026 · 1 revision

Types

Declare the data required by your contracts. Built-ins are always available and need no use or depends on.

Built-in types

Type Meaning
Text Text values
Number Numeric values; no separately declared integer primitive
Boolean true or false
List<T> A list with element type T
Nothing An explicitly valueless callable result

Do not redefine these built-in names or replace them with import aliases. Nothing is for a complete callable result, including an alias to it. It is not a field/parameter type or a member of a tuple, list, optional or union.

Record types

main.expec

type ShoppingCart {
  itemsCount: Number
}
type PlayerStateSnapshot {
  characterPosition: [Number, Number]
  shoppingCart: ShoppingCart
}
examples {
  fixture start: PlayerStateSnapshot = PlayerStateSnapshot {
    characterPosition: [0, 0],
    shoppingCart: ShoppingCart { itemsCount: 0 },
  }
}

Records have nominal identity: two distinct record declarations do not become interchangeable merely because their fields match. A record literal can state its type, or receive an unambiguous expected record type from its context. Unknown, duplicate or missing required fields are errors.

Optional fields

A suffix ? makes a type optional. An optional record field may be omitted. It does not permit silently using an optional value where a nonoptional value is required.

main.expec

type Book {
  title: Text
  subtitle: Text?
  available: Boolean = true
}
examples {
  fixture dune: Book = Book { title: "Dune", available: true }
}

subtitle is optional, so the fixture omits it. available has an authored default, but a fixture must still supply explicit data for every nonoptional field: it does not inherit or evaluate type defaults. Ordinary record expressions and generated defaults have their own checked/output rules. Record-field defaults do not implicitly see sibling fields. See Fixtures.

Aliases and generics

main.expec

type Pair<T> = [T, T]
type Position = Pair<Number>
type Box<T> {
  value: T
}
type BookTitle = Text
type State {
  position: Position
  labels: List<Box<BookTitle>>
}

type Name = ... is a transparent alias. type Name { ... } declares a record. Generic parameters are scoped to their declaring type. Supply the correct number of arguments; there is no inference of omitted type arguments. Generic applications are checked without silently changing their argument types.

Record recursion can be meaningful; a transparent alias cycle is invalid.

Tuples, unions and literal types

main.expec

type Position = [Number, Number]
type Mode = "preview" | "live"
type Selection = "small" | 0 | -1 | true
type Settings {
  position: Position
  mode: Mode
  label: (Text | Number)?
}

A tuple is a nonempty ordered type list and requires the exact number and order of values. A tuple is not automatically a List.

A union A | B accepts either alternative. Optionality binds before union: Text | Number? differs from (Text | Number)?. Literal types restrict a value to specific text, numbers or Booleans. An expression such as "preview" is a Text value; its type context can require that particular literal.

Opaque types

main.expec

opaque type Token
opaque type Handle<T>
function resume(token: Token) returns Nothing
function obtain() returns Handle<Text>

An opaque declaration names a type without exposing record fields. It is not an empty record and cannot be constructed as a record literal. Code output support for such a type depends on the chosen profile.

Related: Expressions and operators · Domain errors · Output support

Clone this wiki locally