#![warn(missing_docs)]
pub mod dyn_tensor;
pub mod nat;
pub mod ty_list;
pub use dyn_tensor::{dyn_tensor_of, dyn_tensor_tycon, shape_witness_of, shape_witness_tycon};
pub use nat::TyNat;
pub use ty_list::TyList;
use bhc_index::Idx;
use bhc_intern::Symbol;
use bhc_span::Span;
use serde::{Deserialize, Serialize};
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub struct TyId(u32);
impl Idx for TyId {
#[allow(clippy::cast_possible_truncation)]
fn new(idx: usize) -> Self {
Self(idx as u32)
}
fn index(self) -> usize {
self.0 as usize
}
}
#[derive(Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub struct TyVar {
pub id: u32,
pub kind: Kind,
}
impl TyVar {
#[must_use]
pub fn new(id: u32, kind: Kind) -> Self {
Self { id, kind }
}
#[must_use]
pub fn new_star(id: u32) -> Self {
Self::new(id, Kind::Star)
}
}
#[derive(Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub enum Kind {
Star,
Arrow(Box<Kind>, Box<Kind>),
Constraint,
Var(u32),
Nat,
List(Box<Kind>),
}
impl Kind {
#[must_use]
pub fn star_to_star() -> Self {
Self::Arrow(Box::new(Self::Star), Box::new(Self::Star))
}
#[must_use]
pub fn is_star(&self) -> bool {
matches!(self, Self::Star)
}
#[must_use]
pub fn is_nat(&self) -> bool {
matches!(self, Self::Nat)
}
#[must_use]
pub fn nat_list() -> Self {
Self::List(Box::new(Self::Nat))
}
#[must_use]
pub fn tensor_kind() -> Self {
Self::Arrow(Box::new(Self::nat_list()), Box::new(Self::star_to_star()))
}
#[must_use]
pub fn is_list(&self) -> bool {
matches!(self, Self::List(_))
}
#[must_use]
pub fn list_elem_kind(&self) -> Option<&Kind> {
match self {
Self::List(k) => Some(k),
_ => None,
}
}
}
#[derive(Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub enum Ty {
Var(TyVar),
Con(TyCon),
Prim(PrimTy),
App(Box<Ty>, Box<Ty>),
Fun(Box<Ty>, Box<Ty>),
Tuple(Vec<Ty>),
List(Box<Ty>),
Forall(Vec<TyVar>, Box<Ty>),
Error,
Nat(TyNat),
TyList(TyList),
}
impl Ty {
#[must_use]
pub fn unit() -> Self {
Self::Tuple(Vec::new())
}
#[must_use]
pub fn fun(from: Ty, to: Ty) -> Self {
Self::Fun(Box::new(from), Box::new(to))
}
#[must_use]
pub fn list(elem: Ty) -> Self {
Self::List(Box::new(elem))
}
#[must_use]
pub fn int_prim() -> Self {
Self::Prim(PrimTy::I64)
}
#[must_use]
pub fn double_prim() -> Self {
Self::Prim(PrimTy::F64)
}
#[must_use]
pub fn float_prim() -> Self {
Self::Prim(PrimTy::F32)
}
#[must_use]
pub fn is_prim(&self) -> bool {
matches!(self, Self::Prim(_))
}
#[must_use]
pub fn as_prim(&self) -> Option<PrimTy> {
match self {
Self::Prim(p) => Some(*p),
_ => None,
}
}
#[must_use]
pub fn is_fun(&self) -> bool {
matches!(self, Self::Fun(_, _))
}
#[must_use]
pub fn is_error(&self) -> bool {
matches!(self, Self::Error)
}
#[must_use]
pub fn free_vars(&self) -> Vec<TyVar> {
let mut vars = Vec::new();
self.collect_free_vars(&mut vars);
vars
}
fn collect_free_vars(&self, vars: &mut Vec<TyVar>) {
match self {
Self::Var(v) => {
if !vars.contains(v) {
vars.push(v.clone());
}
}
Self::Con(_) | Self::Prim(_) | Self::Error => {}
Self::App(f, a) | Self::Fun(f, a) => {
f.collect_free_vars(vars);
a.collect_free_vars(vars);
}
Self::Tuple(tys) => {
for ty in tys {
ty.collect_free_vars(vars);
}
}
Self::List(elem) => elem.collect_free_vars(vars),
Self::Forall(bound, body) => {
let mut body_vars = Vec::new();
body.collect_free_vars(&mut body_vars);
for v in body_vars {
if !bound.contains(&v) && !vars.contains(&v) {
vars.push(v);
}
}
}
Self::Nat(n) => {
for v in n.free_vars() {
if !vars.contains(&v) {
vars.push(v);
}
}
}
Self::TyList(l) => {
for v in l.free_vars() {
if !vars.contains(&v) {
vars.push(v);
}
}
}
}
}
#[must_use]
pub fn is_ground(&self) -> bool {
match self {
Self::Var(_) => false,
Self::Con(_) | Self::Prim(_) | Self::Error => true,
Self::App(f, a) | Self::Fun(f, a) => f.is_ground() && a.is_ground(),
Self::Tuple(tys) => tys.iter().all(Ty::is_ground),
Self::List(elem) => elem.is_ground(),
Self::Forall(_, body) => body.is_ground(),
Self::Nat(n) => n.is_ground(),
Self::TyList(l) => l.is_ground(),
}
}
#[must_use]
pub fn nat_lit(n: u64) -> Self {
Self::Nat(TyNat::lit(n))
}
#[must_use]
pub fn shape(dims: &[u64]) -> Self {
Self::TyList(TyList::shape_from_dims(dims))
}
#[must_use]
pub fn is_nat(&self) -> bool {
matches!(self, Self::Nat(_))
}
#[must_use]
pub fn is_ty_list(&self) -> bool {
matches!(self, Self::TyList(_))
}
#[must_use]
pub fn as_nat(&self) -> Option<&TyNat> {
match self {
Self::Nat(n) => Some(n),
_ => None,
}
}
#[must_use]
pub fn as_ty_list(&self) -> Option<&TyList> {
match self {
Self::TyList(l) => Some(l),
_ => None,
}
}
}
impl std::fmt::Display for Ty {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::Var(v) => write!(f, "t{}", v.id),
Self::Con(c) => write!(f, "{}", c.name.as_str()),
Self::Prim(p) => write!(f, "{p}"),
Self::App(fun, arg) => write!(f, "({fun} {arg})"),
Self::Fun(from, to) => write!(f, "({from} -> {to})"),
Self::Tuple(tys) => {
write!(f, "(")?;
for (i, ty) in tys.iter().enumerate() {
if i > 0 {
write!(f, ", ")?;
}
write!(f, "{ty}")?;
}
write!(f, ")")
}
Self::List(elem) => write!(f, "[{elem}]"),
Self::Forall(vars, body) => {
write!(f, "forall")?;
for v in vars {
write!(f, " t{}", v.id)?;
}
write!(f, ". {body}")
}
Self::Error => write!(f, "<error>"),
Self::Nat(n) => write!(f, "{n}"),
Self::TyList(l) => write!(f, "{l}"),
}
}
}
#[derive(Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub struct TyCon {
pub name: Symbol,
pub kind: Kind,
}
impl TyCon {
#[must_use]
pub fn new(name: Symbol, kind: Kind) -> Self {
Self { name, kind }
}
}
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
pub struct Scheme {
pub vars: Vec<TyVar>,
pub constraints: Vec<Constraint>,
pub ty: Ty,
}
impl Scheme {
#[must_use]
pub fn mono(ty: Ty) -> Self {
Self {
vars: Vec::new(),
constraints: Vec::new(),
ty,
}
}
#[must_use]
pub fn poly(vars: Vec<TyVar>, ty: Ty) -> Self {
Self {
vars,
constraints: Vec::new(),
ty,
}
}
#[must_use]
pub fn qualified(vars: Vec<TyVar>, constraints: Vec<Constraint>, ty: Ty) -> Self {
Self {
vars,
constraints,
ty,
}
}
#[must_use]
pub fn is_mono(&self) -> bool {
self.vars.is_empty()
}
}
#[derive(Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub struct Constraint {
pub class: Symbol,
pub args: Vec<Ty>,
pub span: Span,
}
impl Constraint {
#[must_use]
pub fn new(class: Symbol, ty: Ty, span: Span) -> Self {
Self {
class,
args: vec![ty],
span,
}
}
#[must_use]
pub fn new_multi(class: Symbol, args: Vec<Ty>, span: Span) -> Self {
Self { class, args, span }
}
}
#[derive(Clone, Debug, Default, Serialize, Deserialize)]
pub struct Subst {
mapping: rustc_hash::FxHashMap<u32, Ty>,
}
impl Subst {
#[must_use]
pub fn new() -> Self {
Self::default()
}
pub fn insert(&mut self, var: &TyVar, ty: Ty) {
self.mapping.insert(var.id, ty);
}
#[must_use]
pub fn get(&self, var: &TyVar) -> Option<&Ty> {
self.mapping.get(&var.id)
}
#[must_use]
pub fn contains(&self, var: &TyVar) -> bool {
self.mapping.contains_key(&var.id)
}
#[must_use]
pub fn apply(&self, ty: &Ty) -> Ty {
match ty {
Ty::Var(v) => self.get(v).cloned().unwrap_or_else(|| ty.clone()),
Ty::Con(_) | Ty::Prim(_) => ty.clone(),
Ty::App(f, a) => Ty::App(Box::new(self.apply(f)), Box::new(self.apply(a))),
Ty::Fun(from, to) => Ty::Fun(Box::new(self.apply(from)), Box::new(self.apply(to))),
Ty::Tuple(tys) => Ty::Tuple(tys.iter().map(|t| self.apply(t)).collect()),
Ty::List(elem) => Ty::List(Box::new(self.apply(elem))),
Ty::Forall(vars, body) => {
let mut inner = self.clone();
for v in vars {
inner.mapping.remove(&v.id);
}
Ty::Forall(vars.clone(), Box::new(inner.apply(body)))
}
Ty::Error => Ty::Error,
Ty::Nat(n) => Ty::Nat(self.apply_nat(n)),
Ty::TyList(l) => Ty::TyList(self.apply_ty_list(l)),
}
}
#[must_use]
pub fn apply_nat(&self, n: &TyNat) -> TyNat {
match n {
TyNat::Lit(val) => TyNat::Lit(*val),
TyNat::Var(v) => {
match self.get(v) {
Some(Ty::Nat(replacement)) => replacement.clone(),
Some(_) => n.clone(), None => n.clone(),
}
}
TyNat::Add(a, b) => TyNat::add(self.apply_nat(a), self.apply_nat(b)),
TyNat::Mul(a, b) => TyNat::mul(self.apply_nat(a), self.apply_nat(b)),
}
}
#[must_use]
pub fn apply_ty_list(&self, l: &TyList) -> TyList {
match l {
TyList::Nil => TyList::Nil,
TyList::Cons(head, tail) => TyList::cons(self.apply(head), self.apply_ty_list(tail)),
TyList::Var(v) => {
match self.get(v) {
Some(Ty::TyList(replacement)) => replacement.clone(),
Some(_) => l.clone(), None => l.clone(),
}
}
TyList::Append(xs, ys) => {
TyList::append(self.apply_ty_list(xs), self.apply_ty_list(ys))
}
}
}
#[must_use]
pub fn compose(&self, other: &Self) -> Self {
let mut result = Self::new();
for (k, v) in &other.mapping {
result.mapping.insert(*k, self.apply(v));
}
for (k, v) in &self.mapping {
result.mapping.entry(*k).or_insert_with(|| v.clone());
}
result
}
#[must_use]
pub fn is_empty(&self) -> bool {
self.mapping.is_empty()
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub enum PrimTy {
I32,
I64,
U32,
U64,
F32,
F64,
Char,
Addr,
}
impl PrimTy {
#[must_use]
pub const fn size_bytes(self) -> usize {
match self {
Self::I32 | Self::U32 | Self::F32 => 4,
Self::I64 | Self::U64 | Self::F64 | Self::Addr => 8,
Self::Char => 4, }
}
#[must_use]
pub const fn alignment(self) -> usize {
self.size_bytes()
}
#[must_use]
pub const fn name(self) -> &'static str {
match self {
Self::I32 => "Int32#",
Self::I64 => "Int#",
Self::U32 => "Word32#",
Self::U64 => "Word#",
Self::F32 => "Float#",
Self::F64 => "Double#",
Self::Char => "Char#",
Self::Addr => "Addr#",
}
}
#[must_use]
pub const fn is_signed_int(self) -> bool {
matches!(self, Self::I32 | Self::I64)
}
#[must_use]
pub const fn is_unsigned_int(self) -> bool {
matches!(self, Self::U32 | Self::U64)
}
#[must_use]
pub const fn is_float(self) -> bool {
matches!(self, Self::F32 | Self::F64)
}
#[must_use]
pub const fn is_numeric(self) -> bool {
matches!(
self,
Self::I32 | Self::I64 | Self::U32 | Self::U64 | Self::F32 | Self::F64
)
}
}
impl std::fmt::Display for PrimTy {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.name())
}
}
#[must_use]
pub fn types_match(pattern: &Ty, target: &Ty) -> Option<Subst> {
let mut subst = Subst::new();
if types_match_with_subst(pattern, target, &mut subst) {
Some(subst)
} else {
None
}
}
pub fn types_match_with_subst(pattern: &Ty, target: &Ty, subst: &mut Subst) -> bool {
match (pattern, target) {
(Ty::Var(v), _) => {
if let Some(bound_ty) = subst.get(v) {
types_equal(bound_ty, target)
} else {
subst.insert(v, target.clone());
true
}
}
(Ty::Con(c1), Ty::Con(c2)) => c1.name == c2.name,
(Ty::Prim(p1), Ty::Prim(p2)) => p1 == p2,
(Ty::App(f1, a1), Ty::App(f2, a2)) => {
types_match_with_subst(f1, f2, subst) && types_match_with_subst(a1, a2, subst)
}
(Ty::Fun(a1, r1), Ty::Fun(a2, r2)) => {
types_match_with_subst(a1, a2, subst) && types_match_with_subst(r1, r2, subst)
}
(Ty::Tuple(ts1), Ty::Tuple(ts2)) if ts1.len() == ts2.len() => ts1
.iter()
.zip(ts2.iter())
.all(|(t1, t2)| types_match_with_subst(t1, t2, subst)),
(Ty::List(e1), Ty::List(e2)) => types_match_with_subst(e1, e2, subst),
(Ty::Forall(_, body1), Ty::Forall(_, body2)) => types_match_with_subst(body1, body2, subst),
(Ty::Nat(n1), Ty::Nat(n2)) => n1 == n2,
(Ty::TyList(l1), Ty::TyList(l2)) => l1 == l2,
(Ty::Error, _) | (_, Ty::Error) => true,
_ => false,
}
}
#[must_use]
pub fn types_match_multi(patterns: &[Ty], targets: &[Ty]) -> Option<Subst> {
if patterns.len() != targets.len() {
return None;
}
let mut subst = Subst::new();
for (pattern, target) in patterns.iter().zip(targets.iter()) {
if !types_match_with_subst(pattern, target, &mut subst) {
return None;
}
}
Some(subst)
}
#[must_use]
pub fn types_equal(t1: &Ty, t2: &Ty) -> bool {
match (t1, t2) {
(Ty::Var(v1), Ty::Var(v2)) => v1.id == v2.id,
(Ty::Con(c1), Ty::Con(c2)) => c1.name == c2.name,
(Ty::Prim(p1), Ty::Prim(p2)) => p1 == p2,
(Ty::App(f1, a1), Ty::App(f2, a2)) => types_equal(f1, f2) && types_equal(a1, a2),
(Ty::Fun(a1, r1), Ty::Fun(a2, r2)) => types_equal(a1, a2) && types_equal(r1, r2),
(Ty::Tuple(ts1), Ty::Tuple(ts2)) if ts1.len() == ts2.len() => ts1
.iter()
.zip(ts2.iter())
.all(|(t1, t2)| types_equal(t1, t2)),
(Ty::List(e1), Ty::List(e2)) => types_equal(e1, e2),
(Ty::Error, Ty::Error) => true,
_ => false,
}
}
#[derive(Clone, Debug, thiserror::Error, Serialize, Deserialize)]
pub enum TypeError {
#[error("type mismatch: expected {expected}, found {found}")]
Mismatch {
expected: String,
found: String,
span: Span,
},
#[error("infinite type: {var} occurs in {ty}")]
OccursCheck {
var: String,
ty: String,
span: Span,
},
#[error("unbound type variable: {name}")]
UnboundVar {
name: String,
span: Span,
},
#[error("kind mismatch: expected {expected}, found {found}")]
KindMismatch {
expected: String,
found: String,
span: Span,
},
#[error("ambiguous type variable: {var}")]
Ambiguous {
var: String,
span: Span,
},
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_ty_free_vars() {
let a = TyVar::new_star(0);
let b = TyVar::new_star(1);
let ty = Ty::fun(Ty::Var(a.clone()), Ty::Var(b.clone()));
let vars = ty.free_vars();
assert_eq!(vars.len(), 2);
assert!(vars.contains(&a));
assert!(vars.contains(&b));
}
#[test]
fn test_subst_apply() {
let a = TyVar::new_star(0);
let int_con = TyCon::new(unsafe { Symbol::from_raw(0) }, Kind::Star);
let mut subst = Subst::new();
subst.insert(&a, Ty::Con(int_con.clone()));
let ty = Ty::fun(Ty::Var(a.clone()), Ty::Var(a));
let result = subst.apply(&ty);
match result {
Ty::Fun(from, to) => {
assert!(matches!(*from, Ty::Con(_)));
assert!(matches!(*to, Ty::Con(_)));
}
_ => panic!("expected function type"),
}
}
#[test]
fn test_scheme_mono() {
let int_con = TyCon::new(unsafe { Symbol::from_raw(0) }, Kind::Star);
let scheme = Scheme::mono(Ty::Con(int_con));
assert!(scheme.is_mono());
}
#[test]
fn test_prim_ty_properties() {
assert_eq!(PrimTy::I64.size_bytes(), 8);
assert_eq!(PrimTy::I32.size_bytes(), 4);
assert_eq!(PrimTy::F64.size_bytes(), 8);
assert_eq!(PrimTy::F32.size_bytes(), 4);
assert!(PrimTy::I64.is_signed_int());
assert!(PrimTy::I32.is_signed_int());
assert!(!PrimTy::U64.is_signed_int());
assert!(PrimTy::F64.is_float());
assert!(PrimTy::F32.is_float());
assert!(!PrimTy::I64.is_float());
assert!(PrimTy::I64.is_numeric());
assert!(PrimTy::F64.is_numeric());
assert!(!PrimTy::Char.is_numeric());
assert!(!PrimTy::Addr.is_numeric());
}
#[test]
fn test_prim_ty_names() {
assert_eq!(PrimTy::I64.name(), "Int#");
assert_eq!(PrimTy::F64.name(), "Double#");
assert_eq!(PrimTy::F32.name(), "Float#");
assert_eq!(PrimTy::Char.name(), "Char#");
}
#[test]
fn test_ty_prim_constructors() {
let int = Ty::int_prim();
assert!(int.is_prim());
assert_eq!(int.as_prim(), Some(PrimTy::I64));
let double = Ty::double_prim();
assert!(double.is_prim());
assert_eq!(double.as_prim(), Some(PrimTy::F64));
let unit = Ty::unit();
assert!(!unit.is_prim());
assert_eq!(unit.as_prim(), None);
}
#[test]
fn test_prim_ty_no_free_vars() {
let prim = Ty::int_prim();
assert!(prim.free_vars().is_empty());
}
#[test]
fn test_subst_prim_unchanged() {
let a = TyVar::new_star(0);
let mut subst = Subst::new();
subst.insert(&a, Ty::unit());
let prim = Ty::int_prim();
let result = subst.apply(&prim);
assert_eq!(result, prim);
}
#[test]
fn test_types_match_variable_binding() {
let a = TyVar::new_star(0);
let int_con = TyCon::new(Symbol::intern("Int"), Kind::Star);
let int_ty = Ty::Con(int_con);
let result = types_match(&Ty::Var(a.clone()), &int_ty);
assert!(result.is_some());
let subst = result.unwrap();
assert_eq!(subst.apply(&Ty::Var(a)), int_ty);
}
#[test]
fn test_types_match_constructor() {
let int_ty = Ty::Con(TyCon::new(Symbol::intern("Int"), Kind::Star));
let bool_ty = Ty::Con(TyCon::new(Symbol::intern("Bool"), Kind::Star));
assert!(types_match(&int_ty, &int_ty).is_some());
assert!(types_match(&int_ty, &bool_ty).is_none());
}
#[test]
fn test_types_match_application() {
let a = TyVar::new_star(0);
let list_con = Ty::Con(TyCon::new(Symbol::intern("[]"), Kind::star_to_star()));
let int_ty = Ty::Con(TyCon::new(Symbol::intern("Int"), Kind::Star));
let pattern = Ty::App(Box::new(list_con.clone()), Box::new(Ty::Var(a.clone())));
let target = Ty::App(Box::new(list_con), Box::new(int_ty.clone()));
let result = types_match(&pattern, &target);
assert!(result.is_some());
let subst = result.unwrap();
assert_eq!(subst.apply(&Ty::Var(a)), int_ty);
}
#[test]
fn test_types_match_multi_basic() {
let int_ty = Ty::Con(TyCon::new(Symbol::intern("Int"), Kind::Star));
let bool_ty = Ty::Con(TyCon::new(Symbol::intern("Bool"), Kind::Star));
assert!(
types_match_multi(std::slice::from_ref(&int_ty), std::slice::from_ref(&int_ty))
.is_some()
);
assert!(types_match_multi(std::slice::from_ref(&int_ty), &[bool_ty]).is_none());
assert!(types_match_multi(&[], &[]).is_some());
assert!(types_match_multi(std::slice::from_ref(&int_ty), &[]).is_none());
}
}