pub enum Type {
Variable(Name),
Type(QualName, Vec<Type>),
Record(BTreeMap<Name, Type>),
Arrow(Box<Type>, Box<Type>),
Tuple(Tuple<Type>),
Unit,
}Expand description
A canonical type.
§Why there is no span on this type, or on TypeConstructor
Every other canonical node this module builds carries a [NodeSpan] taken from
the parser node it came from. A Type deliberately does not.
The reason ERR-3 recorded is that a Type does not always come from the module
being canonicalized: Type::from_parser_type resolves a name through the
Environment, which clones types straight out of the Interfaces of the
modules this one imports, so the Type handed back may well have been written
in a different file — and a bare span on it would be read as a position in the
importing module’s source. ERR-5 settled that half: a SpanLabel can now
carry a file of its own, so “written in another file” is no longer the obstacle.
The other half is unfinished work, not an impossibility. parser::Type carries a
[NodeSpan] per node like every other parser node, and Type::from_parser_type
is the recursive walk over it — tpe.span is in hand at every level, and each
branch of an Arrow arrives as its own spanned parser::Type. Those spans are
discarded here by choice. Keeping them means a span field on every canonical
Type node, plus a decision about what a Type cloned out of an Environment
should then report — where it was written, or where it was used. Nobody has
written that.
What exists instead is coarser and enough for the diagnostics that exist: the
declaration a Type is the type of — Value’s own span, UnionType’s,
Infix’s — is what a diagnostic wants to underline (“defined here”), not a
position inside the type expression itself. Interface::values pairs each
value’s Type with that declaration’s [NodeSpan], and
Interface::source_span pairs it with the file, once Interface::file is
set. A type error still points at the whole declaration that failed rather than
the sub-expression that disagrees, which is ERR-4, a separate limit in the
typer’s own Term/Constraint translation.
Variants§
Variable(Name)
Type(QualName, Vec<Type>)
A named type applied to its arguments, the name being the QualName of
the declaration it resolved to.
The module is part of the identity of the type, not decoration on it: a
type Size in Widget and a type Size in Gadget are two types, and
two values of them are interchangeable nowhere. Carrying only the
spelling made them one value here, which is what AST-4 closed. The
package is part of it for the same reason: a local Size and a wrapped
dependency’s, reached as AcmeWidgets.Size, both declare Size.Size.
The name is the declaration’s, never the spelling that reached it.
import Widget as W followed by W.Size records Widget.Size, the same
rule name resolution states for
values: an alias names a route to a declaration and not a second
declaration. A dependency’s namespace is a route too.
Record(BTreeMap<Name, Type>)
A record type: each label, and the type of the field it names.
A map rather than the list the source wrote, because a record type is a set
of fields: { a : Int, b : Char } and { b : Char, a : Int } are one type, and are one value
here. The map is ordered by label (Name’s Ord), so walking it is the
label order two spellings of a type agree on. A label appears once:
Type::from_parser_type reports a repeated one as Error::RepeatedLabel
and builds no Record.
Arrow(Box<Type>, Box<Type>)
Tuple(Tuple<Type>)
A tuple type. Zelkova keeps Elm’s restriction of two or three elements,
which [Tuple] carries in its shape.
Unit
The unit type, ().
A form of its own rather than a Type::Type naming a declaration: () is
syntax, declared nowhere and resolved through no environment, so no module can
declare a type that shares its spelling and there is no name for a lookup to
find. Contrast the scalars, which are declared in std/core and known by
the qualified name of that declaration.