//! Floats milestone post-fieldtest B1: pre-desugar walker that hard- //! rejects `Pattern::Lit { lit: Literal::Float { .. } }`. //! //! Why a separate pre-desugar pass: //! [`ailang_core::desugar::build_eq`] rewrites `Pattern::Lit` arms //! (Int / Bool / Str / **Float**) into `Term::App { callee: ==, .. }` //! before typecheck runs. As a consequence the in-typechecker //! Float-pattern-reject arm in [`crate::type_check_pattern`] is //! unreachable for any input that flows through the public check //! entry points (`check`, `check_module`, `check_workspace`), all of //! which call `desugar_module` first. //! //! This walker runs *before* desugar and short-circuits on the first //! `Pattern::Lit { lit: Literal::Float }` it finds, returning the //! bare [`CheckError::FloatPatternNotAllowed`]. Callers that build a //! diagnostic (the multi-diagnose entry points) wrap the bare error //! in [`CheckError::Def`] themselves so the diagnostic carries the //! offending def's name. The in-typechecker arm at //! [`crate::type_check_pattern`] remains as defence-in-depth for //! direct `synth` callers (the `reject_float_pattern_in_match` unit //! test in `crate::builtins` exercises that path). use ailang_core::ast::*; use crate::CheckError; /// Walks every `Term::Match` arm in `m` and returns /// `Err(CheckError::FloatPatternNotAllowed)` on the first /// `Pattern::Lit { lit: Literal::Float }` encountered. Defs are /// visited in declaration order; the walk bails on the first hit. /// /// The returned error is bare (not wrapped in [`CheckError::Def`]). /// The single-error entry point [`crate::check`] propagates it as-is /// so callers asserting `matches!(err, CheckError::FloatPatternNotAllowed)` /// stay clean. Multi-diagnose entry points should call /// [`reject_float_patterns_in_def`] per def to attach the def name. pub(crate) fn reject_float_patterns_in_module(m: &Module) -> Result<(), CheckError> { for def in &m.defs { reject_float_patterns_in_def(def)?; } Ok(()) } /// Per-def variant of [`reject_float_patterns_in_module`]. Returns /// the bare [`CheckError::FloatPatternNotAllowed`] on first hit; /// callers wrap with [`CheckError::Def`] themselves to keep the /// def-name attribution on the diagnostic. pub(crate) fn reject_float_patterns_in_def(def: &Def) -> Result<(), CheckError> { match def { Def::Fn(f) => walk_term(&f.body), Def::Const(c) => walk_term(&c.value), Def::Type(_) => Ok(()), Def::Class(c) => { for method in &c.methods { if let Some(default) = &method.default { walk_term(default)?; } } Ok(()) } Def::Instance(i) => { for method in &i.methods { walk_term(&method.body)?; } Ok(()) } } } fn walk_term(t: &Term) -> Result<(), CheckError> { match t { Term::Lit { .. } | Term::Var { .. } => Ok(()), Term::App { callee, args, .. } => { walk_term(callee)?; for a in args { walk_term(a)?; } Ok(()) } Term::Let { value, body, .. } => { walk_term(value)?; walk_term(body) } Term::LetRec { body, in_term, .. } => { walk_term(body)?; walk_term(in_term) } Term::If { cond, then, else_ } => { walk_term(cond)?; walk_term(then)?; walk_term(else_) } Term::Do { args, .. } => { for a in args { walk_term(a)?; } Ok(()) } Term::Ctor { args, .. } => { for a in args { walk_term(a)?; } Ok(()) } Term::Match { scrutinee, arms } => { walk_term(scrutinee)?; for arm in arms { walk_pattern(&arm.pat)?; walk_term(&arm.body)?; } Ok(()) } Term::Lam { body, .. } => walk_term(body), Term::Seq { lhs, rhs } => { walk_term(lhs)?; walk_term(rhs) } Term::Clone { value } => walk_term(value), Term::ReuseAs { source, body } => { walk_term(source)?; walk_term(body) } } } fn walk_pattern(p: &Pattern) -> Result<(), CheckError> { match p { Pattern::Wild | Pattern::Var { .. } => Ok(()), Pattern::Lit { lit: Literal::Float { .. }, } => Err(CheckError::FloatPatternNotAllowed), Pattern::Lit { .. } => Ok(()), Pattern::Ctor { fields, .. } => { for sub in fields { walk_pattern(sub)?; } Ok(()) } } }