use bock_types::{FnType, GenericType, NamedType, Predicate, PrimitiveType, Type};
#[must_use]
pub fn format_type(ty: &Type) -> String {
let mut out = String::new();
write_type(&mut out, ty);
out
}
fn write_type(out: &mut String, ty: &Type) {
match ty {
Type::Primitive(p) => out.push_str(primitive_name(p)),
Type::Named(NamedType { name }) => out.push_str(name),
Type::Generic(GenericType { constructor, args }) => {
out.push_str(constructor);
out.push('[');
write_list(out, args);
out.push(']');
}
Type::Tuple(elems) => {
out.push('(');
write_list(out, elems);
out.push(')');
}
Type::Function(f) => write_fn(out, f),
Type::Optional(inner) => {
write_type(out, inner);
out.push('?');
}
Type::Result(ok, err) => {
out.push_str("Result[");
write_type(out, ok);
out.push_str(", ");
write_type(out, err);
out.push(']');
}
Type::TypeVar(id) => {
out.push('?');
out.push_str(&id.to_string());
}
Type::Refined(base, Predicate { source }) => {
write_type(out, base);
out.push_str(" where ");
out.push_str(source);
}
Type::Flexible(_) => out.push_str("<flexible>"),
Type::Error => out.push_str("<error>"),
}
}
fn write_list(out: &mut String, items: &[Type]) {
let mut first = true;
for ty in items {
if !first {
out.push_str(", ");
}
first = false;
write_type(out, ty);
}
}
fn write_fn(out: &mut String, f: &FnType) {
out.push_str("Fn(");
write_list(out, &f.params);
out.push_str(") -> ");
write_type(out, &f.ret);
if !f.effects.is_empty() {
out.push_str(" with ");
let mut first = true;
for effect in &f.effects {
if !first {
out.push_str(", ");
}
first = false;
out.push_str(&effect.name);
}
}
}
fn primitive_name(p: &PrimitiveType) -> &'static str {
match p {
PrimitiveType::Int => "Int",
PrimitiveType::Float => "Float",
PrimitiveType::Int8 => "Int8",
PrimitiveType::Int16 => "Int16",
PrimitiveType::Int32 => "Int32",
PrimitiveType::Int64 => "Int64",
PrimitiveType::Int128 => "Int128",
PrimitiveType::UInt8 => "UInt8",
PrimitiveType::UInt16 => "UInt16",
PrimitiveType::UInt32 => "UInt32",
PrimitiveType::UInt64 => "UInt64",
PrimitiveType::Float32 => "Float32",
PrimitiveType::Float64 => "Float64",
PrimitiveType::BigInt => "BigInt",
PrimitiveType::BigFloat => "BigFloat",
PrimitiveType::Decimal => "Decimal",
PrimitiveType::Bool => "Bool",
PrimitiveType::Char => "Char",
PrimitiveType::String => "String",
PrimitiveType::Byte => "Byte",
PrimitiveType::Bytes => "Bytes",
PrimitiveType::Void => "Void",
PrimitiveType::Never => "Never",
}
}
#[cfg(test)]
mod tests {
use super::*;
use bock_types::{EffectRef, FnType, GenericType, NamedType};
fn int() -> Type {
Type::Primitive(PrimitiveType::Int)
}
fn string_ty() -> Type {
Type::Primitive(PrimitiveType::String)
}
#[test]
fn primitive() {
assert_eq!(format_type(&int()), "Int");
assert_eq!(
format_type(&Type::Primitive(PrimitiveType::Void)),
"Void"
);
}
#[test]
fn named() {
let ty = Type::Named(NamedType {
name: "User".into(),
});
assert_eq!(format_type(&ty), "User");
}
#[test]
fn generic_list_of_int() {
let ty = Type::Generic(GenericType {
constructor: "List".into(),
args: vec![int()],
});
assert_eq!(format_type(&ty), "List[Int]");
}
#[test]
fn generic_map_of_string_int() {
let ty = Type::Generic(GenericType {
constructor: "Map".into(),
args: vec![string_ty(), int()],
});
assert_eq!(format_type(&ty), "Map[String, Int]");
}
#[test]
fn tuple_pair() {
let ty = Type::Tuple(vec![int(), string_ty()]);
assert_eq!(format_type(&ty), "(Int, String)");
}
#[test]
fn function_no_effects() {
let ty = Type::Function(FnType {
params: vec![int(), int()],
ret: Box::new(int()),
effects: vec![],
});
assert_eq!(format_type(&ty), "Fn(Int, Int) -> Int");
}
#[test]
fn function_with_effects() {
let ty = Type::Function(FnType {
params: vec![],
ret: Box::new(Type::Primitive(PrimitiveType::Void)),
effects: vec![EffectRef::new("Logger")],
});
assert_eq!(format_type(&ty), "Fn() -> Void with Logger");
}
#[test]
fn function_with_multiple_effects() {
let ty = Type::Function(FnType {
params: vec![string_ty()],
ret: Box::new(Type::Primitive(PrimitiveType::Void)),
effects: vec![EffectRef::new("Log"), EffectRef::new("Clock")],
});
assert_eq!(format_type(&ty), "Fn(String) -> Void with Log, Clock");
}
#[test]
fn optional() {
let ty = Type::Optional(Box::new(int()));
assert_eq!(format_type(&ty), "Int?");
}
#[test]
fn result_pair() {
let ty = Type::Result(Box::new(int()), Box::new(string_ty()));
assert_eq!(format_type(&ty), "Result[Int, String]");
}
#[test]
fn nested_generic_optional() {
let list_opt = Type::Generic(GenericType {
constructor: "List".into(),
args: vec![Type::Optional(Box::new(int()))],
});
assert_eq!(format_type(&list_opt), "List[Int?]");
}
#[test]
fn type_var_renders_with_id() {
assert_eq!(format_type(&Type::TypeVar(7)), "?7");
}
#[test]
fn refined() {
let ty = Type::Refined(
Box::new(int()),
Predicate {
source: "self > 0".into(),
},
);
assert_eq!(format_type(&ty), "Int where self > 0");
}
#[test]
fn error_type() {
assert_eq!(format_type(&Type::Error), "<error>");
}
}