cl-typeck: Implement a framework for type inference

...And also implement a bunch of the inference rules, too.
This commit is contained in:
John 2025-04-21 04:26:07 -04:00
parent 7ba808594c
commit 4c4b49ce00
11 changed files with 917 additions and 270 deletions

View File

@ -1,12 +1,20 @@
use cl_typeck::{entry::Entry, stage::*, table::Table, type_expression::TypeExpression};
use cl_typeck::{
entry::Entry,
stage::{
infer::{engine::InferenceEngine, error::InferenceError, inference::Inference},
*,
},
table::Table,
type_expression::TypeExpression,
};
use cl_ast::{
Expr, Path, Stmt, Ty,
ast_visitor::{Fold, Visit},
desugar::*,
Stmt, Ty,
};
use cl_lexer::Lexer;
use cl_parser::{inliner::ModuleInliner, Parser};
use cl_parser::{Parser, inliner::ModuleInliner};
use cl_structures::intern::string_interner::StringInterner;
use repline::{error::Error as RlError, prebaked::*};
use std::{
@ -44,8 +52,15 @@ fn main() -> Result<(), Box<dyn Error>> {
};
// This code is special - it gets loaded from a hard-coded project directory (for now)
let code = inline_modules(code, concat!(env!("CARGO_MANIFEST_DIR"), "/../../stdlib"));
let code = cl_ast::desugar::WhileElseDesugar.fold_file(code);
Populator::new(&mut prj).visit_file(interned(code));
for arg in std::env::args().skip(1) {
import_file(&mut prj, arg)?;
}
resolve_all(&mut prj)?;
main_menu(&mut prj)?;
Ok(())
}
@ -53,20 +68,22 @@ fn main() -> Result<(), Box<dyn Error>> {
fn main_menu(prj: &mut Table) -> Result<(), RlError> {
banner();
read_and(C_MAIN, "mu>", "? >", |line| {
match line.trim() {
"c" | "code" => enter_code(prj)?,
"clear" => clear()?,
"d" | "desugar" => live_desugar()?,
"e" | "exit" => return Ok(Response::Break),
"f" | "file" => import_files(prj)?,
"i" | "id" => get_by_id(prj)?,
"l" | "list" => list_types(prj),
"q" | "query" => query_type_expression(prj)?,
"r" | "resolve" => resolve_all(prj)?,
"s" | "strings" => print_strings(),
"h" | "help" | "" => {
println!(
"Valid commands are:
for line in line.trim().split_ascii_whitespace() {
match line {
"c" | "code" => enter_code(prj)?,
"clear" => clear()?,
"d" | "desugar" => live_desugar()?,
"e" | "exit" => return Ok(Response::Break),
"f" | "file" => import_files(prj)?,
"i" | "id" => get_by_id(prj)?,
"l" | "list" => list_types(prj),
"q" | "query" => query_type_expression(prj)?,
"r" | "resolve" => resolve_all(prj)?,
"s" | "strings" => print_strings(),
"t" | "test" => infer_expression(prj)?,
"h" | "help" | "" => {
println!(
"Valid commands are:
clear : Clear the screen
code (c): Enter code to type-check
desugar (d): WIP: Test the experimental desugaring passes
@ -77,10 +94,11 @@ fn main_menu(prj: &mut Table) -> Result<(), RlError> {
resolve (r): Perform type resolution
help (h): Print this list
exit (e): Exit the program"
);
return Ok(Response::Deny);
);
return Ok(Response::Deny);
}
_ => Err(r#"Invalid command. Type "help" to see the list of valid commands."#)?,
}
_ => Err(r#"Invalid command. Type "help" to see the list of valid commands."#)?,
}
Ok(Response::Accept)
})
@ -127,14 +145,47 @@ fn query_type_expression(prj: &mut Table) -> Result<(), RlError> {
if line.trim().is_empty() {
return Ok(Response::Break);
}
// parse it as a path, and convert the path into a borrowed path
let ty: Ty = Parser::new("", Lexer::new(line)).parse()?;
// A query is comprised of a Ty and a relative Path
let mut p = Parser::new("", Lexer::new(line));
let ty: Ty = p.parse()?;
let path: Path = p
.parse()
.map(|p| Path { absolute: false, ..p })
.unwrap_or_default();
let id = ty.evaluate(prj, prj.root())?;
let id = path.evaluate(prj, id)?;
pretty_handle(id.to_entry(prj))?;
Ok(Response::Accept)
})
}
fn infer_expression(prj: &mut Table) -> Result<(), RlError> {
read_and(C_RESV, "ex>", "!?>", |line| {
if line.trim().is_empty() {
return Ok(Response::Break);
}
let mut p = Parser::new("", Lexer::new(line));
let e: Expr = p.parse()?;
let mut inf = InferenceEngine::new(prj, prj.root());
let ty = match exp_terned(e).infer(&mut inf) {
Ok(ty) => ty,
Err(e) => match e {
InferenceError::Mismatch(a, b) => {
eprintln!("Mismatched types: {}, {}", prj.entry(a), prj.entry(b));
return Ok(Response::Deny);
}
InferenceError::Recursive(a, b) => {
eprintln!("Recursive types: {}, {}", prj.entry(a), prj.entry(b));
return Ok(Response::Deny);
}
e => Err(e)?,
},
};
eprintln!("--> {}", prj.entry(ty));
Ok(Response::Accept)
})
}
fn get_by_id(prj: &mut Table) -> Result<(), RlError> {
use cl_parser::parser::Parse;
use cl_structures::index_map::MapIndex;
@ -196,6 +247,32 @@ fn list_types(table: &mut Table) {
}
}
fn import_file(table: &mut Table, path: impl AsRef<std::path::Path>) -> Result<(), Box<dyn Error>> {
let Ok(file) = std::fs::read_to_string(path.as_ref()) else {
for file in std::fs::read_dir(path)? {
println!("{}", file?.path().display())
}
return Ok(());
};
let mut parser = Parser::new("", Lexer::new(&file));
let code = match parser.parse() {
Ok(code) => inline_modules(
code,
PathBuf::from(path.as_ref()).parent().unwrap_or("".as_ref()),
),
Err(e) => {
eprintln!("{C_ERROR}{}:{e}\x1b[0m", path.as_ref().display());
return Ok(());
}
};
let code = cl_ast::desugar::WhileElseDesugar.fold_file(code);
Populator::new(table).visit_file(interned(code));
Ok(())
}
fn import_files(table: &mut Table) -> Result<(), RlError> {
read_and(C_RESV, "fi>", "? >", |line| {
let line = line.trim();
@ -326,3 +403,12 @@ fn interned(file: cl_ast::File) -> &'static cl_ast::File {
INTERNER.get_or_insert(file).to_ref()
}
/// Interns an [Expr](cl_ast::Expr), returning a static reference to it.
fn exp_terned(expr: cl_ast::Expr) -> &'static cl_ast::Expr {
use cl_structures::intern::typed_interner::TypedInterner;
static INTERNER: LazyLock<TypedInterner<'static, cl_ast::Expr>> =
LazyLock::new(Default::default);
INTERNER.get_or_insert(expr).to_ref()
}

View File

@ -8,7 +8,7 @@
use std::collections::HashMap;
use cl_ast::{Meta, PathPart, Sym};
use cl_ast::{Expr, Meta, PathPart, Sym};
use cl_structures::span::Span;
use crate::{
@ -78,6 +78,10 @@ impl<'t, 'a> Entry<'t, 'a> {
self.table.imports(self.id)
}
pub fn bodies(&self) -> Option<&'a Expr> {
self.table.body(self.id)
}
pub fn ty(&self) -> Option<&TypeKind> {
self.table.ty(self.id)
}
@ -154,6 +158,10 @@ impl<'t, 'a> EntryMut<'t, 'a> {
self.table.add_child(self.id, name, child)
}
pub fn set_body(&mut self, body: &'a Expr) -> Option<&'a Expr> {
self.table.set_body(self.id, body)
}
pub fn set_ty(&mut self, kind: TypeKind) -> Option<TypeKind> {
self.table.set_ty(self.id, kind)
}

View File

@ -18,6 +18,8 @@ impl fmt::Display for Entry<'_, '_> {
if let Some(ty) = self.ty() {
match ty {
TypeKind::Uninferred => write!(f, "<_{}>", self.id),
TypeKind::Variable => write!(f, "<?{}>", self.id),
TypeKind::Instance(id) => write!(f, "{}", self.with_id(*id)),
TypeKind::Intrinsic(kind) => write!(f, "{kind}"),
TypeKind::Adt(adt) => write_adt(adt, self, f),

View File

@ -5,244 +5,405 @@
//! [1]: https://github.com/tcr/rust-hindley-milner/
//! [2]: https://github.com/rob-smallshire/hindley-milner-python
use cl_ast::Sym;
use core::fmt;
use std::{cell::RefCell, rc::Rc};
pub mod engine;
/*
Types in Conlang:
- Never type: !
- type !
- for<A> ! -> A
- Primitive types: bool, i32, (), ...
- type bool; ...
- Reference types: &T, *T
- for<T> type ref<T>; for<T> type ptr<T>
- Slice type: [T]
- for<T> type slice<T>
- Array type: [T;usize]
- for<T> type array<T, instanceof<usize>>
- Tuple type: (T, ...Z)
- for<T, ..> type tuple<T, ..> // on a per-case basis!
- Funct type: fn Tuple -> R
- for<T, R> type T -> R // on a per-case basis!
*/
pub mod error;
/// A refcounted [Type]
pub type RcType = Rc<Type>;
pub mod inference {
use std::iter;
#[derive(Debug, PartialEq, Eq)]
pub struct Variable {
pub instance: RefCell<Option<RcType>>,
}
use super::{engine::InferenceEngine, error::InferenceError};
use crate::{handle::Handle, type_kind::TypeKind};
use cl_ast::*;
#[derive(Debug, PartialEq, Eq)]
pub struct Operator {
name: Sym,
types: RefCell<Vec<RcType>>,
}
/// A [Type::Variable] or [Type::Operator]:
/// - A [Type::Variable] can be either bound or unbound (instance: Some(_) | None)
/// - A [Type::Operator] has a name (used to identify the operator) and a list of types.
///
/// A type which contains unbound variables is considered "generic" (see
/// [`Type::is_generic()`]).
#[derive(Debug, PartialEq, Eq)]
pub enum Type {
Variable(Variable),
Operator(Operator),
}
impl Type {
/// Creates a new unbound [type variable](Type::Variable)
pub fn new_var() -> RcType {
Rc::new(Self::Variable(Variable { instance: RefCell::new(None) }))
}
/// Creates a variable that is a new instance of another [Type]
pub fn new_inst(of: &RcType) -> RcType {
Rc::new(Self::Variable(Variable {
instance: RefCell::new(Some(of.clone())),
}))
}
/// Creates a new [type operator](Type::Operator)
pub fn new_op(name: Sym, types: &[RcType]) -> RcType {
Rc::new(Self::Operator(Operator {
name,
types: RefCell::new(types.to_vec()),
}))
}
/// Creates a new [type operator](Type::Operator) representing a lambda
pub fn new_fn(takes: &RcType, returns: &RcType) -> RcType {
Self::new_op("fn".into(), &[takes.clone(), returns.clone()])
}
/// Creates a new [type operator](Type::Operator) representing a primitive type
pub fn new_prim(name: Sym) -> RcType {
Self::new_op(name, &[])
}
/// Creates a new [type operator](Type::Operator) representing a tuple
pub fn new_tuple(members: &[RcType]) -> RcType {
Self::new_op("tuple".into(), members)
pub trait Inference<'a> {
/// Performs type inference
fn infer(&'a self, e: &mut InferenceEngine<'_, 'a>) -> Result<Handle, InferenceError>;
}
/// Sets this type variable to be an instance `of` the other
/// # Panics
/// Panics if `self` is not a type variable
pub fn set_instance(self: &RcType, of: &RcType) {
match self.as_ref() {
Type::Operator(_) => unimplemented!("Cannot set instance of a type operator"),
Type::Variable(Variable { instance }) => *instance.borrow_mut() = Some(of.clone()),
}
}
/// Checks whether there are any unbound type variables in this type.
/// ```rust
/// # use cl_typeck::stage::infer::*;
/// let bool = Type::new_op("bool".into(), &[]);
/// let true_v = Type::new_inst(&bool);
/// let unbound = Type::new_var();
/// let id_fun = Type::new_fn(&unbound, &unbound);
/// let truthy = Type::new_fn(&unbound, &bool);
/// assert!(!bool.is_generic()); // bool contains no unbound type variables
/// assert!(!true_v.is_generic()); // true_v is bound to `bool`
/// assert!(unbound.is_generic()); // unbound is an unbound type variable
/// assert!(id_fun.is_generic()); // id_fun is a function with unbound type variables
/// assert!(truthy.is_generic()); // truthy is a function with one unbound type variable
/// ```
pub fn is_generic(self: &RcType) -> bool {
match self.as_ref() {
Type::Variable(Variable { instance }) => match instance.borrow().as_ref() {
// base case: self is an unbound type variable (instance is none)
None => true,
// Variable is bound to a type which may be generic
Some(instance) => instance.is_generic(),
},
Type::Operator(Operator { types, .. }) => {
// Operator may have generic args
types.borrow().iter().any(Self::is_generic)
}
}
}
/// Makes a deep copy of a type expression.
///
/// Bound variables are shared, unbound variables are duplicated.
pub fn deep_clone(self: &RcType) -> RcType {
// If there aren't any unbound variables, it's fine to clone the entire expression
if !self.is_generic() {
return self.clone();
}
// There are unbound type variables, so we make a new one
match self.as_ref() {
Type::Variable { .. } => Self::new_var(),
Type::Operator(Operator { name, types }) => Self::new_op(
*name,
&types
.borrow()
.iter()
.map(Self::deep_clone)
.collect::<Vec<_>>(),
),
}
}
/// Returns the defining instance of `self`,
/// collapsing type instances along the way.
/// # May panic
/// Panics if this type variable's instance field is already borrowed.
/// # Examples
/// ```rust
/// # use cl_typeck::stage::infer::*;
/// let t_bool = Type::new_op("bool".into(), &[]);
/// let t_nest = Type::new_inst(&Type::new_inst(&Type::new_inst(&t_bool)));
/// let pruned = t_nest.prune();
/// assert_eq!(pruned, t_bool);
/// assert_eq!(t_nest, Type::new_inst(&t_bool));
/// ```
pub fn prune(self: &RcType) -> RcType {
if let Type::Variable(Variable { instance }) = self.as_ref() {
if let Some(old_inst) = instance.borrow_mut().as_mut() {
let new_inst = old_inst.prune(); // get defining instance
*old_inst = new_inst.clone(); // collapse
return new_inst;
}
}
self.clone()
}
/// Checks whether a type expression occurs in another type expression
///
/// # Note:
/// - Since the test uses strict equality, `self` should be pruned prior to testing.
/// - The test is *not guaranteed to terminate* for recursive types.
pub fn occurs_in(self: &RcType, other: &RcType) -> bool {
if self == other {
return true;
}
match other.as_ref() {
Type::Variable(Variable { instance }) => match instance.borrow().as_ref() {
Some(t) => self.occurs_in(t),
None => false,
},
Type::Operator(Operator { types, .. }) => {
// Note: this might panic.
// Think about whether it panics for only recursive types?
types.borrow().iter().any(|other| self.occurs_in(other))
}
impl<'a> Inference<'a> for cl_ast::Expr {
fn infer(&'a self, e: &mut InferenceEngine<'_, 'a>) -> Result<Handle, InferenceError> {
self.kind.infer(e)
}
}
/// Unifies two type expressions, propagating changes via interior mutability
pub fn unify(self: &RcType, other: &RcType) -> Result<(), InferenceError> {
let (a, b) = (self.prune(), other.prune()); // trim the hedges
match (a.as_ref(), b.as_ref()) {
(Type::Variable { .. }, _) if !a.occurs_in(&b) => a.set_instance(&b),
(Type::Variable { .. }, _) => Err(InferenceError::Recursive(a, b))?,
(Type::Operator { .. }, Type::Variable { .. }) => b.unify(&a)?,
(
Type::Operator(Operator { name: a_name, types: a_types }),
Type::Operator(Operator { name: b_name, types: b_types }),
) => {
let (a_types, b_types) = (a_types.borrow(), b_types.borrow());
if a_name != b_name || a_types.len() != b_types.len() {
Err(InferenceError::Mismatch(a.clone(), b.clone()))?
impl<'a> Inference<'a> for cl_ast::ExprKind {
fn infer(&'a self, e: &mut InferenceEngine<'_, 'a>) -> Result<Handle, InferenceError> {
match self {
ExprKind::Empty => Ok(e.from_type_kind(TypeKind::Empty)),
ExprKind::Quote(quote) => todo!("Quote: {quote}"),
ExprKind::Let(l) => {
// Infer the pattern
// Deep copy the ty, if it exists
// Unify the pattern and the ty
// Infer the initializer
// Unify the initializer and the ty
todo!("Let: {l}")
}
for (a, b) in a_types.iter().zip(b_types.iter()) {
a.unify(b)?
ExprKind::Match(m) => {
let Match { scrutinee, arms } = m;
// Infer the scrutinee
let scrutinee = scrutinee.infer(e)?;
let scope = e.new_var();
for arm in arms {
let _ty = arm.infer(e)?;
}
// For each pattern:
// Infer the pattern
// Unify it with the scrutinee
// Infer the Expr
// Unify the expr with the out variable
// Return out
todo!("Match: {m} {scrutinee} {scope}")
}
ExprKind::Assign(assign) => {
// Infer the tail expression
// Infer the head expression
// Unify head and tail
// Return Empty
todo!("Assign {assign}")
}
ExprKind::Modify(modify) => {
// Infer the tail expression
// Infer the head expression
// Search within the head type for `(op)_assign`
// Typecheck `op_assign(&mut head, tail)`
todo!("Modify {modify}")
}
ExprKind::Binary(binary) => {
let Binary { kind: _, parts } = binary;
let (head, tail) = parts.as_ref();
// Infer the tail expression
let tail = tail.infer(e)?;
// Infer the head expression
let head = head.infer(e)?;
// TODO: Search within the head type for `(op)`
e.unify(head, tail)?;
// Typecheck op(head, tail)
Ok(head)
}
ExprKind::Unary(unary) => {
let Unary { kind: _, tail } = unary;
// Infer the tail expression
let tail = tail.infer(e)?;
// TODO: Search within the tail type for `(op)`
// Typecheck `(op)(tail)`
Ok(tail)
}
ExprKind::Cast(cast) => {
// Infer the head expression
// Evaluate the type
// Decide whether the type is castable
// Return the type
todo!("Cast {cast}")
}
ExprKind::Member(member) => {
let Member { head, kind } = member;
// Infer the head expression
let head = head.infer(e)?;
// Get the type of head
let ty = e.entry(e.de_inst(head));
// Search within the head type for the memberkind
match kind {
MemberKind::Call(name, tuple) => match ty.nav(&[PathPart::Ident(*name)]) {
Some(ty) => match e.entry(e.de_inst(ty.id())).ty() {
Some(&TypeKind::FnSig { args, rety }) => {
let values = iter::once(Ok(ty.id()))
.chain(
tuple
.exprs
.iter()
// Infer each member
.map(|expr| expr.infer(e)),
)
// Construct tuple
.collect::<Result<Vec<_>, InferenceError>>()
// Return tuple
.map(|tys| e.from_type_kind(TypeKind::Tuple(tys)))?;
e.unify(args, values)?;
Ok(rety)
}
other => todo!("member-call {other:?}"),
},
None => Err(InferenceError::NotFound(Path::from(*name))),
},
MemberKind::Struct(name) => match ty.nav(&[PathPart::Ident(*name)]) {
Some(ty) => Ok(ty.id()),
None => Err(InferenceError::NotFound(Path::from(*name))),
},
MemberKind::Tuple(Literal::Int(idx)) => match ty.ty() {
Some(TypeKind::Tuple(tys)) => Ok(tys[*idx as usize]),
_ => Err(InferenceError::Mismatch(ty.id(), e.table.root())),
},
_ => Err(InferenceError::Mismatch(ty.id(), ty.root())),
}
// Type is required to be inferred at this point.
}
ExprKind::Index(index) => {
// Infer the head expression
// For each index expression:
// Infer the index type
// Decide whether the head can be indexed by that type
// head = result of indexing head
todo!("Index {index}")
}
ExprKind::Structor(structor) => {
// Evaluate the path in the current context
// Typecheck the fielders against the fields
todo!("Structor {structor}")
}
ExprKind::Path(path) => e
.by_name(path)
.map_err(|_| InferenceError::NotFound(path.clone())),
ExprKind::Literal(literal) => literal.infer(e),
ExprKind::Array(array) => {
let Array { values } = array;
let out = e.new_var();
for value in values {
let ty = value.infer(e)?;
e.unify(out, ty)?;
}
Ok(out)
}
ExprKind::ArrayRep(array_rep) => {
let ArrayRep { value, repeat } = array_rep;
let ty = value.infer(e)?;
Ok(e.from_type_kind(TypeKind::Array(ty, *repeat)))
}
ExprKind::AddrOf(addr_of) => {
let AddrOf { mutable: _, expr } = addr_of;
// TODO: mut ref
let ty = expr.infer(e)?;
Ok(e.from_type_kind(TypeKind::Ref(ty)))
}
ExprKind::Block(block) => block.infer(e),
ExprKind::Group(group) => {
let Group { expr } = group;
expr.infer(e)
}
ExprKind::Tuple(tuple) => tuple.infer(e),
ExprKind::While(w) => {
let While { cond, pass, fail } = w;
// Infer the condition
let cond = cond.infer(e)?;
// Unify the condition with bool
let boule = e
.primitive("bool".into())
.expect("Primitive bool should exist!");
e.unify(boule, cond)?;
// Enter a new breakset
let mut e = e.open_bset();
// Infer the fail branch
let fail = fail.infer(&mut e)?;
// Unify the fail branch with breakset
e.bset = fail;
// Infer the pass branch
let pass = pass.infer(&mut e)?;
// Unify the pass branch with Empty
let empt = e.from_type_kind(TypeKind::Empty);
e.unify(pass, empt)?;
// Return breakset
Ok(e.bset)
}
ExprKind::If(i) => {
let If { cond, pass, fail } = i;
// Do inference on the condition'
let cond = cond.infer(e)?;
// Unify the condition with bool
let boule = e
.primitive("bool".into())
.expect("Primitive bool should exist!");
e.unify(boule, cond)?;
// Do inference on the pass branch
let pass = pass.infer(e)?;
// Do inference on the fail branch
let fail = fail.infer(e)?;
// Unify pass and fail
e.unify(pass, fail)?;
// Return the result
Ok(pass)
}
ExprKind::For(f) => todo!("For {f}"),
ExprKind::Break(b) => {
let Break { body } = b;
// Infer the body of the break
let ty = body.infer(e)?;
// Unify it with the breakset of the loop
e.unify(ty, e.bset)?;
// Return never
Ok(e.from_type_kind(TypeKind::Never))
}
ExprKind::Return(r) => {
let Return { body } = r;
// Infer the body of the return
let ty = body.infer(e)?;
// Unify it with the return-set of the function
e.unify(ty, e.rset)?;
// Return never
Ok(e.from_type_kind(TypeKind::Never))
}
ExprKind::Continue => Ok(e.from_type_kind(TypeKind::Never)),
}
}
}
impl<'a> Inference<'a> for MatchArm {
fn infer(&'a self, e: &mut InferenceEngine<'_, 'a>) -> Result<Handle, InferenceError> {
let MatchArm(pat, expr) = self;
let table = &mut e.table;
let scope = table.new_entry(e.at, crate::table::NodeKind::Local);
let mut e = e.at(scope);
let pat_ty = pat.infer(&mut e)?;
// TODO: bind pattern variables in scope
let expr_ty = expr.infer(&mut e)?;
todo!("Finish pattern-matching: {pat_ty}, {expr_ty}")
}
}
impl<'a> Inference<'a> for Pattern {
fn infer(&'a self, e: &mut InferenceEngine<'_, 'a>) -> Result<Handle, InferenceError> {
match self {
Pattern::Name(name) => e
.table
.get_by_sym(e.at, name)
.ok_or(InferenceError::NotFound((*name).into())),
Pattern::Literal(literal) => literal.infer(e),
Pattern::Rest(_) => todo!("Infer rest-patterns"),
Pattern::Ref(_, pattern) => {
let ty = pattern.infer(e)?;
Ok(e.from_type_kind(TypeKind::Ref(ty)))
}
Pattern::RangeExc(pat1, pat2) => {
let ty1 = pat1.infer(e)?;
let ty2 = pat2.infer(e)?;
e.unify(ty1, ty2)?;
Ok(ty1)
}
Pattern::RangeInc(pat1, pat2) => {
let ty1 = pat1.infer(e)?;
let ty2 = pat2.infer(e)?;
e.unify(ty1, ty2)?;
Ok(ty1)
}
Pattern::Tuple(patterns) => {
let tys = patterns
.iter()
.map(|pat| pat.infer(e))
.collect::<Result<Vec<Handle>, InferenceError>>()?;
Ok(e.from_type_kind(TypeKind::Tuple(tys)))
}
Pattern::Array(patterns) => match patterns.as_slice() {
[one, rest @ ..] => {
let ty = one.infer(e)?;
for rest in rest {
let ty2 = rest.infer(e)?;
e.unify(ty, ty2)?;
}
Ok(e.from_type_kind(TypeKind::Slice(ty)))
}
[] => {
let ty = e.new_var();
Ok(e.from_type_kind(TypeKind::Slice(ty)))
}
},
Pattern::Struct(_path, _items) => todo!(),
Pattern::TupleStruct(_path, _patterns) => todo!(),
}
}
}
impl<'a> Inference<'a> for Tuple {
fn infer(&'a self, e: &mut InferenceEngine<'_, 'a>) -> Result<Handle, InferenceError> {
let Tuple { exprs } = self;
exprs
.iter()
// Infer each member
.map(|expr| expr.infer(e))
// Construct tuple
.collect::<Result<Vec<_>, InferenceError>>()
// Return tuple
.map(|tys| e.from_type_kind(TypeKind::Tuple(tys)))
}
}
impl<'a> Inference<'a> for Block {
fn infer(&'a self, e: &mut InferenceEngine<'_, 'a>) -> Result<Handle, InferenceError> {
let Block { stmts } = self;
let empty = e.from_type_kind(TypeKind::Empty);
if let [stmts @ .., ret] = stmts.as_slice() {
for stmt in stmts {
match (&stmt.kind, &stmt.semi) {
(StmtKind::Expr(expr), Semi::Terminated) => {
expr.infer(e)?;
}
(StmtKind::Expr(expr), Semi::Unterminated) => {
let ty = expr.infer(e)?;
e.unify(ty, empty)?;
}
_ => {}
}
}
match (&ret.kind, &ret.semi) {
(StmtKind::Expr(expr), Semi::Terminated) => {
expr.infer(e)?;
}
(StmtKind::Expr(expr), Semi::Unterminated) => {
return expr.infer(e);
}
_ => {}
}
}
Ok(empty)
}
Ok(())
}
}
impl fmt::Display for Type {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Type::Variable(Variable { instance }) => match instance.borrow().as_ref() {
Some(instance) => write!(f, "{instance}"),
None => write!(f, "_"),
},
Type::Operator(Operator { name, types }) => {
write!(f, "({name}")?;
for ty in types.borrow().iter() {
write!(f, " {ty}")?;
}
f.write_str(")")
impl<'a> Inference<'a> for Else {
fn infer(&'a self, e: &mut InferenceEngine<'_, 'a>) -> Result<Handle, InferenceError> {
self.body.infer(e)
}
}
impl<'a, I: Inference<'a>> Inference<'a> for Option<I> {
fn infer(&'a self, e: &mut InferenceEngine<'_, 'a>) -> Result<Handle, InferenceError> {
match self {
Some(expr) => expr.infer(e),
None => Ok(e.from_type_kind(TypeKind::Empty)),
}
}
}
}
impl<'a, I: Inference<'a>> Inference<'a> for Box<I> {
fn infer(&'a self, e: &mut InferenceEngine<'_, 'a>) -> Result<Handle, InferenceError> {
self.as_ref().infer(e)
}
}
/// An error produced during type inference
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum InferenceError {
Mismatch(RcType, RcType),
Recursive(RcType, RcType),
}
impl fmt::Display for InferenceError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
InferenceError::Mismatch(a, b) => write!(f, "Type mismatch: {a:?} != {b:?}"),
InferenceError::Recursive(_, _) => write!(f, "Recursive type!"),
impl<'a> Inference<'a> for Literal {
fn infer(&'a self, e: &mut InferenceEngine<'_, 'a>) -> Result<Handle, InferenceError> {
let ty = match self {
Literal::Bool(_) => Ok(e
.primitive("bool".into())
.expect("Primitive bool should exist!")),
Literal::Char(_) => Ok(e
.primitive("char".into())
.expect("Primitive char should exist!")),
Literal::Int(_) => Ok(e
.primitive("isize".into())
.expect("Primitive isize should exist!")),
Literal::Float(_) => Ok(e
.primitive("f64".into())
.expect("Primitive f64 should exist!")),
Literal::String(_) => Ok(e
.primitive("str".into())
.expect("Primitive str should exist!")),
}?;
Ok(e.new_inst(ty))
}
}
}

View File

@ -0,0 +1,358 @@
use super::error::InferenceError;
use crate::{
entry::Entry,
handle::Handle,
table::Table,
type_expression::TypeExpression,
type_kind::{Adt, TypeKind},
};
use cl_ast::Sym;
/*
Types in Conlang:
- Never type: !
- type !
- for<A> ! -> A
- Primitive types: bool, i32, (), ...
- type bool; ...
- Reference types: &T, *T
- for<T> type ref<T>; for<T> type ptr<T>
- Slice type: [T]
- for<T> type slice<T>
- Array type: [T;usize]
- for<T> type array<T, instanceof<usize>>
- Tuple type: (T, ...Z)
- for<T, ..> type tuple<T, ..> // on a per-case basis!
- Funct type: fn Tuple -> R
- for<T, R> type T -> R // on a per-case basis!
*/
pub struct InferenceEngine<'table, 'a> {
pub(crate) at: Handle,
pub(super) table: &'table mut Table<'a>,
pub(crate) bset: Handle,
pub(crate) rset: Handle,
}
impl<'table, 'a> InferenceEngine<'table, 'a> {
pub fn new(table: &'table mut Table<'a>, at: Handle) -> Self {
let never = table.anon_type(TypeKind::Never);
Self { at, table, bset: never, rset: never }
}
pub fn at<'b>(&'b mut self, at: Handle) -> InferenceEngine<'b, 'a> {
InferenceEngine { at, table: self.table, bset: self.bset, rset: self.rset }
}
pub fn open_bset<'b>(&'b mut self) -> InferenceEngine<'b, 'a> {
let bset = self.from_type_kind(TypeKind::Empty);
InferenceEngine { at: self.at, table: self.table, bset, rset: self.rset }
}
pub fn open_rset<'b>(&'b mut self) -> InferenceEngine<'b, 'a> {
let rset = self.new_var();
InferenceEngine { at: self.at, table: self.table, bset: self.bset, rset }
}
pub fn entry(&self, of: Handle) -> Entry<'_, 'a> {
self.table.entry(of)
}
pub fn from_type_kind(&mut self, kind: TypeKind) -> Handle {
self.table.anon_type(kind)
}
pub fn by_name<Out, N: TypeExpression<Out>>(
&mut self,
name: &N,
) -> Result<Out, crate::type_expression::Error> {
name.evaluate(self.table, self.at)
}
/// Creates a new unbound [type variable](Handle)
pub fn new_var(&mut self) -> Handle {
self.table.type_variable()
}
/// Creates a variable that is a new instance of another [Type](Handle)
pub fn new_inst(&mut self, of: Handle) -> Handle {
self.table.anon_type(TypeKind::Instance(of))
}
pub fn de_inst(&self, to: Handle) -> Handle {
match self.table.entry(to).ty() {
Some(TypeKind::Instance(id)) => self.de_inst(*id),
_ => to,
}
}
/// Creates a new type variable representing a function (signature)
pub fn new_fn(&mut self, args: Handle, rety: Handle) -> Handle {
self.table.anon_type(TypeKind::FnSig { args, rety })
}
/// Creates a new type variable representing a tuple
pub fn new_tuple(&mut self, tys: Vec<Handle>) -> Handle {
self.table.anon_type(TypeKind::Tuple(tys))
}
/// Creates a new type variable representing an array
pub fn new_array(&mut self, ty: Handle, size: usize) -> Handle {
self.table.anon_type(TypeKind::Array(ty, size))
}
/// All primitives must be predefined in the standard library.
pub fn primitive(&self, name: Sym) -> Option<Handle> {
self.table.get_by_sym(self.table.root(), &name)
}
/// Sets this type variable `to` be an instance `of` the other
/// # Panics
/// Panics if `to` is not a type variable
pub fn set_instance(&mut self, to: Handle, of: Handle) {
let mut e = self.table.entry_mut(to);
match e.as_ref().ty() {
Some(TypeKind::Variable) => e.set_ty(TypeKind::Instance(of)),
other => todo!("Cannot set {} to instance of: {other:?}", e.as_ref()),
};
}
/// Checks whether there are any unbound type variables in this type
pub fn is_generic(&self, ty: Handle) -> bool {
let entry = self.table.entry(ty);
let Some(ty) = entry.ty() else {
return false;
};
match ty {
TypeKind::Uninferred => false,
TypeKind::Variable => true,
&TypeKind::Array(h, _) => self.is_generic(h),
&TypeKind::Instance(h) => self.is_generic(h),
TypeKind::Intrinsic(_) => false,
TypeKind::Adt(Adt::Enum(tys)) => tys
.iter()
.any(|(_, ty)| ty.is_some_and(|ty| self.is_generic(ty))),
TypeKind::Adt(Adt::Struct(tys)) => tys.iter().any(|&(_, _, ty)| self.is_generic(ty)),
TypeKind::Adt(Adt::TupleStruct(tys)) => tys.iter().any(|&(_, ty)| self.is_generic(ty)),
TypeKind::Adt(Adt::UnitStruct) => false,
TypeKind::Adt(Adt::Union(tys)) => tys.iter().any(|&(_, ty)| self.is_generic(ty)),
&TypeKind::Ref(h) => self.is_generic(h),
&TypeKind::Slice(h) => self.is_generic(h),
TypeKind::Tuple(handles) => handles.iter().any(|&ty| self.is_generic(ty)),
&TypeKind::FnSig { args, rety } => self.is_generic(args) || self.is_generic(rety),
TypeKind::Empty | TypeKind::Never | TypeKind::Module => false,
}
}
/// Makes a deep copy of a type expression.
///
/// Bound variables are shared, unbound variables are duplicated.
pub fn deep_clone(&mut self, ty: Handle) -> Handle {
if !self.is_generic(ty) {
return ty;
};
let entry = self.table.entry(ty);
let Some(ty) = entry.ty().cloned() else {
return ty;
};
match ty {
TypeKind::Variable => self.new_var(),
TypeKind::Array(h, s) => {
let ty = self.deep_clone(h);
self.table.anon_type(TypeKind::Array(ty, s))
}
TypeKind::Instance(h) => {
let ty = self.deep_clone(h);
self.table.anon_type(TypeKind::Instance(ty))
}
TypeKind::Adt(Adt::Enum(tys)) => {
let tys = tys
.into_iter()
.map(|(name, ty)| (name, ty.map(|ty| self.deep_clone(ty))))
.collect();
self.table.anon_type(TypeKind::Adt(Adt::Enum(tys)))
}
TypeKind::Adt(Adt::Struct(tys)) => {
let tys = tys
.into_iter()
.map(|(n, v, ty)| (n, v, self.deep_clone(ty)))
.collect();
self.table.anon_type(TypeKind::Adt(Adt::Struct(tys)))
}
TypeKind::Adt(Adt::TupleStruct(tys)) => {
let tys = tys
.into_iter()
.map(|(v, ty)| (v, self.deep_clone(ty)))
.collect();
self.table.anon_type(TypeKind::Adt(Adt::TupleStruct(tys)))
}
TypeKind::Adt(Adt::Union(tys)) => {
let tys = tys
.into_iter()
.map(|(n, ty)| (n, self.deep_clone(ty)))
.collect();
self.table.anon_type(TypeKind::Adt(Adt::Union(tys)))
}
TypeKind::Ref(h) => {
let ty = self.deep_clone(h);
self.table.anon_type(TypeKind::Ref(ty))
}
TypeKind::Slice(h) => {
let ty = self.deep_clone(h);
self.table.anon_type(TypeKind::Slice(ty))
}
TypeKind::Tuple(tys) => {
let tys = tys.into_iter().map(|ty| self.deep_clone(ty)).collect();
self.table.anon_type(TypeKind::Tuple(tys))
}
TypeKind::FnSig { args, rety } => {
let args = self.deep_clone(args);
let rety = self.deep_clone(rety);
self.table.anon_type(TypeKind::FnSig { args, rety })
}
_ => self.table.anon_type(ty),
}
}
/// Returns the defining instance of `self`,
/// collapsing type instances along the way.
pub fn prune(&mut self, ty: Handle) -> Handle {
if let Some(TypeKind::Instance(new_ty)) = self.table.ty(ty) {
let new_ty = self.prune(*new_ty);
self.table.set_ty(ty, TypeKind::Instance(new_ty));
new_ty
} else {
ty
}
}
/// Checks whether a type occurs in another type
///
/// # Note:
/// - Since the test uses strict equality, `self` should be pruned prior to testing.
/// - The test is *not guaranteed to terminate* for recursive types.
pub fn occurs_in(&self, this: Handle, other: Handle) -> bool {
if this == other {
return true;
}
let Some(ty) = self.table.ty(other) else {
return false;
};
match ty {
TypeKind::Instance(other) => self.occurs_in(this, *other),
TypeKind::Adt(Adt::Enum(items)) => items
.iter()
.any(|(_, i)| i.is_some_and(|other| self.occurs_in(this, other))),
TypeKind::Adt(Adt::Struct(items)) => items
.iter()
.any(|(_, _, other)| self.occurs_in(this, *other)),
TypeKind::Adt(Adt::TupleStruct(items)) => {
items.iter().any(|(_, other)| self.occurs_in(this, *other))
}
TypeKind::Adt(Adt::Union(items)) => {
items.iter().any(|(_, other)| self.occurs_in(this, *other))
}
TypeKind::Ref(other) => self.occurs_in(this, *other),
TypeKind::Slice(other) => self.occurs_in(this, *other),
TypeKind::Array(other, _) => self.occurs_in(this, *other),
TypeKind::Tuple(handles) => handles.iter().any(|&other| self.occurs_in(this, other)),
TypeKind::FnSig { args, rety } => {
self.occurs_in(this, *args) || self.occurs_in(this, *rety)
}
TypeKind::Uninferred
| TypeKind::Variable
| TypeKind::Adt(Adt::UnitStruct)
| TypeKind::Intrinsic(_)
| TypeKind::Empty
| TypeKind::Never
| TypeKind::Module => false,
}
}
/// Unifies two types
pub fn unify(&mut self, this: Handle, other: Handle) -> Result<(), InferenceError> {
let (ah, bh) = (self.prune(this), self.prune(other));
let (a, b) = (self.table.entry(ah), self.table.entry(bh));
let (Some(a), Some(b)) = (a.ty(), b.ty()) else {
return Err(InferenceError::Mismatch(ah, bh));
};
match (a, b) {
(TypeKind::Uninferred, _) => {
self.set_instance(ah, bh);
Ok(())
}
(_, TypeKind::Uninferred) => self.unify(bh, ah),
(TypeKind::Variable, _) => {
self.set_instance(ah, bh);
Ok(())
}
(TypeKind::Instance(a), TypeKind::Instance(b)) if !self.occurs_in(*a, *b) => {
self.set_instance(*a, *b);
Ok(())
}
(TypeKind::Instance(_), _) => Err(InferenceError::Recursive(ah, bh)),
(_, TypeKind::Variable) | (_, TypeKind::Instance(_)) => self.unify(bh, ah),
(TypeKind::Intrinsic(ia), TypeKind::Intrinsic(ib)) if ia == ib => Ok(()),
(TypeKind::Adt(Adt::Enum(ia)), TypeKind::Adt(Adt::Enum(ib)))
if ia.len() == ib.len() =>
{
for ((na, a), (nb, b)) in ia.clone().into_iter().zip(ib.clone().into_iter()) {
if na != nb || a.is_some() != b.is_some() {
return Err(InferenceError::Mismatch(ah, bh));
}
let (Some(a), Some(b)) = (a, b) else { continue };
self.unify(a, b)?;
}
Ok(())
}
(TypeKind::Adt(Adt::Struct(ia)), TypeKind::Adt(Adt::Struct(ib)))
if ia.len() == ib.len() =>
{
for ((na, va, a), (nb, vb, b)) in ia.clone().into_iter().zip(ib.clone().into_iter())
{
if na != nb || va != vb {
return Err(InferenceError::Mismatch(ah, bh));
}
self.unify(a, b)?;
}
Ok(())
}
(TypeKind::Adt(Adt::TupleStruct(ia)), TypeKind::Adt(Adt::TupleStruct(ib)))
if ia.len() == ib.len() =>
{
for ((va, a), (vb, b)) in ia.clone().into_iter().zip(ib.clone().into_iter()) {
if va != vb {
return Err(InferenceError::Mismatch(ah, bh));
}
self.unify(a, b)?;
}
Ok(())
}
(TypeKind::Adt(Adt::Union(ia)), TypeKind::Adt(Adt::Union(ib)))
if ia.len() == ib.len() =>
{
todo!()
}
(TypeKind::Ref(a), TypeKind::Ref(b)) => self.unify(*a, *b),
(TypeKind::Slice(a), TypeKind::Slice(b)) => self.unify(*a, *b),
(TypeKind::Array(a, sa), TypeKind::Array(b, sb)) if sa == sb => self.unify(*a, *b),
(TypeKind::Tuple(a), TypeKind::Tuple(b)) => {
if a.len() != b.len() {
return Err(InferenceError::Mismatch(ah, bh));
}
Ok(())
}
(&TypeKind::FnSig { args: a1, rety: r1 }, &TypeKind::FnSig { args: a2, rety: r2 }) => {
self.unify(a1, a2)?;
self.unify(r1, r2)
}
(TypeKind::Empty, TypeKind::Empty) => Ok(()),
(TypeKind::Never, _) | (_, TypeKind::Never) => Ok(()),
(TypeKind::Module, TypeKind::Module) => Ok(()),
_ => Err(InferenceError::Mismatch(ah, bh)),
}
}
}

View File

@ -0,0 +1,23 @@
use cl_ast::Path;
use crate::handle::Handle;
use core::fmt;
/// An error produced during type inference
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum InferenceError {
NotFound(Path),
Mismatch(Handle, Handle),
Recursive(Handle, Handle),
}
impl std::error::Error for InferenceError {}
impl fmt::Display for InferenceError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
InferenceError::NotFound(p) => write!(f, "Path not visible in scope: {p}"),
InferenceError::Mismatch(a, b) => write!(f, "Type mismatch: {a:?} != {b:?}"),
InferenceError::Recursive(_, _) => write!(f, "Recursive type!"),
}
}
}

View File

@ -5,7 +5,7 @@ use crate::{
source::Source,
table::{NodeKind, Table},
};
use cl_ast::{ast_visitor::Visit, ItemKind, Sym};
use cl_ast::{ItemKind, Sym, ast_visitor::Visit};
#[derive(Debug)]
pub struct Populator<'t, 'a> {
@ -76,6 +76,7 @@ impl<'a> Visit<'a> for Populator<'_, 'a> {
fn visit_const(&mut self, c: &'a cl_ast::Const) {
let cl_ast::Const { name, ty, init } = c;
self.inner.set_source(Source::Const(c));
self.inner.set_body(init);
self.set_name(*name);
self.visit_ty(ty);
@ -85,6 +86,7 @@ impl<'a> Visit<'a> for Populator<'_, 'a> {
fn visit_static(&mut self, s: &'a cl_ast::Static) {
let cl_ast::Static { mutable, name, ty, init } = s;
self.inner.set_source(Source::Static(s));
self.inner.set_body(init);
self.set_name(*name);
self.visit_mutability(mutable);
@ -108,8 +110,9 @@ impl<'a> Visit<'a> for Populator<'_, 'a> {
self.set_name(*name);
self.visit_ty_fn(sign);
bind.iter().for_each(|p| self.visit_pattern(p));
self.visit_pattern(bind);
if let Some(b) = body {
self.inner.set_body(b);
self.visit_expr(b)
}
}
@ -148,24 +151,4 @@ impl<'a> Visit<'a> for Populator<'_, 'a> {
self.visit_use_tree(tree);
}
fn visit_let(&mut self, l: &'a cl_ast::Let) {
let cl_ast::Let { mutable, name: _, ty, init } = l;
let mut entry = self.new_entry(NodeKind::Local);
entry.inner.set_source(Source::Local(l));
// entry.set_name(*name);
entry.visit_mutability(mutable);
if let Some(ty) = ty {
entry.visit_ty(ty);
}
if let Some(init) = init {
entry.visit_expr(init)
}
// let child = entry.inner.id();
// self.inner.add_child(*name, child);
todo!("Pattern destructuring in cl-typeck")
}
}

View File

@ -31,7 +31,7 @@ use crate::{
source::Source,
type_kind::TypeKind,
};
use cl_ast::{Meta, PathPart, Sym};
use cl_ast::{Expr, Meta, PathPart, Sym};
use cl_structures::{index_map::IndexMap, span::Span};
use std::collections::HashMap;
@ -50,11 +50,11 @@ pub struct Table<'a> {
pub(crate) children: HashMap<Handle, HashMap<Sym, Handle>>,
pub(crate) imports: HashMap<Handle, HashMap<Sym, Handle>>,
pub(crate) use_items: HashMap<Handle, Vec<Handle>>,
bodies: HashMap<Handle, &'a Expr>,
types: HashMap<Handle, TypeKind>,
spans: HashMap<Handle, Span>,
metas: HashMap<Handle, &'a [Meta]>,
sources: HashMap<Handle, Source<'a>>,
// code: HashMap<Handle, BasicBlock>, // TODO: lower sources
impl_targets: HashMap<Handle, Handle>,
anon_types: HashMap<TypeKind, Handle>,
@ -77,6 +77,7 @@ impl<'a> Table<'a> {
children: HashMap::new(),
imports: HashMap::new(),
use_items: HashMap::new(),
bodies: HashMap::new(),
types: HashMap::new(),
spans: HashMap::new(),
metas: HashMap::new(),
@ -142,6 +143,18 @@ impl<'a> Table<'a> {
entry
}
pub(crate) fn uninferred_type(&mut self) -> Handle {
let handle = self.new_entry(self.root, NodeKind::Type);
self.types.insert(handle, TypeKind::Uninferred);
handle
}
pub(crate) fn type_variable(&mut self) -> Handle {
let handle = self.new_entry(self.root, NodeKind::Type);
self.types.insert(handle, TypeKind::Variable);
handle
}
pub const fn root_entry(&self) -> Entry<'_, 'a> {
self.root.to_entry(self)
}
@ -172,6 +185,10 @@ impl<'a> Table<'a> {
self.imports.get(&node)
}
pub fn body(&self, node: Handle) -> Option<&'a Expr> {
self.bodies.get(&node).copied()
}
pub fn ty(&self, node: Handle) -> Option<&TypeKind> {
self.types.get(&node)
}
@ -192,6 +209,10 @@ impl<'a> Table<'a> {
self.impl_targets.get(&node).copied()
}
pub fn set_body(&mut self, node: Handle, body: &'a Expr) -> Option<&'a Expr> {
self.bodies.insert(node, body)
}
pub fn set_ty(&mut self, node: Handle, kind: TypeKind) -> Option<TypeKind> {
self.types.insert(node, kind)
}
@ -282,6 +303,7 @@ pub enum NodeKind {
Const,
Static,
Function,
Temporary,
Local,
Impl,
Use,
@ -299,6 +321,7 @@ mod display {
NodeKind::Const => write!(f, "const"),
NodeKind::Static => write!(f, "static"),
NodeKind::Function => write!(f, "fn"),
NodeKind::Temporary => write!(f, "temp"),
NodeKind::Local => write!(f, "local"),
NodeKind::Use => write!(f, "use"),
NodeKind::Impl => write!(f, "impl"),

View File

@ -42,10 +42,7 @@ impl TypeExpression for TyKind {
match self {
TyKind::Never => Ok(table.anon_type(TypeKind::Never)),
TyKind::Empty => Ok(table.anon_type(TypeKind::Empty)),
TyKind::Infer => {
eprintln!("TODO: Introduce type variables");
Err(Error::BadPath { parent: node, path: vec![PathPart::Ident("_".into())] })
}
TyKind::Infer => Ok(table.uninferred_type()),
TyKind::Path(p) => p.evaluate(table, node),
TyKind::Array(a) => a.evaluate(table, node),
TyKind::Slice(s) => s.evaluate(table, node),

View File

@ -10,6 +10,10 @@ mod display;
/// (a component of a [Table](crate::table::Table))
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
pub enum TypeKind {
/// A type that is yet to be inferred!
Uninferred,
/// A type variable, to be filled in later
Variable,
/// An alias for an already-defined type
Instance(Handle),
/// A primitive type, built-in to the compiler

View File

@ -8,6 +8,8 @@ use std::fmt::{self, Display, Write};
impl Display for TypeKind {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
TypeKind::Uninferred => write!(f, "_"),
TypeKind::Variable => write!(f, "?"),
TypeKind::Instance(def) => write!(f, "alias to #{def}"),
TypeKind::Intrinsic(i) => i.fmt(f),
TypeKind::Adt(a) => a.fmt(f),