chandeliers_san/
typecheck.rs

1//! Type Checking of a Candle AST
2
3use 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
12/// This is the type of all interfaces (node inputs and outputs),
13/// lets' give it a less confusing alias.
14type SpTyBaseTuple = Sp<Tuple<Sp<ty::Base>>>;
15
16/// Ordered sequence of names of type variables.
17type GenericParams = Vec<Sp<String>>;
18/// Ordered sequence of instanciations of type variables.
19/// Goes hand in hand with `GenericParams`: you should read
20/// a `(k: GenericParams, v: GenericInstances)` as a mapping where the
21/// value associated with `k[i]` is `v[i]`.
22type GenericInstances = Vec<Sp<ty::Base>>;
23
24/// Immutable accessor for an array.
25/// Catches out of bounds with a nice error message.
26macro_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
38/// Mutable accessor for an array.
39/// Catches out of bounds with a nice error message.
40macro_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/// Context that the typechecking is done in.
53#[derive(Debug)]
54struct TyCtx<'i> {
55    /// Global variables with their types (all scalar).
56    global: &'i HashMap<var::Global, WithDefSite<Sp<ty::Base>>>,
57    /// Local variables (both inputs/outputs and hidden locals).
58    vars: HashMap<var::Local, WithDefSite<Sp<ty::Base>>>,
59    /// Registers storing delayed values.
60    registers: HashMap<Sp<var::Register>, Sp<ty::Tuple>>,
61    /// Known input and output types of nodes.
62    /// Notice how these maps use `Node`s, not `NodeName`s:
63    /// at the level at which typechecking on expressions is done, we have
64    /// forgotten the name that blocks bind to and we only know their unique
65    /// identifier.
66    nodes_in: HashMap<var::Node, SpTyBaseTuple>,
67    /// Outputs, same as the inputs above.
68    nodes_out: HashMap<var::Node, SpTyBaseTuple>,
69    /// Generic variables and their instanciations for each node.
70    nodes_generics: HashMap<var::Node, (GenericParams, Option<GenericInstances>)>,
71}
72
73impl<'i> TyCtx<'i> {
74    /// Construct a fresh context with known global variables but no
75    /// locals or blocks.
76    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    /// Interpret a variable as a local variable and get its type if it exists.
90    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    /// Try to get the type of a local variable, meant to be used during the
114    /// typechecking of types so we don't have access to globals and we haven't
115    /// yet declared all the local variables.
116    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    /// Interpret a variable as a global variable and get its type if it exists.
137    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    /// Get the output tuple of a nade.
161    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    /// Get the input tuple of a nade.
171    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    /// Get the (ordered) sequence of type variables that the node declares.
181    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    /// A `Node` introduces a hole in the context to eventually put the actual
191    /// values of type variables that the node declaration introduces.
192    /// This function fills in that hole from a computed unifier.
193    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
219/// Typechecking a statement is only a yes-or-no problem, as statements
220/// do not introduce type constraints.
221trait TypeCheckStmt {
222    /// Verify internal consistency.
223    /// # Errors
224    /// Fails if one of the elements of the statement cannot be typed.
225    fn typecheck(&mut self, eaccum: &mut EAccum, span: Span, ctx: &mut TyCtx) -> Option<()>;
226}
227
228/// Helper trait for `Sp<T>` to project `TypeCheckStmt` to its contents.
229trait TypeCheckSpanStmt {
230    /// Verify that the inner content is consistent.
231    /// # Errors
232    /// Fails if one of the elements of the statement cannot be typed.
233    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
244/// Verify that the expression is internally consistent, and get the type
245/// of the resulting value.
246trait TypeCheckExpr {
247    /// Typechecking expressions is slightly more tricky, because we need to check
248    /// not only if the expression is internally consistent, but also we need to
249    /// compute its type to check it against the immediate context.
250    ///
251    /// # Errors
252    /// Fails if the expression is not internally consistent
253    /// (e.g. operators applied to the wrong type, incompatible left- and right-
254    /// hand side, ...)
255    fn typecheck(&self, eaccum: &mut EAccum, span: Span, ctx: &mut TyCtx) -> Option<ty::Tuple>;
256    /// Verify that the expression is valid as a `const`.
257    ///
258    /// # Errors
259    /// Fails when the expression contains some constructors that are not
260    /// valid in const contexts, such as function calls or temporal operators.
261    fn is_const(&self, eaccum: &mut EAccum, span: Span) -> Option<()>;
262}
263
264/// Helper trait for `Sp<T>` to project `TypeCheckExpr` to its contents.
265trait TypeCheckSpanExpr {
266    /// Get the inner type.
267    ///
268    /// # Errors
269    /// Fails if the expression is not internally consistent
270    /// (e.g. operators applied to the wrong type, incompatible left- and right-
271    /// hand side, ...)
272    fn typecheck(&self, eaccum: &mut EAccum, ctx: &mut TyCtx) -> Option<Sp<ty::Tuple>>;
273    /// Verify that the inner contents are const computable.
274    ///
275    /// # Errors
276    /// Fails when the expression contains some constructors that are not
277    /// valid in const contexts, such as function calls or temporal operators.
278    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
303/// Typecheck a statement.
304///
305/// Warning: as indicated by the `&mut`, this is not pure,
306/// the method may modify the statement in-place to update it with information
307/// that was not available at translation time such as output types.
308impl 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 needs the target to have the same type as the source.
313                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                // Assert requires exactly one bool.
319                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                // This whole thing has already been verified by `TypeCheckExpr`
325                // for `FetchRegister`
326                Some(())
327            }
328        }
329    }
330}
331
332/// Most Expr cases are exactly recursing into all Expr fields
333/// and checking that they are identical or in some other way compatible.
334impl 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            // Transparent containers
339            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            // Both sides must be equal and accepted by `op`.
354            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            // Must be accepted by `op`.
366            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            // Both sides must be equal and accepted by `op`.
372            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            // Both sides must be equal.
383            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            // Both branches must be equal. Condition must be a boolean.
406            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            // All arguments need to have the right input type.
420            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                // It's a bit more subtle than plain equality between `expected_tys` and
425                // `actual_tys` because there might be subtyping or substitutions.
426                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); // The node might contain generics that we
429                                                        // now learned about.
430                Some(ctx.get_node_out(id.as_ref()).t.substitute(&unifier))
431            }
432            // Clocked by a boolean always.
433            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            // Merge operates on a boolean, and both branches must have the same type.
446            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            // Base cases
474            Self::Lit(inner) => {
475                inner.is_const(eaccum)?;
476                Some(())
477            }
478            Self::Reference(inner) => {
479                inner.is_const(eaccum)?;
480                Some(())
481            }
482            // Transparent containers (i.e. all Rust-comptime-computable
483            // operations)
484            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            // Currently unsupported, but not outrageous to add support in the future.
516            Self::Tuple(_) => eaccum.error(err::NotConst {
517                what: "Tuples are currently",
518                site: span,
519            }),
520            // Unambiguous failures: not only are they not computable at compile-time by Rust,
521            // it just doesn't make sense to put the temporal operators in a constant.
522            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
548/// No surprises here: an Int has type int, a Bool has type bool, and a Float has type float.
549impl 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
566/// Typechecking references involves translation-time heuristics on whether
567/// this should be assumed to be a local or a global variable.
568/// This is not modifiable after generation and this function will only check
569/// for one of the two.
570impl 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        /// Recursion helper: applies `typecheck` to every element of the tuple.
587        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    /// Determines if the binary operator can be applied to these arguments.
615    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    /// Determines if the unary operator can be applied to this argument.
672    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    /// Determines if the comparison operator can be applied to these arguments.
701    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    /// Verify that this is a boolean
742    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/// We implement unification via mapping because the language is simple
755/// enough that there is always one of the two tuples that we need to unify
756/// that is fully instanciated.
757///
758/// For example
759/// - (T, U, V) ~ (T, int, T) introduces { T => T; V => T; U => int },
760/// - (A, B) ~ (B, float) introduces { A => B; B => float },
761/// - (A, A) ~ (int, float) is impossible.
762///
763/// In particular there is no relation between type variables of the same
764/// name on the left or right side, and all that we need is for every variable
765/// on the left to be uniquely mapped to a variable or type on the right.
766///
767/// The variants of this enum provide different behaviors towards type variables.
768#[derive(Debug)]
769enum TyUnifier {
770    /// Trivial mapping. Every variable is unchanged.
771    Identity,
772    /// Absence of a mapping.
773    /// This panics if there is a type variable.
774    Null,
775    /// Description of a `Type -> Type` substitution.
776    /// Preserves order so that we can reproduce the correct sequence
777    /// of instanciated type variables.
778    Mapping {
779        /// Function call that introduced this mapping.
780        site: Span,
781        /// `Type -> id` part of the full mapping `Type -> Type`.
782        idx: HashMap<Sp<String>, usize>,
783        /// `id -> Type` part of the full mapping `Type -> Type`.
784        subst: Vec<Option<Sp<ty::Base>>>,
785    },
786}
787
788impl TyUnifier {
789    /// Fresh mapping unifier.
790    /// `generics` gives the variables that the mapping should have as keys
791    /// (requires no duplicates).
792    ///
793    /// `site` will be shown as the "thing that introduced this mapping" in
794    /// error messages, so you should typically set it to the location of
795    /// the full function call or other overapproximation of the statement
796    /// that introduces a mapping.
797    fn from_generics(generics: Vec<Sp<String>>, site: Span) -> Self {
798        let mut idx = HashMap::new();
799        let len = generics.len();
800        // Each type variable is a key. A duplicate means that the caller didn't properly
801        // check the uniqueness of type variables.
802        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    /// Extract the type that `s` should be substituted with.
815    ///
816    /// For non-mapping `TyUnifier`s this is the identity, otherwise this
817    /// is quite straightforwardly the value in the `HashMap` that is associated
818    /// with the key `s`. This requires that the map be fully instanciated, so
819    /// make sure to check somewhere that all type variables introduced by the
820    /// declaration are actually bound by the declarations.
821    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    /// Add a new type substitution `T => U`.
843    /// If any substitution `T => V` already exists, it must be for `V = U`.
844    /// It is the *left* argument that gets coerced to the right one, so
845    /// `require_identical(T, int)` will produce `T => int` but
846    /// `require_identical(int, T)` will always fail.
847    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            // Fully instanciated types don't get substituted obviously,
857            // so they just need to compare identical.
858            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                        // We get here if we try to use a malformed signature.
868                        // This occurs when the signature contains an undeclared
869                        // generic argument.
870                        //
871                        // We can just let this one go and the error will be
872                        // caught by someone else.
873                        // As a side effect, this will produce extraneous
874                        // TypeMismatch errors, but better that than risk
875                        // being unsound.
876                    };
877
878                    return match at_mut!(subst, *i) {
879                        x @ None => {
880                            // Not substituted yet.
881                            *x = Some(right.cloned());
882                            Some(())
883                        }
884                        Some(ref mut v) => {
885                            if v.as_ref() == right {
886                                // Already has some compatible substitution.
887                                Some(())
888                            } else {
889                                // Already has another substitution.
890                                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
917/// Apply a type substitution given by a `TyUnifier`.
918trait TySubstitute {
919    /// If `TyUnifier` is a `Mapping`, replace every occurence of a type with
920    /// its image by the substitution.
921    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    /// Check that two tuple types are identical:
956    /// both tuples of the same length, or both scalars.
957    ///
958    /// This function *does not* identify `(T,)` with `T`: one is a
959    /// size-1 `Multiple` while the other is a `Single`. If your language
960    /// is such that `(T,)` and `T` are known to be isomorphic, you should
961    /// compress `Multiple`s of size 1 earlier in the AST generation.
962    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    /// Whether this type is a `Single`.
1017    /// This function *does not* identify `(T,)` with `T`, the first will raise
1018    /// an error. If your language is such that `(T,)` is a valid scalar,
1019    /// you should compress `Multiple`s of size 1 earlier in the AST generation.
1020    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    /// Reinterpret a `Tuple<ty::Base>` as a non-nested `ty::Tuple`.
1032    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
1056/// Typing interface for toplevel declarations (nodes and constants).
1057trait TypeCheckDecl {
1058    /// Public interface of the object.
1059    type Signature;
1060
1061    /// Verify that the object is internally consistent.
1062    ///
1063    /// May depend on `extfun` and `extvar` the contexts of typed function and global
1064    /// names respectively.
1065    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    /// Extract the public interface of the item, typically its name and input/output types
1074    /// when relevant.
1075    fn signature(&self) -> Self::Signature;
1076}
1077
1078/// Helper trait for `Sp<T>` to implement `TypeCheckDecl`.
1079trait TypeCheckSpanDecl {
1080    /// Projection to the inner `Signature`.
1081    type Signature;
1082    /// Projection to the inner `typecheck` with an added span available.
1083    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    /// Projection to the inner `signature`.
1090    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
1108/// Typechecking a node involves first building the context that it makes available
1109/// to its statements, and then checking those.
1110impl TypeCheckDecl for ast::decl::Node {
1111    type Signature = (
1112        Sp<ast::decl::NodeName>,
1113        GenericParams,
1114        SpTyBaseTuple,
1115        SpTyBaseTuple,
1116    );
1117    /// Verify inner consistency.
1118    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        // Special case for functions declared as toplevel executables:
1127        // they must have empty inputs and outputs.
1128        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        // Check that the generic parameters are all inferrable from the inputs only.
1148        // That is
1149        // - all type variables of the declaration appear in the inputs, and
1150        // - all type variables in the inputs, outputs, or locals are declared.
1151        {
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                    // Inputs count towards being used
1166                    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                    // Outputs and locals don't count towards usage
1172                    // and only need to be declared.
1173                    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        // These are all the extra variables that we provide in addition
1190        // to `extvar`.
1191        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                // Don't forget to typecheck the type.
1203                // (i.e. if there are clocks in the type, check that they are of type `bool`)
1204                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        // Then also register the per-var::Node types of the blocks.
1216        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 /* (1) to be filled in */));
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        // Finally instanciate the learned generic parameters that were
1235        // inserted into (1) above.
1236        for (id, gens) in ctx.nodes_generics {
1237            at_mut!(self.blocks, id.id.t).generics = gens.1;
1238        }
1239        // And propagate upwards the learned types of registers
1240        for reg in &mut self.registers {
1241            reg.typ = ctx.registers.get(&reg.id).cloned();
1242        }
1243        Some(())
1244    }
1245
1246    /// Fetch the input and output tuple types of this node.
1247    #[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    /// Same signature as `Node` but we trust its type as there are no contents to check.
1282    /// We still check that there are no duplicate declarations of variables.
1283    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        // Special case for functions declared as toplevel executables:
1293        // they must have empty inputs and outputs.
1294        // Extern nodes cannot be marked #[test] so we don't need to handle that.
1295        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        // Check that the generic parameters are all inferrable from the inputs only.
1310        // That is
1311        // - all type variables of the declaration appear in the inputs, and
1312        // - all type variables in the inputs, outputs, or locals are declared.
1313        {
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                    // Inputs count towards being used
1328                    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                    // Outputs and locals don't count towards usage
1334                    // and only need to be declared.
1335                    // What this means concretely is that a generic parameter
1336                    // that only appears in the output is not considered used,
1337                    // which is justifiable by fact that it is not possible
1338                    // for the caller to know with which type to instanciate the
1339                    // node.
1340                    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        // These are all the extra variables that we provide in addition
1357        // to `extvar`.
1358        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                // Don't forget to typecheck the type.
1370                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    /// Get the declared inputs and outputs of this node, assuming that
1384    /// they have already been checked to be internally consistent.
1385    #[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    /// Verify inner consistency.
1441    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    /// The (name, type) pair that we need to add to the context.
1459    #[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    /// The (name, type) pair that we need to add to the context.
1478    #[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    /// Iterate through declarations and iteratively build the context
1486    /// to check subsequent declarations against.
1487    ///
1488    /// # Errors
1489    /// If any of the internal declarations are not consistent
1490    /// with their types, will return a typing error to be printed by the compiler.
1491    /// Will also report duplicate definitions.
1492    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}