1use std::collections::HashMap;
4
5use chandeliers_err::{self as err, EAccum, Transparent};
6
7use crate::ast::{self, ty, var, Tuple};
8use crate::sp::{Sp, Span, WithDefSite, WithSpan};
9
10use ast::options::usage::Typecheck as This;
11
12type SpTyBaseTuple = Sp<Tuple<Sp<ty::Base>>>;
15
16type GenericParams = Vec<Sp<String>>;
18type GenericInstances = Vec<Sp<ty::Base>>;
23
24macro_rules! at {
27 ( $arr:expr, $idx:expr ) => {
28 $arr.get($idx).unwrap_or_else(|| {
29 err::abort!(
30 "Index {} out of bounds of array {}.",
31 $idx,
32 stringify!($arr),
33 )
34 })
35 };
36}
37
38macro_rules! at_mut {
41 ( $arr:expr, $idx:expr ) => {
42 $arr.get_mut($idx).unwrap_or_else(|| {
43 err::abort!(
44 "Index {} out of bounds of array {}.",
45 $idx,
46 stringify!($arr),
47 )
48 })
49 };
50}
51
52#[derive(Debug)]
54struct TyCtx<'i> {
55 global: &'i HashMap<var::Global, WithDefSite<Sp<ty::Base>>>,
57 vars: HashMap<var::Local, WithDefSite<Sp<ty::Base>>>,
59 registers: HashMap<Sp<var::Register>, Sp<ty::Tuple>>,
61 nodes_in: HashMap<var::Node, SpTyBaseTuple>,
67 nodes_out: HashMap<var::Node, SpTyBaseTuple>,
69 nodes_generics: HashMap<var::Node, (GenericParams, Option<GenericInstances>)>,
71}
72
73impl<'i> TyCtx<'i> {
74 fn from_ext(global: &'i HashMap<var::Global, WithDefSite<Sp<ty::Base>>>) -> TyCtx<'i> {
77 Self {
78 global,
79 registers: HashMap::default(),
80 vars: HashMap::default(),
81 nodes_in: HashMap::default(),
82 nodes_out: HashMap::default(),
83 nodes_generics: HashMap::default(),
84 }
85 }
86}
87
88impl TyCtx<'_> {
89 fn get_var(
91 &self,
92 eaccum: &mut EAccum,
93 var: Sp<&var::Local>,
94 ) -> Option<WithDefSite<Sp<ty::Tuple>>> {
95 match self.vars.get(var.t) {
96 Some(vardef) => Some(
97 vardef
98 .as_sp_ref()
99 .sp_map(|span, ty| ty::Tuple::Single(ty.clone().with_span(span))),
100 ),
101 None => eaccum.error(err::VarNotFound {
102 var: &var,
103 suggest1: err::Suggest {
104 available: self.vars.keys(),
105 },
106 suggest2: err::Suggest {
107 available: self.global.keys(),
108 },
109 }),
110 }
111 }
112
113 fn get_var_during_ty(
117 &self,
118 eaccum: &mut EAccum,
119 var: Sp<&var::Local>,
120 ) -> Option<WithDefSite<Sp<ty::Tuple>>> {
121 match self.vars.get(var.t) {
122 Some(vardef) => Some(
123 vardef
124 .as_sp_ref()
125 .sp_map(|span, ty| ty::Tuple::Single(ty.clone().with_span(span))),
126 ),
127 None => eaccum.error(err::TyVarNotFound {
128 var: &var,
129 suggest: err::Suggest {
130 available: self.vars.keys(),
131 },
132 }),
133 }
134 }
135
136 fn get_global(
138 &self,
139 eaccum: &mut EAccum,
140 var: Sp<&var::Global>,
141 ) -> Option<WithDefSite<Sp<ty::Tuple>>> {
142 match self.global.get(var.t) {
143 Some(vardef) => Some(
144 vardef
145 .as_sp_ref()
146 .sp_map(|span, ty| ty::Tuple::Single(ty.clone().with_span(span))),
147 ),
148 None => eaccum.error(err::VarNotFound {
149 var: &var,
150 suggest1: err::Suggest {
151 available: self.vars.keys(),
152 },
153 suggest2: err::Suggest {
154 available: self.global.keys(),
155 },
156 }),
157 }
158 }
159
160 fn get_node_out(&self, node: Sp<&var::Node>) -> Sp<ty::Tuple> {
162 match self.nodes_out.get(node.t) {
163 Some(tup) => tup.as_flat_tytuple(),
164 None => {
165 err::abort!("The typing context is improperly initialized: either {node} is missing and it should have been caught during the causality check, or it was not added to the map.");
166 }
167 }
168 }
169
170 fn get_node_in(&self, node: Sp<&var::Node>) -> Sp<ty::Tuple> {
172 match self.nodes_in.get(node.t) {
173 Some(tup) => tup.as_flat_tytuple(),
174 None => {
175 err::abort!("The typing context is improperly initialized: either {node} is missing and it should have been caught during the causality check, or it was not added to the map.");
176 }
177 }
178 }
179
180 fn get_node_generics(&self, node: Sp<&var::Node>) -> GenericParams {
182 match self.nodes_generics.get(node.t) {
183 Some(tup) => tup.0.clone(),
184 None => {
185 err::abort!("The typing context is improperly initialized: either {node} is missing and it should have been caught during the causality check, or it was not added to the map.");
186 }
187 }
188 }
189
190 fn instantiate(&mut self, node: Sp<&var::Node>, unifier: &TyUnifier) {
194 let gen = at_mut!(self.nodes_generics, node.t);
195 match unifier {
196 TyUnifier::Mapping {
197 site: _,
198 idx: _,
199 subst,
200 } => {
201 gen.1 = Some(
202 subst
203 .iter()
204 .map(|v| {
205 v.clone().unwrap_or_else(|| {
206 err::abort!("Substitution is not fully instanciated")
207 })
208 })
209 .collect(),
210 );
211 }
212 _ => {
213 gen.1 = Some(vec![]);
214 }
215 }
216 }
217}
218
219trait TypeCheckStmt {
222 fn typecheck(&mut self, eaccum: &mut EAccum, span: Span, ctx: &mut TyCtx) -> Option<()>;
226}
227
228trait TypeCheckSpanStmt {
230 fn typecheck(&mut self, eaccum: &mut EAccum, ctx: &mut TyCtx) -> Option<Sp<()>>;
234}
235
236impl<T: TypeCheckStmt> TypeCheckSpanStmt for Sp<T> {
237 fn typecheck(&mut self, eaccum: &mut EAccum, ctx: &mut TyCtx) -> Option<Sp<()>> {
238 self.as_ref_mut()
239 .map(|span, t| t.typecheck(eaccum, span, ctx))
240 .transpose()
241 }
242}
243
244trait TypeCheckExpr {
247 fn typecheck(&self, eaccum: &mut EAccum, span: Span, ctx: &mut TyCtx) -> Option<ty::Tuple>;
256 fn is_const(&self, eaccum: &mut EAccum, span: Span) -> Option<()>;
262}
263
264trait TypeCheckSpanExpr {
266 fn typecheck(&self, eaccum: &mut EAccum, ctx: &mut TyCtx) -> Option<Sp<ty::Tuple>>;
273 fn is_const(&self, eaccum: &mut EAccum) -> Option<Sp<()>>;
279}
280
281impl<T: TypeCheckExpr> TypeCheckSpanExpr for Sp<T> {
282 fn typecheck(&self, eaccum: &mut EAccum, ctx: &mut TyCtx) -> Option<Sp<ty::Tuple>> {
283 self.as_ref()
284 .map(|span, t| t.typecheck(eaccum, span, ctx))
285 .transpose()
286 }
287 fn is_const(&self, eaccum: &mut EAccum) -> Option<Sp<()>> {
288 self.as_ref()
289 .map(|span, t| t.is_const(eaccum, span))
290 .transpose()
291 }
292}
293
294impl<T: TypeCheckExpr> TypeCheckExpr for Box<T> {
295 fn typecheck(&self, eaccum: &mut EAccum, span: Span, ctx: &mut TyCtx) -> Option<ty::Tuple> {
296 self.as_ref().typecheck(eaccum, span, ctx)
297 }
298 fn is_const(&self, eaccum: &mut EAccum, span: Span) -> Option<()> {
299 self.as_ref().is_const(eaccum, span)
300 }
301}
302
303impl TypeCheckStmt for ast::stmt::Statement {
309 fn typecheck(&mut self, eaccum: &mut EAccum, span: Span, ctx: &mut TyCtx) -> Option<()> {
310 match self {
311 Self::Let { target, source } => {
312 let target_ty = target.typecheck(eaccum, ctx);
314 let source_ty = source.typecheck(eaccum, ctx);
315 Some(target_ty?.identical(eaccum, &mut TyUnifier::Identity, &source_ty?, span)?)
316 }
317 Self::Assert(e) => {
318 let t = e.typecheck(eaccum, ctx)?.is_primitive(eaccum)?;
320 t.as_ref().is_bool(eaccum, "The argument of assert", span)?;
321 Some(())
322 }
323 Self::UpdateRegister { .. } | Self::InitRegister { .. } => {
324 Some(())
327 }
328 }
329 }
330}
331
332impl TypeCheckExpr for ast::expr::Expr {
335 #[expect(clippy::too_many_lines, reason = "Match on Expr needs many cases")]
336 fn typecheck(&self, eaccum: &mut EAccum, span: Span, ctx: &mut TyCtx) -> Option<ty::Tuple> {
337 match self {
338 Self::Lit(inner) => Some(inner.typecheck(eaccum, ctx)?.t),
340 Self::Reference(inner) => Some(inner.typecheck(eaccum, ctx)?.t),
341 Self::DummyPre(inner) => Some(inner.typecheck(eaccum, ctx)?.t),
342 Self::DummyParen(inner) => Some(inner.typecheck(eaccum, ctx)?.t),
343 Self::Tuple(es) => {
344 es.as_ref()
345 .map(|span, es| {
346 let ts = es.try_map(eaccum, |eaccum, e: &Sp<ast::expr::Expr>| {
347 e.typecheck(eaccum, ctx)
348 })?;
349 Some(ty::Tuple::Multiple(ts.with_span(span)))
350 })
351 .t
352 }
353 Self::Bin { op, lhs, rhs } => {
355 let left = lhs
356 .typecheck(eaccum, ctx)
357 .and_then(|ty| ty.is_primitive(eaccum));
358 let right = rhs
359 .typecheck(eaccum, ctx)
360 .and_then(|ty| ty.is_primitive(eaccum));
361 let left = left?;
362 op.accepts(eaccum, left.as_ref(), right?.as_ref())?;
363 Some(ty::Tuple::Single(left))
364 }
365 Self::Un { op, inner } => {
367 let inner = inner.typecheck(eaccum, ctx)?.is_primitive(eaccum)?;
368 op.accepts(eaccum, span, inner.as_ref())?;
369 Some(ty::Tuple::Single(inner))
370 }
371 Self::Cmp { op, lhs, rhs } => {
373 let left = lhs
374 .typecheck(eaccum, ctx)
375 .and_then(|ty| ty.is_primitive(eaccum));
376 let right = rhs
377 .typecheck(eaccum, ctx)
378 .and_then(|ty| ty.is_primitive(eaccum));
379 op.accepts(eaccum, left?.as_ref(), right?.as_ref())?;
380 Some(ty::Tuple::Single(ty::Base::Bool.with_span(span)))
381 }
382 Self::Later {
384 delay: _,
385 before,
386 after,
387 } => {
388 let left = before.typecheck(eaccum, ctx);
389 let right = after.typecheck(eaccum, ctx);
390 let left = left?;
391 left.identical(eaccum, &mut TyUnifier::Identity, &right?, span)?;
392 Some(left.t)
393 }
394 Self::Flip {
395 id: _,
396 initial,
397 continued,
398 } => {
399 let left = initial.typecheck(eaccum, ctx);
400 let right = continued.typecheck(eaccum, ctx);
401 let left = left?;
402 left.identical(eaccum, &mut TyUnifier::Identity, &right?, span)?;
403 Some(left.t)
404 }
405 Self::Ifx { cond, yes, no } => {
407 let cond = cond
408 .typecheck(eaccum, ctx)
409 .and_then(|ty| ty.is_primitive(eaccum));
410 let yes = yes.typecheck(eaccum, ctx);
411 let no = no.typecheck(eaccum, ctx);
412 cond?
413 .as_ref()
414 .is_bool(eaccum, "The condition of if", span)?;
415 let yes = yes?;
416 yes.identical(eaccum, &mut TyUnifier::Identity, &no?, span)?;
417 Some(yes.t)
418 }
419 Self::Substep { delay: _, id, args } => {
421 let expected_tys = ctx.get_node_in(id.as_ref());
422 let generics = ctx.get_node_generics(id.as_ref());
423 let actual_tys = args.typecheck(eaccum, ctx)?;
424 let mut unifier = TyUnifier::from_generics(generics, expected_tys.span);
427 expected_tys.identical(eaccum, &mut unifier, &actual_tys, span)?;
428 ctx.instantiate(id.as_ref(), &unifier); Some(ctx.get_node_out(id.as_ref()).t.substitute(&unifier))
431 }
432 Self::Clock {
434 op: _,
435 inner,
436 activate,
437 } => {
438 let activate = activate
439 .typecheck(eaccum, ctx)
440 .and_then(|ty| ty.is_primitive(eaccum));
441 let inner = inner.typecheck(eaccum, ctx)?;
442 activate?.as_ref().is_bool(eaccum, "A clock", span)?;
443 Some(inner.t)
444 }
445 Self::Merge { switch, on, off } => {
447 let switch = switch.typecheck(eaccum, ctx)?;
448 let on = on.typecheck(eaccum, ctx)?;
449 let off = off.typecheck(eaccum, ctx)?;
450 let switch = switch.is_primitive(eaccum)?;
451 switch.as_ref().is_bool(eaccum, "A clock", span)?;
452 on.identical(eaccum, &mut TyUnifier::Identity, &off, span)?;
453 Some(on.t)
454 }
455 Self::FetchRegister {
456 id,
457 dummy_init,
458 dummy_followed_by,
459 } => {
460 let followed_by = dummy_followed_by.typecheck(eaccum, ctx)?;
461 if let Some(init) = dummy_init {
462 let init = init.typecheck(eaccum, ctx)?;
463 followed_by.identical(eaccum, &mut TyUnifier::Identity, &init, span)?;
464 }
465 ctx.registers.insert(*id, followed_by.clone());
466 Some(followed_by.t)
467 }
468 }
469 }
470
471 fn is_const(&self, eaccum: &mut EAccum, span: Span) -> Option<()> {
472 match self {
473 Self::Lit(inner) => {
475 inner.is_const(eaccum)?;
476 Some(())
477 }
478 Self::Reference(inner) => {
479 inner.is_const(eaccum)?;
480 Some(())
481 }
482 Self::Bin { lhs, rhs, .. } => {
485 let lhs = lhs.is_const(eaccum);
486 let rhs = rhs.is_const(eaccum);
487 lhs?;
488 rhs?;
489 Some(())
490 }
491 Self::DummyParen(inner) => {
492 inner.is_const(eaccum)?;
493 Some(())
494 }
495 Self::Un { inner, .. } => {
496 inner.is_const(eaccum)?;
497 Some(())
498 }
499 Self::Cmp { lhs, rhs, .. } => {
500 let lhs = lhs.is_const(eaccum);
501 let rhs = rhs.is_const(eaccum);
502 lhs?;
503 rhs?;
504 Some(())
505 }
506 Self::Ifx { cond, yes, no } => {
507 let cond = cond.is_const(eaccum);
508 let yes = yes.is_const(eaccum);
509 let no = no.is_const(eaccum);
510 cond?;
511 yes?;
512 no?;
513 Some(())
514 }
515 Self::Tuple(_) => eaccum.error(err::NotConst {
517 what: "Tuples are currently",
518 site: span,
519 }),
520 Self::DummyPre(_) => eaccum.error(err::NotConst {
523 what: "The temporal operator `pre` is",
524 site: span,
525 }),
526 Self::Later { .. } | Self::FetchRegister { .. } | Self::Flip { .. } => {
527 eaccum.error(err::NotConst {
528 what: "The delay operator (-> / fby) is",
529 site: span,
530 })
531 }
532 Self::Substep { .. } => eaccum.error(err::NotConst {
533 what: "Function calls are",
534 site: span,
535 }),
536 Self::Merge { .. } => eaccum.error(err::NotConst {
537 what: "The merge builtin is",
538 site: span,
539 }),
540 Self::Clock { .. } => eaccum.error(err::NotConst {
541 what: "Clock operators (when/whenot) are",
542 site: span,
543 }),
544 }
545 }
546}
547
548impl TypeCheckExpr for ast::expr::Lit {
550 fn typecheck(&self, _eaccum: &mut EAccum, span: Span, _ctx: &mut TyCtx) -> Option<ty::Tuple> {
551 Some(ty::Tuple::Single(
552 match self {
553 Self::Int(_) => ty::Base::Int,
554 Self::Float(_) => ty::Base::Float,
555 Self::Bool(_) => ty::Base::Bool,
556 }
557 .with_span(span),
558 ))
559 }
560
561 fn is_const(&self, _eaccum: &mut EAccum, _span: Span) -> Option<()> {
562 Some(())
563 }
564}
565
566impl TypeCheckExpr for var::Reference {
571 fn typecheck(&self, eaccum: &mut EAccum, _span: Span, ctx: &mut TyCtx) -> Option<ty::Tuple> {
572 Some(match self {
573 Self::Var(v) => ctx.get_var(eaccum, v.as_ref().map(|_, v| &v.var.t))?.data.t,
574 Self::Global(v) => ctx.get_global(eaccum, v.as_ref())?.data.t,
575 })
576 }
577
578 fn is_const(&self, _eaccum: &mut EAccum, _span: Span) -> Option<()> {
579 Some(())
580 }
581}
582
583impl TypeCheckExpr for ast::stmt::VarTuple {
584 fn typecheck(&self, eaccum: &mut EAccum, _span: Span, ctx: &mut TyCtx) -> Option<ty::Tuple> {
585 use ast::stmt::VarTuple;
586 fn aux_multiple(
588 eaccum: &mut EAccum,
589 span: Span,
590 vs: &Tuple<Sp<VarTuple>>,
591 ctx: &mut TyCtx,
592 ) -> Option<ty::Tuple> {
593 let ts = vs.try_map(&mut *eaccum, |eaccum, v: &Sp<VarTuple>| {
594 v.typecheck(eaccum, ctx)
595 })?;
596 Some(ty::Tuple::Multiple(ts.with_span(span)))
597 }
598 match self {
599 VarTuple::Single(v) => Some(ctx.get_var(eaccum, v.as_ref())?.data.t),
600 VarTuple::Multiple(vs) => {
601 vs.as_ref()
602 .map(|span, vs| aux_multiple(eaccum, span, vs, ctx))
603 .t
604 }
605 }
606 }
607
608 fn is_const(&self, _eaccum: &mut EAccum, _span: Span) -> Option<()> {
609 Some(())
610 }
611}
612
613impl ast::op::Bin {
614 fn accepts(self, eaccum: &mut EAccum, left: Sp<&ty::Base>, right: Sp<&ty::Base>) -> Option<()> {
616 use ast::op::Bin;
617 let span = left
618 .span
619 .join(right.span)
620 .unwrap_or_else(|| err::abort!("Malformed span between {left:?} and {right:?}"));
621 if left.t != right.t {
622 eaccum.error(err::BinopMismatch {
623 oper: self,
624 site: span,
625 expect: "the same type",
626 left: &left,
627 right: &right,
628 })?;
629 }
630 match (self, left.t) {
631 (Bin::Add | Bin::Mul | Bin::Div | Bin::Sub, ty::Base::Bool) => {
632 eaccum.error(err::BinopMismatch {
633 oper: self,
634 site: span,
635 expect: "type int or float, found bool",
636 left: &left,
637 right: &right,
638 })
639 }
640 (Bin::Rem, ty::Base::Bool | ty::Base::Float) => eaccum.error(err::BinopMismatch {
641 oper: self,
642 site: span,
643 expect: format!("type int, found {left}"),
644 left: &left,
645 right: &right,
646 }),
647 (Bin::BitAnd | Bin::BitOr | Bin::BitXor, ty::Base::Float) => {
648 eaccum.error(err::BinopMismatch {
649 oper: self,
650 site: span,
651 expect: "type int or bool, found float",
652 left: &left,
653 right: &right,
654 })
655 }
656 (Bin::Add | Bin::Mul | Bin::Sub | Bin::Div, ty::Base::Int | ty::Base::Float)
657 | (Bin::Rem, ty::Base::Int)
658 | (Bin::BitAnd | Bin::BitOr | Bin::BitXor, ty::Base::Int | ty::Base::Bool) => Some(()),
659 (_, ty::Base::Other(t)) => eaccum.error(err::BinopMismatch {
660 oper: self,
661 site: span,
662 expect: format!("a concrete type, found type variable {t}"),
663 left: &left,
664 right: &right,
665 }),
666 }
667 }
668}
669
670impl ast::op::Un {
671 fn accepts(self, eaccum: &mut EAccum, span: Span, inner: Sp<&ty::Base>) -> Option<()> {
673 use ast::op::Un;
674 match (self, &inner.t) {
675 (Un::Neg, ty::Base::Bool) => eaccum.error(err::UnopMismatch {
676 oper: self,
677 site: span,
678 expect: "type int or float, found bool",
679 inner: &inner,
680 }),
681 (Un::Not, ty::Base::Float) => eaccum.error(err::UnopMismatch {
682 oper: self,
683 site: span,
684 expect: "type bool or int, found float",
685 inner: &inner,
686 }),
687 (Un::Neg, ty::Base::Int | ty::Base::Float)
688 | (Un::Not, ty::Base::Int | ty::Base::Bool) => Some(()),
689 (_, ty::Base::Other(t)) => eaccum.error(err::UnopMismatch {
690 oper: self,
691 site: span,
692 expect: format!("a concrete type, found type variable {t}"),
693 inner: &inner,
694 }),
695 }
696 }
697}
698
699impl ast::op::Cmp {
700 fn accepts(self, eaccum: &mut EAccum, left: Sp<&ty::Base>, right: Sp<&ty::Base>) -> Option<()> {
702 use ast::op::Cmp;
703 let span = left
704 .span
705 .join(right.span)
706 .unwrap_or_else(|| err::abort!("Malformed span between {left:?} and {right:?}"));
707 if left.t != right.t {
708 eaccum.error(err::BinopMismatch {
709 oper: self,
710 site: span,
711 expect: "the same type",
712 left: &left,
713 right: &right,
714 })?;
715 }
716 match (self, &left.t) {
717 (
718 Cmp::Le | Cmp::Ge | Cmp::Lt | Cmp::Gt,
719 ty::Base::Int | ty::Base::Bool | ty::Base::Float,
720 )
721 | (Cmp::Ne | Cmp::Eq, ty::Base::Int | ty::Base::Bool) => Some(()),
722 (Cmp::Ne | Cmp::Eq, ty::Base::Float) => eaccum.error(err::BinopMismatch {
723 oper: self,
724 site: span,
725 expect: "type int or bool, not float because equality on float is not reliable",
726 left: &left,
727 right: &right,
728 }),
729 (_, ty::Base::Other(t)) => eaccum.error(err::BinopMismatch {
730 oper: self,
731 site: span,
732 expect: format!("a concrete type, found type variable {t}"),
733 left: &left,
734 right: &right,
735 }),
736 }
737 }
738}
739
740impl Sp<&ty::Base> {
741 fn is_bool(self, eaccum: &mut EAccum, req: &str, span: Span) -> Option<()> {
743 match self.t {
744 ty::Base::Bool => Some(()),
745 _ => eaccum.error(err::BoolRequired {
746 what: req,
747 site: span,
748 inner: &self,
749 }),
750 }
751 }
752}
753
754#[derive(Debug)]
769enum TyUnifier {
770 Identity,
772 Null,
775 Mapping {
779 site: Span,
781 idx: HashMap<Sp<String>, usize>,
783 subst: Vec<Option<Sp<ty::Base>>>,
785 },
786}
787
788impl TyUnifier {
789 fn from_generics(generics: Vec<Sp<String>>, site: Span) -> Self {
798 let mut idx = HashMap::new();
799 let len = generics.len();
800 for (i, gen) in generics.into_iter().enumerate() {
803 if idx.insert(gen, i).is_some() {
804 err::abort!("Failed to catch duplicate type variable.")
805 }
806 }
807 Self::Mapping {
808 site,
809 idx,
810 subst: vec![None; len],
811 }
812 }
813
814 fn image(&self, s: Sp<String>) -> ty::Base {
822 match self {
823 Self::Mapping {
824 site: _,
825 idx,
826 subst,
827 } => {
828 if let Some(i) = idx.get(&s) {
829 at!(subst, *i)
830 .clone()
831 .unwrap_or_else(|| err::abort!("Mapping is not fully instanciated"))
832 .t
833 } else {
834 ty::Base::Other(s)
835 }
836 }
837 Self::Identity => ty::Base::Other(s),
838 Self::Null => err::abort!("This context should never contain generics"),
839 }
840 }
841
842 fn require_identical(
848 &mut self,
849 eaccum: &mut EAccum,
850 left: Sp<&ty::Base>,
851 right: Sp<&ty::Base>,
852 source: Span,
853 ) -> Option<()> {
854 use ty::Base as B;
855 match &left.t {
856 l @ (B::Int | B::Float | B::Bool) => {
859 if l == &right.t {
860 return Some(());
861 }
862 }
863 B::Other(l) => match self {
864 Self::Mapping { site, idx, subst } => {
865 let Some(i) = idx.get(l) else {
866 unimplemented!()
867 };
877
878 return match at_mut!(subst, *i) {
879 x @ None => {
880 *x = Some(right.cloned());
882 Some(())
883 }
884 Some(ref mut v) => {
885 if v.as_ref() == right {
886 Some(())
888 } else {
889 eaccum.error(err::UnsatGenericConstraint {
891 variable: &l,
892 previous: v.as_ref(),
893 new: right,
894 context: &*site,
895 })
896 }
897 }
898 };
899 }
900 _ => {
901 if left == right {
902 return Some(());
903 }
904 }
905 },
906 }
907 let msg = format!("Base types should be unifiable: expected {left}, got {right}");
908 eaccum.error(err::TypeMismatch {
909 source,
910 left,
911 right,
912 msg,
913 })
914 }
915}
916
917trait TySubstitute {
919 fn substitute(self, unifier: &TyUnifier) -> Self;
922}
923
924impl TySubstitute for ty::Base {
925 fn substitute(self, unifier: &TyUnifier) -> Self {
926 match self {
927 Self::Other(s) => unifier.image(s),
928 _ => self,
929 }
930 }
931}
932
933impl<T: TySubstitute> TySubstitute for ast::Tuple<T> {
934 fn substitute(self, unifier: &TyUnifier) -> Self {
935 self.map(|t| t.substitute(unifier))
936 }
937}
938
939impl<T: TySubstitute> TySubstitute for Sp<T> {
940 fn substitute(self, unifier: &TyUnifier) -> Self {
941 self.map(|_, t| t.substitute(unifier))
942 }
943}
944
945impl TySubstitute for ty::Tuple {
946 fn substitute(self, unifier: &TyUnifier) -> Self {
947 match self {
948 Self::Single(t) => Self::Single(t.substitute(unifier)),
949 Self::Multiple(t) => Self::Multiple(t.substitute(unifier)),
950 }
951 }
952}
953
954impl Sp<ty::Tuple> {
955 fn identical(
963 &self,
964 eaccum: &mut EAccum,
965 unifier: &mut TyUnifier,
966 other: &Self,
967 source: Span,
968 ) -> Option<()> {
969 use ty::Tuple::{Multiple, Single};
970 match (&self.t, &other.t) {
971 (Single(left), Single(right)) => unifier.require_identical(
972 eaccum,
973 left.as_ref().with_span(self.span),
974 right.as_ref().with_span(other.span),
975 source,
976 ),
977 (Multiple(ts), Multiple(us)) => {
978 if ts.t.len() != us.t.len() {
979 let msg = format!(
980 "expected {self}, got {other} instead that does not have the same length"
981 );
982 eaccum.error(err::TypeMismatch {
983 source,
984 left: self,
985 right: other,
986 msg,
987 })?;
988 }
989 let mut scope = eaccum.scope();
990 for (t, u) in ts.t.iter().zip(us.t.iter()) {
991 scope.compute(|eaccum| t.identical(eaccum, unifier, u, source));
992 }
993 scope.close()
994 }
995 (Multiple(_), Single(_)) => {
996 let msg = format!("expected a tuple {self}, got a scalar {other}");
997 eaccum.error(err::TypeMismatch {
998 source,
999 left: self,
1000 right: other,
1001 msg,
1002 })
1003 }
1004 (Single(_), Multiple(_)) => {
1005 let msg = format!("expected a scalar {self}, got a tuple {other}");
1006 eaccum.error(err::TypeMismatch {
1007 source,
1008 left: self,
1009 right: other,
1010 msg,
1011 })
1012 }
1013 }
1014 }
1015
1016 fn is_primitive(&self, eaccum: &mut EAccum) -> Option<Sp<ty::Base>> {
1021 self.as_ref()
1022 .map(|_, t| match t {
1023 ty::Tuple::Single(t) => Some(t.t.clone()),
1024 ty::Tuple::Multiple(_) => eaccum.error(err::ScalarNotTuple { typ: self }),
1025 })
1026 .transpose()
1027 }
1028}
1029
1030impl SpTyBaseTuple {
1031 fn as_flat_tytuple(&self) -> Sp<ty::Tuple> {
1033 self.as_ref().map(|span, tup| {
1034 if tup.len() == 1 {
1035 ty::Tuple::Single(
1036 tup.iter()
1037 .last()
1038 .unwrap_or_else(|| err::malformed!())
1039 .t
1040 .clone()
1041 .with_span(span),
1042 )
1043 } else {
1044 ty::Tuple::Multiple(
1045 tup.map_ref(|t| {
1046 t.clone()
1047 .map(|span, t| ty::Tuple::Single(t.with_span(span)))
1048 })
1049 .with_span(span),
1050 )
1051 }
1052 })
1053 }
1054}
1055
1056trait TypeCheckDecl {
1058 type Signature;
1060
1061 fn typecheck(
1066 &mut self,
1067 eaccum: &mut EAccum,
1068 span: Span,
1069 extfun: &HashMap<ast::decl::NodeName, (GenericParams, SpTyBaseTuple, SpTyBaseTuple)>,
1070 extvar: &HashMap<var::Global, WithDefSite<Sp<ty::Base>>>,
1071 ) -> Option<()>;
1072
1073 fn signature(&self) -> Self::Signature;
1076}
1077
1078trait TypeCheckSpanDecl {
1080 type Signature;
1082 fn typecheck(
1084 &mut self,
1085 eaccum: &mut EAccum,
1086 extfun: &HashMap<ast::decl::NodeName, (GenericParams, SpTyBaseTuple, SpTyBaseTuple)>,
1087 extvar: &HashMap<var::Global, WithDefSite<Sp<ty::Base>>>,
1088 ) -> Option<()>;
1089 fn signature(&self) -> Self::Signature;
1091}
1092
1093impl<T: TypeCheckDecl> TypeCheckSpanDecl for Sp<T> {
1094 type Signature = T::Signature;
1095 fn typecheck(
1096 &mut self,
1097 eaccum: &mut EAccum,
1098 extfun: &HashMap<ast::decl::NodeName, (GenericParams, SpTyBaseTuple, SpTyBaseTuple)>,
1099 extvar: &HashMap<var::Global, WithDefSite<Sp<ty::Base>>>,
1100 ) -> Option<()> {
1101 self.t.typecheck(eaccum, self.span, extfun, extvar)
1102 }
1103 fn signature(&self) -> Self::Signature {
1104 self.t.signature()
1105 }
1106}
1107
1108impl TypeCheckDecl for ast::decl::Node {
1111 type Signature = (
1112 Sp<ast::decl::NodeName>,
1113 GenericParams,
1114 SpTyBaseTuple,
1115 SpTyBaseTuple,
1116 );
1117 fn typecheck(
1119 &mut self,
1120 eaccum: &mut EAccum,
1121 span: Span,
1122 extfun: &HashMap<ast::decl::NodeName, (GenericParams, SpTyBaseTuple, SpTyBaseTuple)>,
1123 extvar: &HashMap<var::Global, WithDefSite<Sp<ty::Base>>>,
1124 ) -> Option<()> {
1125 let mut ctx = TyCtx::from_ext(extvar);
1126 let is_main = self.options.main.fetch::<This>().is_some();
1129 let is_test = self.options.test.fetch::<This>().is_some();
1130 let inputs_nonempty = !self.inputs.t.is_empty();
1131 let outputs_nonempty = !self.outputs.t.is_empty();
1132 if (is_main || is_test) && (inputs_nonempty || outputs_nonempty) {
1133 eaccum.error(err::ExecutableNodeSig {
1134 reason: if is_main {
1135 "annotation #[main]"
1136 } else {
1137 "annotation #[test]"
1138 },
1139 inputs_nonempty,
1140 outputs_nonempty,
1141 site: span,
1142 inputs: &self.inputs,
1143 outputs: &self.outputs,
1144 })?;
1145 }
1146
1147 {
1152 let mut declared_generics = std::collections::HashSet::new();
1153 for g in self.options.generics.fetch::<This>() {
1154 declared_generics.insert(g);
1155 }
1156
1157 let mut unused_generics = declared_generics.clone();
1158 for i in self.inputs.t.iter() {
1159 if let ty::Base::Other(t) = &i.t.ty.t.ty.t.inner.t {
1160 if !declared_generics.contains(&t) {
1161 eaccum.error(err::UndeclaredGeneric {
1162 undeclared: &i.t.ty.t.ty.t.inner,
1163 })?;
1164 }
1165 unused_generics.remove(&t);
1167 }
1168 }
1169 for i in self.outputs.t.iter().chain(self.locals.t.iter()) {
1170 if let ty::Base::Other(t) = &i.t.ty.t.ty.t.inner.t {
1171 if !declared_generics.contains(&t) {
1174 eaccum.error(err::UndeclaredGeneric {
1175 undeclared: &i.t.ty.t.ty.t.inner,
1176 })?;
1177 }
1178 }
1179 }
1180
1181 for unused in unused_generics {
1182 eaccum.error(err::UnusedGeneric {
1183 unused,
1184 inputs: &self.inputs,
1185 })?;
1186 }
1187 }
1188
1189 let mut scope = eaccum.scope();
1192 for vs in &[&self.inputs, &self.outputs, &self.locals] {
1193 for v in vs.t.iter() {
1194 if let Some(prior) = ctx.vars.get(&v.t.name.t) {
1195 scope.error(err::GraphUnitDeclTwice {
1196 unit: &v.t.name,
1197 prior: "a prior item",
1198 new_site: v.span,
1199 prior_site: prior.def_site,
1200 });
1201 }
1202 scope.compute(|eaccum| v.t.ty.t.ty.t.clk.typecheck(eaccum, &mut ctx).map(|_| ()));
1205 ctx.vars.insert(
1206 v.t.name.t.clone(),
1207 WithDefSite {
1208 def_site: Transparent::from(Some(v.span)),
1209 data: v.t.ty.as_ref().map(|_, t| t.ty.t.inner.t.clone()),
1210 },
1211 );
1212 }
1213 }
1214 scope.close()?;
1215 for (id, blk) in self.blocks.iter().enumerate() {
1217 let Some((gen, i, o)) = extfun.get(&blk.name.t) else {
1218 return eaccum.error(err::FunNotFound { fun: &blk.name });
1219 };
1220 let id = var::Node {
1221 id: id.with_span(blk.name.span),
1222 repr: blk.name.t.repr.clone(),
1223 };
1224 ctx.nodes_in.insert(id.clone(), i.clone());
1225 ctx.nodes_out.insert(id.clone(), o.clone());
1226 ctx.nodes_generics
1227 .insert(id, (gen.clone(), None ));
1228 }
1229 let mut scope = eaccum.scope();
1230 for st in &mut self.stmts {
1231 scope.compute(|eaccum| st.typecheck(eaccum, &mut ctx).map(|_| ()));
1232 }
1233 scope.close()?;
1234 for (id, gens) in ctx.nodes_generics {
1237 at_mut!(self.blocks, id.id.t).generics = gens.1;
1238 }
1239 for reg in &mut self.registers {
1241 reg.typ = ctx.registers.get(®.id).cloned();
1242 }
1243 Some(())
1244 }
1245
1246 #[must_use]
1248 fn signature(
1249 &self,
1250 ) -> (
1251 Sp<ast::decl::NodeName>,
1252 GenericParams,
1253 SpTyBaseTuple,
1254 SpTyBaseTuple,
1255 ) {
1256 let generics = self.options.generics.fetch::<This>();
1257 let inputs = self
1258 .inputs
1259 .t
1260 .map_ref(|v| v.t.ty.as_ref().map(|_, t| t.ty.t.inner.t.clone()));
1261 let outputs = self
1262 .outputs
1263 .t
1264 .map_ref(|v| v.t.ty.as_ref().map(|_, t| t.ty.t.inner.t.clone()));
1265 (
1266 self.name.clone(),
1267 generics.clone(),
1268 inputs.with_span(self.inputs.span),
1269 outputs.with_span(self.outputs.span),
1270 )
1271 }
1272}
1273
1274impl TypeCheckDecl for ast::decl::ExtNode {
1275 type Signature = (
1276 Sp<ast::decl::NodeName>,
1277 GenericParams,
1278 SpTyBaseTuple,
1279 SpTyBaseTuple,
1280 );
1281 fn typecheck(
1284 &mut self,
1285 eaccum: &mut EAccum,
1286 span: Span,
1287 _extfun: &HashMap<ast::decl::NodeName, (GenericParams, SpTyBaseTuple, SpTyBaseTuple)>,
1288 _extvar: &HashMap<var::Global, WithDefSite<Sp<ty::Base>>>,
1289 ) -> Option<()> {
1290 let extvar = HashMap::default();
1291 let mut ctx = TyCtx::from_ext(&extvar);
1292 let is_main = self.options.main.fetch::<This>().is_some();
1296 let inputs_nonempty = !self.inputs.t.is_empty();
1297 let outputs_nonempty = !self.outputs.t.is_empty();
1298 if is_main && (inputs_nonempty || outputs_nonempty) {
1299 eaccum.error(err::ExecutableNodeSig {
1300 reason: "annotation #[main]",
1301 inputs_nonempty,
1302 outputs_nonempty,
1303 site: span,
1304 inputs: &self.inputs,
1305 outputs: &self.outputs,
1306 })?;
1307 }
1308
1309 {
1314 let mut declared_generics = std::collections::HashSet::new();
1315 for g in self.options.generics.fetch::<This>() {
1316 declared_generics.insert(g);
1317 }
1318
1319 let mut unused_generics = declared_generics.clone();
1320 for i in self.inputs.t.iter() {
1321 if let ty::Base::Other(t) = &i.t.ty.t.ty.t.inner.t {
1322 if !declared_generics.contains(&t) {
1323 eaccum.error(err::UndeclaredGeneric {
1324 undeclared: &i.t.ty.t.ty.t.inner,
1325 })?;
1326 }
1327 unused_generics.remove(&t);
1329 }
1330 }
1331 for i in self.outputs.t.iter() {
1332 if let ty::Base::Other(t) = &i.t.ty.t.ty.t.inner.t {
1333 if !declared_generics.contains(&t) {
1341 eaccum.error(err::UndeclaredGeneric {
1342 undeclared: &i.t.ty.t.ty.t.inner,
1343 })?;
1344 }
1345 }
1346 }
1347
1348 for unused in unused_generics {
1349 eaccum.error(err::UnusedGeneric {
1350 unused,
1351 inputs: &self.inputs,
1352 })?;
1353 }
1354 }
1355
1356 let mut scope = eaccum.scope();
1359 for vs in &[&self.inputs, &self.outputs] {
1360 for v in vs.t.iter() {
1361 if let Some(prior) = ctx.vars.get(&v.t.name.t) {
1362 scope.error(err::GraphUnitDeclTwice {
1363 unit: &v.t.name,
1364 prior: "a prior item",
1365 new_site: v.span,
1366 prior_site: prior.def_site,
1367 });
1368 }
1369 scope.compute(|eaccum| v.t.ty.t.ty.t.clk.typecheck(eaccum, &mut ctx).map(|_| ()));
1371 ctx.vars.insert(
1372 v.t.name.t.clone(),
1373 WithDefSite {
1374 def_site: Transparent::from(Some(v.span)),
1375 data: v.t.ty.as_ref().map(|_, t| t.ty.t.inner.t.clone()),
1376 },
1377 );
1378 }
1379 }
1380 scope.close()
1381 }
1382
1383 #[must_use]
1386 fn signature(
1387 &self,
1388 ) -> (
1389 Sp<ast::decl::NodeName>,
1390 GenericParams,
1391 SpTyBaseTuple,
1392 SpTyBaseTuple,
1393 ) {
1394 let generics = self.options.generics.fetch::<This>();
1395 let inputs = self
1396 .inputs
1397 .t
1398 .map_ref(|v| v.t.ty.as_ref().map(|_, t| t.ty.t.inner.t.clone()));
1399 let outputs = self
1400 .outputs
1401 .t
1402 .map_ref(|v| v.t.ty.as_ref().map(|_, t| t.ty.t.inner.t.clone()));
1403 (
1404 self.name.clone(),
1405 generics.clone(),
1406 inputs.with_span(self.inputs.span),
1407 outputs.with_span(self.outputs.span),
1408 )
1409 }
1410}
1411
1412impl TypeCheckExpr for ty::Clock {
1413 fn typecheck(&self, eaccum: &mut EAccum, span: Span, ctx: &mut TyCtx) -> Option<ty::Tuple> {
1414 match self {
1415 Self::Implicit | Self::Adaptative => {
1416 Some(ty::Tuple::Multiple(Tuple::default().with_span(span)))
1417 }
1418 Self::Explicit { id, .. } => {
1419 let ty = ctx.get_var_during_ty(
1420 eaccum,
1421 id.as_ref()
1422 .map(|span, repr| var::Local {
1423 repr: repr.clone().with_span(span),
1424 })
1425 .as_ref(),
1426 )?;
1427 let ty = ty.data.is_primitive(eaccum)?;
1428 ty.as_ref().is_bool(eaccum, "Clock", span)?;
1429 Some(ty::Tuple::Single(ty))
1430 }
1431 }
1432 }
1433 fn is_const(&self, _eaccum: &mut EAccum, _: Span) -> Option<()> {
1434 err::abort!("We shouldn't even attempt to use a clock in a const context")
1435 }
1436}
1437
1438impl TypeCheckDecl for ast::decl::Const {
1439 type Signature = (Sp<var::Global>, Sp<ty::Base>);
1440 fn typecheck(
1442 &mut self,
1443 eaccum: &mut EAccum,
1444 span: Span,
1445 _functx: &HashMap<ast::decl::NodeName, (GenericParams, SpTyBaseTuple, SpTyBaseTuple)>,
1446 varctx: &HashMap<var::Global, WithDefSite<Sp<ty::Base>>>,
1447 ) -> Option<()> {
1448 self.value.is_const(eaccum)?;
1449 let mut unifier = TyUnifier::Null;
1450 let e = self.value.typecheck(eaccum, &mut TyCtx::from_ext(varctx))?;
1451 self.ty
1452 .clone()
1453 .map(|span, t| ty::Tuple::Single(t.with_span(span)))
1454 .identical(eaccum, &mut unifier, &e, span)?;
1455 Some(())
1456 }
1457
1458 #[must_use]
1460 fn signature(&self) -> (Sp<var::Global>, Sp<ty::Base>) {
1461 (self.name.clone(), self.ty.clone())
1462 }
1463}
1464
1465impl TypeCheckDecl for ast::decl::ExtConst {
1466 type Signature = (Sp<var::Global>, Sp<ty::Base>);
1467 fn typecheck(
1468 &mut self,
1469 _eaccum: &mut EAccum,
1470 _span: Span,
1471 _functx: &HashMap<ast::decl::NodeName, (GenericParams, SpTyBaseTuple, SpTyBaseTuple)>,
1472 _varctx: &HashMap<var::Global, WithDefSite<Sp<ty::Base>>>,
1473 ) -> Option<()> {
1474 Some(())
1475 }
1476
1477 #[must_use]
1479 fn signature(&self) -> (Sp<var::Global>, Sp<ty::Base>) {
1480 (self.name.clone(), self.ty.clone())
1481 }
1482}
1483
1484impl Sp<ast::decl::Prog> {
1485 pub fn typecheck(&mut self, eaccum: &mut EAccum) -> Option<()> {
1493 let mut varctx = HashMap::new();
1494 let mut functx = HashMap::new();
1495 let mut scope = eaccum.scope();
1496 for decl in &mut self.t.decls {
1497 match &mut decl.t {
1498 ast::decl::Decl::Const(c) => {
1499 scope.compute(|eaccum| c.typecheck(eaccum, &functx, &varctx));
1500 let (name, ty) = c.signature();
1501 if varctx
1502 .insert(
1503 name.t.clone(),
1504 WithDefSite {
1505 def_site: Transparent::from(Some(name.span)),
1506 data: ty,
1507 },
1508 )
1509 .is_some()
1510 {
1511 err::abort!(
1512 "Duplicate const definition should have been caught during causality check"
1513 );
1514 }
1515 }
1516 ast::decl::Decl::Node(node) => {
1517 scope.compute(|eaccum| node.typecheck(eaccum, &functx, &varctx));
1518 let (name, tyvars, ins, outs) = node.signature();
1519 if functx.insert(name.t, (tyvars, ins, outs)).is_some() {
1520 err::abort!(
1521 "Duplicate node definition should have been caught during causality check"
1522 );
1523 }
1524 }
1525 ast::decl::Decl::ExtConst(c) => {
1526 scope.compute(|eaccum| c.typecheck(eaccum, &functx, &varctx));
1527 let (name, ty) = c.signature();
1528 if varctx
1529 .insert(
1530 name.t.clone(),
1531 WithDefSite {
1532 def_site: Transparent::from(Some(name.span)),
1533 data: ty,
1534 },
1535 )
1536 .is_some()
1537 {
1538 err::abort!(
1539 "Duplicate const definition should have been caught during causality check"
1540 );
1541 }
1542 }
1543 ast::decl::Decl::ExtNode(node) => {
1544 scope.compute(|eaccum| node.typecheck(eaccum, &functx, &varctx));
1545 let (name, tyvars, ins, outs) = node.signature();
1546 if functx.insert(name.t, (tyvars, ins, outs)).is_some() {
1547 err::abort!(
1548 "Duplicate node definition should have been caught during causality check"
1549 );
1550 }
1551 }
1552 }
1553 }
1554 scope.close()
1555 }
1556}