141 lines
4.7 KiB
Rust
141 lines
4.7 KiB
Rust
//! 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(())
|
|
}
|
|
}
|
|
}
|