Skip to main content

rustyfi_lang/
visit.rs

1//! One generated, exhaustive traversal of the **type representation** —
2//! [`MonoType`](crate::types::MonoType), [`Row`](crate::types::Row) and
3//! [`CmdArgType`](crate::types::CmdArgType).
4//!
5//! # Why this exists
6//!
7//! The same reason `rustyfi-backend`'s `visit` module exists: a hand-written
8//! walk that forgets one container fails **silently**. This family had a live
9//! instance of exactly that, and it was not theoretical —
10//! [`CmdArgType::opt_labels`](crate::types::CmdArgType::opt_labels), the
11//! closed `?(l : τ, …)` map on a 0.1 command-argument slot, is a second
12//! `MonoType`-bearing field next to `ty`, and four of the family's walks
13//! recursed into `ty` alone:
14//!
15//! ```text
16//!     MonoType::InlineCmd(cs) | .. => cs.iter().any(|c| f(&c.ty))
17//!                                                      ^^^^^ and not c.opt_labels
18//! ```
19//!
20//! In `typecheck::synonym_refs` — which builds the graph
21//! `check_synonym_cycles` walks — that omission meant a synonym cycle routed
22//! through an optional-label slot was never seen, while `expand_synonyms`
23//! (whose termination that check is the *only* guarantee of, per its own doc
24//! comment) happily expanded into it. `type t = inline [?(l : t) string]`,
25//! once referenced, aborted the process with a stack overflow instead of
26//! reporting `cyclic type synonym`. `tests/synonym_cycle_opt_labels.rs` is
27//! that repro.
28//!
29//! # What it covers, and what it deliberately does not
30//!
31//! **A type variable is a LEAF.** Neither `TyVarRef` nor `RowVarRef` appears
32//! in any `#[subast]` list, so `MonoType::Var` / `Row::Var` are not descended
33//! into and a `TyVarLink::Bound(τ)` payload is never reached. Three separate
34//! reasons, and the first alone is decisive:
35//!
36//! 1. **It cannot be expressed.** A bound payload lives behind
37//!    `Rc<RefCell<..>>`; the `Ref<'_, TyVarLink>` guard cannot outlive the
38//!    frame that took it, so there is no `&MonoType` *inside the original
39//!    tree* to hand a visitor. [`crate::types::resolve`] returns a
40//!    [`Cow`](std::borrow::Cow) for precisely this reason, and its doc
41//!    comment records the measurement (the alternative — an owned clone —
42//!    was 87-89% of all type nodes cloned during typechecking). syan has no
43//!    view impl for `Rc` or `RefCell` in any case; a field of that shape is
44//!    a hard compile error, not a silent skip.
45//! 2. **Termination.** With `Var` a leaf the walk is structurally
46//!    well-founded. Following links is only sound because `bind` is
47//!    occurs-checked — a property of the *unifier*, not of the type.
48//! 3. **It matches the walks that use this.** The family splits cleanly in
49//!    two. *Resolving* walks (`unify`, `occurs_*`, `collect_generalizable`,
50//!    `substitute`, `fmt_mono`, `mono_mentions_stamp`, …) call `resolve` at
51//!    every level and see the post-unification tree. *Structural* walks
52//!    (`synonym_refs`, `xver_adapt::check_mono_type`) `match ty` directly and
53//!    see the tree as written. This traversal has structural semantics, so
54//!    only the second class may use it — which is fine, because the second
55//!    class is where the tree is a tree.
56//!
57//! A resolving walk that adopted this would silently change meaning. Do not.
58//!
59//! # Using it
60//!
61//! Every visited type gains inherent `visit` / `visit_mut` methods taking a
62//! closure (or a tuple of closures, one per node type). A visit is
63//! **pre-order and inclusive**: `t.visit(|x: &MonoType| ..)` sees `t` itself
64//! before its children, and descent is unconditional — a consumer that needs
65//! to prune must implement the generated `Visit` trait by hand.
66//!
67//! # The one trap
68//!
69//! `visitor!` follows a field only if the field's *peeled* head type is named
70//! in the owning type's `#[subast(..)]` list. A new `MonoType`-bearing field
71//! whose head the list does not mention is reclassified a leaf and its
72//! generated body is empty — **zero errors, zero warnings**. That is the same
73//! failure this module exists to prevent, one level up.
74//! `tests/type_visit_reachability.rs` is the missing check, done at runtime:
75//! it plants a uniquely identifiable marker type under every recursive field
76//! of every visited type and asserts each one is reached.
77
78syan::visit::visitor!(
79    crate::types::MonoType,
80    crate::types::Row,
81    crate::types::CmdArgType
82);