Object types
An object type describes a record of named properties, each with its own type and optional readonly or optional flag, written as an anonymous shape and usually named with a type alias. Assignability is structural and checks both width and depth. There is no interface keyword, no index signatures, and no excess-property check.
An object type describes a record: a set of named properties, each with its own type. You write one as an anonymous shape and usually give it a name with a type alias.
type User = { id: number; name: string }
type M = { readonly id: number; label?: string }
If you have used TypeScript, these are its object types with one notable subtraction: there is no interface keyword. Every object type is an anonymous structural shape, named only through a type alias. You describe what a value looks like, not what class it came from.
What a member carries
Each property in an object type has four pieces of information: its name, its value type, whether it is readonly, and whether it is optional (marked with ?). A readonly property may be read but not reassigned. An optional property may be absent entirely.
type M = { readonly id: number; label?: string }
What you can assign
Assignability is structural, and it checks both width and depth. A source value satisfies a target object type when, for every property the target declares, the source has a same-named property whose type is assignable to the target's. The source is free to have extra properties the target never mentions.
const p: { id: number; name: string } = { id: 1, name: "a", extra: true } // ok
const q: { id: number; label?: string } = { id: 1 } // ok, label optional
Three rules shape the details:
- A missing property is allowed only when the target marks it optional. A required property with no source counterpart is an error.
- A
readonlytarget property requires areadonlysource property. You cannot satisfy a read-only slot with a mutable one, because that would hand out a mutable view of something the target promised not to change. - Extra source properties are permitted. Unlike TypeScript, CruftScript has no excess-property check, so an object literal with more fields than the target declares is accepted rather than flagged.
const r: { readonly id: number } = mutableIdObject // error: readonly not satisfied
Where a position must match exactly rather than merely be assignable — for example a generic type argument, which is invariant — the test is stricter. There the two object types must have the same set of members, each agreeing on name, modifiers, and value type. Assignable-but-not-equal shapes that pass width subtyping are rejected in those positions.
Why it works this way
Structural typing lets you describe the shape a piece of code needs without forcing every caller to name a shared type, which keeps unrelated modules decoupled. Making readonly a real assignability constraint means the modifier carries a guarantee rather than a hint: a value typed as read-only cannot be aliased through a mutable type and quietly changed. The absence of an excess-property check is a deliberate simplification. Width subtyping already permits extra properties everywhere else, so the special case for fresh object literals was dropped for consistency.
Limitations
- No
interface. Object types exist only as anonymous shapes behindtypealiases. Theinterfacekeyword is not accepted syntax. - No index signatures. You cannot write
{ [key: string]: number }in an object type. There is no way to describe an open-ended set of keys this way today. - No excess-property check. An object with more properties than the target declares is accepted. If you rely on TypeScript flagging a misspelled property in a literal, that check does not happen here.