use alloc::vec::Vec;
use core::fmt;
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct TyVar(u32);
impl TyVar {
#[inline]
pub(crate) const fn from_index(index: u32) -> Self {
Self(index)
}
#[inline]
#[must_use]
pub const fn to_u32(self) -> u32 {
self.0
}
}
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct TyCon(u32);
impl TyCon {
#[inline]
#[must_use]
pub const fn new(tag: u32) -> Self {
Self(tag)
}
#[inline]
#[must_use]
pub const fn to_u32(self) -> u32 {
self.0
}
}
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
pub enum Type {
Var(TyVar),
App(TyCon, Vec<Type>),
}
impl Type {
#[inline]
#[must_use]
pub const fn var(var: TyVar) -> Self {
Self::Var(var)
}
#[inline]
#[must_use]
pub const fn con(head: TyCon) -> Self {
Self::App(head, Vec::new())
}
#[inline]
#[must_use]
pub fn app(head: TyCon, args: impl Into<Vec<Type>>) -> Self {
Self::App(head, args.into())
}
#[inline]
#[must_use]
pub const fn as_var(&self) -> Option<TyVar> {
match self {
Self::Var(v) => Some(*v),
Self::App(..) => None,
}
}
#[inline]
#[must_use]
pub const fn head(&self) -> Option<TyCon> {
match self {
Self::App(head, _) => Some(*head),
Self::Var(_) => None,
}
}
#[inline]
#[must_use]
pub fn args(&self) -> &[Type] {
match self {
Self::App(_, args) => args,
Self::Var(_) => &[],
}
}
#[inline]
#[must_use]
pub const fn is_var(&self) -> bool {
matches!(self, Self::Var(_))
}
}
impl fmt::Display for Type {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Var(v) => write!(f, "?{}", v.to_u32()),
Self::App(head, args) => {
write!(f, "#{}", head.to_u32())?;
if let [first, rest @ ..] = args.as_slice() {
write!(f, "({first}")?;
for arg in rest {
write!(f, ", {arg}")?;
}
write!(f, ")")?;
}
Ok(())
}
}
}
}
#[cfg(test)]
mod tests {
extern crate alloc;
use alloc::string::ToString;
use alloc::vec;
use super::*;
const INT: TyCon = TyCon::new(0);
const PAIR: TyCon = TyCon::new(5);
#[test]
fn test_con_is_app_with_no_arguments() {
assert_eq!(Type::con(INT), Type::app(INT, vec![]));
}
#[test]
fn test_accessors_split_var_from_app() {
let v = TyVar::from_index(2);
assert_eq!(Type::var(v).as_var(), Some(v));
assert_eq!(Type::var(v).head(), None);
assert!(Type::var(v).is_var());
let pair = Type::app(PAIR, vec![Type::con(INT), Type::var(v)]);
assert_eq!(pair.as_var(), None);
assert_eq!(pair.head(), Some(PAIR));
assert_eq!(pair.args().len(), 2);
assert!(!pair.is_var());
}
#[test]
fn test_display_renders_structure() {
assert_eq!(Type::var(TyVar::from_index(4)).to_string(), "?4");
assert_eq!(Type::con(INT).to_string(), "#0");
let nested = Type::app(PAIR, vec![Type::con(INT), Type::var(TyVar::from_index(1))]);
assert_eq!(nested.to_string(), "#5(#0, ?1)");
}
#[test]
fn test_handles_work_as_map_keys() {
use alloc::collections::BTreeMap;
let mut cons: BTreeMap<TyCon, u32> = BTreeMap::new();
let _ = cons.insert(INT, 0);
let _ = cons.insert(PAIR, 1);
assert_eq!(cons.get(&PAIR), Some(&1));
let mut vars: BTreeMap<TyVar, &str> = BTreeMap::new();
let _ = vars.insert(TyVar::from_index(0), "a");
let _ = vars.insert(TyVar::from_index(1), "b");
assert_eq!(vars.get(&TyVar::from_index(1)), Some(&"b"));
}
}