cl-typeck: Implement a framework for type inference
...And also implement a bunch of the inference rules, too.
This commit is contained in:
parent
7ba808594c
commit
4c4b49ce00
@ -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()
|
||||
}
|
||||
|
@ -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)
|
||||
}
|
||||
|
@ -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),
|
||||
|
@ -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))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
358
compiler/cl-typeck/src/stage/infer/engine.rs
Normal file
358
compiler/cl-typeck/src/stage/infer/engine.rs
Normal 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)),
|
||||
}
|
||||
}
|
||||
}
|
23
compiler/cl-typeck/src/stage/infer/error.rs
Normal file
23
compiler/cl-typeck/src/stage/infer/error.rs
Normal 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!"),
|
||||
}
|
||||
}
|
||||
}
|
@ -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")
|
||||
}
|
||||
}
|
||||
|
@ -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"),
|
||||
|
@ -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),
|
||||
|
@ -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
|
||||
|
@ -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),
|
||||
|
Loading…
x
Reference in New Issue
Block a user