use crate::ir::{Primitive, TypeDef, TypeRef};
pub trait TautType {
fn ir_type_ref() -> TypeRef;
#[must_use]
fn ir_type_def() -> Option<TypeDef> {
None
}
fn collect_type_defs(out: &mut Vec<TypeDef>) {
if let Some(d) = Self::ir_type_def() {
out.push(d);
}
}
}
macro_rules! impl_primitive {
($rust:ty, $variant:ident) => {
impl TautType for $rust {
fn ir_type_ref() -> TypeRef {
TypeRef::Primitive(Primitive::$variant)
}
}
};
}
impl_primitive!(bool, Bool);
impl_primitive!(u8, U8);
impl_primitive!(u16, U16);
impl_primitive!(u32, U32);
impl_primitive!(u64, U64);
impl_primitive!(u128, U128);
impl_primitive!(i8, I8);
impl_primitive!(i16, I16);
impl_primitive!(i32, I32);
impl_primitive!(i64, I64);
impl_primitive!(i128, I128);
impl_primitive!(f32, F32);
impl_primitive!(f64, F64);
impl_primitive!(String, String);
impl_primitive!((), Unit);
impl TautType for &'static str {
fn ir_type_ref() -> TypeRef {
TypeRef::Primitive(Primitive::String)
}
}
impl TautType for char {
fn ir_type_ref() -> TypeRef {
TypeRef::Primitive(Primitive::String)
}
}
impl<T: TautType> TautType for Option<T> {
fn ir_type_ref() -> TypeRef {
TypeRef::Option(Box::new(T::ir_type_ref()))
}
fn collect_type_defs(out: &mut Vec<TypeDef>) {
T::collect_type_defs(out);
}
}
impl<T: TautType> TautType for Vec<T> {
fn ir_type_ref() -> TypeRef {
TypeRef::Vec(Box::new(T::ir_type_ref()))
}
fn collect_type_defs(out: &mut Vec<TypeDef>) {
T::collect_type_defs(out);
}
}
impl<T: TautType, const N: usize> TautType for [T; N] {
fn ir_type_ref() -> TypeRef {
TypeRef::FixedArray {
elem: Box::new(T::ir_type_ref()),
len: N as u64,
}
}
fn collect_type_defs(out: &mut Vec<TypeDef>) {
T::collect_type_defs(out);
}
}
impl<K: TautType, V: TautType, S: std::hash::BuildHasher> TautType
for std::collections::HashMap<K, V, S>
{
fn ir_type_ref() -> TypeRef {
TypeRef::Map {
key: Box::new(K::ir_type_ref()),
value: Box::new(V::ir_type_ref()),
}
}
fn collect_type_defs(out: &mut Vec<TypeDef>) {
K::collect_type_defs(out);
V::collect_type_defs(out);
}
}
impl<T: TautType> TautType for Box<T> {
fn ir_type_ref() -> TypeRef {
T::ir_type_ref()
}
fn ir_type_def() -> Option<TypeDef> {
T::ir_type_def()
}
fn collect_type_defs(out: &mut Vec<TypeDef>) {
T::collect_type_defs(out);
}
}
impl<A: TautType, B: TautType> TautType for (A, B) {
fn ir_type_ref() -> TypeRef {
TypeRef::Tuple(vec![A::ir_type_ref(), B::ir_type_ref()])
}
fn collect_type_defs(out: &mut Vec<TypeDef>) {
A::collect_type_defs(out);
B::collect_type_defs(out);
}
}
impl<A: TautType, B: TautType, C: TautType> TautType for (A, B, C) {
fn ir_type_ref() -> TypeRef {
TypeRef::Tuple(vec![A::ir_type_ref(), B::ir_type_ref(), C::ir_type_ref()])
}
fn collect_type_defs(out: &mut Vec<TypeDef>) {
A::collect_type_defs(out);
B::collect_type_defs(out);
C::collect_type_defs(out);
}
}
impl<A: TautType, B: TautType, C: TautType, D: TautType> TautType for (A, B, C, D) {
fn ir_type_ref() -> TypeRef {
TypeRef::Tuple(vec![
A::ir_type_ref(),
B::ir_type_ref(),
C::ir_type_ref(),
D::ir_type_ref(),
])
}
fn collect_type_defs(out: &mut Vec<TypeDef>) {
A::collect_type_defs(out);
B::collect_type_defs(out);
C::collect_type_defs(out);
D::collect_type_defs(out);
}
}
#[cfg(feature = "uuid")]
impl TautType for uuid::Uuid {
fn ir_type_ref() -> TypeRef {
TypeRef::Primitive(Primitive::Uuid)
}
}
#[cfg(feature = "chrono")]
impl TautType for chrono::DateTime<chrono::Utc> {
fn ir_type_ref() -> TypeRef {
TypeRef::Primitive(Primitive::DateTime)
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::collections::HashMap;
#[test]
fn primitive_impls_return_expected_variants() {
assert_eq!(bool::ir_type_ref(), TypeRef::Primitive(Primitive::Bool));
assert_eq!(u8::ir_type_ref(), TypeRef::Primitive(Primitive::U8));
assert_eq!(u16::ir_type_ref(), TypeRef::Primitive(Primitive::U16));
assert_eq!(u32::ir_type_ref(), TypeRef::Primitive(Primitive::U32));
assert_eq!(u64::ir_type_ref(), TypeRef::Primitive(Primitive::U64));
assert_eq!(u128::ir_type_ref(), TypeRef::Primitive(Primitive::U128));
assert_eq!(i8::ir_type_ref(), TypeRef::Primitive(Primitive::I8));
assert_eq!(i16::ir_type_ref(), TypeRef::Primitive(Primitive::I16));
assert_eq!(i32::ir_type_ref(), TypeRef::Primitive(Primitive::I32));
assert_eq!(i64::ir_type_ref(), TypeRef::Primitive(Primitive::I64));
assert_eq!(i128::ir_type_ref(), TypeRef::Primitive(Primitive::I128));
assert_eq!(f32::ir_type_ref(), TypeRef::Primitive(Primitive::F32));
assert_eq!(f64::ir_type_ref(), TypeRef::Primitive(Primitive::F64));
assert_eq!(String::ir_type_ref(), TypeRef::Primitive(Primitive::String));
assert_eq!(
<&'static str as TautType>::ir_type_ref(),
TypeRef::Primitive(Primitive::String),
);
assert_eq!(char::ir_type_ref(), TypeRef::Primitive(Primitive::String));
assert_eq!(
<() as TautType>::ir_type_ref(),
TypeRef::Primitive(Primitive::Unit),
);
}
#[test]
fn option_of_u32_wraps_correctly() {
assert_eq!(
Option::<u32>::ir_type_ref(),
TypeRef::Option(Box::new(TypeRef::Primitive(Primitive::U32))),
);
}
#[test]
fn vec_of_string_wraps_correctly() {
assert_eq!(
Vec::<String>::ir_type_ref(),
TypeRef::Vec(Box::new(TypeRef::Primitive(Primitive::String))),
);
}
#[test]
fn vec_of_option_i64_composes() {
assert_eq!(
Vec::<Option<i64>>::ir_type_ref(),
TypeRef::Vec(Box::new(TypeRef::Option(Box::new(TypeRef::Primitive(
Primitive::I64,
))))),
);
}
#[test]
fn fixed_array_u8_16_has_len_16() {
assert_eq!(
<[u8; 16] as TautType>::ir_type_ref(),
TypeRef::FixedArray {
elem: Box::new(TypeRef::Primitive(Primitive::U8)),
len: 16,
},
);
}
#[test]
fn hashmap_string_to_u64_is_map() {
assert_eq!(
HashMap::<String, u64>::ir_type_ref(),
TypeRef::Map {
key: Box::new(TypeRef::Primitive(Primitive::String)),
value: Box::new(TypeRef::Primitive(Primitive::U64)),
},
);
}
#[test]
fn tuple_of_two_primitives() {
assert_eq!(
<(u32, String) as TautType>::ir_type_ref(),
TypeRef::Tuple(vec![
TypeRef::Primitive(Primitive::U32),
TypeRef::Primitive(Primitive::String),
]),
);
}
#[test]
fn collect_type_defs_for_primitives_is_empty() {
let mut out = Vec::new();
<u32 as TautType>::collect_type_defs(&mut out);
<bool as TautType>::collect_type_defs(&mut out);
<String as TautType>::collect_type_defs(&mut out);
<() as TautType>::collect_type_defs(&mut out);
assert!(
out.is_empty(),
"primitives should not contribute any TypeDefs, got {out:?}",
);
}
#[test]
fn collect_type_defs_for_composites_of_primitives_is_empty() {
let mut out = Vec::new();
<Option<u32> as TautType>::collect_type_defs(&mut out);
<Vec<Option<i64>> as TautType>::collect_type_defs(&mut out);
<[u8; 4] as TautType>::collect_type_defs(&mut out);
<HashMap<String, u64> as TautType>::collect_type_defs(&mut out);
<(u32, String, bool, char) as TautType>::collect_type_defs(&mut out);
<Box<u64> as TautType>::collect_type_defs(&mut out);
assert!(out.is_empty(), "expected no defs, got {out:?}");
}
}