078c39a76a
Second iteration of the kernel-extension-mechanics milestone. Ships
the `new` Form-A keyword + `Term::New { type_name, args: Vec<NewArg> }`
AST variant + `NewArg::Type|Value` enum end-to-end through schema /
surface / checker, with two new diagnostics
(`NewTypeNotConstructible`, `NewArgKindMismatch`), a new arm in
prep.1's workspace-wide normalisation pre-pass `qualify_workspace_term`
that rewrites bare `Term::New.type_name` to qualified form
(symmetric to the existing `Term::Ctor` arm), and the data-model.md
+ form_a.md anchor additions. Codegen out of scope per spec; codegen
sites get `CodegenError::Internal` arms naming the raw-buf milestone
as the carrier.
Verification:
- `cargo test --workspace`: 654 tests passing, 0 failed.
- 3 new in-source checker tests pin the elaboration paths:
`new_resolves_via_type_scope` (the spec's worked Counter example
checks cleanly), `new_type_not_constructible` (missing `new` def
in home module fires the new diagnostic), `new_arg_kind_mismatch_value_where_type`
(kind-mismatch detection).
- 2 new schema-drift pins (`term_new_round_trips`,
`term_new_type_arg_round_trips`) ratify the JSON byte shape:
`{"t": "new", "type": "<TypeName>", "args": [{"kind": "type"|"value",
"value": ...}]}`.
- 1 new surface round-trip pin exercises mixed-kind args through
Form-A → JSON → Form-A.
- 44+ exhaustive Term-match sites across 24 files extended with
`Term::New` arms — workspace-wide cargo build clean.
Concerns documented inline:
- `crates/ailang-core/tests/schema_coverage.rs` deliberately does
NOT register `VariantTag::TermNew` in the `EXPECTED_VARIANTS`
set. The coverage test asserts every declared variant is
observed in the .ail fixture corpus; no .ail fixture in the
current tree emits `"t": "new"` (codegen-runnable Term::New
programs require the raw-buf milestone's plugin registry).
Registering would fail the coverage check. The `visit_term` arm
IS extended (compile-forced); only the enum registration is
deferred. Inline rationale in the source. Future milestone that
ships a Term::New fixture extends both sides.
- `crates/ailang-surface/src/print.rs` `write_term` Term::New arm
was added in Task 1 (not Task 2 as planned), because without it
ailang-core's tests fail to compile (the surface crate is in
the dependency graph of ailang-core's test binaries). Task 2's
round-trip pin verified the arm's correctness.
- Task 3 (synth elaboration) needed one implementer re-loop: the
first attempt over-qualified within-module type-references via
`qualify_local_types` on `new`'s signature when the home module
was the calling module itself. Fixed by skipping qualification
when `home_module == env.current_module`.
Architectural composition with prep.1: `Term::New` joins
`Term::Ctor` as a `type_name`-bearing site that goes through
`qualify_workspace_term`'s bare→qualified rewrite before the
checker sees it. The checker's `synth` arm for `Term::New`
therefore receives an already-qualified `type_name` (or a local
bare name for same-module construction).
Milestone status: kernel-extension-mechanics (Gitea #6) advances
2/3 iters. Next: prep.3 (kernel-tier modules + param-in), issue #33.
165 lines
5.5 KiB
Rust
165 lines
5.5 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)
|
|
}
|
|
Term::Loop { binders, body } => {
|
|
for b in binders {
|
|
walk_term(&b.init)?;
|
|
}
|
|
walk_term(body)
|
|
}
|
|
Term::Recur { args } => {
|
|
for a in args {
|
|
walk_term(a)?;
|
|
}
|
|
Ok(())
|
|
}
|
|
// prep.2 (kernel-extension-mechanics): structural recurse
|
|
// through NewArg::Value subterms; NewArg::Type carries no term
|
|
// to validate at this stage (pre-desugar). Validity of the
|
|
// resolved `new` def is checked at synth time.
|
|
Term::New { args, .. } => {
|
|
for arg in args {
|
|
if let NewArg::Value(v) = arg {
|
|
walk_term(v)?;
|
|
}
|
|
}
|
|
Ok(())
|
|
}
|
|
}
|
|
}
|
|
|
|
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(())
|
|
}
|
|
}
|
|
}
|