use crate::types::*;
use indexmap::IndexMap;
#[derive(Debug, Clone)]
pub struct TypeDef {
pub params: Vec<String>,
pub kind: TypeDefKind,
}
#[derive(Debug, Clone)]
pub enum TypeDefKind {
Union(IndexMap<String, Option<Ty>>),
Alias(Ty),
Opaque,
}
#[derive(Debug, Clone, Default)]
pub struct TypeEnv {
pub types: IndexMap<String, TypeDef>,
pub ctor_to_type: IndexMap<String, String>,
}
impl TypeEnv {
pub fn new_with_builtins() -> Self {
let mut e = TypeEnv::default();
let mut r_variants = IndexMap::new();
r_variants.insert("Ok".into(), Some(Ty::Var(0))); r_variants.insert("Err".into(), Some(Ty::Var(1))); e.types.insert("Result".into(), TypeDef {
params: vec!["T".into(), "E".into()],
kind: TypeDefKind::Union(r_variants),
});
e.ctor_to_type.insert("Ok".into(), "Result".into());
e.ctor_to_type.insert("Err".into(), "Result".into());
let mut o_variants = IndexMap::new();
o_variants.insert("Some".into(), Some(Ty::Var(0))); o_variants.insert("None".into(), None);
e.types.insert("Option".into(), TypeDef {
params: vec!["T".into()],
kind: TypeDefKind::Union(o_variants),
});
e.ctor_to_type.insert("Some".into(), "Option".into());
e.ctor_to_type.insert("None".into(), "Option".into());
e.types.insert("Nil".into(), TypeDef {
params: vec![],
kind: TypeDefKind::Alias(Ty::Unit),
});
e.types.insert("Map".into(), TypeDef { params: vec!["K".into(), "V".into()], kind: TypeDefKind::Opaque });
e.types.insert("Set".into(), TypeDef { params: vec!["T".into()], kind: TypeDefKind::Opaque });
let mut tz_variants = IndexMap::new();
tz_variants.insert("Utc".into(), None);
tz_variants.insert("Local".into(), None);
tz_variants.insert("Offset".into(), Some(Ty::int()));
tz_variants.insert("Iana".into(), Some(Ty::str()));
e.types.insert("Tz".into(), TypeDef {
params: vec![],
kind: TypeDefKind::Union(tz_variants),
});
for ctor in &["Utc", "Local", "Offset", "Iana"] {
e.ctor_to_type.insert((*ctor).into(), "Tz".into());
}
let mut http_err_variants = IndexMap::new();
http_err_variants.insert("NetworkError".into(), Some(Ty::str()));
http_err_variants.insert("TimeoutError".into(), None);
http_err_variants.insert("TlsError".into(), Some(Ty::str()));
http_err_variants.insert("DecodeError".into(), Some(Ty::str()));
e.types.insert("HttpError".into(), TypeDef {
params: vec![],
kind: TypeDefKind::Union(http_err_variants),
});
for ctor in &["NetworkError", "TimeoutError", "TlsError", "DecodeError"] {
e.ctor_to_type.insert((*ctor).into(), "HttpError".into());
}
let mut req_fields = IndexMap::new();
req_fields.insert("method".into(), Ty::str());
req_fields.insert("url".into(), Ty::str());
req_fields.insert("headers".into(), Ty::Con("Map".into(), vec![Ty::str(), Ty::str()]));
req_fields.insert("body".into(), Ty::Con("Option".into(), vec![Ty::bytes()]));
req_fields.insert("timeout_ms".into(), Ty::Con("Option".into(), vec![Ty::int()]));
e.types.insert("HttpRequest".into(), TypeDef {
params: vec![],
kind: TypeDefKind::Alias(Ty::Record(req_fields)),
});
let mut resp_fields = IndexMap::new();
resp_fields.insert("status".into(), Ty::int());
resp_fields.insert("headers".into(), Ty::Con("Map".into(), vec![Ty::str(), Ty::str()]));
resp_fields.insert("body".into(), Ty::bytes());
e.types.insert("HttpResponse".into(), TypeDef {
params: vec![],
kind: TypeDefKind::Alias(Ty::Record(resp_fields)),
});
let mut mat_fields = IndexMap::new();
mat_fields.insert("rows".into(), Ty::int());
mat_fields.insert("cols".into(), Ty::int());
mat_fields.insert("data".into(), Ty::List(Box::new(Ty::float())));
e.types.insert("Matrix".into(), TypeDef {
params: vec![],
kind: TypeDefKind::Alias(Ty::Record(mat_fields)),
});
e
}
pub fn add_user_type(&mut self, name: &str, decl: lex_ast::TypeDecl) -> Result<(), String> {
match &decl.definition {
lex_ast::TypeExpr::Union { variants } => {
let mut vmap = IndexMap::new();
for v in variants {
let payload = v.payload.as_ref().map(|p| ty_from_canon(p, &decl.params));
vmap.insert(v.name.clone(), payload);
self.ctor_to_type.insert(v.name.clone(), name.to_string());
}
self.types.insert(name.to_string(), TypeDef {
params: decl.params.clone(),
kind: TypeDefKind::Union(vmap),
});
}
other => {
let ty = ty_from_canon(other, &decl.params);
self.types.insert(name.to_string(), TypeDef {
params: decl.params.clone(),
kind: TypeDefKind::Alias(ty),
});
}
}
Ok(())
}
}
pub fn ty_from_canon(t: &lex_ast::TypeExpr, params: &[String]) -> Ty {
match t {
lex_ast::TypeExpr::Named { name, args } => {
if let Some(idx) = params.iter().position(|p| p == name) {
if !args.is_empty() {
return Ty::Con(name.clone(), args.iter().map(|a| ty_from_canon(a, params)).collect());
}
return Ty::Var(idx as u32);
}
match name.as_str() {
"Int" => return Ty::int(),
"Float" => return Ty::float(),
"Bool" => return Ty::bool(),
"Str" => return Ty::str(),
"Bytes" => return Ty::bytes(),
"Unit" | "Nil" => return Ty::Unit,
"Never" => return Ty::Never,
"List" if args.len() == 1 => return Ty::List(Box::new(ty_from_canon(&args[0], params))),
"Tuple" => return Ty::Tuple(args.iter().map(|a| ty_from_canon(a, params)).collect()),
_ => {}
}
Ty::Con(name.clone(), args.iter().map(|a| ty_from_canon(a, params)).collect())
}
lex_ast::TypeExpr::Record { fields } => {
let mut m = IndexMap::new();
for f in fields { m.insert(f.name.clone(), ty_from_canon(&f.ty, params)); }
Ty::Record(m)
}
lex_ast::TypeExpr::Tuple { items } => Ty::Tuple(items.iter().map(|t| ty_from_canon(t, params)).collect()),
lex_ast::TypeExpr::Function { params: ps, effects, ret } => {
let effs = EffectSet {
concrete: {
let mut s = std::collections::BTreeSet::new();
for e in effects { s.insert(e.name.clone()); }
s
},
var: None,
};
Ty::Function {
params: ps.iter().map(|t| ty_from_canon(t, params)).collect(),
effects: effs,
ret: Box::new(ty_from_canon(ret, params)),
}
}
lex_ast::TypeExpr::Union { .. } => {
Ty::Unit
}
}
}