// A declared field's TYPE, as data the runtime can walk.
//
// This is the schema's type lattice projected into the value world. It used to
// arrive here flattened into four independent parts of `FieldInfo` — a `ty`
// string holding the WIT spelling, a `kind` from a parallel ten-case enum, an
// `elem` string, a `slot` string and a `members` list — which had two costs.
//
// A caller could not DESCEND. An inspector holding `ty = "list"` and
// `elem = "card"` has two strings and no way to ask what a `card` is, so it
// stops at the first level. And nothing tied the parts together: a hand-written
// descriptor picked the spelling and the kind separately, so `("rows",
// "value-omap", FMap)` was accepted while the kind an ordered map actually has
// is `FOMap`. Here the spelling and the kind are both FUNCTIONS of the type, so
// they cannot disagree.
//
// It is a PROJECTION of `@statedef.StateTy`, not the same enum. Two differences,
// both because generation-time lookups are not available here: a `flags` set
// carries its members inline rather than by reference to a declaration, and a
// record / enum / variant keeps only its name — enough to label it, not enough
// to open it. Opening those is what the instance itself answers.
///|
/// What a declared field carries.
pub(all) enum TyInfo {
TyBool
/// Every `s8`..`s32` / `u8`..`u32`. The width is a decode-time range check,
/// which the generated codec has already applied by the time a value is here.
TyInt
TyFloat
TyText
TyList(TyInfo)
TyTuple(Array[TyInfo])
TyOption(TyInfo)
/// A named record. The name labels it; its fields are the instance's to
/// answer.
TyRecord(String)
TyEnum(String)
TyVariant(String)
/// A set with CLOSED membership, carrying the members the schema declared —
/// the one thing about a set that only the schema knows, since the encoded
/// value is just a Map.
TyFlags(String, Array[String])
/// A set with OPEN membership.
TySet
/// An ordered map. WIT's own `map` is unordered by definition, so this is a
/// refinement of it rather than a spelling for it.
TyOMap(TyInfo)
/// A child-component slot; None when the schema said only `component`.
TyComp(String?)
/// Tabular data: typed columns of equal width (`tables.table`).
///
/// The one named shape whose contents the schema DOES know. `TyRecord` keeps
/// a name and nothing else because a record's fields are the instance's to
/// answer; a table is standard, so the projection can describe it fully, a
/// validator can check it, and an agent can be told how to write one. That
/// is the whole reason it is a case here rather than `TyRecord("table")`.
TyTable
TyAny
} derive(Eq, Debug)
///|
/// How the author wrote it. The WIT spelling, because that is what is in the
/// file and what they will edit.
pub fn TyInfo::show_source(self : TyInfo) -> String {
match self {
TyBool => "Bool"
TyInt => "Int"
TyFloat => "Double"
TyText => "String"
TyList(e) => "Array[" + e.show_source() + "]"
TyTuple(ts) => "(" + ts.map(t => t.show_source()).join(", ") + ")"
TyOption(e) => e.show_source() + "?"
TyRecord(n) | TyEnum(n) | TyVariant(n) => n
// A closed set names its enum, which is where the membership is written.
TyFlags(n, _) => "Set[" + n + "]"
TySet => "Set[String]"
TyOMap(v) => "Map[String, " + v.show_source() + "]"
TyComp(Some(c)) => "Component[" + c + "]"
TyComp(None) => "Component"
TyTable => "Table"
TyAny => "Any"
}
}
///|
/// What this type CONTAINS, or None when it contains nothing.
///
/// Strict, and deliberately not the same question `@statedef.StateTy::elem`
/// answers. That one asks "may a view `@each` over this?", where `any` and a
/// child slot must both say yes — a dynamic value may turn out to be a list and
/// a component iterates its own entries, neither knowable at generation time.
/// Answering the two with one method put a meaningless `elem: any` on 117
/// fields of the corpus and showed it in the inspector, where the question is
/// the other one: what is in here?
pub fn TyInfo::elem(self : TyInfo) -> TyInfo? {
match self {
TyList(e) | TyOMap(e) => Some(e)
// A set iterates its members, which are text either way.
TySet | TyFlags(_, _) => Some(TyText)
_ => None
}
}
///|
/// The component a child slot holds, or None for a data field. `Some("")` is a
/// slot whose component the schema did not name.
pub fn TyInfo::slot(self : TyInfo) -> String? {
match self {
TyComp(Some(c)) => Some(c)
TyComp(None) => Some("")
_ => None
}
}
///|
/// A closed set's declared members. Empty for every other type, and for a set
/// with open membership — which is a real answer, not a missing one.
pub fn TyInfo::members(self : TyInfo) -> Array[String] {
match self {
TyFlags(_, ms) => ms
_ => []
}
}
///|
/// The runtime kind: how a value of this type is REPRESENTED, once the shape
/// that distinguishes it no longer matters.
///
/// A projection rather than a stored field, which is the point — the kind used
/// to be written beside the spelling and could contradict it.
pub fn TyInfo::kind(self : TyInfo) -> FieldKind {
match self {
TyBool => FBool
TyInt => FInt
TyFloat => FFloat
// An enum is payload-free, so it travels as its case name.
TyText | TyEnum(_) => FText
TyList(_) | TyTuple(_) => FList
// A table travels as `{"columns": [...]}` — a Map, like a record. What
// makes it different is what the SCHEMA can say about it, not how the
// value is carried.
TyRecord(_) | TyTable => FMap
// A variant travels as EITHER a bare case name or a case name followed by
// its payload — `Str("Ping")` or `List(["SetTo", 7])`. No single narrow
// kind covers both, and the old table said `FMap`, which covers neither: a
// write of a perfectly good variant value hit `coerce_or_default`, failed
// the `Map` shape test and was replaced by the field's default.
TyVariant(_) => FAny
TySet | TyFlags(_, _) => FSet
TyOMap(_) => FOMap
// The slot's construction arguments are not part of its TYPE; the component
// layer fills them in from what the author wired.
TyComp(c) => FComp(comp=c.unwrap_or(""), args=Map([]))
// An `option` is its value or absent, so the kind is the widest one: the
// field may hold the inner shape or nothing.
TyOption(_) | TyAny => FAny
}
}
///|
/// The empty value of this type, as the runtime carries it.
///
/// Type-directed, like the generated `State::zero()` — which is the point:
/// a default is not an independent fact about a field, so storing one beside
/// the type would be a second thing to keep true. The one default a TYPE cannot
/// give is a child slot's construction arguments, and those are a value the
/// author chooses rather than anything the schema knows.
///
/// This is what an inspector shows in a field's "default" column, reachable
/// from a bare `Value` — it used to come off `Component.specs`, which a holder
/// of an instance and no registry could not get to.
pub fn TyInfo::zero(self : TyInfo) -> Value {
match self {
TyBool => Bool(false)
TyInt | TyFloat => Num(0.0)
TyText | TyEnum(_) => Str("")
TyList(_) | TyTuple(_) => List([])
TyRecord(_) | TyOMap(_) => Map(Map([]))
// No columns and therefore no rows. Spelled out rather than left as an
// empty Map because `{"columns": []}` is a VALID table and `{}` is not:
// the zero of a table should decode, not fail the first check made of it.
TyTable => Map({ "columns": List([]) })
// Both sets start EMPTY, and the closed one used to not.
//
// A `flags` field began with every member present, because the type
// carried its membership and the zero was written from the type — so a
// fresh `visibility` was both `Done` and `Active`, which no author has
// ever meant. Spelled `Set[Visibility]` the CONTAINER decides the zero and
// the enum decides the membership, which is one idea in each place.
TyFlags(_, _) | TySet => Map(Map([]))
// A variant's zero is its first case, which this cannot name; an option
// is absent; a slot is filled by the registration scope at make time.
TyVariant(_) | TyOption(_) | TyComp(_) | TyAny => Null
}
}