From ac9171ebf0899545d1c6a7201b8c6a33ef1d1a20 Mon Sep 17 00:00:00 2001 From: Brummel Date: Tue, 2 Jun 2026 01:39:35 +0200 Subject: [PATCH] refactor: collapse duplicated helpers and drop dead code across check/core/codegen MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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. --- crates/ailang-check/src/lib.rs | 65 +++++++----- crates/ailang-check/src/linearity.rs | 25 +---- crates/ailang-check/src/mono.rs | 29 +----- crates/ailang-check/src/reuse_shape.rs | 21 +--- crates/ailang-check/src/uniqueness.rs | 20 +--- crates/ailang-codegen/src/drop.rs | 25 ++--- crates/ailang-codegen/src/lib.rs | 42 -------- crates/ailang-core/src/ast.rs | 19 +--- crates/ailang-core/src/desugar.rs | 139 ++++++++----------------- crates/ailang-core/src/workspace.rs | 20 ---- 10 files changed, 112 insertions(+), 293 deletions(-) diff --git a/crates/ailang-check/src/lib.rs b/crates/ailang-check/src/lib.rs index 643b1a0..92fb915 100644 --- a/crates/ailang-check/src/lib.rs +++ b/crates/ailang-check/src/lib.rs @@ -43,6 +43,30 @@ mod reuse_shape; mod suppress_filter; pub mod uniqueness; +/// Returns the inner type of a top-level `Forall` (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 { + 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` in /// `Type::Var.name`) to the type they have been unified against. #[derive(Debug, Default, Clone)] @@ -3818,7 +3842,15 @@ pub(crate) fn synth( }); 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, .. } => { 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 /// `local_types` into qualified `module.Type` form. Used when pulling a /// fn type across module boundaries: a `Maybe a` declared inside @@ -5084,7 +5105,14 @@ pub(crate) fn type_check_pattern( return Err(CheckError::FloatPatternNotAllowed); } }; - expect_eq(expected, <)?; + // 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(<), + }); + } Ok(vec![]) } 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. /// /// Cloned cheaply when we need a scoped extension (e.g. installing diff --git a/crates/ailang-check/src/linearity.rs b/crates/ailang-check/src/linearity.rs index 38b54cd..932a154 100644 --- a/crates/ailang-check/src/linearity.rs +++ b/crates/ailang-check/src/linearity.rs @@ -143,6 +143,7 @@ use crate::diagnostic::Diagnostic; use crate::uniqueness::{infer_module as infer_uniqueness, UniquenessTable}; 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 std::collections::HashMap; @@ -339,16 +340,6 @@ pub(crate) fn check_module_with_visible( diags } -/// Returns the inner type of a top-level `Forall` (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 /// type, unwrapping a leading `forall`. Returns `None` for any /// 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 { - 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 /// type, by walking the pattern alongside the ctor field types. /// Mirrors `pattern_has_consumed_heap_binder_at`'s descent: a diff --git a/crates/ailang-check/src/mono.rs b/crates/ailang-check/src/mono.rs index 45e1933..2e6faff 100644 --- a/crates/ailang-check/src/mono.rs +++ b/crates/ailang-check/src/mono.rs @@ -55,7 +55,7 @@ //! `module_hash` is unchanged end-to-end for any workspace with no //! 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 crate::Result; use indexmap::IndexMap; @@ -1415,7 +1415,7 @@ fn rewrite_mono_calls( 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); for Arm { pat, body } in arms { - let binders = pattern_binders(pat); + let binders = crate::collect_pattern_binders(pat); let mut inserted: Vec = Vec::new(); for b in &binders { 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 { - let mut out: Vec = Vec::new(); - fn rec(pat: &Pattern, out: &mut Vec) { - 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. /// Returns `Some(class)` for a discharge-ready residual (single-class /// or refined-multi-candidate); `None` if the multi-candidate residual @@ -1862,7 +1839,7 @@ fn interleave_slots( 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); for Arm { pat, body } in arms { - let binders = pattern_binders(pat); + let binders = crate::collect_pattern_binders(pat); let mut inserted: Vec = Vec::new(); for b in &binders { if locals.insert(b.clone()) { diff --git a/crates/ailang-check/src/reuse_shape.rs b/crates/ailang-check/src/reuse_shape.rs index e83d87a..f0ce9a0 100644 --- a/crates/ailang-check/src/reuse_shape.rs +++ b/crates/ailang-check/src/reuse_shape.rs @@ -66,6 +66,7 @@ use crate::diagnostic::Diagnostic; 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 std::collections::HashMap; @@ -114,15 +115,6 @@ fn check_fn(f: &FnDef, types: &HashMap, diags: &mut Vec` (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 `` carries /// on the current control-flow path. #[derive(Debug, Clone)] @@ -487,17 +479,6 @@ fn llvm_kind(t: &Type) -> &'static str { } } -fn collect_pattern_binders(p: &Pattern) -> Vec { - 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 /// rewrite drops the wrapper and keeps the body alone — the LLM can /// then either accept the normal allocator path or reshape the body. diff --git a/crates/ailang-check/src/uniqueness.rs b/crates/ailang-check/src/uniqueness.rs index afc5922..6e934e2 100644 --- a/crates/ailang-check/src/uniqueness.rs +++ b/crates/ailang-check/src/uniqueness.rs @@ -58,7 +58,8 @@ //! dec (uniform `drop__` call vs inlined partial drop) is a //! 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; /// Per-binder classification produced by the inference. See module @@ -175,13 +176,6 @@ pub fn infer_module_with_cross( 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, /// snapshot the per-binder consume counts into the side table. fn infer_fn( @@ -490,18 +484,10 @@ fn merge_states(into: &mut BTreeMap, other: &BTreeMap Vec { - 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)] mod tests { 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 { Def::Fn(FnDef { diff --git a/crates/ailang-codegen/src/drop.rs b/crates/ailang-codegen/src/drop.rs index 518e3bc..9d33cd5 100644 --- a/crates/ailang-codegen/src/drop.rs +++ b/crates/ailang-codegen/src/drop.rs @@ -501,16 +501,7 @@ impl<'a> Emitter<'a> { /// with the emission loop, so a call symbol matches its definition /// byte-for-byte (a mismatch is an IR link error). pub(crate) fn adt_drop_symbol(&self, name: &str, args: &[Type]) -> String { - let key = super::dropmono::resolve_adt_key( - 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, - } + self.adt_symbol("drop_", name, args) } /// resolve the `partial_drop__` symbol for an ADT @@ -518,13 +509,23 @@ impl<'a> Emitter<'a> { /// to [`Self::adt_drop_symbol`]. The caller has already excluded /// `Str` / non-`Con` shapes. 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 `_` + /// 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( name, self.module_name, &self.import_map, ); - let base = - format!("partial_drop_{owner}_{bare}", owner = key.0, bare = key.1); + let base = format!("{prefix}{owner}_{bare}", owner = key.0, bare = key.1); match self.drop_monos.suffix_for(&key, args) { Some(suffix) => format!("{base}{suffix}"), None => base, diff --git a/crates/ailang-codegen/src/lib.rs b/crates/ailang-codegen/src/lib.rs index 2a212ea..65458d0 100644 --- a/crates/ailang-codegen/src/lib.rs +++ b/crates/ailang-codegen/src/lib.rs @@ -787,12 +787,6 @@ struct Emitter<'a> { /// `drop_`/`partial_drop_` symbol. Shared by reference with every /// `Emitter`. 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>, /// cross-module ctor index, keyed by module name. Used by /// `lookup_ctor_by_type` (for `Term::Ctor.type_name`) and /// `lookup_ctor_in_pattern` (for `Pattern::Ctor.ctor`). Built once @@ -938,13 +932,6 @@ struct Emitter<'a> { entry_block_end_marker: Option, } -#[derive(Debug, Clone)] -#[allow(dead_code)] -struct CtorInfo { - name: String, - fields: Vec, // llvm types -} - #[derive(Debug, Clone)] struct CtorRef { type_name: String, @@ -993,34 +980,6 @@ impl<'a> Emitter<'a> { drop_monos: &'a dropmono::DropAdtMeta, alloc: AllocStrategy, ) -> Self { - let mut types: BTreeMap> = 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 = 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 // lower_to_mir from the single uniqueness pass) instead of // re-running the uniqueness pass here. @@ -1049,7 +1008,6 @@ impl<'a> Emitter<'a> { module_user_fns, import_map, drop_monos, - types, module_ctor_index, module_consts, current_block: String::new(), diff --git a/crates/ailang-core/src/ast.rs b/crates/ailang-core/src/ast.rs index 6215518..06c2299 100644 --- a/crates/ailang-core/src/ast.rs +++ b/crates/ailang-core/src/ast.rs @@ -41,7 +41,7 @@ pub struct Module { /// from canonical JSON when `false`, so every pre-existing /// fixture's hash is bit-stable. See prep.3 of the /// kernel-extension-mechanics milestone. - #[serde(default, skip_serializing_if = "is_false")] + #[serde(default, skip_serializing_if = "std::ops::Not::not")] pub kernel: bool, /// Imports of other modules. Resolved by the workspace loader as /// `/.ail.json`. @@ -174,7 +174,7 @@ pub struct TypeDef { #[serde( default, rename = "drop-iterative", - skip_serializing_if = "is_false" + skip_serializing_if = "std::ops::Not::not" )] pub drop_iterative: bool, /// Closed-set restriction on type-parameter instantiation. Maps @@ -452,7 +452,7 @@ pub enum Term { callee: Box, #[serde(default)] args: Vec, - #[serde(default, skip_serializing_if = "is_false")] + #[serde(default, skip_serializing_if = "std::ops::Not::not")] tail: bool, }, /// Let-binding: `value` is evaluated and bound to `name` in `body`. @@ -494,7 +494,7 @@ pub enum Term { Do { op: String, args: Vec, - #[serde(default, skip_serializing_if = "is_false")] + #[serde(default, skip_serializing_if = "std::ops::Not::not")] tail: bool, }, /// Constructor application. `type_name` binds the ADT, `ctor` the @@ -903,17 +903,6 @@ impl PartialEq 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)] mod tests { use super::*; diff --git a/crates/ailang-core/src/desugar.rs b/crates/ailang-core/src/desugar.rs index becde37..2e9c75b 100644 --- a/crates/ailang-core/src/desugar.rs +++ b/crates/ailang-core/src/desugar.rs @@ -384,7 +384,7 @@ fn collect_used_in_term(t: &Term, used: &mut BTreeSet) { Term::Match { scrutinee, arms } => { collect_used_in_term(scrutinee, used); for arm in arms { - collect_used_in_pattern(&arm.pat, used); + pattern_binds(&arm.pat, used); collect_used_in_term(&arm.body, used); } } @@ -439,22 +439,6 @@ fn collect_used_in_term(t: &Term, used: &mut BTreeSet) { } } -/// Walks a pattern and inserts every [`Pattern::Var.name`] into `used`. -fn collect_used_in_pattern(p: &Pattern, used: &mut BTreeSet) { - 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. /// /// `counter` is incremented for every fresh-name request; `used` @@ -1540,11 +1524,7 @@ fn rename_pattern_binders(pat: &Pattern, renamed: &BTreeMap) -> /// collect every name a pattern binds into `out`. Mirrors /// `Pattern::pattern_bound_names` in `ailang-codegen`; duplicated here /// because `ailang-core` cannot depend on the codegen crate. -/// -/// 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) { +fn pattern_binds(p: &Pattern, out: &mut BTreeSet) { match p { Pattern::Wild | Pattern::Lit { .. } => {} Pattern::Var { name } => { @@ -1949,85 +1929,58 @@ pub fn find_non_callee_use(t: &Term, name: &str) -> Option { mod tests { 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 /// any reachable [`Term::Match`] has a non-flat field. fn any_nested_ctor(t: &Term) -> bool { - match t { - Term::Lit { .. } | Term::Var { .. } => false, - 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, - } + any_term(t, &|x| { + matches!(x, Term::Match { arms, .. } if arms.iter().any(|a| !is_flat(&a.pat))) + }) } /// Helper: walk a term, return true iff any [`Term::LetRec`] is /// reachable. After 16b.1 desugaring this should be `false`. fn any_let_rec(t: &Term) -> bool { - match t { - 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, - } + any_term(t, &|x| matches!(x, Term::LetRec { .. })) } /// a `match` containing `(Cons a (Cons b _))` desugars to diff --git a/crates/ailang-core/src/workspace.rs b/crates/ailang-core/src/workspace.rs index 906eff0..4d22e51 100644 --- a/crates/ailang-core/src/workspace.rs +++ b/crates/ailang-core/src/workspace.rs @@ -1659,16 +1659,6 @@ mod tests { 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` // relocated to `crates/ailang-core/tests/workspace_pin.rs` in iter // 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 // 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) // loads cleanly and produces one registry entry from the // fixture itself. With the auto-loaded prelude (iter 23.2)