use std::{borrow::Cow, fmt};
use crate::{
arith::{CompleteConstraints, ConstraintSet, Num, ObjectSafeConstraint, WithBoolean},
PrimitiveType,
};
mod fn_type;
mod object;
mod quantifier;
mod tuple;
pub(crate) use self::{
fn_type::{FnParams, ParamConstraints},
quantifier::ParamQuantifier,
tuple::IndexError,
};
pub use self::{
fn_type::{FnWithConstraints, Function, FunctionBuilder},
object::Object,
tuple::{LengthVar, Slice, Tuple, TupleIndex, TupleLen, UnknownLen},
};
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct TypeVar {
index: usize,
is_free: bool,
}
impl fmt::Display for TypeVar {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
if self.is_free {
formatter.write_str("_")
} else {
write!(formatter, "'{}", Self::param_str(self.index))
}
}
}
impl TypeVar {
fn param_str(index: usize) -> Cow<'static, str> {
const PARAM_NAMES: &str = "TUVXYZ";
PARAM_NAMES.get(index..=index).map_or_else(
|| Cow::from(format!("T{}", index - PARAM_NAMES.len())),
Cow::from,
)
}
pub const fn param(index: usize) -> Self {
Self {
index,
is_free: false,
}
}
pub fn index(self) -> usize {
self.index
}
pub fn is_free(self) -> bool {
self.is_free
}
}
#[derive(Debug, Clone)]
#[non_exhaustive]
pub enum Type<Prim: PrimitiveType = Num> {
Any,
Dyn(DynConstraints<Prim>),
Prim(Prim),
Function(Box<Function<Prim>>),
Tuple(Tuple<Prim>),
Object(Object<Prim>),
Var(TypeVar),
}
impl<Prim: PrimitiveType> PartialEq for Type<Prim> {
fn eq(&self, other: &Self) -> bool {
match (self, other) {
(Self::Any, _) | (_, Self::Any) => true,
(Self::Dyn(x), Self::Dyn(y)) => x == y,
(Self::Prim(x), Self::Prim(y)) => x == y,
(Self::Var(x), Self::Var(y)) => x == y,
(Self::Tuple(xs), Self::Tuple(ys)) => xs == ys,
(Self::Object(x), Self::Object(y)) => x == y,
(Self::Function(x), Self::Function(y)) => x == y,
_ => false,
}
}
}
impl<Prim: PrimitiveType> fmt::Display for Type<Prim> {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Any => formatter.write_str("any"),
Self::Dyn(constraints) => {
if constraints.inner.is_empty() {
formatter.write_str("dyn")
} else {
write!(formatter, "dyn {}", constraints)
}
}
Self::Var(var) => fmt::Display::fmt(var, formatter),
Self::Prim(num) => fmt::Display::fmt(num, formatter),
Self::Function(fn_type) => fmt::Display::fmt(fn_type, formatter),
Self::Tuple(tuple) => fmt::Display::fmt(tuple, formatter),
Self::Object(obj) => fmt::Display::fmt(obj, formatter),
}
}
}
impl<Prim: PrimitiveType> From<Function<Prim>> for Type<Prim> {
fn from(fn_type: Function<Prim>) -> Self {
Self::Function(Box::new(fn_type))
}
}
impl<Prim: PrimitiveType> From<Tuple<Prim>> for Type<Prim> {
fn from(tuple: Tuple<Prim>) -> Self {
Self::Tuple(tuple)
}
}
impl<Prim: PrimitiveType> From<Slice<Prim>> for Type<Prim> {
fn from(slice: Slice<Prim>) -> Self {
Self::Tuple(slice.into())
}
}
impl<Prim: PrimitiveType> From<Object<Prim>> for Type<Prim> {
fn from(object: Object<Prim>) -> Self {
Self::Object(object)
}
}
impl<Prim: PrimitiveType> From<DynConstraints<Prim>> for Type<Prim> {
fn from(constraints: DynConstraints<Prim>) -> Self {
Self::Dyn(constraints)
}
}
macro_rules! impl_from_tuple_for_type {
($($var:tt : $ty:ident),*) => {
impl<Prim, $($ty : Into<Type<Prim>>,)*> From<($($ty,)*)> for Type<Prim>
where
Prim: PrimitiveType,
{
#[allow(unused_variables)] fn from(tuple: ($($ty,)*)) -> Self {
Self::Tuple(Tuple::from(vec![$(tuple.$var.into(),)*]))
}
}
};
}
impl_from_tuple_for_type!();
impl_from_tuple_for_type!(0: T);
impl_from_tuple_for_type!(0: T, 1: U);
impl_from_tuple_for_type!(0: T, 1: U, 2: V);
impl_from_tuple_for_type!(0: T, 1: U, 2: V, 3: W);
impl_from_tuple_for_type!(0: T, 1: U, 2: V, 3: W, 4: X);
impl_from_tuple_for_type!(0: T, 1: U, 2: V, 3: W, 4: X, 5: Y);
impl_from_tuple_for_type!(0: T, 1: U, 2: V, 3: W, 4: X, 5: Y, 6: Z);
impl_from_tuple_for_type!(0: T, 1: U, 2: V, 3: W, 4: X, 5: Y, 6: Z, 7: A);
impl_from_tuple_for_type!(0: T, 1: U, 2: V, 3: W, 4: X, 5: Y, 6: Z, 7: A, 8: B);
impl_from_tuple_for_type!(0: T, 1: U, 2: V, 3: W, 4: X, 5: Y, 6: Z, 7: A, 8: B, 9: C);
impl Type {
pub const NUM: Self = Type::Prim(Num::Num);
}
impl<Prim: WithBoolean> Type<Prim> {
pub const BOOL: Self = Type::Prim(Prim::BOOL);
}
impl<Prim: PrimitiveType> Type<Prim> {
pub fn void() -> Self {
Self::Tuple(Tuple::empty())
}
pub fn param(index: usize) -> Self {
Self::Var(TypeVar::param(index))
}
pub(crate) fn free_var(index: usize) -> Self {
Self::Var(TypeVar {
index,
is_free: true,
})
}
pub fn slice(element: impl Into<Type<Prim>>, length: impl Into<TupleLen>) -> Self {
Self::Tuple(Slice::new(element.into(), length).into())
}
pub fn repeat(self, length: impl Into<TupleLen>) -> Slice<Prim> {
Slice::new(self, length)
}
pub fn is_void(&self) -> bool {
matches!(self, Self::Tuple(tuple) if tuple.is_empty())
}
pub(crate) fn is_primitive(&self) -> Option<bool> {
match self {
Self::Prim(_) => Some(true),
Self::Tuple(_) | Self::Object(_) | Self::Function(_) => Some(false),
_ => None,
}
}
pub fn is_concrete(&self) -> bool {
match self {
Self::Var(var) => !var.is_free,
Self::Any | Self::Prim(_) => true,
Self::Dyn(constraints) => constraints.is_concrete(),
Self::Function(fn_type) => fn_type.is_concrete(),
Self::Tuple(tuple) => tuple.is_concrete(),
Self::Object(obj) => obj.is_concrete(),
}
}
}
#[derive(Clone, PartialEq)]
pub struct DynConstraints<Prim: PrimitiveType> {
pub(crate) inner: CompleteConstraints<Prim>,
}
impl<Prim: PrimitiveType> fmt::Debug for DynConstraints<Prim> {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
fmt::Debug::fmt(&self.inner, formatter)
}
}
impl<Prim: PrimitiveType> fmt::Display for DynConstraints<Prim> {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
fmt::Display::fmt(&self.inner, formatter)
}
}
impl<Prim: PrimitiveType> From<Object<Prim>> for DynConstraints<Prim> {
fn from(object: Object<Prim>) -> Self {
Self {
inner: object.into(),
}
}
}
impl<Prim: PrimitiveType> DynConstraints<Prim> {
pub fn just(constraint: impl ObjectSafeConstraint<Prim>) -> Self {
Self {
inner: CompleteConstraints::from(ConstraintSet::just(constraint)),
}
}
pub fn is_empty(&self) -> bool {
self.inner.is_empty()
}
pub fn object(&self) -> Option<&Object<Prim>> {
self.inner.object.as_ref()
}
fn is_concrete(&self) -> bool {
self.inner.object.as_ref().map_or(true, Object::is_concrete)
}
pub fn insert(&mut self, constraint: impl ObjectSafeConstraint<Prim>) {
self.inner.simple.insert(constraint);
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::ast::TypeAst;
use std::convert::TryFrom;
#[test]
fn types_are_equal_to_self() -> anyhow::Result<()> {
const SAMPLE_TYPES: &[&str] = &[
"Num",
"(Num, Bool)",
"(Num, ...[Bool; N]) -> ()",
"(Num) -> Num",
"for<'T: Lin> (['T; N]) -> 'T",
];
for &sample_type in SAMPLE_TYPES {
let ty = <Type>::try_from(&TypeAst::try_from(sample_type)?)?;
assert!(ty.eq(&ty), "Type is not equal to self: {}", ty);
}
Ok(())
}
#[test]
fn equality_is_preserved_on_renaming_params() {
const EQUAL_FNS: &[&str] = &[
"for<'T: Lin> (['T; N]) -> 'T",
"for<'T: Lin> (['T; L]) -> 'T",
"for<'Ty: Lin> (['Ty; N]) -> 'Ty",
"for<'N: Lin> (['N; T]) -> 'N",
];
let functions: Vec<Type> = EQUAL_FNS
.iter()
.map(|&s| Type::try_from(&TypeAst::try_from(s).unwrap()).unwrap())
.collect();
for (i, function) in functions.iter().enumerate() {
for other_function in &functions[(i + 1)..] {
assert_eq!(function, other_function);
}
}
}
#[test]
fn unequal_functions() {
const FUNCTIONS: &[&str] = &[
"for<'T: Lin> (['T; N]) -> 'T",
"for<len! N; 'T: Lin> (['T; N]) -> 'T",
"(['T; N]) -> 'T",
"for<'T: Lin> (['T; N], 'T) -> 'T",
"for<'T: Lin> (['T; N]) -> ('T)",
];
let functions: Vec<Type> = FUNCTIONS
.iter()
.map(|&s| Type::try_from(&TypeAst::try_from(s).unwrap()).unwrap())
.collect();
for (i, function) in functions.iter().enumerate() {
for other_function in &functions[(i + 1)..] {
assert_ne!(function, other_function);
}
}
}
#[test]
fn concrete_types() {
let sample_types = &[
Type::NUM,
Type::BOOL,
Type::Any,
(Type::BOOL, Type::NUM).into(),
Type::try_from(&TypeAst::try_from("for<'T: Lin> (['T; N]) -> 'T").unwrap()).unwrap(),
];
for ty in sample_types {
assert!(ty.is_concrete(), "{:?}", ty);
}
}
#[test]
fn non_concrete_types() {
let sample_types = &[
Type::free_var(2),
(Type::NUM, Type::free_var(0)).into(),
Function::builder()
.with_arg(Type::free_var(0))
.returning(Type::void())
.into(),
];
for ty in sample_types {
assert!(!ty.is_concrete(), "{:?}", ty);
}
}
}