1use crate::builtins::{module_for_import, module_scope};
5use crate::env::{TypeDefKind, TypeEnv, ty_from_canon_env};
6use crate::error::{PositionedError, TypeError};
7use crate::position::Position;
8use crate::types::*;
9use crate::unifier::{UnifyError, Unifier};
10use indexmap::IndexMap;
11use lex_ast as a;
12use std::collections::{BTreeMap, HashMap};
13
14type FieldSchema = (Vec<String>, Vec<(String, String)>);
17
18pub struct ProgramTypes {
20 pub fn_signatures: IndexMap<String, Scheme>,
21 pub type_env: TypeEnv,
22 pub parse_required_fields: HashMap<usize, Vec<String>>,
31 pub parse_type_schemas: HashMap<usize, Vec<(String, String)>>,
36}
37
38pub fn check_program_with_positions(
52 stages: &[a::Stage],
53 positions: &BTreeMap<String, Position>,
54) -> Result<ProgramTypes, Vec<PositionedError>> {
55 check_program_inner(stages, Some(positions))
56 .map_err(|errs| errs.into_iter().map(|(e, fn_name)| {
57 let pos = fn_name.as_deref().and_then(|n| positions.get(n)).cloned();
58 PositionedError::new(e, pos)
59 }).collect())
60}
61
62pub fn check_program(stages: &[a::Stage]) -> Result<ProgramTypes, Vec<TypeError>> {
63 check_program_inner(stages, None)
64 .map_err(|errs| errs.into_iter().map(|(e, _)| e).collect())
65}
66
67fn check_program_inner(
68 stages: &[a::Stage],
69 _positions: Option<&BTreeMap<String, Position>>,
70) -> Result<ProgramTypes, Vec<(TypeError, Option<String>)>> {
71 let mut tcx = Checker::new();
72 let mut errors: Vec<(TypeError, Option<String>)> = Vec::new();
75
76 for stage in stages {
78 if let a::Stage::Import(i) = stage {
79 if let Some(mod_name) = module_for_import(&i.reference) {
80 if let Some(ty) = module_scope(mod_name, &tcx.type_env) {
81 tcx.globals.insert(i.alias.clone(), Scheme {
82 vars: collect_vars(&ty),
86 eff_vars: collect_eff_vars(&ty),
87 ty,
88 });
89 tcx.module_aliases.insert(i.alias.clone(), mod_name.to_string());
90 }
91 }
92 }
93 }
94
95 for stage in stages {
97 if let a::Stage::TypeDecl(td) = stage {
98 if let Err(e) = tcx.type_env.add_user_type(&td.name, td.clone()) {
99 errors.push((TypeError::RecursiveTypeWithoutConstructor {
100 at_node: "n_0".into(),
101 name: e,
102 }, None));
103 }
104 }
105 }
106
107 for stage in stages {
109 if let a::Stage::FnDecl(fd) = stage {
110 let scheme = function_scheme(fd, &tcx.type_env);
111 tcx.globals.insert(fd.name.clone(), scheme);
112 tcx.fn_params.insert(fd.name.clone(), fd.params.clone());
116 }
117 }
118
119 let mut signatures = IndexMap::new();
124 for stage in stages {
125 if let a::Stage::FnDecl(fd) = stage {
126 match tcx.check_fn(fd) {
127 Ok(scheme) => { signatures.insert(fd.name.clone(), scheme); }
128 Err(es) => {
129 errors.extend(es.into_iter().map(|e| (e, Some(fd.name.clone()))));
130 }
131 }
132 }
133 }
134
135 if errors.is_empty() {
136 let mut parse_required_fields = HashMap::new();
142 let mut parse_type_schemas = HashMap::new();
143 for (call_ptr, ret_ty) in &tcx.pending_parse_calls {
144 if let Some((fields, schema)) = extract_record_fields_and_schema(&tcx.u, &tcx.type_env, ret_ty) {
145 parse_required_fields.insert(*call_ptr, fields);
146 parse_type_schemas.insert(*call_ptr, schema);
147 }
148 }
149 Ok(ProgramTypes {
150 fn_signatures: signatures,
151 type_env: tcx.type_env,
152 parse_required_fields,
153 parse_type_schemas,
154 })
155 } else {
156 Err(errors)
157 }
158}
159
160pub fn check_and_rewrite_program(
166 stages: &mut [a::Stage],
167) -> Result<ProgramTypes, Vec<TypeError>> {
168 let pt = check_program(&*stages)?;
173 if !pt.parse_required_fields.is_empty() {
174 rewrite_parse_calls(stages, &pt.parse_required_fields, &pt.parse_type_schemas);
175 }
176 Ok(pt)
177}
178
179fn rewrite_parse_calls(
193 stages: &mut [a::Stage],
194 required: &HashMap<usize, Vec<String>>,
195 schemas: &HashMap<usize, Vec<(String, String)>>,
196) {
197 for stage in stages.iter_mut() {
198 if let a::Stage::FnDecl(fd) = stage {
199 rewrite_in_expr(&mut fd.body, required, schemas);
200 }
201 }
202}
203
204fn rewrite_in_expr(
205 expr: &mut a::CExpr,
206 required: &HashMap<usize, Vec<String>>,
207 schemas: &HashMap<usize, Vec<(String, String)>>,
208) {
209 let ptr = expr as *const a::CExpr as usize;
210 let do_rewrite = required.get(&ptr).cloned();
211 let do_schema = schemas.get(&ptr).cloned();
212 match expr {
218 a::CExpr::Call { callee, args } => {
219 rewrite_in_expr(callee, required, schemas);
220 for a in args.iter_mut() { rewrite_in_expr(a, required, schemas); }
221 }
222 a::CExpr::Let { value, body, .. } => {
223 rewrite_in_expr(value, required, schemas);
224 rewrite_in_expr(body, required, schemas);
225 }
226 a::CExpr::Match { scrutinee, arms } => {
227 rewrite_in_expr(scrutinee, required, schemas);
228 for arm in arms.iter_mut() { rewrite_in_expr(&mut arm.body, required, schemas); }
229 }
230 a::CExpr::Block { statements, result } => {
231 for s in statements.iter_mut() { rewrite_in_expr(s, required, schemas); }
232 rewrite_in_expr(result, required, schemas);
233 }
234 a::CExpr::Constructor { args, .. } => {
235 for a in args.iter_mut() { rewrite_in_expr(a, required, schemas); }
236 }
237 a::CExpr::RecordLit { fields } => {
238 for f in fields.iter_mut() { rewrite_in_expr(&mut f.value, required, schemas); }
239 }
240 a::CExpr::TupleLit { items } | a::CExpr::ListLit { items } => {
241 for it in items.iter_mut() { rewrite_in_expr(it, required, schemas); }
242 }
243 a::CExpr::FieldAccess { value, .. } => rewrite_in_expr(value, required, schemas),
244 a::CExpr::Lambda { body, .. } => rewrite_in_expr(body, required, schemas),
245 a::CExpr::BinOp { lhs, rhs, .. } => {
246 rewrite_in_expr(lhs, required, schemas);
247 rewrite_in_expr(rhs, required, schemas);
248 }
249 a::CExpr::UnaryOp { expr, .. } => rewrite_in_expr(expr, required, schemas),
250 a::CExpr::Return { value } => rewrite_in_expr(value, required, schemas),
251 a::CExpr::Literal { .. } | a::CExpr::Var { .. } => {}
252 }
253 if let Some(fields) = do_rewrite {
254 match expr {
255 a::CExpr::Call { callee, args } => {
256 if let a::CExpr::FieldAccess { field, .. } = callee.as_mut() {
257 let typed = match field.as_str() {
261 "parse" => "parse_strict_typed", "json_body" => "json_body_typed", other => unreachable!(
264 "rewrite_in_expr: unexpected decode field `{other}`"),
265 };
266 *field = typed.to_string();
267 }
268 args.push(a::CExpr::ListLit {
270 items: fields.into_iter()
271 .map(|f| a::CExpr::Literal {
272 value: a::CLit::Str { value: f },
273 })
274 .collect(),
275 });
276 let schema = do_schema.unwrap_or_default();
278 args.push(a::CExpr::ListLit {
279 items: schema.into_iter()
280 .map(|(name, tag)| a::CExpr::TupleLit {
281 items: vec![
282 a::CExpr::Literal { value: a::CLit::Str { value: name } },
283 a::CExpr::Literal { value: a::CLit::Str { value: tag } },
284 ],
285 })
286 .collect(),
287 });
288 }
289 _ => unreachable!("rewrite table key must point to a Call expression"),
290 }
291 }
292}
293
294fn extract_record_fields_and_schema(
300 u: &Unifier,
301 env: &TypeEnv,
302 ty: &Ty,
303) -> Option<FieldSchema> {
304 let resolved = u.resolve(ty);
305 let Ty::Con(ref name, ref args) = resolved else { return None; };
306 if name != "Result" || args.len() != 2 { return None; }
307 let ok_ty = u.resolve(&args[0]);
308 let unfolded = unfold_record_alias_static(env, ok_ty);
309 if let Ty::Record(fields) = unfolded {
310 let schema: Vec<(String, String)> = fields.iter()
311 .map(|(k, v)| (k.clone(), ty_to_tag(u, v)))
312 .collect();
313 let names: Vec<String> = schema.iter()
319 .filter(|(_, tag)| !tag.starts_with("Option["))
320 .map(|(k, _)| k.clone())
321 .collect();
322 Some((names, schema))
323 } else {
324 None
325 }
326}
327
328fn ty_to_tag(u: &Unifier, ty: &Ty) -> String {
332 let resolved = u.resolve(ty);
333 match &resolved {
334 Ty::Prim(Prim::Int) => "Int".to_string(),
335 Ty::Prim(Prim::Float) => "Float".to_string(),
336 Ty::Prim(Prim::Bool) => "Bool".to_string(),
337 Ty::Prim(Prim::Str) => "Str".to_string(),
338 Ty::Con(name, args) if name == "Option" && args.len() == 1 => {
339 format!("Option[{}]", ty_to_tag(u, &args[0]))
340 }
341 Ty::List(inner) => {
342 format!("List[{}]", ty_to_tag(u, inner))
343 }
344 Ty::Record(_) => "Record".to_string(),
345 _ => "Any".to_string(),
346 }
347}
348
349fn unfold_record_alias_static(env: &TypeEnv, ty: Ty) -> Ty {
355 if let Ty::Con(ref n, ref args) = ty {
356 if let Some(td) = env.types.get(n) {
357 if let TypeDefKind::Alias(inner) = &td.kind {
358 if td.params.len() != args.len() {
359 return ty;
360 }
361 if td.params.is_empty() {
362 return inner.clone();
363 }
364 let mut subst = IndexMap::new();
365 for (i, a) in args.iter().enumerate() {
366 subst.insert(i as u32, a.clone());
367 }
368 return subst_vars(inner, &subst, &IndexMap::new());
369 }
370 }
371 }
372 ty
373}
374
375fn collect_vars(t: &Ty) -> Vec<TyVarId> {
376 let mut out = Vec::new();
377 fn walk(t: &Ty, out: &mut Vec<TyVarId>) {
378 match t {
379 Ty::Var(v) => { if !out.contains(v) { out.push(*v); } }
380 Ty::Prim(_) | Ty::Unit | Ty::Never => {}
381 Ty::List(inner) => walk(inner, out),
382 Ty::Tuple(items) => for it in items { walk(it, out); },
383 Ty::Record(fs) => for v in fs.values() { walk(v, out); },
384 Ty::Con(_, args) => for a in args { walk(a, out); },
385 Ty::Function { params, ret, .. } => {
386 for p in params { walk(p, out); }
387 walk(ret, out);
388 }
389 }
390 }
391 walk(t, &mut out);
392 out
393}
394
395fn collect_eff_vars(t: &Ty) -> Vec<u32> {
399 let mut out = Vec::new();
400 fn walk(t: &Ty, out: &mut Vec<u32>) {
401 match t {
402 Ty::Var(_) | Ty::Prim(_) | Ty::Unit | Ty::Never => {}
403 Ty::List(inner) => walk(inner, out),
404 Ty::Tuple(items) => for it in items { walk(it, out); },
405 Ty::Record(fs) => for v in fs.values() { walk(v, out); },
406 Ty::Con(_, args) => for a in args { walk(a, out); },
407 Ty::Function { params, effects, ret } => {
408 if let Some(v) = effects.var {
409 if !out.contains(&v) { out.push(v); }
410 }
411 for p in params { walk(p, out); }
412 walk(ret, out);
413 }
414 }
415 }
416 walk(t, &mut out);
417 out
418}
419
420fn function_scheme(fd: &a::FnDecl, env: &TypeEnv) -> Scheme {
421 let params: Vec<Ty> = fd.params.iter().map(|p| ty_from_canon_env(&p.ty, &fd.type_params, env)).collect();
423 let ret = ty_from_canon_env(&fd.return_type, &fd.type_params, env);
424 let effects = EffectSet {
428 concrete: {
429 let mut s = std::collections::BTreeSet::new();
430 for e in &fd.effects {
431 let arg = e.arg.as_ref().map(|a| match a {
432 a::EffectArg::Str { value } => crate::types::EffectArg::Str(value.clone()),
433 a::EffectArg::Int { value } => crate::types::EffectArg::Int(*value),
434 a::EffectArg::Ident { value } => crate::types::EffectArg::Ident(value.clone()),
435 });
436 s.insert(crate::types::EffectKind { name: e.name.clone(), arg });
437 }
438 s
439 },
440 var: fd.effect_row_var
445 .as_ref()
446 .and_then(|n| fd.type_params.iter().position(|p| p == n))
447 .map(|i| i as u32),
448 };
449 let ty = Ty::Function { params, effects, ret: Box::new(ret) };
450 let vars: Vec<TyVarId> = (0..fd.type_params.len() as u32).collect();
451 let eff_vars = collect_eff_vars(&ty);
458 Scheme { vars, eff_vars, ty }
459}
460
461struct Checker {
462 u: Unifier,
463 type_env: TypeEnv,
464 globals: IndexMap<String, Scheme>,
465 module_aliases: IndexMap<String, String>,
469 pending_parse_calls: Vec<(usize, Ty)>,
477 fn_params: IndexMap<String, Vec<a::Param>>,
483 recovered_errors: Vec<TypeError>,
489 eff_row_scope: IndexMap<String, u32>,
497}
498
499impl Checker {
500 fn new() -> Self {
501 Self {
502 u: Unifier::new(),
503 type_env: TypeEnv::new_with_builtins(),
504 globals: IndexMap::new(),
505 module_aliases: IndexMap::new(),
506 pending_parse_calls: Vec::new(),
507 fn_params: IndexMap::new(),
508 recovered_errors: Vec::new(),
509 eff_row_scope: IndexMap::new(),
510 }
511 }
512
513 fn check_expr_recover(
521 &mut self,
522 e: &a::CExpr,
523 node_id: &str,
524 locals: &mut IndexMap<String, Ty>,
525 effs: &mut EffectSet,
526 ) -> Ty {
527 match self.check_expr(e, node_id, locals, effs) {
528 Ok(ty) => ty,
529 Err(err) => {
530 self.recovered_errors.push(err);
531 self.u.fresh()
532 }
533 }
534 }
535
536 fn unfold_record_alias(&self, ty: Ty) -> Ty {
546 if let Ty::Con(ref n, ref args) = ty {
547 if let Some(td) = self.type_env.types.get(n) {
548 if let TypeDefKind::Alias(inner) = &td.kind {
549 if td.params.len() != args.len() {
550 return ty;
551 }
552 if td.params.is_empty() {
553 return inner.clone();
554 }
555 let mut subst = IndexMap::new();
556 for (i, a) in args.iter().enumerate() {
557 subst.insert(i as u32, a.clone());
558 }
559 return subst_vars(inner, &subst, &IndexMap::new());
560 }
561 }
562 }
563 ty
564 }
565
566 fn is_alias_con(&self, ty: &Ty) -> bool {
573 if let Ty::Con(name, args) = ty {
574 if let Some(td) = self.type_env.types.get(name) {
575 if matches!(td.kind, TypeDefKind::Alias(_))
576 && td.params.len() == args.len()
577 {
578 return true;
579 }
580 }
581 }
582 false
583 }
584
585 fn is_module_parse_call(&self, callee: &a::CExpr) -> bool {
594 if let a::CExpr::FieldAccess { value, field } = callee {
595 if let a::CExpr::Var { name } = value.as_ref() {
596 if let Some(module) = self.module_aliases.get(name) {
597 return matches!(
598 (module.as_str(), field.as_str()),
599 ("json" | "toml" | "yaml", "parse") | ("http", "json_body")
600 );
601 }
602 }
603 }
604 false
605 }
606
607 fn unify_with_record_coercion(&mut self, a: &Ty, b: &Ty) -> Result<(), UnifyError> {
619 let a = self.u.resolve(a);
620 let b = self.u.resolve(b);
621 self.unify_coerce_inner(a, b)
622 }
623
624 fn unify_coerce_inner(&mut self, a: Ty, b: Ty) -> Result<(), UnifyError> {
625 let (a, b) = match (&a, &b) {
651 (Ty::Con(n1, _), Ty::Con(n2, _)) if n1 == n2 => (a, b),
652 (Ty::Var(_), _) | (_, Ty::Var(_)) => (a, b),
653 (Ty::Con(_, _), Ty::Con(_, _))
654 if self.is_alias_con(&a) && self.is_alias_con(&b) =>
655 {
656 (a, b)
657 }
658 _ => {
659 let a_u = if let Ty::Con(_, _) = &a {
660 self.unfold_record_alias(a.clone())
661 } else {
662 a
663 };
664 let b_u = if let Ty::Con(_, _) = &b {
665 self.unfold_record_alias(b.clone())
666 } else {
667 b
668 };
669 (a_u, b_u)
670 }
671 };
672
673 match (&a, &b) {
674 (Ty::Record(fa), Ty::Record(fb)) => {
675 if fa.len() != fb.len() {
676 return Err(UnifyError::Mismatch { a: a.clone(), b: b.clone() });
677 }
678 for (k, va) in fa.clone() {
679 match fb.get(&k) {
680 Some(vb) => self.unify_coerce_inner(va, vb.clone())?,
681 None => return Err(UnifyError::Mismatch { a: a.clone(), b: b.clone() }),
682 }
683 }
684 Ok(())
685 }
686 (Ty::List(ta), Ty::List(tb)) => {
687 self.unify_coerce_inner((**ta).clone(), (**tb).clone())
688 }
689 (Ty::Tuple(xs), Ty::Tuple(ys)) if xs.len() == ys.len() => {
690 for (x, y) in xs.clone().into_iter().zip(ys.clone()) {
691 self.unify_coerce_inner(x, y)?;
692 }
693 Ok(())
694 }
695 (Ty::Con(n1, a1), Ty::Con(n2, a2)) if n1 == n2 && a1.len() == a2.len() => {
698 for (x, y) in a1.clone().into_iter().zip(a2.clone()) {
699 self.unify_coerce_inner(x, y)?;
700 }
701 Ok(())
702 }
703 (Ty::Function { params: pa, effects: ea, ret: ra },
708 Ty::Function { params: pb, effects: eb, ret: rb })
709 if pa.len() == pb.len() => {
710 for (x, y) in pa.clone().into_iter().zip(pb.clone()) {
711 self.unify_coerce_inner(x, y)?;
712 }
713 self.u.unify_effects(ea, eb)?;
718 self.unify_coerce_inner((**ra).clone(), (**rb).clone())
719 }
720 _ => self.u.unify(&a, &b),
721 }
722 }
723
724 fn check_fn(&mut self, fd: &a::FnDecl) -> Result<Scheme, Vec<TypeError>> {
725 let scheme = function_scheme(fd, &self.type_env);
727 let (inst_ty, eff_subst) = instantiate_with_eff(&scheme, &mut self.u);
728 let (param_tys, declared_effects, ret_ty) = match inst_ty {
729 Ty::Function { params, effects, ret } => (params, effects, *ret),
730 _ => unreachable!(),
731 };
732
733 let saved_scope = std::mem::take(&mut self.eff_row_scope);
739 for (i, name) in fd.type_params.iter().enumerate() {
740 if let Some(fresh) = eff_subst.get(&(i as u32)) {
741 self.eff_row_scope.insert(name.clone(), *fresh);
742 }
743 }
744
745 let mut locals: IndexMap<String, Ty> = IndexMap::new();
746 for (p, t) in fd.params.iter().zip(param_tys.iter()) {
747 locals.insert(p.name.clone(), t.clone());
748 }
749
750 let mut errors: Vec<TypeError> = Vec::new();
754 let mut inferred_effects = EffectSet::empty();
755
756 let body_ok = match self.check_expr(&fd.body, "n_0", &mut locals, &mut inferred_effects) {
758 Ok(body_ty) => {
759 if let Err(e) = self.unify_with_record_coercion(&body_ty, &ret_ty) {
765 errors.push(mismatch_err("n_0", e, &self.u, vec![format!("in function `{}`", fd.name)]));
766 false
767 } else {
768 true
769 }
770 }
771 Err(e) => { errors.push(e); false }
772 };
773
774 let body_had_recovered = !self.recovered_errors.is_empty();
778 errors.append(&mut self.recovered_errors);
779
780 if body_ok && !body_had_recovered && !inferred_effects.is_subset(&declared_effects) {
785 for e in inferred_effects.concrete.iter() {
786 if !declared_effects.concrete.iter().any(|d| d.subsumes(e)) {
787 errors.push(TypeError::EffectNotDeclared {
788 at_node: "n_0".into(),
789 effect: e.pretty(),
790 });
791 break;
792 }
793 }
794 }
795
796 if !fd.examples.is_empty() {
801 if !declared_effects.concrete.is_empty() {
802 errors.push(TypeError::ExamplesOnEffectfulFn {
803 at_node: "n_0".into(),
804 fn_name: fd.name.clone(),
805 });
806 } else {
807 for (case_index, ex) in fd.examples.iter().enumerate() {
808 if ex.args.len() != param_tys.len() {
809 errors.push(TypeError::ExampleArityMismatch {
810 at_node: "n_0".into(),
811 fn_name: fd.name.clone(),
812 case_index,
813 expected: param_tys.len(),
814 got: ex.args.len(),
815 });
816 continue;
817 }
818 let mut example_locals: IndexMap<String, Ty> = IndexMap::new();
819 let mut example_effects = EffectSet::empty();
820 let mut args_ok = true;
821 for (i, (arg, expected_ty)) in
822 ex.args.iter().zip(param_tys.iter()).enumerate()
823 {
824 match self.check_expr(arg, "n_0", &mut example_locals, &mut example_effects) {
825 Ok(arg_ty) => {
826 if let Err(e) = self.unify_with_record_coercion(&arg_ty, expected_ty) {
827 errors.push(mismatch_err(
828 "n_0", e, &self.u,
829 vec![format!("in example #{} for `{}`, argument {}", case_index + 1, fd.name, i + 1)],
830 ));
831 args_ok = false;
832 }
833 }
834 Err(e) => { errors.push(e); args_ok = false; }
835 }
836 }
837 if args_ok {
838 match self.check_expr(&ex.expected, "n_0", &mut example_locals, &mut example_effects) {
839 Ok(expected_ty) => {
840 if let Err(e) = self.unify_with_record_coercion(&expected_ty, &ret_ty) {
841 errors.push(mismatch_err(
842 "n_0", e, &self.u,
843 vec![format!("in example #{} for `{}`, expected value", case_index + 1, fd.name)],
844 ));
845 }
846 }
847 Err(e) => errors.push(e),
848 }
849 }
850 if let Some(e) = example_effects.concrete.iter().next() {
855 errors.push(TypeError::EffectNotDeclared {
856 at_node: "n_0".into(),
857 effect: e.pretty(),
858 });
859 }
860 }
861 }
862 }
863
864 errors.append(&mut self.recovered_errors);
866 self.eff_row_scope = saved_scope;
869 if errors.is_empty() { Ok(scheme) } else { Err(errors) }
870 }
871
872 fn check_expr(
873 &mut self,
874 e: &a::CExpr,
875 node_id: &str,
876 locals: &mut IndexMap<String, Ty>,
877 effs: &mut EffectSet,
878 ) -> Result<Ty, TypeError> {
879 match e {
880 a::CExpr::Literal { value } => Ok(lit_type(value)),
881 a::CExpr::Var { name } => {
882 if let Some(t) = locals.get(name) {
883 return Ok(t.clone());
884 }
885 if let Some(scheme) = self.globals.get(name).cloned() {
886 return Ok(instantiate(&scheme, &mut self.u));
887 }
888 Err(TypeError::UnknownIdentifier { at_node: node_id.into(), name: name.clone() })
889 }
890 a::CExpr::Constructor { name, args } => self.check_constructor(name, args, node_id, locals, effs),
891 a::CExpr::Call { callee, args } => self.check_call(e, callee, args, node_id, locals, effs),
892 a::CExpr::Let { name, ty, value, body } => {
893 let v_ty = self.check_expr_recover(value, node_id, locals, effs);
897 if let Some(declared) = ty {
898 let d = ty_from_canon_env(declared, &[], &self.type_env);
899 if let Err(err) = self.unify_with_record_coercion(&v_ty, &d) {
900 return Err(mismatch_err(node_id, err, &self.u, vec![format!("in let `{}`", name)]));
901 }
902 }
903 let prev = locals.insert(name.clone(), v_ty);
904 let body_ty = self.check_expr(body, node_id, locals, effs)?;
905 match prev {
906 Some(p) => { locals.insert(name.clone(), p); }
907 None => { locals.shift_remove(name); }
908 }
909 Ok(body_ty)
910 }
911 a::CExpr::Match { scrutinee, arms } => {
912 let scrut_ty = self.check_expr(scrutinee, node_id, locals, effs)?;
913 if arms.is_empty() {
914 return Err(TypeError::NonExhaustiveMatch {
915 at_node: node_id.into(), missing: vec!["_".into()]
916 });
917 }
918 let result_ty = self.u.fresh();
919 for arm in arms {
920 let mut arm_locals = locals.clone();
921 self.bind_pattern(&arm.pattern, &scrut_ty, &mut arm_locals, node_id)?;
922 let arm_ty = self.check_expr(&arm.body, node_id, &mut arm_locals, effs)?;
923 if let Err(err) = self.unify_with_record_coercion(&arm_ty, &result_ty) {
924 return Err(mismatch_err(node_id, err, &self.u, vec!["in match arm".into()]));
925 }
926 }
927 Ok(result_ty)
928 }
929 a::CExpr::Block { statements, result } => {
930 for s in statements {
934 let _ = self.check_expr_recover(s, node_id, locals, effs);
935 }
936 self.check_expr(result, node_id, locals, effs)
937 }
938 a::CExpr::RecordLit { fields } => {
939 let mut tys = IndexMap::new();
940 for f in fields {
941 if tys.contains_key(&f.name) {
942 return Err(TypeError::DuplicateField {
943 at_node: node_id.into(), field: f.name.clone()
944 });
945 }
946 let ft = self.check_expr(&f.value, node_id, locals, effs)?;
947 tys.insert(f.name.clone(), ft);
948 }
949 Ok(Ty::Record(tys))
950 }
951 a::CExpr::TupleLit { items } => {
952 let mut ts = Vec::new();
953 for it in items { ts.push(self.check_expr(it, node_id, locals, effs)?); }
954 Ok(Ty::Tuple(ts))
955 }
956 a::CExpr::ListLit { items } => {
957 let elem = self.u.fresh();
958 for it in items {
959 let t = self.check_expr(it, node_id, locals, effs)?;
960 if let Err(err) = self.unify_with_record_coercion(&t, &elem) {
961 return Err(mismatch_err(node_id, err, &self.u, vec!["in list literal".into()]));
962 }
963 }
964 Ok(Ty::List(Box::new(elem)))
965 }
966 a::CExpr::FieldAccess { value, field } => {
967 let vt = self.check_expr(value, node_id, locals, effs)?;
968 let resolved = self.u.resolve(&vt);
969 let resolved = if let Ty::Con(_, _) = &resolved {
977 let unfolded = self.unfold_record_alias(resolved.clone());
978 if matches!(unfolded, Ty::Record(_)) {
979 unfolded
980 } else {
981 resolved
982 }
983 } else {
984 resolved
985 };
986 match resolved {
987 Ty::Record(fields) => fields.get(field).cloned()
988 .ok_or_else(|| TypeError::UnknownField {
989 at_node: node_id.into(),
990 record_type: Ty::Record(fields.clone()).pretty(),
991 field: field.clone(),
992 }),
993 other => Err(TypeError::TypeMismatch {
994 at_node: node_id.into(),
995 expected: "record".into(),
996 got: other.pretty(),
997 context: vec![format!("field access `.{}`", field)],
998 }),
999 }
1000 }
1001 a::CExpr::Lambda { params, return_type, effects: l_effects, effect_row_var: l_row_var, body } => {
1002 let param_tys: Vec<Ty> = params.iter().map(|p| ty_from_canon_env(&p.ty, &[], &self.type_env)).collect();
1003 let ret_ty = ty_from_canon_env(return_type, &[], &self.type_env);
1004 let row_var = match l_row_var {
1011 Some(name) => match self.eff_row_scope.get(name) {
1012 Some(id) => Some(*id),
1013 None => {
1014 return Err(TypeError::EffectNotDeclared {
1015 at_node: node_id.into(),
1016 effect: format!("unbound effect-row variable `{}`", name),
1017 });
1018 }
1019 },
1020 None => None,
1021 };
1022 let declared = EffectSet {
1023 concrete: {
1024 let mut s = std::collections::BTreeSet::new();
1025 for e in l_effects {
1026 let arg = e.arg.as_ref().map(|a| match a {
1027 a::EffectArg::Str { value } => crate::types::EffectArg::Str(value.clone()),
1028 a::EffectArg::Int { value } => crate::types::EffectArg::Int(*value),
1029 a::EffectArg::Ident { value } => crate::types::EffectArg::Ident(value.clone()),
1030 });
1031 s.insert(crate::types::EffectKind { name: e.name.clone(), arg });
1032 }
1033 s
1034 },
1035 var: row_var,
1036 };
1037 let mut inner_locals = locals.clone();
1038 for (p, t) in params.iter().zip(param_tys.iter()) {
1039 inner_locals.insert(p.name.clone(), t.clone());
1040 }
1041 let mut inner_effs = EffectSet::empty();
1042 let body_ty = self.check_expr(body, node_id, &mut inner_locals, &mut inner_effs)?;
1043 if let Err(err) = self.unify_with_record_coercion(&body_ty, &ret_ty) {
1044 return Err(mismatch_err(node_id, err, &self.u, vec!["in lambda body".into()]));
1045 }
1046 if !inner_effs.is_subset(&declared) {
1047 for e in inner_effs.concrete.iter() {
1048 if !declared.concrete.iter().any(|d| d.subsumes(e)) {
1049 return Err(TypeError::EffectNotDeclared {
1050 at_node: node_id.into(),
1051 effect: e.pretty(),
1052 });
1053 }
1054 }
1055 }
1056 if let Some(iv) = inner_effs.var {
1062 if declared.var != Some(iv) {
1063 return Err(TypeError::EffectNotDeclared {
1064 at_node: node_id.into(),
1065 effect: "open effect row (annotate the lambda's effects with `| <row-var>`)".into(),
1066 });
1067 }
1068 }
1069 Ok(Ty::function(param_tys, declared, ret_ty))
1070 }
1071 a::CExpr::BinOp { op, lhs, rhs } => self.check_binop(op, lhs, rhs, node_id, locals, effs),
1072 a::CExpr::UnaryOp { op, expr } => {
1073 let t = self.check_expr(expr, node_id, locals, effs)?;
1074 match op.as_str() {
1075 "-" => {
1076 let r = self.u.resolve(&t);
1078 match r {
1079 Ty::Prim(Prim::Int) | Ty::Prim(Prim::Float) => Ok(t),
1080 Ty::Var(_) => {
1081 self.u.unify(&t, &Ty::int()).map_err(|e| mismatch_err(node_id, e, &self.u, vec![]))?;
1083 Ok(Ty::int())
1084 }
1085 other => Err(TypeError::TypeMismatch {
1086 at_node: node_id.into(),
1087 expected: "Int or Float".into(),
1088 got: other.pretty(),
1089 context: vec!["unary `-`".into()],
1090 }),
1091 }
1092 }
1093 "not" => {
1094 self.u.unify(&t, &Ty::bool()).map_err(|e| mismatch_err(node_id, e, &self.u, vec!["unary `not`".into()]))?;
1095 Ok(Ty::bool())
1096 }
1097 other => panic!("unknown unary op: {other}"),
1098 }
1099 }
1100 a::CExpr::Return { value } => {
1101 self.check_expr(value, node_id, locals, effs)?;
1104 Ok(Ty::Never)
1105 }
1106 }
1107 }
1108
1109 fn check_binop(
1110 &mut self,
1111 op: &str,
1112 lhs: &a::CExpr,
1113 rhs: &a::CExpr,
1114 node_id: &str,
1115 locals: &mut IndexMap<String, Ty>,
1116 effs: &mut EffectSet,
1117 ) -> Result<Ty, TypeError> {
1118 let lt = self.check_expr(lhs, node_id, locals, effs)?;
1119 let rt = self.check_expr(rhs, node_id, locals, effs)?;
1120 match op {
1121 "+" => {
1122 self.u.unify(<, &rt).map_err(|e| mismatch_err(node_id, e, &self.u, vec![format!("operator `{op}`")]))?;
1130 let r = self.unfold_record_alias(self.u.resolve(<));
1131 match r {
1132 Ty::Prim(Prim::Int) | Ty::Prim(Prim::Float) | Ty::Prim(Prim::Str) => Ok(lt),
1133 Ty::Var(_) => {
1134 self.u.unify(<, &Ty::int()).map_err(|e| mismatch_err(node_id, e, &self.u, vec![format!("operator `{op}`")]))?;
1135 Ok(Ty::int())
1136 }
1137 other => Err(TypeError::TypeMismatch {
1138 at_node: node_id.into(),
1139 expected: "Int, Float, or Str".into(),
1140 got: other.pretty(),
1141 context: vec![format!("operator `{op}`")],
1142 }),
1143 }
1144 }
1145 "-" | "*" | "/" | "%" => {
1146 self.u.unify(<, &rt).map_err(|e| mismatch_err(node_id, e, &self.u, vec![format!("operator `{op}`")]))?;
1147 let r = self.unfold_record_alias(self.u.resolve(<));
1148 match r {
1149 Ty::Prim(Prim::Int) | Ty::Prim(Prim::Float) => Ok(lt),
1150 Ty::Var(_) => {
1151 self.u.unify(<, &Ty::int()).map_err(|e| mismatch_err(node_id, e, &self.u, vec![format!("operator `{op}`")]))?;
1152 Ok(Ty::int())
1153 }
1154 other => Err(TypeError::TypeMismatch {
1155 at_node: node_id.into(),
1156 expected: "Int or Float".into(),
1157 got: other.pretty(),
1158 context: vec![format!("operator `{op}`")],
1159 }),
1160 }
1161 }
1162 "==" | "!=" => {
1163 self.u.unify(<, &rt).map_err(|e| mismatch_err(node_id, e, &self.u, vec![format!("operator `{op}`")]))?;
1164 Ok(Ty::bool())
1165 }
1166 "<" | "<=" | ">" | ">=" => {
1167 self.u.unify(<, &rt).map_err(|e| mismatch_err(node_id, e, &self.u, vec![format!("operator `{op}`")]))?;
1168 let r = self.unfold_record_alias(self.u.resolve(<));
1169 match r {
1170 Ty::Prim(Prim::Int) | Ty::Prim(Prim::Float) | Ty::Prim(Prim::Str) => Ok(Ty::bool()),
1171 Ty::Var(_) => {
1172 self.u.unify(<, &Ty::int()).map_err(|e| mismatch_err(node_id, e, &self.u, vec![format!("operator `{op}`")]))?;
1173 Ok(Ty::bool())
1174 }
1175 other => Err(TypeError::TypeMismatch {
1176 at_node: node_id.into(),
1177 expected: "Int, Float, or Str".into(),
1178 got: other.pretty(),
1179 context: vec![format!("operator `{op}`")],
1180 }),
1181 }
1182 }
1183 "and" | "or" => {
1184 self.u.unify(<, &Ty::bool()).map_err(|e| mismatch_err(node_id, e, &self.u, vec![format!("operator `{op}`")]))?;
1185 self.u.unify(&rt, &Ty::bool()).map_err(|e| mismatch_err(node_id, e, &self.u, vec![format!("operator `{op}`")]))?;
1186 Ok(Ty::bool())
1187 }
1188 other => panic!("unknown binop: {other}"),
1189 }
1190 }
1191
1192 fn check_call(
1193 &mut self,
1194 call_expr: &a::CExpr,
1195 callee: &a::CExpr,
1196 args: &[a::CExpr],
1197 node_id: &str,
1198 locals: &mut IndexMap<String, Ty>,
1199 effs: &mut EffectSet,
1200 ) -> Result<Ty, TypeError> {
1201 let parse_call_ptr = if self.is_module_parse_call(callee) {
1209 Some(call_expr as *const a::CExpr as usize)
1210 } else {
1211 None
1212 };
1213 let callee_ty = self.check_expr(callee, node_id, locals, effs)?;
1214 let resolved = self.u.resolve(&callee_ty);
1215 match resolved {
1216 Ty::Function { params, effects, ret } => {
1217 if params.len() != args.len() {
1218 return Err(TypeError::ArityMismatch {
1219 at_node: node_id.into(),
1220 expected: params.len(),
1221 got: args.len(),
1222 });
1223 }
1224 for (i, (a, p)) in args.iter().zip(params.iter()).enumerate() {
1225 let at = self.check_expr(a, node_id, locals, effs)?;
1226 if let Err(err) = self.unify_with_record_coercion(&at, p) {
1227 return Err(mismatch_err(node_id, err, &self.u, vec![format!("argument {} of call", i + 1)]));
1228 }
1229 }
1230 if let a::CExpr::Var { name: callee_name } = callee {
1237 if let Some(callee_params) = self.fn_params.get(callee_name).cloned() {
1238 for (i, (param, arg)) in callee_params.iter().zip(args.iter()).enumerate() {
1239 if let a::TypeExpr::Refined { binding, predicate, .. } = ¶m.ty {
1240 let outcome = crate::discharge::try_discharge(
1241 predicate, binding, arg);
1242 if let crate::discharge::DischargeOutcome::Refuted { reason } = outcome {
1243 return Err(TypeError::RefinementViolation {
1244 at_node: node_id.into(),
1245 fn_name: callee_name.clone(),
1246 param_index: i,
1247 binding: binding.clone(),
1248 reason,
1249 });
1250 }
1251 }
1252 }
1253 }
1254 }
1255 let resolved_effects = self.u.resolve_effects(&effects);
1260 effs.extend(&resolved_effects);
1261 if let Some(ptr) = parse_call_ptr {
1269 self.pending_parse_calls.push((ptr, (*ret).clone()));
1270 }
1271 Ok(*ret)
1272 }
1273 Ty::Var(_) => {
1274 let mut p_tys = Vec::new();
1276 for a in args { p_tys.push(self.check_expr(a, node_id, locals, effs)?); }
1277 let r = self.u.fresh();
1278 let f = Ty::function(p_tys, EffectSet::empty(), r.clone());
1279 self.u.unify(&callee_ty, &f).map_err(|e| mismatch_err(node_id, e, &self.u, vec!["in call".into()]))?;
1280 Ok(r)
1281 }
1282 other => Err(TypeError::TypeMismatch {
1283 at_node: node_id.into(),
1284 expected: "function".into(),
1285 got: other.pretty(),
1286 context: vec!["in call".into()],
1287 }),
1288 }
1289 }
1290
1291 fn check_constructor(
1292 &mut self,
1293 name: &str,
1294 args: &[a::CExpr],
1295 node_id: &str,
1296 locals: &mut IndexMap<String, Ty>,
1297 effs: &mut EffectSet,
1298 ) -> Result<Ty, TypeError> {
1299 let owning = self.type_env.ctor_to_type.get(name).cloned()
1300 .ok_or_else(|| TypeError::UnknownVariant {
1301 at_node: node_id.into(),
1302 constructor: name.to_string(),
1303 })?;
1304 let def = self.type_env.types.get(&owning).cloned()
1305 .expect("ctor_to_type points to a real type");
1306 let variants = match &def.kind {
1307 TypeDefKind::Union(v) => v.clone(),
1308 _ => return Err(TypeError::UnknownVariant {
1309 at_node: node_id.into(),
1310 constructor: name.to_string(),
1311 }),
1312 };
1313 let mut subst = IndexMap::new();
1316 let mut con_args = Vec::with_capacity(def.params.len());
1317 for (i, _p) in def.params.iter().enumerate() {
1318 let fresh = self.u.fresh();
1319 subst.insert(i as u32, fresh.clone());
1320 con_args.push(fresh);
1321 }
1322 let payload = variants.get(name).cloned().flatten();
1323 match (payload, args) {
1324 (None, []) => Ok(Ty::Con(owning, con_args)),
1325 (Some(payload), args) => {
1326 let inst_payload = subst_vars(&payload, &subst, &IndexMap::new());
1327 let arg_count = match &inst_payload {
1328 Ty::Tuple(items) => items.len(),
1329 _ => 1,
1330 };
1331 if arg_count != args.len() {
1332 return Err(TypeError::ArityMismatch {
1333 at_node: node_id.into(),
1334 expected: arg_count,
1335 got: args.len(),
1336 });
1337 }
1338 if args.len() == 1 {
1339 let at = self.check_expr(&args[0], node_id, locals, effs)?;
1340 self.unify_with_record_coercion(&at, &inst_payload).map_err(|e| mismatch_err(node_id, e, &self.u, vec![format!("constructor `{}`", name)]))?;
1341 } else if let Ty::Tuple(items) = inst_payload {
1342 for (i, (a, t)) in args.iter().zip(items.iter()).enumerate() {
1343 let at = self.check_expr(a, node_id, locals, effs)?;
1344 self.unify_with_record_coercion(&at, t).map_err(|e| mismatch_err(node_id, e, &self.u, vec![format!("constructor `{}` arg {}", name, i + 1)]))?;
1345 }
1346 }
1347 Ok(Ty::Con(owning, con_args))
1348 }
1349 (None, _) => Err(TypeError::ArityMismatch {
1350 at_node: node_id.into(), expected: 0, got: args.len(),
1351 }),
1352 }
1353 }
1354
1355 fn bind_pattern(
1356 &mut self,
1357 pat: &a::Pattern,
1358 ty: &Ty,
1359 locals: &mut IndexMap<String, Ty>,
1360 node_id: &str,
1361 ) -> Result<(), TypeError> {
1362 match pat {
1363 a::Pattern::PWild => Ok(()),
1364 a::Pattern::PVar { name } => {
1365 locals.insert(name.clone(), ty.clone());
1366 Ok(())
1367 }
1368 a::Pattern::PLiteral { value } => {
1369 let lt = lit_type(value);
1370 self.unify_with_record_coercion(<, ty).map_err(|e| mismatch_err(node_id, e, &self.u, vec!["in pattern".into()]))?;
1371 Ok(())
1372 }
1373 a::Pattern::PConstructor { name, args } => {
1374 let owning = self.type_env.ctor_to_type.get(name).cloned()
1376 .ok_or_else(|| TypeError::UnknownVariant {
1377 at_node: node_id.into(), constructor: name.clone(),
1378 })?;
1379 let def = self.type_env.types.get(&owning).cloned().unwrap();
1380 let mut subst = IndexMap::new();
1381 let mut con_args = Vec::new();
1382 for (i, _) in def.params.iter().enumerate() {
1383 let fresh = self.u.fresh();
1384 subst.insert(i as u32, fresh.clone());
1385 con_args.push(fresh);
1386 }
1387 let con_ty = Ty::Con(owning.clone(), con_args);
1388 self.unify_with_record_coercion(&con_ty, ty).map_err(|e| mismatch_err(node_id, e, &self.u, vec![format!("constructor pattern `{}`", name)]))?;
1389 let payload = match &def.kind {
1390 TypeDefKind::Union(v) => v.get(name).cloned().flatten(),
1391 _ => None,
1392 };
1393 match (payload, args.as_slice()) {
1394 (None, []) => Ok(()),
1395 (Some(payload), args) => {
1396 let inst = subst_vars(&payload, &subst, &IndexMap::new());
1397 if args.len() == 1 {
1398 self.bind_pattern(&args[0], &inst, locals, node_id)?;
1399 } else if let Ty::Tuple(items) = inst {
1400 for (a, t) in args.iter().zip(items.iter()) {
1401 self.bind_pattern(a, t, locals, node_id)?;
1402 }
1403 }
1404 Ok(())
1405 }
1406 (None, _) => Err(TypeError::ArityMismatch {
1407 at_node: node_id.into(), expected: 0, got: args.len(),
1408 }),
1409 }
1410 }
1411 a::Pattern::PRecord { fields } => {
1412 let resolved = self.unfold_record_alias(self.u.resolve(ty));
1417 let rec = match resolved {
1418 Ty::Record(r) => r,
1419 _ => return Err(TypeError::TypeMismatch {
1420 at_node: node_id.into(),
1421 expected: "record".into(),
1422 got: ty.pretty(),
1423 context: vec!["in record pattern".into()],
1424 }),
1425 };
1426 for f in fields {
1427 let ft = rec.get(&f.name).cloned()
1428 .ok_or_else(|| TypeError::UnknownField {
1429 at_node: node_id.into(),
1430 record_type: Ty::Record(rec.clone()).pretty(),
1431 field: f.name.clone(),
1432 })?;
1433 self.bind_pattern(&f.pattern, &ft, locals, node_id)?;
1434 }
1435 Ok(())
1436 }
1437 a::Pattern::PTuple { items } => {
1438 if items.is_empty() {
1440 return self.unify_with_record_coercion(&Ty::Unit, ty)
1441 .map_err(|e| mismatch_err(node_id, e, &self.u, vec!["in unit pattern".into()]));
1442 }
1443 let resolved = self.u.resolve(ty);
1444 let tup = match resolved {
1445 Ty::Tuple(t) => t,
1446 Ty::Var(_) => {
1447 let fresh: Vec<Ty> = items.iter().map(|_| self.u.fresh()).collect();
1448 let tup_ty = Ty::Tuple(fresh.clone());
1449 self.unify_with_record_coercion(&tup_ty, ty).map_err(|e| mismatch_err(node_id, e, &self.u, vec!["in tuple pattern".into()]))?;
1450 fresh
1451 }
1452 other => {
1453 return Err(TypeError::TypeMismatch {
1454 at_node: node_id.into(),
1455 expected: "tuple".into(),
1456 got: other.pretty(),
1457 context: vec!["in tuple pattern".into()],
1458 });
1459 }
1460 };
1461 if tup.len() != items.len() {
1462 return Err(TypeError::ArityMismatch {
1463 at_node: node_id.into(), expected: tup.len(), got: items.len(),
1464 });
1465 }
1466 for (p, t) in items.iter().zip(tup.iter()) {
1467 self.bind_pattern(p, t, locals, node_id)?;
1468 }
1469 Ok(())
1470 }
1471 }
1472 }
1473}
1474
1475fn lit_type(l: &a::CLit) -> Ty {
1476 match l {
1477 a::CLit::Int { .. } => Ty::int(),
1478 a::CLit::Float { .. } => Ty::float(),
1479 a::CLit::Str { .. } => Ty::str(),
1480 a::CLit::Bytes { .. } => Ty::bytes(),
1481 a::CLit::Bool { .. } => Ty::bool(),
1482 a::CLit::Unit => Ty::Unit,
1483 }
1484}
1485
1486fn instantiate(s: &Scheme, u: &mut Unifier) -> Ty {
1487 instantiate_with_eff(s, u).0
1488}
1489
1490fn instantiate_with_eff(s: &Scheme, u: &mut Unifier) -> (Ty, IndexMap<u32, u32>) {
1495 let mut ty_subst = IndexMap::new();
1496 for v in &s.vars { ty_subst.insert(*v, u.fresh()); }
1497 let mut eff_subst = IndexMap::new();
1498 for v in &s.eff_vars { eff_subst.insert(*v, u.fresh_eff_id()); }
1499 let ty = subst_vars(&s.ty, &ty_subst, &eff_subst);
1500 (ty, eff_subst)
1501}
1502
1503fn subst_vars(
1504 t: &Ty,
1505 subst: &IndexMap<TyVarId, Ty>,
1506 eff_subst: &IndexMap<u32, u32>,
1507) -> Ty {
1508 match t {
1509 Ty::Var(v) => subst.get(v).cloned().unwrap_or_else(|| Ty::Var(*v)),
1510 Ty::Prim(_) | Ty::Unit | Ty::Never => t.clone(),
1511 Ty::List(inner) => Ty::List(Box::new(subst_vars(inner, subst, eff_subst))),
1512 Ty::Tuple(items) => Ty::Tuple(items.iter().map(|t| subst_vars(t, subst, eff_subst)).collect()),
1513 Ty::Record(fs) => {
1514 let mut out = IndexMap::new();
1515 for (k, v) in fs { out.insert(k.clone(), subst_vars(v, subst, eff_subst)); }
1516 Ty::Record(out)
1517 }
1518 Ty::Con(n, args) => Ty::Con(n.clone(),
1519 args.iter().map(|t| subst_vars(t, subst, eff_subst)).collect()),
1520 Ty::Function { params, effects, ret } => {
1521 let new_effects = EffectSet {
1524 concrete: effects.concrete.clone(),
1525 var: effects.var.and_then(|v| eff_subst.get(&v).copied()).or(effects.var),
1526 };
1527 Ty::Function {
1528 params: params.iter().map(|t| subst_vars(t, subst, eff_subst)).collect(),
1529 effects: new_effects,
1530 ret: Box::new(subst_vars(ret, subst, eff_subst)),
1531 }
1532 }
1533 }
1534}
1535
1536fn mismatch_err(node_id: &str, e: UnifyError, u: &Unifier, context: Vec<String>) -> TypeError {
1537 match e {
1538 UnifyError::Mismatch { a, b } => TypeError::TypeMismatch {
1539 at_node: node_id.into(),
1540 expected: u.resolve(&b).pretty(),
1541 got: u.resolve(&a).pretty(),
1542 context,
1543 },
1544 UnifyError::Infinite { .. } => TypeError::InfiniteType { at_node: node_id.into() },
1545 UnifyError::EffectMismatch { a, b } => {
1546 let render = |e: &EffectSet| -> String {
1551 let mut parts: Vec<String> = e.concrete.iter()
1552 .map(crate::types::EffectKind::pretty).collect();
1553 if let Some(v) = e.var { parts.push(format!("?e{}", v)); }
1554 if parts.is_empty() { "[]".into() } else { format!("[{}]", parts.join(", ")) }
1555 };
1556 TypeError::EffectRowMismatch {
1557 at_node: node_id.into(),
1558 expected: render(&b),
1559 got: render(&a),
1560 context,
1561 }
1562 }
1563 }
1564}