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}