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:
@@ -43,6 +43,30 @@ mod reuse_shape;
|
||||
mod suppress_filter;
|
||||
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
|
||||
/// `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
|
||||
|
||||
@@ -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<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
|
||||
/// 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<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
|
||||
/// type, by walking the pattern alongside the ctor field types.
|
||||
/// Mirrors `pattern_has_consumed_heap_binder_at`'s descent: a
|
||||
|
||||
@@ -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<String> = 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<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.
|
||||
/// 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<String> = Vec::new();
|
||||
for b in &binders {
|
||||
if locals.insert(b.clone()) {
|
||||
|
||||
@@ -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<String, TypeDef>, diags: &mut Vec<Diagnos
|
||||
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
|
||||
/// 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<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
|
||||
/// rewrite drops the wrapper and keeps the body alone — the LLM can
|
||||
/// then either accept the normal allocator path or reshape the body.
|
||||
|
||||
@@ -58,7 +58,8 @@
|
||||
//! dec (uniform `drop_<m>_<T>` 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<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)]
|
||||
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 {
|
||||
|
||||
@@ -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_<owner>_<T>` 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 `<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(
|
||||
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,
|
||||
|
||||
@@ -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<String, Vec<CtorInfo>>,
|
||||
/// 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<usize>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
#[allow(dead_code)]
|
||||
struct CtorInfo {
|
||||
name: String,
|
||||
fields: Vec<String>, // 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<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
|
||||
// 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(),
|
||||
|
||||
@@ -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
|
||||
/// `<root_dir>/<module>.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<Term>,
|
||||
#[serde(default)]
|
||||
args: Vec<Term>,
|
||||
#[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<Term>,
|
||||
#[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::*;
|
||||
|
||||
@@ -384,7 +384,7 @@ fn collect_used_in_term(t: &Term, used: &mut BTreeSet<String>) {
|
||||
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<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.
|
||||
///
|
||||
/// `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
|
||||
/// `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<String>) {
|
||||
fn pattern_binds(p: &Pattern, out: &mut BTreeSet<String>) {
|
||||
match p {
|
||||
Pattern::Wild | Pattern::Lit { .. } => {}
|
||||
Pattern::Var { name } => {
|
||||
@@ -1949,85 +1929,58 @@ pub fn find_non_callee_use(t: &Term, name: &str) -> Option<Term> {
|
||||
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
|
||||
|
||||
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user