Skip to main content

rustyfi_lang/
types.rs

1//! The type language: base types, the mutable (union-find) representation
2//! of type/row variables, monomorphic and polymorphic types, and
3//! level-based generalization. Mirrors `mono_type_main` / `poly_type` /
4//! `kind` in v0.0.6's `src/frontend/types.cppo.ml`, with two deliberate
5//! departures documented at their definitions:
6//!
7//! 1. **Generalization is level-based (Rémy levels)**, not v0.0.6's
8//!    `quantifiability` flag. See [`TypeContext`], [`generalize`] and
9//!    [`instantiate`].
10//! 2. **Extensible records are a first-class row type** (`Row::Empty` /
11//!    `Row::Var` / `Row::Cons`), not v0.0.6's closed `RecordType` plus a
12//!    plain type variable carrying a `RecordKind` label-subset constraint.
13//!    See [`Row`].
14
15use std::borrow::Cow;
16use std::cell::RefCell;
17use std::collections::{BTreeSet, HashMap};
18use std::fmt;
19use std::rc::Rc;
20use std::sync::atomic::{AtomicU64, Ordering};
21
22// ============================================================================
23// Base types
24// ============================================================================
25
26/// Primitive types with no internal structure — the subset of v0.0.6's
27/// `base_type` (`types.cppo.ml:255`) that this port's primitives
28/// need. (`EnvType`/`RegExpType`/`InputPosType` are unused and left out;
29/// add them when a primitive needs them.)
30#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
31pub enum BaseType {
32    Unit,
33    Bool,
34    Int,
35    Float,
36    Length,
37    String,
38    /// `inline-text` (v0.0.6: `TextRowType`).
39    InlineText,
40    /// `block-text` (v0.0.6: `TextColType`).
41    BlockText,
42    /// `math` (v0.0.6: `MathType`) — quoted math text. Reused, unmodified,
43    /// as V0_1's `math-text` (upstream literally renamed 0.0.6's `math`);
44    /// see `MathBoxes` for the new V0_1-only half of the split.
45    MathText,
46    /// `math-boxes` (V0_1 only; `dev-0-1-0` `MathBoxesType`) — the
47    /// evaluated math tree, bridged from `MathText` by the V0_1 primitive
48    /// `read-math`. 0.0.6 has no name for this type (its `math` conflates
49    /// both halves) and no value ever types as this under V0_0.
50    MathBoxes,
51    /// `image` (v0.0.6: `ImageType`) — a decoded raster image resource
52    /// (`load-image`'s result).
53    Image,
54    /// `inline-boxes` (v0.0.6: `BoxRowType`).
55    InlineBoxes,
56    /// `block-boxes` (v0.0.6: `BoxColType`).
57    BlockBoxes,
58    Context,
59    Document,
60    /// `pre-path` (v0.0.6: `PrePathType`).
61    PrePath,
62    /// `path` (v0.0.6: `PathType`).
63    Path,
64    /// `graphics` (v0.0.6: `GraphicsType`).
65    Graphics,
66    /// `font` (**V0_1 only**; upstream `saphe-split`
67    /// `types.cppo.ml`'s `FontType`, registered in that generation's
68    /// `base_type_hash_table` as `("font", FontType)` and spelled `tFONTKEY`
69    /// in `primitives.cppo.ml:45`) — an OPAQUE handle on one loaded face.
70    /// Its value is [`Value::Font`](crate::value::Value::Font), a
71    /// `rustyfi_backend::FontKey` index into the metrics provider's font
72    /// store, matching upstream's `BCFontKey of FontKey.t`.
73    ///
74    /// **0.0.6 has no such type at all** — verified against upstream
75    /// `v0.0.6 src/frontend/types.cppo.ml:280-303`, whose
76    /// `base_type_hash_table` has no `"font"` row, and against
77    /// `lib-satysfi/dist/packages/*.satyh`, which declare no `type font`
78    /// either. What 0.0.6 calls "a font" is the bare product
79    /// `string * float * float` (`primitives.cppo.ml:69`'s `tFONT = tPROD
80    /// [tS; tFL; tFL]`), whose head is an ABBREV naming a row of
81    /// `dist/hash/fonts.satysfi-hash`. So under `V0_0` the NAME `font`
82    /// falls through `name_to_mono` to the opaque user-nominal
83    /// `Variant("font", [])` — an unrelated type that happens to share a
84    /// spelling. That disagreement is exactly what keeps `font` inside
85    /// `typecheck::forked_type_names()`, and it is a REPRESENTATION fork,
86    /// not a missing feature: see `v1::xver_adapt::forked_note`.
87    Font,
88    /// `text-info` (v0.0.6: `TextInfoType`) — the text-mode context
89    /// (`deepen-indent`/`get-initial-text-info`/`break`; sliver — see
90    /// primitives.rs for the scoping note: the text/html backends
91    /// themselves are out of scope).
92    TextInfo,
93}
94
95impl BaseType {
96    /// The SATySFi surface-syntax name, used by `Display`.
97    pub fn name(self) -> &'static str {
98        match self {
99            BaseType::Unit => "unit",
100            BaseType::Bool => "bool",
101            BaseType::Int => "int",
102            BaseType::Float => "float",
103            BaseType::Length => "length",
104            BaseType::String => "string",
105            BaseType::InlineText => "inline-text",
106            BaseType::BlockText => "block-text",
107            BaseType::MathText => "math",
108            BaseType::MathBoxes => "math-boxes",
109            BaseType::Image => "image",
110            BaseType::InlineBoxes => "inline-boxes",
111            BaseType::BlockBoxes => "block-boxes",
112            BaseType::Context => "context",
113            BaseType::Document => "document",
114            BaseType::PrePath => "pre-path",
115            BaseType::Path => "path",
116            BaseType::Graphics => "graphics",
117            BaseType::Font => "font",
118            BaseType::TextInfo => "text-info",
119        }
120    }
121}
122
123impl fmt::Display for BaseType {
124    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
125        f.write_str(self.name())
126    }
127}
128
129// ============================================================================
130// A process-wide id source for variables minted where no `TypeContext` is
131// available (see the doc comment on `instantiate` for why that happens).
132// ============================================================================
133
134/// `TypeContext` hands out ids from its own small counter for ordinary
135/// inference-time freshness. `instantiate` and `unify`'s row extension
136/// have fixed signatures carrying no `TypeContext`, yet still need to mint
137/// fresh variables, so they draw ids from this separate, process-wide
138/// counter instead.
139///
140/// This is purely cosmetic: variable *identity* is always pointer equality
141/// (`TyVarRef::same`/`RowVarRef::same`), never id equality, so the two
142/// counters can never collide in any way that affects correctness — at
143/// worst two unrelated variables print with the same debug id. Seeded far
144/// from `TypeContext`'s own counter to make that overlap unlikely in small
145/// examples.
146static FRESH_ID: AtomicU64 = AtomicU64::new(1 << 32);
147
148fn fresh_id() -> u64 {
149    FRESH_ID.fetch_add(1, Ordering::Relaxed)
150}
151
152// ============================================================================
153// Kind (mirrors v0.0.6's `mono_kind` / `FreeID_.kind`, types.cppo.ml:330-333)
154// ============================================================================
155
156/// The kind of a free type variable.
157///
158/// `Record(labels)` mirrors v0.0.6's `RecordKind`: it constrains a variable
159/// not yet known to be anything in particular, but which field access
160/// (`e#lbl`) has already shown must resolve to *some* record type
161/// containing (at least) `labels`. Unlike v0.0.6, which pairs each
162/// required label with its field type directly in the kind, this port
163/// stores only the label *names* here — the field types are tracked by
164/// the [`Row`] the variable eventually binds to (`unify::bind_var`'s
165/// `Kind::Record` branch). This loses nothing because a concrete record's
166/// structure here is *always* a first-class `Row`; v0.0.6 needed field
167/// types in the kind because its closed `RecordType` has no notion of
168/// "the type of label `l`" apart from the whole association list.
169#[derive(Clone, Debug, PartialEq, Eq)]
170pub enum Kind {
171    Universal,
172    Record(BTreeSet<String>),
173}
174
175// ============================================================================
176// Type variables (mirrors v0.0.6's `FreeID_`/`mono_type_variable_info`,
177// types.cppo.ml:121-191, 347-349)
178// ============================================================================
179
180/// The mutable union-find cell behind a type variable.
181#[derive(Debug)]
182enum TyVarLink {
183    Free {
184        id: u64,
185        level: u32,
186        kind: Kind,
187    },
188    /// This variable has been unified with a concrete type; `resolve`
189    /// chases through this exactly like v0.0.6's `MonoLink`.
190    Bound(MonoType),
191}
192
193/// A reference-counted handle to a type variable's union-find cell. Cloning
194/// a `TyVarRef` shares the same cell (this is the union-find "pointer");
195/// identity (not structure) is what `unify` and `generalize` compare.
196#[derive(Clone, Debug)]
197pub struct TyVarRef(Rc<RefCell<TyVarLink>>);
198
199impl TyVarRef {
200    pub(crate) fn new(id: u64, level: u32, kind: Kind) -> Self {
201        TyVarRef(Rc::new(RefCell::new(TyVarLink::Free { id, level, kind })))
202    }
203
204    /// Identity comparison — the only correct notion of "same variable"
205    /// once links can be mutated in place.
206    pub fn same(&self, other: &TyVarRef) -> bool {
207        Rc::ptr_eq(&self.0, &other.0)
208    }
209
210    pub(crate) fn ptr_key(&self) -> usize {
211        Rc::as_ptr(&self.0) as usize
212    }
213
214    /// `None` if this variable has already been bound.
215    pub fn id(&self) -> Option<u64> {
216        match &*self.0.borrow() {
217            TyVarLink::Free { id, .. } => Some(*id),
218            TyVarLink::Bound(_) => None,
219        }
220    }
221
222    pub fn level(&self) -> Option<u32> {
223        match &*self.0.borrow() {
224            TyVarLink::Free { level, .. } => Some(*level),
225            TyVarLink::Bound(_) => None,
226        }
227    }
228
229    /// No-op if this variable has already been bound.
230    pub fn set_level(&self, new_level: u32) {
231        if let TyVarLink::Free { level, .. } = &mut *self.0.borrow_mut() {
232            *level = new_level;
233        }
234    }
235
236    /// `Kind::Universal` if this variable has already been bound (asking a
237    /// bound variable for its kind is meaningless; callers should `resolve`
238    /// first).
239    pub fn kind(&self) -> Kind {
240        match &*self.0.borrow() {
241            TyVarLink::Free { kind, .. } => kind.clone(),
242            TyVarLink::Bound(_) => Kind::Universal,
243        }
244    }
245
246    pub fn set_kind(&self, new_kind: Kind) {
247        if let TyVarLink::Free { kind, .. } = &mut *self.0.borrow_mut() {
248            *kind = new_kind;
249        }
250    }
251
252    /// Link this variable to a concrete type. Callers (`unify::bind_var`)
253    /// are responsible for the occurs check; this just performs the store.
254    pub fn bind(&self, ty: MonoType) {
255        *self.0.borrow_mut() = TyVarLink::Bound(ty);
256    }
257
258    pub fn is_free(&self) -> bool {
259        matches!(&*self.0.borrow(), TyVarLink::Free { .. })
260    }
261}
262
263impl PartialEq for TyVarRef {
264    fn eq(&self, other: &Self) -> bool {
265        self.same(other)
266    }
267}
268impl Eq for TyVarRef {}
269
270pub(crate) fn new_ty_var(level: u32) -> TyVarRef {
271    TyVarRef::new(fresh_id(), level, Kind::Universal)
272}
273
274// ============================================================================
275// Row variables — the tail of an extensible record row. Structurally a
276// mirror of `TyVarRef`/`TyVarLink`, except its "kind" is the set of labels
277// already known to appear in whatever row it resolves to (no `Universal`
278// case: an empty set already means "no labels required yet").
279// ============================================================================
280
281#[derive(Debug)]
282enum RowVarLink {
283    Free {
284        id: u64,
285        level: u32,
286        kind: BTreeSet<String>,
287    },
288    Bound(Row),
289}
290
291#[derive(Clone, Debug)]
292pub struct RowVarRef(Rc<RefCell<RowVarLink>>);
293
294impl RowVarRef {
295    pub(crate) fn new(id: u64, level: u32, kind: BTreeSet<String>) -> Self {
296        RowVarRef(Rc::new(RefCell::new(RowVarLink::Free { id, level, kind })))
297    }
298
299    pub fn same(&self, other: &RowVarRef) -> bool {
300        Rc::ptr_eq(&self.0, &other.0)
301    }
302
303    pub(crate) fn ptr_key(&self) -> usize {
304        Rc::as_ptr(&self.0) as usize
305    }
306
307    pub fn id(&self) -> Option<u64> {
308        match &*self.0.borrow() {
309            RowVarLink::Free { id, .. } => Some(*id),
310            RowVarLink::Bound(_) => None,
311        }
312    }
313
314    pub fn level(&self) -> Option<u32> {
315        match &*self.0.borrow() {
316            RowVarLink::Free { level, .. } => Some(*level),
317            RowVarLink::Bound(_) => None,
318        }
319    }
320
321    pub fn set_level(&self, new_level: u32) {
322        if let RowVarLink::Free { level, .. } = &mut *self.0.borrow_mut() {
323            *level = new_level;
324        }
325    }
326
327    pub fn kind(&self) -> BTreeSet<String> {
328        match &*self.0.borrow() {
329            RowVarLink::Free { kind, .. } => kind.clone(),
330            RowVarLink::Bound(_) => BTreeSet::new(),
331        }
332    }
333
334    pub fn set_kind(&self, new_kind: BTreeSet<String>) {
335        if let RowVarLink::Free { kind, .. } = &mut *self.0.borrow_mut() {
336            *kind = new_kind;
337        }
338    }
339
340    pub fn bind(&self, row: Row) {
341        *self.0.borrow_mut() = RowVarLink::Bound(row);
342    }
343
344    pub fn is_free(&self) -> bool {
345        matches!(&*self.0.borrow(), RowVarLink::Free { .. })
346    }
347}
348
349impl PartialEq for RowVarRef {
350    fn eq(&self, other: &Self) -> bool {
351        self.same(other)
352    }
353}
354impl Eq for RowVarRef {}
355
356pub(crate) fn new_row_var(level: u32) -> RowVarRef {
357    RowVarRef::new(fresh_id(), level, BTreeSet::new())
358}
359
360// ============================================================================
361// Monomorphic types
362// ============================================================================
363
364/// Which stage an expression is being read at (upstream's `stage`,
365/// `types.cppo.ml:400-403`).
366///
367/// SATySFi is a two-stage language: a document is typeset at **stage 1**, and
368/// **stage 0** is the earlier stage that can compute *code* to be run at stage
369/// 1. `&e` quotes (stage 0 -> a `code` value), `~e` splices (stage 1 -> runs
370/// `e` at stage 0 and drops its code in). **Persistent** bindings are the only
371/// ones nameable from both.
372///
373/// A 0.0.6 file declares its stage in its `@stage:` header and every binding in
374/// it takes that stage; a document is always stage 1.
375#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
376pub enum Stage {
377    /// `@stage: persistent` — usable from either stage.
378    Persistent0,
379    /// `@stage: 0` — the program stage, run before typesetting.
380    Stage0,
381    /// `@stage: 1` — the document stage, and what a document must be.
382    #[default]
383    Stage1,
384}
385
386impl Stage {
387    pub fn as_str(self) -> &'static str {
388        match self {
389            Stage::Persistent0 => "persistent stage",
390            Stage::Stage0 => "stage 0",
391            Stage::Stage1 => "stage 1",
392        }
393    }
394
395    /// Parse a `@stage:` header's value.
396    pub fn parse(s: &str) -> Option<Stage> {
397        match s.trim() {
398            "persistent" => Some(Stage::Persistent0),
399            "0" => Some(Stage::Stage0),
400            "1" => Some(Stage::Stage1),
401            _ => None,
402        }
403    }
404
405    /// May an expression being read at `self` name a binding introduced at
406    /// `bound`? The whole staging discipline for *occurrences*, as opposed to
407    /// the `&`/`~` operator rules — upstream's `UTContentOf` arm, whose
408    /// accepting cases are written out one by one and whose `_` fallthrough
409    /// raises `InvalidOccurrenceAsToStaging`:
410    ///
411    /// | use \ bind | `persistent` | `0` | `1` |
412    /// |------------|--------------|-----|-----|
413    /// | `persistent` | yes        | NO  | NO  |
414    /// | `0`          | yes        | yes | NO  |
415    /// | `1`          | yes (lift) | NO  | yes |
416    ///
417    /// So: a `persistent` binding is nameable from everywhere, every other
418    /// binding only from its own stage. The two generations agree on the
419    /// accept/reject split and differ only in which *node* an accepted
420    /// persistent occurrence compiles to — 0.0.6 emits `Persistent(rng, evid)`
421    /// for all three uses (`typechecker.ml:667-681`), `dev-0-1-0` only for the
422    /// `Stage1` use (`typechecker.ml:340-353`), against `ContentOf(rng, evid)`
423    /// everywhere else.
424    ///
425    /// **What that node does upstream, and why this port needs no counterpart.**
426    /// It is capture-avoidance bookkeeping for a FIRST-ORDER code value, not a
427    /// semantic distinction:
428    ///
429    /// * upstream's stage-1 pass (`interpret_1`, `evaluator.cppo.ml:429` in
430    ///   0.0.6 / `:609` on `dev-0-1-0`) does not evaluate — it BUILDS a
431    ///   `code_value`, minting a fresh `CodeSymbol` for every binder it walks
432    ///   under and resolving an ordinary `ContentOf` through `find_symbol`;
433    /// * a persistent binding is not one of those binders: it was already
434    ///   evaluated at stage 0 (`interpret_bindings_0`'s `Persistent0 | Stage0`
435    ///   arm, `dev-0-1-0 evaluator.cppo.ml:1177-1195`) and lives in the VALUE
436    ///   environment, so `find_symbol` would miss it and upstream would
437    ///   `report_bug_ast "symbol not found"`;
438    /// * hence `CdPersistent(rng, evid)`, carried through verbatim and mapped
439    ///   straight back to `ContentOf(rng, evid)` by `unlift_code`
440    ///   (`types.cppo.ml:1506` in 0.0.6, `:1340` on `dev-0-1-0`) — an ordinary
441    ///   environment lookup, by the SAME `EvalVarID` the typechecker resolved,
442    ///   once the generated code finally runs. (`bytecomp` does not implement
443    ///   it at all on `dev-0-1-0`: `ir.cppo.ml:565` is `failwith "TODO"`.)
444    ///
445    /// This port has no such pass and no renaming: a quote is a CLOSURE
446    /// (`compile.rs`'s `Ast::Next` — the compiled body paired with the
447    /// environment reaching it), and every free name in it was already resolved
448    /// against the scope the quote was WRITTEN in, a top-level binding to its
449    /// own `Globals` slot. That slot is this port's `EvalVarID`: it fixes the
450    /// reference to the BINDING rather than the name, which is the one property
451    /// `CdPersistent` exists to preserve. So the verdict below is the whole of
452    /// what is needed — pinned end to end, as values, by `tests/staging.rs`'s
453    /// "`(Stage1, Persistent0)` cell, as a VALUE" block and its 0.1 twins in
454    /// `tests/staging_v1.rs`.
455    pub fn can_reference(self, bound: Stage) -> bool {
456        matches!(
457            (self, bound),
458            (_, Stage::Persistent0) | (Stage::Stage0, Stage::Stage0) | (Stage::Stage1, Stage::Stage1)
459        )
460    }
461}
462
463/// A monomorphic type. Mirrors v0.0.6's `mono_type` (the `type_main`
464/// variant instantiated at `mono_type_variable_info ref`), minus
465/// `SynonymType` (no type synonyms in this port) and with `Row`-based
466/// records instead of a closed `RecordType` (see [`Row`]).
467#[derive(Clone, Debug)]
468pub enum MonoType {
469    Var(TyVarRef),
470    Base(BaseType),
471    /// `?(row) dom -> cod` — a function type carrying a labeled
472    /// optional-argument [`Row`] (upstream `FuncType of row * typ * typ`,
473    /// SATySFi 0.1). The row is `Row::Empty` for every 0.0.6-constructed
474    /// function ([`crate::prim_types::arrow`]), printing nothing and
475    /// unifying trivially, so 0.0.6 behavior is byte-identical. A
476    /// non-empty row (`Cons(label, option-payload-type, …)`) records the
477    /// value-level `?(l = e)` labeled optional arguments the function
478    /// accepts. The field is **positional** (no `..` in any destructure)
479    /// deliberately: widening this variant makes the compiler flag every
480    /// match site, guarding against a silently-dropped row in the
481    /// sealed-module subsumption path.
482    ///
483    /// The row is **boxed** (`Box<Row>`, not inline) so widening `Func`
484    /// does not enlarge `MonoType` itself: `Row` is a ~40-byte enum, and
485    /// inlining it would make `Func` the largest variant, growing every
486    /// stack frame holding a `MonoType` by value enough to tip a deep
487    /// recursive typecheck over the default stack. `Box<Row>` keeps
488    /// `MonoType` at its pre-widening size, so 0.0.6 stack usage is
489    /// unchanged.
490    Func(Box<Row>, Box<MonoType>, Box<MonoType>),
491    /// A tuple type, always with at least two elements.
492    Product(Vec<MonoType>),
493    List(Box<MonoType>),
494    Ref(Box<MonoType>),
495    Record(Row),
496    /// A user-defined variant type applied to its arguments, e.g.
497    /// `Variant("option", [int])` for `int option`. Identified by name
498    /// rather than by a fresh `TypeID.t` as in v0.0.6 (`types.cppo.ml:318`)
499    /// — this port has no notion of shadowing/re-declaring a variant
500    /// type under the same name within one compilation, so a `String` is
501    /// a simpler stand-in for v0.0.6's globally-fresh `TypeID.t`.
502    Variant(String, Vec<MonoType>),
503    /// `code ty` — the type of a quoted (`&e`) fragment awaiting the next
504    /// stage. Upstream's `CodeType` (`types.cppo.ml:324`). Structurally it
505    /// behaves exactly like [`MonoType::Ref`]: one covariant argument,
506    /// unified pointwise.
507    Code(Box<MonoType>),
508    /// `[...] inline-cmd` (v0.0.6: `HorzCommandType`).
509    InlineCmd(Vec<CmdArgType>),
510    /// `[...] block-cmd` (v0.0.6: `VertCommandType`).
511    BlockCmd(Vec<CmdArgType>),
512    /// `[...] math-cmd` (v0.0.6: `MathCommandType`).
513    MathCmd(Vec<CmdArgType>),
514}
515
516/// One command argument type: `ty` for a mandatory argument, or `ty?` for
517/// an optional one (v0.0.6: `MandatoryArgumentType` / `OptionalArgumentType`,
518/// types.cppo.ml:326-328). `optional`/`opt_labels` are version-discriminated
519/// by construction: under `V0_0`
520/// (positional model) `optional` marks a whole-slot `ty?` optional and
521/// `opt_labels` is always empty; under `V0_1` (labeled model, upstream
522/// `CommandArgType of typ LabelMap.t * typ`, `types.cppo.ml:214`) `optional`
523/// is always `false` and `opt_labels` carries this slot's `?(l:τ,…)` bundle —
524/// a CLOSED map (no row variable: upstream discards one if written,
525/// `parser.mly:866`'s `TODO (error)`). Kept **sorted by label** at every
526/// producer (`command_scheme`'s harvest, `lower_type_atom`'s sig lowering) so
527/// `unify`/`Display`/sealing are order-insensitive — see `unify_cmd_args`'s
528/// zip-equal equal-domain test.
529#[derive(Clone, Debug)]
530pub struct CmdArgType {
531    pub optional: bool,
532    pub opt_labels: Vec<(String, MonoType)>,
533    pub ty: MonoType,
534}
535
536/// An extensible record row: a sequence of `label : type` bindings ending
537/// either in `Empty` (a *closed* record — exactly these labels and no
538/// others) or in `Var` (an *open* record — at least these labels, plus
539/// whatever the row variable's eventual binding adds).
540///
541/// **Deviation from v0.0.6**: its `RecordType` (types.cppo.ml:319) is
542/// always closed; the only record polymorphism is indirect, via a plain
543/// type variable carrying a `RecordKind` (a label-typed lower bound) that
544/// unifies against a closed `RecordType` when the kind's labels are a
545/// subset of the record's (typechecker.ml:480-500,
546/// `Assoc.domain_included`) — which cannot express an open record type
547/// standing on its own (only a variable can be "open"). Giving rows their
548/// own recursive type former (`Row::Cons`/`Var`/`Empty`, Rémy-style row
549/// polymorphism) is strictly more general and lets `unify` do genuine
550/// label-subsumption with a *remainder* row variable
551/// (`unify::row_extract`). `Kind::Record` is kept for the one case v0.0.6
552/// also has it for: a variable not yet known to be a record at all.
553#[derive(Clone, Debug)]
554pub enum Row {
555    Empty,
556    Var(RowVarRef),
557    Cons(String, Box<MonoType>, Box<Row>),
558}
559
560// ============================================================================
561// resolve / shallow_follow — chase `Bound` links, union-find "find".
562// ============================================================================
563
564/// Follow `Var(_)` → `Bound(ty)` links until reaching either a free
565/// variable or a non-variable type. Does **not** recurse into the
566/// structure of compound types (that's what makes it "shallow": a `Func`
567/// whose domain is itself a bound variable is returned as-is, domain still
568/// unresolved) — callers that need a fully dereferenced tree should
569/// `resolve` again at each level as they recurse, which is exactly what
570/// `unify` and `Display` do.
571///
572/// # Why `Cow`
573///
574/// This is the hottest function in the typechecker — ~316k calls on a corpus
575/// document, and its cost is dominated by COPYING types, not by following
576/// links. Do NOT make it return an owned `MonoType`: that makes the common
577/// case (the argument is already resolved — not a variable, or a free one)
578/// end in `ty.clone()`, a full deep copy produced solely to hand back an owned
579/// value the caller then only inspects. Measured, that pointless tail copy was
580/// **87-89% of all type nodes cloned during typechecking**, and typecheck time
581/// tracks cloned-node volume near-linearly across the whole corpus.
582///
583/// So the common case borrows. Only the link-following path allocates,
584/// and only because the `Bound` payload lives behind a `RefCell` whose guard
585/// cannot outlive this frame. Callers that just match on the result want
586/// `&*resolve(..)`; the few that keep it want `.into_owned()`.
587pub fn resolve(ty: &MonoType) -> Cow<'_, MonoType> {
588    if let MonoType::Var(v) = ty {
589        let next = match &*v.0.borrow() {
590            TyVarLink::Bound(inner) => Some(inner.clone()),
591            TyVarLink::Free { .. } => None,
592        };
593        if let Some(inner) = next {
594            return Cow::Owned(resolve(&inner).into_owned());
595        }
596    }
597    Cow::Borrowed(ty)
598}
599
600/// The row analogue of [`resolve`], `Cow` for the same reason.
601pub fn resolve_row(row: &Row) -> Cow<'_, Row> {
602    if let Row::Var(v) = row {
603        let next = match &*v.0.borrow() {
604            RowVarLink::Bound(inner) => Some(inner.clone()),
605            RowVarLink::Free { .. } => None,
606        };
607        if let Some(inner) = next {
608            return Cow::Owned(resolve_row(&inner).into_owned());
609        }
610    }
611    Cow::Borrowed(row)
612}
613
614// ============================================================================
615// Polymorphic types and level-based generalization
616// ============================================================================
617
618/// A type scheme: a monomorphic body plus the set of that body's free
619/// variables which are quantified over it.
620///
621/// **Deviation from v0.0.6**: v0.0.6 (types.cppo.ml:351-364) converts a
622/// generalized variable's `MonoFree` cell into a `PolyBound` id, so the
623/// same physical type reads differently as "a mono type" vs "a poly type",
624/// and instantiating rebuilds `PolyBound` occurrences into fresh
625/// `MonoFree` cells. This port instead keeps quantified variables as
626/// ordinary (still-`Free`) `TyVarRef`/`RowVarRef` cells and just remembers
627/// which ones they are (`vars`/`row_vars` below); `instantiate` deep-copies
628/// `body`, replacing each remembered variable (by pointer identity) with a
629/// fresh one and leaving everything else shared untouched. This is the
630/// standard "generalization via levels" technique, and it replaces
631/// v0.0.6's `quantifiability` flag (`Quantifiable`/`Unquantifiable`,
632/// types.cppo.ml:54) — which guards against generalizing a variable
633/// unification already linked outside the current let binding — with a
634/// consequence of levels instead: a variable unification touches from an
635/// outer scope gets its level lowered (`unify::occurs_var`/
636/// `occurs_var_in_row`), so by the time `generalize` runs it no longer
637/// looks "deep enough" to quantify.
638#[derive(Clone, Debug)]
639pub struct PolyType {
640    vars: Vec<TyVarRef>,
641    row_vars: Vec<RowVarRef>,
642    body: MonoType,
643}
644
645impl PolyType {
646    /// A trivial scheme with no quantified variables at all.
647    pub fn mono(ty: MonoType) -> PolyType {
648        PolyType {
649            vars: Vec::new(),
650            row_vars: Vec::new(),
651            body: ty,
652        }
653    }
654
655    /// Build a scheme by hand, explicitly naming which variables (which
656    /// must occur free in `body`) are quantified. Used by `prim_types`,
657    /// which constructs polymorphic primitive signatures (`::`, `!`)
658    /// directly rather than via `generalize` (there is no enclosing
659    /// inference level to generalize *from* at primitive-table
660    /// construction time).
661    pub(crate) fn from_vars(
662        vars: Vec<TyVarRef>,
663        row_vars: Vec<RowVarRef>,
664        body: MonoType,
665    ) -> PolyType {
666        PolyType {
667            vars,
668            row_vars,
669            body,
670        }
671    }
672
673    /// The scheme's body, before instantiation — exposed for inspection
674    /// (e.g. arity-checking) without needing to mint fresh variables.
675    pub fn body(&self) -> &MonoType {
676        &self.body
677    }
678
679    pub fn is_monomorphic(&self) -> bool {
680        self.vars.is_empty() && self.row_vars.is_empty()
681    }
682}
683
684impl fmt::Display for PolyType {
685    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
686        fmt::Display::fmt(&self.body, f)
687    }
688}
689
690/// Per-inference-run state: the level stack for generalization, and a
691/// counter for fresh variable ids (see `FRESH_ID`'s doc comment for why
692/// `instantiate`/`unify` use a *different* counter than this one — the two
693/// never need to agree, since identity is always by pointer).
694pub struct TypeContext {
695    next_id: u64,
696    level: u32,
697}
698
699impl TypeContext {
700    pub fn new() -> Self {
701        TypeContext {
702            next_id: 0,
703            level: 0,
704        }
705    }
706
707    fn next_id(&mut self) -> u64 {
708        let id = self.next_id;
709        self.next_id += 1;
710        id
711    }
712
713    pub fn level(&self) -> u32 {
714        self.level
715    }
716
717    /// Enter a new `let`-nesting level. Call before inferring the
718    /// right-hand side of a `let`.
719    pub fn enter_level(&mut self) {
720        self.level += 1;
721    }
722
723    /// Leave the current level. Call after inferring the right-hand side
724    /// of a `let`, before calling `generalize`.
725    pub fn leave_level(&mut self) {
726        self.level -= 1;
727    }
728
729    pub fn fresh_var(&mut self) -> TyVarRef {
730        self.fresh_var_with_kind(Kind::Universal)
731    }
732
733    pub fn fresh_var_with_kind(&mut self, kind: Kind) -> TyVarRef {
734        TyVarRef::new(self.next_id(), self.level, kind)
735    }
736
737    pub fn fresh_row_var(&mut self) -> RowVarRef {
738        self.fresh_row_var_with_kind(BTreeSet::new())
739    }
740
741    pub fn fresh_row_var_with_kind(&mut self, kind: BTreeSet<String>) -> RowVarRef {
742        RowVarRef::new(self.next_id(), self.level, kind)
743    }
744}
745
746impl Default for TypeContext {
747    fn default() -> Self {
748        Self::new()
749    }
750}
751
752/// Quantify every free variable in `ty` whose level is deeper than `level`
753/// (i.e. was created after entering the let binding being generalized).
754/// Typical usage:
755///
756/// ```ignore
757/// ctx.enter_level();
758/// let ty = infer(ctx, rhs)?;
759/// ctx.leave_level();
760/// let scheme = generalize(ctx.level(), &ty);
761/// ```
762pub fn generalize(level: u32, ty: &MonoType) -> PolyType {
763    let mut vars = Vec::new();
764    let mut row_vars = Vec::new();
765    collect_generalizable(level, ty, &mut vars, &mut row_vars);
766    PolyType {
767        vars,
768        row_vars,
769        body: ty.clone(),
770    }
771}
772
773fn collect_generalizable(
774    level: u32,
775    ty: &MonoType,
776    vars: &mut Vec<TyVarRef>,
777    row_vars: &mut Vec<RowVarRef>,
778) {
779    match &*resolve(ty) {
780        MonoType::Var(v) => {
781            if let Some(lv) = v.level() {
782                if lv > level && !vars.iter().any(|x| x.same(v)) {
783                    vars.push(v.clone());
784                }
785            }
786        }
787        MonoType::Base(_) => {}
788        MonoType::Func(row, a, b) => {
789            collect_generalizable_row(level, &row, vars, row_vars);
790            collect_generalizable(level, &a, vars, row_vars);
791            collect_generalizable(level, &b, vars, row_vars);
792        }
793        MonoType::Product(ts) => {
794            for t in ts {
795                collect_generalizable(level, t, vars, row_vars);
796            }
797        }
798        MonoType::List(t) | MonoType::Ref(t) | MonoType::Code(t) => {
799            collect_generalizable(level, &t, vars, row_vars)
800        }
801        MonoType::Record(row) => collect_generalizable_row(level, &row, vars, row_vars),
802        MonoType::Variant(_, args) => {
803            for t in args {
804                collect_generalizable(level, t, vars, row_vars);
805            }
806        }
807        MonoType::InlineCmd(cs) | MonoType::BlockCmd(cs) | MonoType::MathCmd(cs) => {
808            for c in cs {
809                collect_generalizable(level, &c.ty, vars, row_vars);
810                for (_, lty) in &c.opt_labels {
811                    collect_generalizable(level, lty, vars, row_vars);
812                }
813            }
814        }
815    }
816}
817
818fn collect_generalizable_row(
819    level: u32,
820    row: &Row,
821    vars: &mut Vec<TyVarRef>,
822    row_vars: &mut Vec<RowVarRef>,
823) {
824    match &*resolve_row(row) {
825        Row::Empty => {}
826        Row::Var(v) => {
827            if let Some(lv) = v.level() {
828                if lv > level && !row_vars.iter().any(|x| x.same(v)) {
829                    row_vars.push(v.clone());
830                }
831            }
832        }
833        Row::Cons(_, t, rest) => {
834            collect_generalizable(level, &t, vars, row_vars);
835            collect_generalizable_row(level, &rest, vars, row_vars);
836        }
837    }
838}
839
840/// Instantiate a scheme: replace every quantified variable with a fresh
841/// one at `level`, leaving everything else in the body shared as-is.
842///
843/// This takes no `&mut TypeContext` (per this module's contract) — see
844/// `FRESH_ID`'s doc comment for how it still mints fresh, correctly
845/// leveled variables.
846pub fn instantiate(poly: &PolyType, level: u32) -> MonoType {
847    let mut var_map: HashMap<usize, MonoType> = HashMap::new();
848    for v in &poly.vars {
849        let fresh = TyVarRef::new(fresh_id(), level, v.kind());
850        var_map.insert(v.ptr_key(), MonoType::Var(fresh));
851    }
852    let mut row_map: HashMap<usize, Row> = HashMap::new();
853    for v in &poly.row_vars {
854        let fresh = RowVarRef::new(fresh_id(), level, v.kind());
855        row_map.insert(v.ptr_key(), Row::Var(fresh));
856    }
857    substitute(&poly.body, &var_map, &row_map)
858}
859
860/// Deep-copy `ty`, replacing any (resolved) variable found in `var_map`/
861/// `row_map` by pointer identity with its mapped replacement, and cloning
862/// everything else structurally. Shared by `instantiate` (mapping
863/// quantified variables to fresh ones) and by `prim_types::VariantDecl`
864/// (mapping a declaration's parameter placeholders to the concrete
865/// arguments of one particular constructor application).
866pub(crate) fn substitute(
867    ty: &MonoType,
868    var_map: &HashMap<usize, MonoType>,
869    row_map: &HashMap<usize, Row>,
870) -> MonoType {
871    match &*resolve(ty) {
872        MonoType::Var(v) => var_map
873            .get(&v.ptr_key())
874            .cloned()
875            .unwrap_or_else(|| MonoType::Var(v.clone())),
876        MonoType::Base(b) => MonoType::Base(*b),
877        MonoType::Func(row, a, b) => MonoType::Func(
878            Box::new(substitute_row(&row, var_map, row_map)),
879            Box::new(substitute(&a, var_map, row_map)),
880            Box::new(substitute(&b, var_map, row_map)),
881        ),
882        MonoType::Product(ts) => {
883            MonoType::Product(ts.iter().map(|t| substitute(t, var_map, row_map)).collect())
884        }
885        MonoType::List(t) => MonoType::List(Box::new(substitute(&t, var_map, row_map))),
886        MonoType::Ref(t) => MonoType::Ref(Box::new(substitute(&t, var_map, row_map))),
887        MonoType::Code(t) => MonoType::Code(Box::new(substitute(&t, var_map, row_map))),
888        MonoType::Record(row) => MonoType::Record(substitute_row(&row, var_map, row_map)),
889        MonoType::Variant(name, args) => MonoType::Variant(
890            name.clone(),
891            args.iter()
892                .map(|t| substitute(t, var_map, row_map))
893                .collect(),
894        ),
895        MonoType::InlineCmd(cs) => MonoType::InlineCmd(substitute_cmd_args(&cs, var_map, row_map)),
896        MonoType::BlockCmd(cs) => MonoType::BlockCmd(substitute_cmd_args(&cs, var_map, row_map)),
897        MonoType::MathCmd(cs) => MonoType::MathCmd(substitute_cmd_args(&cs, var_map, row_map)),
898    }
899}
900
901pub(crate) fn substitute_row(
902    row: &Row,
903    var_map: &HashMap<usize, MonoType>,
904    row_map: &HashMap<usize, Row>,
905) -> Row {
906    match &*resolve_row(row) {
907        Row::Empty => Row::Empty,
908        Row::Var(v) => row_map
909            .get(&v.ptr_key())
910            .cloned()
911            .unwrap_or_else(|| Row::Var(v.clone())),
912        Row::Cons(label, t, rest) => Row::Cons(
913            label.clone(),
914            Box::new(substitute(&t, var_map, row_map)),
915            Box::new(substitute_row(&rest, var_map, row_map)),
916        ),
917    }
918}
919
920fn substitute_cmd_args(
921    cs: &[CmdArgType],
922    var_map: &HashMap<usize, MonoType>,
923    row_map: &HashMap<usize, Row>,
924) -> Vec<CmdArgType> {
925    cs.iter()
926        .map(|c| CmdArgType {
927            optional: c.optional,
928            opt_labels: c
929                .opt_labels
930                .iter()
931                .map(|(l, t)| (l.clone(), substitute(t, var_map, row_map)))
932                .collect(),
933            ty: substitute(&c.ty, var_map, row_map),
934        })
935        .collect()
936}
937
938pub(crate) fn ptr_key(v: &TyVarRef) -> usize {
939    v.ptr_key()
940}
941
942// ============================================================================
943// Display — a SATySFi-syntax-ish pretty printer for error messages.
944//
945// Intentionally not byte-for-byte identical to v0.0.6's own printer
946// (`display.ml`); it exists to make unification errors readable, with a
947// simple parenthesization convention: atoms never need parens;
948// `list`/`ref`/single-argument variants are postfix and only parenthesize
949// a compound (function/product) operand; a function's codomain is
950// parenthesized whenever it isn't itself an atom — so `int -> (string
951// list)` gets parens around the list even though `list` binds tighter
952// than `->` (there's no source-level ambiguity; it's purely for
953// readability).
954// ============================================================================
955
956struct VarNamer {
957    names: HashMap<usize, String>,
958    next: usize,
959}
960
961impl VarNamer {
962    fn new() -> Self {
963        VarNamer {
964            names: HashMap::new(),
965            next: 0,
966        }
967    }
968
969    fn name_for(&mut self, key: usize) -> String {
970        if let Some(n) = self.names.get(&key) {
971            return n.clone();
972        }
973        let n = Self::letter(self.next);
974        self.next += 1;
975        self.names.insert(key, n.clone());
976        n
977    }
978
979    fn letter(i: usize) -> String {
980        let letter = (b'a' + (i % 26) as u8) as char;
981        let suffix = i / 26;
982        if suffix == 0 {
983            format!("'{letter}")
984        } else {
985            format!("'{letter}{suffix}")
986        }
987    }
988}
989
990fn is_atomic(ty: &MonoType) -> bool {
991    match ty {
992        MonoType::Base(_) | MonoType::Var(_) | MonoType::Record(_) => true,
993        MonoType::Variant(_, args) => args.is_empty(),
994        MonoType::Func(_, _, _)
995        | MonoType::Product(_)
996        | MonoType::List(_)
997        | MonoType::Ref(_)
998        | MonoType::Code(_)
999        | MonoType::InlineCmd(_)
1000        | MonoType::BlockCmd(_)
1001        | MonoType::MathCmd(_) => false,
1002    }
1003}
1004
1005fn needs_parens_as_operand(ty: &MonoType) -> bool {
1006    matches!(ty, MonoType::Func(_, _, _) | MonoType::Product(_))
1007}
1008
1009fn fmt_operand(ty: &MonoType, f: &mut fmt::Formatter<'_>, namer: &mut VarNamer) -> fmt::Result {
1010    if needs_parens_as_operand(&resolve(ty)) {
1011        f.write_str("(")?;
1012        fmt_mono(ty, f, namer)?;
1013        f.write_str(")")
1014    } else {
1015        fmt_mono(ty, f, namer)
1016    }
1017}
1018
1019fn fmt_mono(ty: &MonoType, f: &mut fmt::Formatter<'_>, namer: &mut VarNamer) -> fmt::Result {
1020    let ty = resolve(ty);
1021    match &*ty {
1022        MonoType::Var(v) => write!(f, "{}", namer.name_for(v.ptr_key())),
1023        MonoType::Base(b) => write!(f, "{b}"),
1024        MonoType::Func(row, dom, cod) => {
1025            fmt_func_row(row, f, namer)?;
1026            fmt_operand(dom, f, namer)?;
1027            f.write_str(" -> ")?;
1028            let rcod = resolve(cod);
1029            if is_atomic(&rcod) {
1030                fmt_mono(cod, f, namer)
1031            } else {
1032                f.write_str("(")?;
1033                fmt_mono(cod, f, namer)?;
1034                f.write_str(")")
1035            }
1036        }
1037        MonoType::Product(ts) => {
1038            for (i, t) in ts.iter().enumerate() {
1039                if i > 0 {
1040                    f.write_str(" * ")?;
1041                }
1042                fmt_operand(t, f, namer)?;
1043            }
1044            Ok(())
1045        }
1046        MonoType::List(t) => fmt_postfix(t, "list", f, namer),
1047        MonoType::Ref(t) => fmt_postfix(t, "ref", f, namer),
1048        MonoType::Code(t) => fmt_postfix(t, "code", f, namer),
1049        MonoType::Record(row) => fmt_row(row, f, namer),
1050        MonoType::Variant(name, args) => match args.as_slice() {
1051            [] => write!(f, "{name}"),
1052            [one] => fmt_postfix(one, name, f, namer),
1053            many => {
1054                f.write_str("(")?;
1055                for (i, t) in many.iter().enumerate() {
1056                    if i > 0 {
1057                        f.write_str(", ")?;
1058                    }
1059                    fmt_mono(t, f, namer)?;
1060                }
1061                write!(f, ") {name}")
1062            }
1063        },
1064        MonoType::InlineCmd(cs) => fmt_cmd(cs, "inline-cmd", f, namer),
1065        MonoType::BlockCmd(cs) => fmt_cmd(cs, "block-cmd", f, namer),
1066        MonoType::MathCmd(cs) => fmt_cmd(cs, "math-cmd", f, namer),
1067    }
1068}
1069
1070fn fmt_postfix(
1071    operand: &MonoType,
1072    suffix: &str,
1073    f: &mut fmt::Formatter<'_>,
1074    namer: &mut VarNamer,
1075) -> fmt::Result {
1076    fmt_operand(operand, f, namer)?;
1077    write!(f, " {suffix}")
1078}
1079
1080fn fmt_cmd(
1081    cs: &[CmdArgType],
1082    suffix: &str,
1083    f: &mut fmt::Formatter<'_>,
1084    namer: &mut VarNamer,
1085) -> fmt::Result {
1086    f.write_str("[")?;
1087    for (i, c) in cs.iter().enumerate() {
1088        if i > 0 {
1089            f.write_str("; ")?;
1090        }
1091        fmt_opt_labels(&c.opt_labels, f, namer)?;
1092        fmt_mono(&c.ty, f, namer)?;
1093        if c.optional {
1094            f.write_str("?")?;
1095        }
1096    }
1097    write!(f, "] {suffix}")
1098}
1099
1100/// Prefix-print a command argument slot's closed optional-label map (0.1's
1101/// `CmdArgType.opt_labels`): `?(l : τ, …) ` before the slot's mandatory `ty`,
1102/// or nothing at all when the map is empty (guaranteeing byte-identical
1103/// output for every 0.0.6-reachable `CmdArgType`, since those are always
1104/// `opt_labels == []`) — the command-type analogue of `fmt_func_row`, minus
1105/// the row-variable tail (command optional maps are closed, never open).
1106fn fmt_opt_labels(
1107    labels: &[(String, MonoType)],
1108    f: &mut fmt::Formatter<'_>,
1109    namer: &mut VarNamer,
1110) -> fmt::Result {
1111    if labels.is_empty() {
1112        return Ok(());
1113    }
1114    let mut fields = labels.to_vec();
1115    fields.sort_by(|a, b| a.0.cmp(&b.0));
1116    f.write_str("?(")?;
1117    for (i, (label, ty)) in fields.iter().enumerate() {
1118        if i > 0 {
1119            f.write_str(", ")?;
1120        }
1121        write!(f, "{label} : ")?;
1122        fmt_mono(ty, f, namer)?;
1123    }
1124    f.write_str(") ")
1125}
1126
1127/// Prefix-print a function type's optional-argument row: nothing at all for
1128/// an empty (0.0.6) row — guaranteeing byte-identical output — or `?(l : τ,
1129/// …) ` (a free-var tail adding `| ?'rN`) for a non-empty 0.1 row.
1130fn fmt_func_row(row: &Row, f: &mut fmt::Formatter<'_>, namer: &mut VarNamer) -> fmt::Result {
1131    let mut fields: Vec<(String, MonoType)> = Vec::new();
1132    let mut cur = resolve_row(row).into_owned();
1133    let tail_name = loop {
1134        match cur {
1135            Row::Empty => break None,
1136            Row::Var(v) => break Some(namer.name_for(v.ptr_key())),
1137            Row::Cons(label, ty, rest) => {
1138                fields.push((label, *ty));
1139                cur = resolve_row(&rest).into_owned();
1140            }
1141        }
1142    };
1143    if fields.is_empty() && tail_name.is_none() {
1144        return Ok(());
1145    }
1146    fields.sort_by(|a, b| a.0.cmp(&b.0));
1147    f.write_str("?(")?;
1148    for (i, (label, ty)) in fields.iter().enumerate() {
1149        if i > 0 {
1150            f.write_str(", ")?;
1151        }
1152        write!(f, "{label} : ")?;
1153        fmt_mono(ty, f, namer)?;
1154    }
1155    if let Some(name) = tail_name {
1156        if !fields.is_empty() {
1157            f.write_str(" ")?;
1158        }
1159        write!(f, "| ?{name}")?;
1160    }
1161    f.write_str(") ")
1162}
1163
1164fn fmt_row(row: &Row, f: &mut fmt::Formatter<'_>, namer: &mut VarNamer) -> fmt::Result {
1165    let mut fields: Vec<(String, MonoType)> = Vec::new();
1166    let mut cur = resolve_row(row).into_owned();
1167    let tail_name = loop {
1168        match cur {
1169            Row::Empty => break None,
1170            Row::Var(v) => break Some(namer.name_for(v.ptr_key())),
1171            Row::Cons(label, ty, rest) => {
1172                fields.push((label, *ty));
1173                cur = resolve_row(&rest).into_owned();
1174            }
1175        }
1176    };
1177    fields.sort_by(|a, b| a.0.cmp(&b.0));
1178    f.write_str("(| ")?;
1179    for (i, (label, ty)) in fields.iter().enumerate() {
1180        if i > 0 {
1181            f.write_str("; ")?;
1182        }
1183        write!(f, "{label} : ")?;
1184        fmt_mono(ty, f, namer)?;
1185    }
1186    if let Some(name) = tail_name {
1187        if !fields.is_empty() {
1188            f.write_str(" ")?;
1189        }
1190        write!(f, "| {name}")?;
1191    }
1192    f.write_str(" |)")
1193}
1194
1195impl fmt::Display for MonoType {
1196    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1197        let mut namer = VarNamer::new();
1198        fmt_mono(self, f, &mut namer)
1199    }
1200}
1201
1202impl fmt::Display for Row {
1203    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1204        let mut namer = VarNamer::new();
1205        fmt_row(self, f, &mut namer)
1206    }
1207}