Literals
Numbers, strings, and booleans are the primitive literal forms. Booleans are modeled set-theoretically as the union of the two literal values true and false — so every boolean expression has type Bool = true | false.
number literal solves to Number
Inferred
Reading tip: "solves to" is how we say "the type checker worked out its type to be …". A type is a compact description of what a value can be — Number is the set of all numbers. Every expression in TypeFighter has a type; the job of the inference engine is to figure that type out, usually without you writing it down.
F# test body
X.Lit 42
|> solve [] None
|> shouldSolveType (Mono BuiltinTypes.number)
string literal solves to String
Inferred
Mirrors the number case: a string literal is self-evidently a String. These baseline cases are the anchors the rest of inference builds on — if literals can't be trusted, nothing built from them can be either.
F# test body
X.Lit "hello"
|> solve [] None
|> shouldSolveType (Mono BuiltinTypes.string)
true literal solves to Bool
Inferred
Bool is not a primitive here — it's the set-theoretic union true | false. Each literal (true, false) is already a type on its own, and Bool is the union of those two literal types. So when we say the literal true "solves to Bool", we mean the inferencer widened its exact literal type to the enclosing union. (Numeric and string literals, by contrast, collapse straight to Number/String — only booleans are modelled this way today.)
F# test body
X.Lit true
|> solve [] None
|> shouldSolveType (Mono BuiltinTypes.boolean)
false literal solves to Bool
Inferred
The companion to the true test. Confirms the Bool = true | false modelling is symmetric — both literals widen to the same union type. Without this, there'd be a suspicion that the encoding only works for one side.
F# test body
X.Lit false
|> solve [] None
|> shouldSolveType (Mono BuiltinTypes.boolean)