///|
/// Internal types. `Con` names are canonical FQName text.
#derive(Eq, Debug)
priv enum Ty {
  Meta(Int32)
  /// A declared variable, by key: `|`, where the owner is the canonical FQName of the
  /// declaration that declares it (see `rigid_key`). The class set comes from the `typeclass` constraint of
  /// the variable, else its name prefix, so the variable carries its classes wherever it goes.
  Rigid(String, Array[String])
  /// A quantified variable inside a `Poly`.
  Gen(Int32)
  Con(String, Array[Ty])
  Fun(Ty, Ty)
  Tup(Array[Ty])
  Union(Array[Ty])
  Attributed(@ir.TypeAttributes, Ty)
  /// Fields by canonical name. The tail is a `Meta` or a `Rigid`; `None` is a closed record.
  Rec(Map[String, Ty], Ty?)
  UnitTy
}

///|
/// The state of a meta variable. An unbound meta has a binding level and a class set of at most one class.
priv enum MetaState {
  Unbound(level~ : Int32, classes~ : Array[String])
  Link(Ty)
}

///|
/// A type scheme. `gens[i]` holds the classes of `Gen(i)`.
priv struct Poly {
  gens : Array[Array[String]]
  body : Ty
}

///|
/// A unification failure: a code, the expected type and the found type.
/// The code is `type_mismatch`, `occurs`, `rigid_variable`, `no_instance` or `missing_field`.
priv suberror Mismatch {
  Mismatch(String, Ty, Ty)
}

///|
/// The substitution and the context of unification.
priv struct Solver {
  metas : Array[MetaState]
  /// Alias expansion. `None` means the type is nominal.
  expand : (String, Array[Ty]) -> Ty?
}