refactor: collapse duplicated helpers and drop dead code across check/core/codegen

Pure simplification pass, no behaviour change — hashes, canonical
forms, diagnostic codes, and emitted IR are byte-identical (hash-pin
and IR-pin tests unchanged and green).

ailang-check:
- strip_forall and collect_pattern_binders were triplicated verbatim
  across linearity.rs / reuse_shape.rs / uniqueness.rs; hoisted each to
  a single pub(crate) fn in lib.rs.
- mono.rs::pattern_binders was a fourth copy of collect_pattern_binders
  (iterative style, same result for every Pattern shape); deleted, now
  calls the shared fn.
- maybe_instantiate and expect_eq were single-call wrappers; inlined
  at their sole call sites and removed.

ailang-core:
- collect_used_in_pattern was a verbatim duplicate of pattern_binds;
  deleted, call site repointed. pattern_binds made private (no external
  callers; the doc-comment's lift_letrecs claim was stale).
- is_false serde helper replaced by std::ops::Not::not at the four
  skip_serializing_if sites (all plain-bool fields); helper removed.
- test-only any_nested_ctor / any_let_rec folded into one generic
  any_term(t, &pred) walker.
- removed dead test helpers tmp_dir / examples_dir, orphaned when their
  tests relocated to tests/workspace_pin.rs.

ailang-codegen:
- removed the write-only Emitter::types field, its populating loop, and
  the now-unused CtorInfo struct it fed.
- adt_drop_symbol / adt_partial_drop_symbol differed only in a prefix
  literal; folded into one adt_symbol(prefix, ...). Symbol strings
  unchanged.

Net -180 LOC.
This commit is contained in:
2026-06-02 01:39:35 +02:00
parent e25580e3a3
commit ac9171ebf0
10 changed files with 112 additions and 293 deletions
+41 -24
View File
@@ -43,6 +43,30 @@ mod reuse_shape;
mod suppress_filter; mod suppress_filter;
pub mod uniqueness; pub mod uniqueness;
/// Returns the inner type of a top-level `Forall<Fn ...>` (or `t`
/// unchanged if not a `Forall`). The uniqueness/linearity/reuse-shape
/// analyses only inspect `param_modes`, which sit on the inner
/// `Type::Fn`.
pub(crate) fn strip_forall(t: &Type) -> &Type {
match t {
Type::Forall { body, .. } => body,
other => other,
}
}
/// Recursively collect every binder name introduced by a pattern.
/// Wildcards and literals contribute nothing; `Var` adds itself; `Ctor`
/// recurses into fields.
pub(crate) fn collect_pattern_binders(p: &Pattern) -> Vec<String> {
match p {
Pattern::Wild | Pattern::Lit { .. } => vec![],
Pattern::Var { name } => vec![name.clone()],
Pattern::Ctor { fields, .. } => {
fields.iter().flat_map(collect_pattern_binders).collect()
}
}
}
/// Metavariable substitution. Maps fresh metavar ids (from `$m<id>` in /// Metavariable substitution. Maps fresh metavar ids (from `$m<id>` in
/// `Type::Var.name`) to the type they have been unified against. /// `Type::Var.name`) to the type they have been unified against.
#[derive(Debug, Default, Clone)] #[derive(Debug, Default, Clone)]
@@ -3818,7 +3842,15 @@ pub(crate) fn synth(
}); });
return Ok(inst); return Ok(inst);
} }
Ok(maybe_instantiate(raw, counter)) // Inlined from the former `maybe_instantiate` wrapper: if
// `raw` is a `Forall`, instantiate it with fresh metavars;
// otherwise pass through unchanged.
if let Type::Forall { vars, constraints: _, body } = &raw {
let (_, inst) = instantiate(vars, body, counter);
Ok(inst)
} else {
Ok(raw)
}
} }
Term::App { callee, args, .. } => { Term::App { callee, args, .. } => {
let cty = synth(callee, env, locals, loop_stack, effects, in_def, subst, counter, residuals, free_fn_calls, warnings, let_binder_types)?; let cty = synth(callee, env, locals, loop_stack, effects, in_def, subst, counter, residuals, free_fn_calls, warnings, let_binder_types)?;
@@ -4652,17 +4684,6 @@ pub(crate) fn synth(
} }
/// If `t` is a `Forall`, instantiate it with fresh metavars; otherwise
/// pass through unchanged. Used at every var-resolution site.
fn maybe_instantiate(t: Type, counter: &mut u32) -> Type {
if let Type::Forall { vars, constraints: _, body } = &t {
let (_, inst) = instantiate(vars, body, counter);
inst
} else {
t
}
}
/// rewrites bare `Type::Con` references that resolve against /// rewrites bare `Type::Con` references that resolve against
/// `local_types` into qualified `module.Type` form. Used when pulling a /// `local_types` into qualified `module.Type` form. Used when pulling a
/// fn type across module boundaries: a `Maybe a` declared inside /// fn type across module boundaries: a `Maybe a` declared inside
@@ -5084,7 +5105,14 @@ pub(crate) fn type_check_pattern(
return Err(CheckError::FloatPatternNotAllowed); return Err(CheckError::FloatPatternNotAllowed);
} }
}; };
expect_eq(expected, &lt)?; // Inlined from the former `expect_eq` helper: the literal
// pattern's type must equal the expected scrutinee type.
if *expected != lt {
return Err(CheckError::TypeMismatch {
expected: ailang_core::pretty::type_to_string(expected),
got: ailang_core::pretty::type_to_string(&lt),
});
}
Ok(vec![]) Ok(vec![])
} }
Pattern::Ctor { ctor, fields } => { Pattern::Ctor { ctor, fields } => {
@@ -5206,17 +5234,6 @@ fn callee_name(t: &Term) -> String {
} }
} }
fn expect_eq(expected: &Type, got: &Type) -> Result<()> {
if expected == got {
Ok(())
} else {
Err(CheckError::TypeMismatch {
expected: ailang_core::pretty::type_to_string(expected),
got: ailang_core::pretty::type_to_string(got),
})
}
}
/// Typechecker environment for a single module-check pass. /// Typechecker environment for a single module-check pass.
/// ///
/// Cloned cheaply when we need a scoped extension (e.g. installing /// Cloned cheaply when we need a scoped extension (e.g. installing
+1 -24
View File
@@ -143,6 +143,7 @@
use crate::diagnostic::Diagnostic; use crate::diagnostic::Diagnostic;
use crate::uniqueness::{infer_module as infer_uniqueness, UniquenessTable}; use crate::uniqueness::{infer_module as infer_uniqueness, UniquenessTable};
use ailang_core::ast::{Arm, Ctor, Def, FnDef, Module, NewArg, ParamMode, Pattern, Term, Type}; use ailang_core::ast::{Arm, Ctor, Def, FnDef, Module, NewArg, ParamMode, Pattern, Term, Type};
use crate::{collect_pattern_binders, strip_forall};
use ailang_surface::{term_to_form_a, type_to_form_a}; use ailang_surface::{term_to_form_a, type_to_form_a};
use std::collections::HashMap; use std::collections::HashMap;
@@ -339,16 +340,6 @@ pub(crate) fn check_module_with_visible(
diags diags
} }
/// Returns the inner type of a top-level `Forall<Fn ...>` (or `t`
/// unchanged if not a `Forall`). The linearity check only inspects
/// `param_modes`, which sit on the inner `Type::Fn`.
fn strip_forall(t: &Type) -> &Type {
match t {
Type::Forall { body, .. } => body,
other => other,
}
}
/// Pull a function-typed value's declared parameter modes out of its /// Pull a function-typed value's declared parameter modes out of its
/// type, unwrapping a leading `forall`. Returns `None` for any /// type, unwrapping a leading `forall`. Returns `None` for any
/// non-function type and for a fn-type with no explicit modes (legacy /// non-function type and for a fn-type with no explicit modes (legacy
@@ -965,20 +956,6 @@ impl<'a> Checker<'a> {
} }
} }
/// Recursively collect every binder name introduced by a pattern.
/// Wildcards and literals contribute nothing; `Var` adds itself; `Ctor`
/// recurses into fields.
fn collect_pattern_binders(p: &Pattern) -> Vec<String> {
match p {
Pattern::Wild | Pattern::Lit { .. } => vec![],
Pattern::Var { name } => vec![name.clone()],
Pattern::Ctor { fields, .. } => fields
.iter()
.flat_map(collect_pattern_binders)
.collect(),
}
}
/// Collect the names of pattern binders that have an unboxed value /// Collect the names of pattern binders that have an unboxed value
/// type, by walking the pattern alongside the ctor field types. /// type, by walking the pattern alongside the ctor field types.
/// Mirrors `pattern_has_consumed_heap_binder_at`'s descent: a /// Mirrors `pattern_has_consumed_heap_binder_at`'s descent: a
+3 -26
View File
@@ -55,7 +55,7 @@
//! `module_hash` is unchanged end-to-end for any workspace with no //! `module_hash` is unchanged end-to-end for any workspace with no
//! monomorphisation targets. //! monomorphisation targets.
use ailang_core::ast::{Arm, ClassDef, ConstDef, Def, FnDef as AstFnDef, InstanceDef, NewArg, Pattern, Term, Type}; use ailang_core::ast::{Arm, ClassDef, ConstDef, Def, FnDef as AstFnDef, InstanceDef, NewArg, Term, Type};
use ailang_core::workspace::Workspace; use ailang_core::workspace::Workspace;
use crate::Result; use crate::Result;
use indexmap::IndexMap; use indexmap::IndexMap;
@@ -1415,7 +1415,7 @@ fn rewrite_mono_calls(
Term::Match { scrutinee, arms } => { Term::Match { scrutinee, arms } => {
rewrite_mono_calls(scrutinee, method_to_candidate_classes, poly_free_fns, poly_free_fn_ccounts, caller_module, ordered_targets, cursor, locals); rewrite_mono_calls(scrutinee, method_to_candidate_classes, poly_free_fns, poly_free_fn_ccounts, caller_module, ordered_targets, cursor, locals);
for Arm { pat, body } in arms { for Arm { pat, body } in arms {
let binders = pattern_binders(pat); let binders = crate::collect_pattern_binders(pat);
let mut inserted: Vec<String> = Vec::new(); let mut inserted: Vec<String> = Vec::new();
for b in &binders { for b in &binders {
if locals.insert(b.clone()) { if locals.insert(b.clone()) {
@@ -1531,29 +1531,6 @@ fn rewrite_mono_calls(
} }
} }
/// collect the variable binders introduced by a
/// match pattern. Used by the walker's `Term::Match` arm to extend
/// `locals` for each arm body. Mirrors `Pattern`'s shape:
/// - [`Pattern::Wild`] / [`Pattern::Lit`] bind nothing,
/// - [`Pattern::Var`] binds its `name`,
/// - [`Pattern::Ctor`] recurses through its `fields`.
fn pattern_binders(pat: &Pattern) -> Vec<String> {
let mut out: Vec<String> = Vec::new();
fn rec(pat: &Pattern, out: &mut Vec<String>) {
match pat {
Pattern::Wild | Pattern::Lit { .. } => {}
Pattern::Var { name } => out.push(name.clone()),
Pattern::Ctor { fields, .. } => {
for f in fields {
rec(f, out);
}
}
}
}
rec(pat, &mut out);
out
}
/// resolve a residual's class for mono target construction. /// resolve a residual's class for mono target construction.
/// Returns `Some(class)` for a discharge-ready residual (single-class /// Returns `Some(class)` for a discharge-ready residual (single-class
/// or refined-multi-candidate); `None` if the multi-candidate residual /// or refined-multi-candidate); `None` if the multi-candidate residual
@@ -1862,7 +1839,7 @@ fn interleave_slots(
Term::Match { scrutinee, arms } => { Term::Match { scrutinee, arms } => {
interleave_slots(scrutinee, method_to_candidate_classes, poly_free_fns, poly_free_fn_ccounts, class_slots, free_fn_slots, class_cur, free_cur, locals, out); interleave_slots(scrutinee, method_to_candidate_classes, poly_free_fns, poly_free_fn_ccounts, class_slots, free_fn_slots, class_cur, free_cur, locals, out);
for Arm { pat, body } in arms { for Arm { pat, body } in arms {
let binders = pattern_binders(pat); let binders = crate::collect_pattern_binders(pat);
let mut inserted: Vec<String> = Vec::new(); let mut inserted: Vec<String> = Vec::new();
for b in &binders { for b in &binders {
if locals.insert(b.clone()) { if locals.insert(b.clone()) {
+1 -20
View File
@@ -66,6 +66,7 @@
use crate::diagnostic::Diagnostic; use crate::diagnostic::Diagnostic;
use ailang_core::ast::{Arm, Def, FnDef, Module, NewArg, ParamMode, Pattern, Term, Type, TypeDef}; use ailang_core::ast::{Arm, Def, FnDef, Module, NewArg, ParamMode, Pattern, Term, Type, TypeDef};
use crate::{collect_pattern_binders, strip_forall};
use ailang_surface::term_to_form_a; use ailang_surface::term_to_form_a;
use std::collections::HashMap; use std::collections::HashMap;
@@ -114,15 +115,6 @@ fn check_fn(f: &FnDef, types: &HashMap<String, TypeDef>, diags: &mut Vec<Diagnos
checker.walk(&f.body); checker.walk(&f.body);
} }
/// Returns the inner type of a top-level `Forall<Fn ...>` (or `t`
/// unchanged if not a `Forall`).
fn strip_forall(t: &Type) -> &Type {
match t {
Type::Forall { body, .. } => body,
other => other,
}
}
/// One entry in the path-ctor stack: the bare ctor name `<var>` carries /// One entry in the path-ctor stack: the bare ctor name `<var>` carries
/// on the current control-flow path. /// on the current control-flow path.
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
@@ -487,17 +479,6 @@ fn llvm_kind(t: &Type) -> &'static str {
} }
} }
fn collect_pattern_binders(p: &Pattern) -> Vec<String> {
match p {
Pattern::Wild | Pattern::Lit { .. } => vec![],
Pattern::Var { name } => vec![name.clone()],
Pattern::Ctor { fields, .. } => fields
.iter()
.flat_map(collect_pattern_binders)
.collect(),
}
}
/// Build a `reuse-as-shape-mismatch` diagnostic. The suggested /// Build a `reuse-as-shape-mismatch` diagnostic. The suggested
/// rewrite drops the wrapper and keeps the body alone — the LLM can /// rewrite drops the wrapper and keeps the body alone — the LLM can
/// then either accept the normal allocator path or reshape the body. /// then either accept the normal allocator path or reshape the body.
+3 -17
View File
@@ -58,7 +58,8 @@
//! dec (uniform `drop_<m>_<T>` call vs inlined partial drop) is a //! dec (uniform `drop_<m>_<T>` call vs inlined partial drop) is a
//! codegen-side decision driven by `moved_slots`. //! codegen-side decision driven by `moved_slots`.
use ailang_core::ast::{Arm, Def, FnDef, Module, NewArg, ParamMode, Pattern, Term, Type}; use ailang_core::ast::{Arm, Def, FnDef, Module, NewArg, ParamMode, Term, Type};
use crate::{collect_pattern_binders, strip_forall};
use std::collections::BTreeMap; use std::collections::BTreeMap;
/// Per-binder classification produced by the inference. See module /// Per-binder classification produced by the inference. See module
@@ -175,13 +176,6 @@ pub fn infer_module_with_cross(
table table
} }
fn strip_forall(t: &Type) -> &Type {
match t {
Type::Forall { body, .. } => body,
other => other,
}
}
/// Per-fn driver: install the parameters as binders, walk the body, /// Per-fn driver: install the parameters as binders, walk the body,
/// snapshot the per-binder consume counts into the side table. /// snapshot the per-binder consume counts into the side table.
fn infer_fn( fn infer_fn(
@@ -490,18 +484,10 @@ fn merge_states(into: &mut BTreeMap<String, BinderState>, other: &BTreeMap<Strin
} }
} }
fn collect_pattern_binders(p: &Pattern) -> Vec<String> {
match p {
Pattern::Wild | Pattern::Lit { .. } => vec![],
Pattern::Var { name } => vec![name.clone()],
Pattern::Ctor { fields, .. } => fields.iter().flat_map(collect_pattern_binders).collect(),
}
}
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::*; use super::*;
use ailang_core::ast::{FnDef, Literal}; use ailang_core::ast::{FnDef, Literal, Pattern};
fn fn_simple(name: &str, params: Vec<&str>, body: Term) -> Def { fn fn_simple(name: &str, params: Vec<&str>, body: Term) -> Def {
Def::Fn(FnDef { Def::Fn(FnDef {
+13 -12
View File
@@ -501,16 +501,7 @@ impl<'a> Emitter<'a> {
/// with the emission loop, so a call symbol matches its definition /// with the emission loop, so a call symbol matches its definition
/// byte-for-byte (a mismatch is an IR link error). /// byte-for-byte (a mismatch is an IR link error).
pub(crate) fn adt_drop_symbol(&self, name: &str, args: &[Type]) -> String { pub(crate) fn adt_drop_symbol(&self, name: &str, args: &[Type]) -> String {
let key = super::dropmono::resolve_adt_key( self.adt_symbol("drop_", name, args)
name,
self.module_name,
&self.import_map,
);
let base = format!("drop_{owner}_{bare}", owner = key.0, bare = key.1);
match self.drop_monos.suffix_for(&key, args) {
Some(suffix) => format!("{base}{suffix}"),
None => base,
}
} }
/// resolve the `partial_drop_<owner>_<T>` symbol for an ADT /// resolve the `partial_drop_<owner>_<T>` symbol for an ADT
@@ -518,13 +509,23 @@ impl<'a> Emitter<'a> {
/// to [`Self::adt_drop_symbol`]. The caller has already excluded /// to [`Self::adt_drop_symbol`]. The caller has already excluded
/// `Str` / non-`Con` shapes. /// `Str` / non-`Con` shapes.
pub(crate) fn adt_partial_drop_symbol(&self, name: &str, args: &[Type]) -> String { pub(crate) fn adt_partial_drop_symbol(&self, name: &str, args: &[Type]) -> String {
self.adt_symbol("partial_drop_", name, args)
}
/// shared core of [`Self::adt_drop_symbol`] and
/// [`Self::adt_partial_drop_symbol`]: build the `<prefix><owner>_<bare>`
/// base from the resolved ADT key and append the leg-C per-monomorph
/// suffix when one applies. The two public symbols differ only in the
/// `prefix` literal (`drop_` vs `partial_drop_`); the emitted string is
/// byte-identical to the pre-dedup form, which matters because these are
/// IR symbol names (a mismatch is a link error).
fn adt_symbol(&self, prefix: &str, name: &str, args: &[Type]) -> String {
let key = super::dropmono::resolve_adt_key( let key = super::dropmono::resolve_adt_key(
name, name,
self.module_name, self.module_name,
&self.import_map, &self.import_map,
); );
let base = let base = format!("{prefix}{owner}_{bare}", owner = key.0, bare = key.1);
format!("partial_drop_{owner}_{bare}", owner = key.0, bare = key.1);
match self.drop_monos.suffix_for(&key, args) { match self.drop_monos.suffix_for(&key, args) {
Some(suffix) => format!("{base}{suffix}"), Some(suffix) => format!("{base}{suffix}"),
None => base, None => base,
-42
View File
@@ -787,12 +787,6 @@ struct Emitter<'a> {
/// `drop_`/`partial_drop_` symbol. Shared by reference with every /// `drop_`/`partial_drop_` symbol. Shared by reference with every
/// `Emitter`. /// `Emitter`.
drop_monos: &'a dropmono::DropAdtMeta, drop_monos: &'a dropmono::DropAdtMeta,
/// ADT table: type_name -> list of ctors in definition order.
/// Tag of a ctor = index in this list.
/// Kept around for future tools (pretty-printer for ADT values,
/// decision-tree optimization).
#[allow(dead_code)]
types: BTreeMap<String, Vec<CtorInfo>>,
/// cross-module ctor index, keyed by module name. Used by /// cross-module ctor index, keyed by module name. Used by
/// `lookup_ctor_by_type` (for `Term::Ctor.type_name`) and /// `lookup_ctor_by_type` (for `Term::Ctor.type_name`) and
/// `lookup_ctor_in_pattern` (for `Pattern::Ctor.ctor`). Built once /// `lookup_ctor_in_pattern` (for `Pattern::Ctor.ctor`). Built once
@@ -938,13 +932,6 @@ struct Emitter<'a> {
entry_block_end_marker: Option<usize>, entry_block_end_marker: Option<usize>,
} }
#[derive(Debug, Clone)]
#[allow(dead_code)]
struct CtorInfo {
name: String,
fields: Vec<String>, // llvm types
}
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
struct CtorRef { struct CtorRef {
type_name: String, type_name: String,
@@ -993,34 +980,6 @@ impl<'a> Emitter<'a> {
drop_monos: &'a dropmono::DropAdtMeta, drop_monos: &'a dropmono::DropAdtMeta,
alloc: AllocStrategy, alloc: AllocStrategy,
) -> Self { ) -> Self {
let mut types: BTreeMap<String, Vec<CtorInfo>> = BTreeMap::new();
for def in &module.defs {
if let Def::Type(td) = def {
let mut infos = Vec::new();
for c in td.ctors.iter() {
// precomputed LLVM field types are only
// meaningful for monomorphic ADTs. For parameterised
// ADTs the field types reference free `Type::Var`s
// and must be derived per use site after
// substituting; we still populate the slot with
// `i64`/`ptr` placeholders so the index shape stays
// uniform, but neither `lower_ctor` nor
// `lower_match` reads from it when `type_vars` is
// non-empty.
let fields: Vec<String> = c
.fields
.iter()
.map(|t| llvm_type(t).unwrap_or_else(|_| "ptr".into()))
.collect();
infos.push(CtorInfo {
name: c.name.clone(),
fields,
});
}
types.insert(td.name.clone(), infos);
}
}
// mir.3a: read the per-binder consume_count off MIR (filled by // mir.3a: read the per-binder consume_count off MIR (filled by
// lower_to_mir from the single uniqueness pass) instead of // lower_to_mir from the single uniqueness pass) instead of
// re-running the uniqueness pass here. // re-running the uniqueness pass here.
@@ -1049,7 +1008,6 @@ impl<'a> Emitter<'a> {
module_user_fns, module_user_fns,
import_map, import_map,
drop_monos, drop_monos,
types,
module_ctor_index, module_ctor_index,
module_consts, module_consts,
current_block: String::new(), current_block: String::new(),
+4 -15
View File
@@ -41,7 +41,7 @@ pub struct Module {
/// from canonical JSON when `false`, so every pre-existing /// from canonical JSON when `false`, so every pre-existing
/// fixture's hash is bit-stable. See prep.3 of the /// fixture's hash is bit-stable. See prep.3 of the
/// kernel-extension-mechanics milestone. /// kernel-extension-mechanics milestone.
#[serde(default, skip_serializing_if = "is_false")] #[serde(default, skip_serializing_if = "std::ops::Not::not")]
pub kernel: bool, pub kernel: bool,
/// Imports of other modules. Resolved by the workspace loader as /// Imports of other modules. Resolved by the workspace loader as
/// `<root_dir>/<module>.ail.json`. /// `<root_dir>/<module>.ail.json`.
@@ -174,7 +174,7 @@ pub struct TypeDef {
#[serde( #[serde(
default, default,
rename = "drop-iterative", rename = "drop-iterative",
skip_serializing_if = "is_false" skip_serializing_if = "std::ops::Not::not"
)] )]
pub drop_iterative: bool, pub drop_iterative: bool,
/// Closed-set restriction on type-parameter instantiation. Maps /// Closed-set restriction on type-parameter instantiation. Maps
@@ -452,7 +452,7 @@ pub enum Term {
callee: Box<Term>, callee: Box<Term>,
#[serde(default)] #[serde(default)]
args: Vec<Term>, args: Vec<Term>,
#[serde(default, skip_serializing_if = "is_false")] #[serde(default, skip_serializing_if = "std::ops::Not::not")]
tail: bool, tail: bool,
}, },
/// Let-binding: `value` is evaluated and bound to `name` in `body`. /// Let-binding: `value` is evaluated and bound to `name` in `body`.
@@ -494,7 +494,7 @@ pub enum Term {
Do { Do {
op: String, op: String,
args: Vec<Term>, args: Vec<Term>,
#[serde(default, skip_serializing_if = "is_false")] #[serde(default, skip_serializing_if = "std::ops::Not::not")]
tail: bool, tail: bool,
}, },
/// Constructor application. `type_name` binds the ADT, `ctor` the /// Constructor application. `type_name` binds the ADT, `ctor` the
@@ -903,17 +903,6 @@ impl PartialEq for Type {
} }
impl Eq for Type {} impl Eq for Type {}
/// Serde helper for `#[serde(skip_serializing_if = "is_false")]`.
///
/// Used by [`Term::App::tail`] and [`Term::Do::tail`] so the `tail`
/// flag is omitted from the canonical JSON whenever it is false,
/// preserving bit-identical hashes for every fixture that does not
/// carry the flag.
#[allow(clippy::trivially_copy_pass_by_ref)]
fn is_false(b: &bool) -> bool {
!*b
}
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::*; use super::*;
+46 -93
View File
@@ -384,7 +384,7 @@ fn collect_used_in_term(t: &Term, used: &mut BTreeSet<String>) {
Term::Match { scrutinee, arms } => { Term::Match { scrutinee, arms } => {
collect_used_in_term(scrutinee, used); collect_used_in_term(scrutinee, used);
for arm in arms { for arm in arms {
collect_used_in_pattern(&arm.pat, used); pattern_binds(&arm.pat, used);
collect_used_in_term(&arm.body, used); collect_used_in_term(&arm.body, used);
} }
} }
@@ -439,22 +439,6 @@ fn collect_used_in_term(t: &Term, used: &mut BTreeSet<String>) {
} }
} }
/// Walks a pattern and inserts every [`Pattern::Var.name`] into `used`.
fn collect_used_in_pattern(p: &Pattern, used: &mut BTreeSet<String>) {
match p {
Pattern::Wild => {}
Pattern::Var { name } => {
used.insert(name.clone());
}
Pattern::Lit { .. } => {}
Pattern::Ctor { fields, .. } => {
for sub in fields {
collect_used_in_pattern(sub, used);
}
}
}
}
/// State carried across a module-wide desugar pass. /// State carried across a module-wide desugar pass.
/// ///
/// `counter` is incremented for every fresh-name request; `used` /// `counter` is incremented for every fresh-name request; `used`
@@ -1540,11 +1524,7 @@ fn rename_pattern_binders(pat: &Pattern, renamed: &BTreeMap<String, String>) ->
/// collect every name a pattern binds into `out`. Mirrors /// collect every name a pattern binds into `out`. Mirrors
/// `Pattern::pattern_bound_names` in `ailang-codegen`; duplicated here /// `Pattern::pattern_bound_names` in `ailang-codegen`; duplicated here
/// because `ailang-core` cannot depend on the codegen crate. /// because `ailang-core` cannot depend on the codegen crate.
/// fn pattern_binds(p: &Pattern, out: &mut BTreeSet<String>) {
/// made `pub` so callers (e.g. `ailang-check::lift_letrecs`)
/// can reproduce the same shadowing semantics during their own
/// scope-aware walks.
pub fn pattern_binds(p: &Pattern, out: &mut BTreeSet<String>) {
match p { match p {
Pattern::Wild | Pattern::Lit { .. } => {} Pattern::Wild | Pattern::Lit { .. } => {}
Pattern::Var { name } => { Pattern::Var { name } => {
@@ -1949,85 +1929,58 @@ pub fn find_non_callee_use(t: &Term, name: &str) -> Option<Term> {
mod tests { mod tests {
use super::*; use super::*;
/// Generic test-only tree walk: returns true if `pred` holds for
/// `t` or any of its subterms. The structural recursion visits the
/// same subterm set every node-specific walker needs (callee/args,
/// let value+body, if branches, match scrutinee + arm bodies, etc.).
fn any_term(t: &Term, pred: &impl Fn(&Term) -> bool) -> bool {
if pred(t) {
return true;
}
match t {
Term::Lit { .. } | Term::Var { .. } | Term::Intrinsic => false,
Term::App { callee, args, .. } => {
any_term(callee, pred) || args.iter().any(|a| any_term(a, pred))
}
Term::Let { value, body, .. } => any_term(value, pred) || any_term(body, pred),
Term::If { cond, then, else_ } => {
any_term(cond, pred) || any_term(then, pred) || any_term(else_, pred)
}
Term::Do { args, .. } => args.iter().any(|a| any_term(a, pred)),
Term::Ctor { args, .. } => args.iter().any(|a| any_term(a, pred)),
Term::Match { scrutinee, arms } => {
any_term(scrutinee, pred) || arms.iter().any(|a| any_term(&a.body, pred))
}
Term::Lam { body, .. } => any_term(body, pred),
Term::Seq { lhs, rhs } => any_term(lhs, pred) || any_term(rhs, pred),
Term::LetRec { body, in_term, .. } => {
any_term(body, pred) || any_term(in_term, pred)
}
Term::Clone { value } => any_term(value, pred),
Term::ReuseAs { source, body } => any_term(source, pred) || any_term(body, pred),
Term::Loop { binders, body } => {
binders.iter().any(|b| any_term(&b.init, pred)) || any_term(body, pred)
}
Term::Recur { args } => args.iter().any(|a| any_term(a, pred)),
Term::New { args, .. } => args.iter().any(|arg| match arg {
NewArg::Value(v) => any_term(v, pred),
NewArg::Type(_) => false,
}),
}
}
/// Helper: walk a term, return true iff any [`Pattern::Ctor`] in /// Helper: walk a term, return true iff any [`Pattern::Ctor`] in
/// any reachable [`Term::Match`] has a non-flat field. /// any reachable [`Term::Match`] has a non-flat field.
fn any_nested_ctor(t: &Term) -> bool { fn any_nested_ctor(t: &Term) -> bool {
match t { any_term(t, &|x| {
Term::Lit { .. } | Term::Var { .. } => false, matches!(x, Term::Match { arms, .. } if arms.iter().any(|a| !is_flat(&a.pat)))
Term::App { callee, args, .. } => { })
any_nested_ctor(callee) || args.iter().any(any_nested_ctor)
}
Term::Let { value, body, .. } => any_nested_ctor(value) || any_nested_ctor(body),
Term::If { cond, then, else_ } => {
any_nested_ctor(cond) || any_nested_ctor(then) || any_nested_ctor(else_)
}
Term::Do { args, .. } => args.iter().any(any_nested_ctor),
Term::Ctor { args, .. } => args.iter().any(any_nested_ctor),
Term::Match { scrutinee, arms } => {
if any_nested_ctor(scrutinee) {
return true;
}
for a in arms {
if !is_flat(&a.pat) {
return true;
}
if any_nested_ctor(&a.body) {
return true;
}
}
false
}
Term::Lam { body, .. } => any_nested_ctor(body),
Term::Seq { lhs, rhs } => any_nested_ctor(lhs) || any_nested_ctor(rhs),
Term::LetRec { body, in_term, .. } => {
any_nested_ctor(body) || any_nested_ctor(in_term)
}
Term::Clone { value } => any_nested_ctor(value),
Term::ReuseAs { source, body } => any_nested_ctor(source) || any_nested_ctor(body),
Term::Loop { binders, body } => {
binders.iter().any(|b| any_nested_ctor(&b.init)) || any_nested_ctor(body)
}
Term::Recur { args } => args.iter().any(any_nested_ctor),
Term::New { args, .. } => args.iter().any(|arg| match arg {
NewArg::Value(v) => any_nested_ctor(v),
NewArg::Type(_) => false,
}),
Term::Intrinsic => false,
}
} }
/// Helper: walk a term, return true iff any [`Term::LetRec`] is /// Helper: walk a term, return true iff any [`Term::LetRec`] is
/// reachable. After 16b.1 desugaring this should be `false`. /// reachable. After 16b.1 desugaring this should be `false`.
fn any_let_rec(t: &Term) -> bool { fn any_let_rec(t: &Term) -> bool {
match t { any_term(t, &|x| matches!(x, Term::LetRec { .. }))
Term::Lit { .. } | Term::Var { .. } => false,
Term::App { callee, args, .. } => {
any_let_rec(callee) || args.iter().any(any_let_rec)
}
Term::Let { value, body, .. } => any_let_rec(value) || any_let_rec(body),
Term::If { cond, then, else_ } => {
any_let_rec(cond) || any_let_rec(then) || any_let_rec(else_)
}
Term::Do { args, .. } => args.iter().any(any_let_rec),
Term::Ctor { args, .. } => args.iter().any(any_let_rec),
Term::Match { scrutinee, arms } => {
any_let_rec(scrutinee) || arms.iter().any(|a| any_let_rec(&a.body))
}
Term::Lam { body, .. } => any_let_rec(body),
Term::Seq { lhs, rhs } => any_let_rec(lhs) || any_let_rec(rhs),
Term::LetRec { .. } => true,
Term::Clone { value } => any_let_rec(value),
Term::ReuseAs { source, body } => any_let_rec(source) || any_let_rec(body),
Term::Loop { binders, body } => {
binders.iter().any(|b| any_let_rec(&b.init)) || any_let_rec(body)
}
Term::Recur { args } => args.iter().any(any_let_rec),
Term::New { args, .. } => args.iter().any(|arg| match arg {
NewArg::Value(v) => any_let_rec(v),
NewArg::Type(_) => false,
}),
Term::Intrinsic => false,
}
} }
/// a `match` containing `(Cons a (Cons b _))` desugars to /// a `match` containing `(Cons a (Cons b _))` desugars to
-20
View File
@@ -1659,16 +1659,6 @@ mod tests {
path path
} }
fn tmp_dir(tag: &str) -> PathBuf {
let d = std::env::temp_dir().join(format!(
"ailang_workspace_test_{tag}_{}",
std::process::id()
));
let _ = fs::remove_dir_all(&d);
fs::create_dir_all(&d).unwrap();
d
}
// `loads_example_workspace_happy_path` + `loads_workspace_auto_injects_prelude` // `loads_example_workspace_happy_path` + `loads_workspace_auto_injects_prelude`
// relocated to `crates/ailang-core/tests/workspace_pin.rs` in iter // relocated to `crates/ailang-core/tests/workspace_pin.rs` in iter
// form-a.1 Task 5 (use `ailang_surface::load_workspace` on `.ail` // form-a.1 Task 5 (use `ailang_surface::load_workspace` on `.ail`
@@ -1692,16 +1682,6 @@ mod tests {
// `iter22b1_workspace_with_no_classes_has_empty_registry` relocated // `iter22b1_workspace_with_no_classes_has_empty_registry` relocated
// to `crates/ailang-core/tests/workspace_pin.rs` in iter form-a.1 Task 5. // to `crates/ailang-core/tests/workspace_pin.rs` in iter form-a.1 Task 5.
fn examples_dir() -> PathBuf {
let manifest_dir = env!("CARGO_MANIFEST_DIR");
Path::new(manifest_dir)
.parent()
.unwrap()
.parent()
.unwrap()
.join("examples")
}
// a coherent instance (in the class's module) // a coherent instance (in the class's module)
// loads cleanly and produces one registry entry from the // loads cleanly and produces one registry entry from the
// fixture itself. With the auto-loaded prelude (iter 23.2) // fixture itself. With the auto-loaded prelude (iter 23.2)