use crate::ast::PrimType;
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct Symbol(pub String);
#[derive(Debug, Clone, PartialEq)]
pub enum QalaType {
I64,
F64,
Bool,
Str,
Byte,
Void,
Array(Box<QalaType>, Option<usize>),
Tuple(Vec<QalaType>),
Function {
params: Vec<QalaType>,
returns: Box<QalaType>,
},
Named(Symbol),
Result(Box<QalaType>, Box<QalaType>),
Option(Box<QalaType>),
FileHandle,
Unknown,
}
impl QalaType {
pub fn types_match(&self, other: &QalaType) -> bool {
use QalaType::*;
match (self, other) {
(Unknown, _) | (_, Unknown) => true,
(I64, I64)
| (F64, F64)
| (Bool, Bool)
| (Str, Str)
| (Byte, Byte)
| (Void, Void)
| (FileHandle, FileHandle) => true,
(Array(a, asize), Array(b, bsize)) => asize == bsize && a.types_match(b),
(Tuple(a), Tuple(b)) => {
a.len() == b.len() && a.iter().zip(b.iter()).all(|(x, y)| x.types_match(y))
}
(
Function {
params: ap,
returns: ar,
},
Function {
params: bp,
returns: br,
},
) => {
ap.len() == bp.len()
&& ap.iter().zip(bp.iter()).all(|(x, y)| x.types_match(y))
&& ar.types_match(br)
}
(Named(a), Named(b)) => a == b,
(Result(a_ok, a_err), Result(b_ok, b_err)) => {
a_ok.types_match(b_ok) && a_err.types_match(b_err)
}
(Option(a), Option(b)) => a.types_match(b),
_ => false,
}
}
pub fn display(&self) -> String {
use QalaType::*;
match self {
I64 => "i64".to_string(),
F64 => "f64".to_string(),
Bool => "bool".to_string(),
Str => "str".to_string(),
Byte => "byte".to_string(),
Void => "void".to_string(),
Array(elem, Some(n)) => format!("[{}; {}]", elem.display(), n),
Array(elem, None) => format!("[{}]", elem.display()),
Tuple(elems) => {
let inner: Vec<String> = elems.iter().map(|t| t.display()).collect();
format!("({})", inner.join(", "))
}
Function { params, returns } => {
let inner: Vec<String> = params.iter().map(|t| t.display()).collect();
format!("fn({}) -> {}", inner.join(", "), returns.display())
}
Named(Symbol(name)) => name.clone(),
Result(ok, err) => format!("Result<{}, {}>", ok.display(), err.display()),
Option(inner) => format!("Option<{}>", inner.display()),
FileHandle => "FileHandle".to_string(),
Unknown => "?".to_string(),
}
}
pub fn from_prim_type(p: &PrimType) -> Self {
match p {
PrimType::I64 => QalaType::I64,
PrimType::F64 => QalaType::F64,
PrimType::Bool => QalaType::Bool,
PrimType::Str => QalaType::Str,
PrimType::Byte => QalaType::Byte,
PrimType::Void => QalaType::Void,
}
}
}
impl core::fmt::Display for QalaType {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
f.write_str(&self.display())
}
}
#[cfg(test)]
mod tests {
use super::*;
fn ty_match(a: &QalaType, b: &QalaType) -> bool {
a.types_match(b)
}
fn b(t: QalaType) -> Box<QalaType> {
Box::new(t)
}
#[test]
fn every_primitive_equals_itself() {
let prims = [
QalaType::I64,
QalaType::F64,
QalaType::Bool,
QalaType::Str,
QalaType::Byte,
QalaType::Void,
QalaType::FileHandle,
];
for p in &prims {
assert!(ty_match(p, p), "{p:?} should match itself");
assert_eq!(p.clone(), p.clone(), "{p:?} should be == itself");
}
}
#[test]
fn distinct_primitives_are_not_equal() {
assert!(!ty_match(&QalaType::I64, &QalaType::F64));
assert!(!ty_match(&QalaType::Bool, &QalaType::Str));
assert!(!ty_match(&QalaType::Byte, &QalaType::I64));
assert!(!ty_match(&QalaType::Void, &QalaType::Bool));
assert!(!ty_match(&QalaType::FileHandle, &QalaType::Str));
}
#[test]
fn arrays_compare_element_and_length_kind() {
let fixed5 = QalaType::Array(b(QalaType::I64), Some(5));
let fixed5_again = QalaType::Array(b(QalaType::I64), Some(5));
let fixed6 = QalaType::Array(b(QalaType::I64), Some(6));
let dynamic = QalaType::Array(b(QalaType::I64), None);
assert!(ty_match(&fixed5, &fixed5_again));
assert!(!ty_match(&fixed5, &fixed6));
assert!(!ty_match(&fixed5, &dynamic));
assert!(!ty_match(&dynamic, &fixed6));
let dynamic_bool = QalaType::Array(b(QalaType::Bool), None);
assert!(!ty_match(&dynamic, &dynamic_bool));
}
#[test]
fn tuples_compare_elementwise_with_order_significant() {
let i64_bool = QalaType::Tuple(vec![QalaType::I64, QalaType::Bool]);
let i64_bool_again = QalaType::Tuple(vec![QalaType::I64, QalaType::Bool]);
let bool_i64 = QalaType::Tuple(vec![QalaType::Bool, QalaType::I64]);
let single = QalaType::Tuple(vec![QalaType::I64]);
assert!(ty_match(&i64_bool, &i64_bool_again));
assert!(!ty_match(&i64_bool, &bool_i64));
assert!(!ty_match(&i64_bool, &single));
}
#[test]
fn function_types_compare_params_in_order_and_return() {
let f_i64_to_i64 = QalaType::Function {
params: vec![QalaType::I64],
returns: b(QalaType::I64),
};
let f_i64_to_i64_again = QalaType::Function {
params: vec![QalaType::I64],
returns: b(QalaType::I64),
};
let f_str_to_i64 = QalaType::Function {
params: vec![QalaType::Str],
returns: b(QalaType::I64),
};
let f_i64_to_str = QalaType::Function {
params: vec![QalaType::I64],
returns: b(QalaType::Str),
};
let f_i64_i64_to_i64 = QalaType::Function {
params: vec![QalaType::I64, QalaType::I64],
returns: b(QalaType::I64),
};
assert!(ty_match(&f_i64_to_i64, &f_i64_to_i64_again));
assert!(!ty_match(&f_i64_to_i64, &f_str_to_i64));
assert!(!ty_match(&f_i64_to_i64, &f_i64_to_str));
assert!(!ty_match(&f_i64_to_i64, &f_i64_i64_to_i64));
}
#[test]
fn named_types_compare_nominally_by_symbol() {
let shape = QalaType::Named(Symbol("Shape".to_string()));
let shape_again = QalaType::Named(Symbol("Shape".to_string()));
let point = QalaType::Named(Symbol("Point".to_string()));
assert!(ty_match(&shape, &shape_again));
assert!(!ty_match(&shape, &point));
assert!(!ty_match(
&shape,
&QalaType::Named(Symbol("shape".to_string()))
));
}
#[test]
fn result_and_option_compare_their_arguments_structurally() {
let r_i64_str = QalaType::Result(b(QalaType::I64), b(QalaType::Str));
let r_i64_str_again = QalaType::Result(b(QalaType::I64), b(QalaType::Str));
let r_i64_bool = QalaType::Result(b(QalaType::I64), b(QalaType::Bool));
let r_str_str = QalaType::Result(b(QalaType::Str), b(QalaType::Str));
let o_i64 = QalaType::Option(b(QalaType::I64));
assert!(ty_match(&r_i64_str, &r_i64_str_again));
assert!(!ty_match(&r_i64_str, &r_i64_bool));
assert!(!ty_match(&r_i64_str, &r_str_str));
assert!(!ty_match(&r_i64_str, &o_i64));
}
#[test]
fn unknown_is_symmetrically_equal_to_anything() {
let samples = [
QalaType::I64,
QalaType::Str,
QalaType::Array(b(QalaType::I64), None),
QalaType::Named(Symbol("X".to_string())),
QalaType::Result(b(QalaType::I64), b(QalaType::Str)),
QalaType::Unknown,
];
for s in &samples {
assert!(
ty_match(&QalaType::Unknown, s),
"Unknown should match {s:?}"
);
assert!(
ty_match(s, &QalaType::Unknown),
"{s:?} should match Unknown"
);
}
assert!(ty_match(&QalaType::Unknown, &QalaType::Unknown));
}
#[test]
fn types_match_is_symmetric_and_reflexive_on_concrete_pairs() {
let samples: Vec<QalaType> = vec![
QalaType::I64,
QalaType::F64,
QalaType::Bool,
QalaType::Str,
QalaType::Byte,
QalaType::Void,
QalaType::FileHandle,
QalaType::Array(b(QalaType::I64), Some(3)),
QalaType::Array(b(QalaType::I64), None),
QalaType::Tuple(vec![QalaType::I64, QalaType::Bool]),
QalaType::Function {
params: vec![QalaType::I64],
returns: b(QalaType::Bool),
},
QalaType::Named(Symbol("Shape".to_string())),
QalaType::Result(b(QalaType::I64), b(QalaType::Str)),
QalaType::Option(b(QalaType::I64)),
];
for a in &samples {
assert!(ty_match(a, a), "reflexive failure on {a:?}");
for c in &samples {
assert_eq!(
ty_match(a, c),
ty_match(c, a),
"symmetry failure on ({a:?}, {c:?})",
);
}
}
}
#[test]
fn display_produces_the_canonical_lowercase_form() {
assert_eq!(QalaType::I64.display(), "i64");
assert_eq!(QalaType::F64.display(), "f64");
assert_eq!(QalaType::Bool.display(), "bool");
assert_eq!(QalaType::Str.display(), "str");
assert_eq!(QalaType::Byte.display(), "byte");
assert_eq!(QalaType::Void.display(), "void");
assert_eq!(QalaType::FileHandle.display(), "FileHandle");
assert_eq!(
QalaType::Array(b(QalaType::I64), Some(5)).display(),
"[i64; 5]",
);
assert_eq!(QalaType::Array(b(QalaType::I64), None).display(), "[i64]");
assert_eq!(
QalaType::Tuple(vec![QalaType::I64, QalaType::Bool]).display(),
"(i64, bool)",
);
assert_eq!(
QalaType::Function {
params: vec![QalaType::I64],
returns: b(QalaType::I64)
}
.display(),
"fn(i64) -> i64",
);
assert_eq!(
QalaType::Function {
params: vec![QalaType::I64, QalaType::Bool],
returns: b(QalaType::Str),
}
.display(),
"fn(i64, bool) -> str",
);
assert_eq!(
QalaType::Named(Symbol("Shape".to_string())).display(),
"Shape"
);
assert_eq!(
QalaType::Result(b(QalaType::I64), b(QalaType::Str)).display(),
"Result<i64, str>",
);
assert_eq!(QalaType::Option(b(QalaType::I64)).display(), "Option<i64>");
assert_eq!(QalaType::Unknown.display(), "?");
assert_eq!(format!("{}", QalaType::I64), "i64");
assert_eq!(format!("{}", QalaType::Unknown), "?");
}
#[test]
fn from_prim_type_bridges_every_prim_type_variant() {
assert!(ty_match(
&QalaType::from_prim_type(&PrimType::I64),
&QalaType::I64
));
assert!(ty_match(
&QalaType::from_prim_type(&PrimType::F64),
&QalaType::F64
));
assert!(ty_match(
&QalaType::from_prim_type(&PrimType::Bool),
&QalaType::Bool
));
assert!(ty_match(
&QalaType::from_prim_type(&PrimType::Str),
&QalaType::Str
));
assert!(ty_match(
&QalaType::from_prim_type(&PrimType::Byte),
&QalaType::Byte
));
assert!(ty_match(
&QalaType::from_prim_type(&PrimType::Void),
&QalaType::Void
));
}
}