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///
468/// The `#[subast]` list names every *other* type in this family reachable
469/// from a field; see [`crate::visit`] for what the generated traversal
470/// covers and — importantly — what it deliberately does not.
471///
472/// [`MonoType::Var`]'s [`TyVarRef`] is **not** listed, so the traversal
473/// treats a type variable as a leaf and never follows a `Bound` link. That
474/// is not an oversight: see [`crate::visit`].
475#[derive(Clone, Debug, syan::visit::Ast)]
476#[subast(crate::types::Row, crate::types::CmdArgType)]
477pub enum MonoType {
478 Var(TyVarRef),
479 Base(BaseType),
480 /// `?(row) dom -> cod` — a function type carrying a labeled
481 /// optional-argument [`Row`] (upstream `FuncType of row * typ * typ`,
482 /// SATySFi 0.1). The row is `Row::Empty` for every 0.0.6-constructed
483 /// function ([`crate::prim_types::arrow`]), printing nothing and
484 /// unifying trivially, so 0.0.6 behavior is byte-identical. A
485 /// non-empty row (`Cons(label, option-payload-type, …)`) records the
486 /// value-level `?(l = e)` labeled optional arguments the function
487 /// accepts. The field is **positional** (no `..` in any destructure)
488 /// deliberately: widening this variant makes the compiler flag every
489 /// match site, guarding against a silently-dropped row in the
490 /// sealed-module subsumption path.
491 ///
492 /// The row is **boxed** (`Box<Row>`, not inline) so widening `Func`
493 /// does not enlarge `MonoType` itself: `Row` is a ~40-byte enum, and
494 /// inlining it would make `Func` the largest variant, growing every
495 /// stack frame holding a `MonoType` by value enough to tip a deep
496 /// recursive typecheck over the default stack. `Box<Row>` keeps
497 /// `MonoType` at its pre-widening size, so 0.0.6 stack usage is
498 /// unchanged.
499 Func(Box<Row>, Box<MonoType>, Box<MonoType>),
500 /// A tuple type, always with at least two elements.
501 Product(Vec<MonoType>),
502 List(Box<MonoType>),
503 Ref(Box<MonoType>),
504 Record(Row),
505 /// A user-defined variant type applied to its arguments, e.g.
506 /// `Variant("option", [int])` for `int option`. Identified by name
507 /// rather than by a fresh `TypeID.t` as in v0.0.6 (`types.cppo.ml:318`)
508 /// — this port has no notion of shadowing/re-declaring a variant
509 /// type under the same name within one compilation, so a `String` is
510 /// a simpler stand-in for v0.0.6's globally-fresh `TypeID.t`.
511 Variant(String, Vec<MonoType>),
512 /// `code ty` — the type of a quoted (`&e`) fragment awaiting the next
513 /// stage. Upstream's `CodeType` (`types.cppo.ml:324`). Structurally it
514 /// behaves exactly like [`MonoType::Ref`]: one covariant argument,
515 /// unified pointwise.
516 Code(Box<MonoType>),
517 /// `[...] inline-cmd` (v0.0.6: `HorzCommandType`).
518 InlineCmd(Vec<CmdArgType>),
519 /// `[...] block-cmd` (v0.0.6: `VertCommandType`).
520 BlockCmd(Vec<CmdArgType>),
521 /// `[...] math-cmd` (v0.0.6: `MathCommandType`).
522 MathCmd(Vec<CmdArgType>),
523}
524
525/// One command argument type: `ty` for a mandatory argument, or `ty?` for
526/// an optional one (v0.0.6: `MandatoryArgumentType` / `OptionalArgumentType`,
527/// types.cppo.ml:326-328). `optional`/`opt_labels` are version-discriminated
528/// by construction: under `V0_0`
529/// (positional model) `optional` marks a whole-slot `ty?` optional and
530/// `opt_labels` is always empty; under `V0_1` (labeled model, upstream
531/// `CommandArgType of typ LabelMap.t * typ`, `types.cppo.ml:214`) `optional`
532/// is always `false` and `opt_labels` carries this slot's `?(l:τ,…)` bundle —
533/// a CLOSED map (no row variable: upstream discards one if written,
534/// `parser.mly:866`'s `TODO (error)`). Kept **sorted by label** at every
535/// producer (`command_scheme`'s harvest, `lower_type_atom`'s sig lowering) so
536/// `unify`/`Display`/sealing are order-insensitive — see `unify_cmd_args`'s
537/// zip-equal equal-domain test.
538///
539/// See [`MonoType`] for what the `#[subast]` list means. `opt_labels` is the
540/// field four hand-written walks forgot; the generated traversal cannot.
541#[derive(Clone, Debug, syan::visit::Ast)]
542#[subast(crate::types::MonoType)]
543pub struct CmdArgType {
544 pub optional: bool,
545 pub opt_labels: Vec<(String, MonoType)>,
546 pub ty: MonoType,
547}
548
549/// An extensible record row: a sequence of `label : type` bindings ending
550/// either in `Empty` (a *closed* record — exactly these labels and no
551/// others) or in `Var` (an *open* record — at least these labels, plus
552/// whatever the row variable's eventual binding adds).
553///
554/// **Deviation from v0.0.6**: its `RecordType` (types.cppo.ml:319) is
555/// always closed; the only record polymorphism is indirect, via a plain
556/// type variable carrying a `RecordKind` (a label-typed lower bound) that
557/// unifies against a closed `RecordType` when the kind's labels are a
558/// subset of the record's (typechecker.ml:480-500,
559/// `Assoc.domain_included`) — which cannot express an open record type
560/// standing on its own (only a variable can be "open"). Giving rows their
561/// own recursive type former (`Row::Cons`/`Var`/`Empty`, Rémy-style row
562/// polymorphism) is strictly more general and lets `unify` do genuine
563/// label-subsumption with a *remainder* row variable
564/// (`unify::row_extract`). `Kind::Record` is kept for the one case v0.0.6
565/// also has it for: a variable not yet known to be a record at all.
566///
567/// See [`MonoType`] for what the `#[subast]` list means. As with
568/// `MonoType::Var`, [`RowVarRef`] is not listed and a row variable is a leaf.
569#[derive(Clone, Debug, syan::visit::Ast)]
570#[subast(crate::types::MonoType)]
571pub enum Row {
572 Empty,
573 Var(RowVarRef),
574 Cons(String, Box<MonoType>, Box<Row>),
575}
576
577// ============================================================================
578// resolve / shallow_follow — chase `Bound` links, union-find "find".
579// ============================================================================
580
581/// Follow `Var(_)` → `Bound(ty)` links until reaching either a free
582/// variable or a non-variable type. Does **not** recurse into the
583/// structure of compound types (that's what makes it "shallow": a `Func`
584/// whose domain is itself a bound variable is returned as-is, domain still
585/// unresolved) — callers that need a fully dereferenced tree should
586/// `resolve` again at each level as they recurse, which is exactly what
587/// `unify` and `Display` do.
588///
589/// # Why `Cow`
590///
591/// This is the hottest function in the typechecker — ~316k calls on a corpus
592/// document, and its cost is dominated by COPYING types, not by following
593/// links. Do NOT make it return an owned `MonoType`: that makes the common
594/// case (the argument is already resolved — not a variable, or a free one)
595/// end in `ty.clone()`, a full deep copy produced solely to hand back an owned
596/// value the caller then only inspects. Measured, that pointless tail copy was
597/// **87-89% of all type nodes cloned during typechecking**, and typecheck time
598/// tracks cloned-node volume near-linearly across the whole corpus.
599///
600/// So the common case borrows. Only the link-following path allocates,
601/// and only because the `Bound` payload lives behind a `RefCell` whose guard
602/// cannot outlive this frame. Callers that just match on the result want
603/// `&*resolve(..)`; the few that keep it want `.into_owned()`.
604pub fn resolve(ty: &MonoType) -> Cow<'_, MonoType> {
605 if let MonoType::Var(v) = ty {
606 let next = match &*v.0.borrow() {
607 TyVarLink::Bound(inner) => Some(inner.clone()),
608 TyVarLink::Free { .. } => None,
609 };
610 if let Some(inner) = next {
611 return Cow::Owned(resolve(&inner).into_owned());
612 }
613 }
614 Cow::Borrowed(ty)
615}
616
617/// The row analogue of [`resolve`], `Cow` for the same reason.
618pub fn resolve_row(row: &Row) -> Cow<'_, Row> {
619 if let Row::Var(v) = row {
620 let next = match &*v.0.borrow() {
621 RowVarLink::Bound(inner) => Some(inner.clone()),
622 RowVarLink::Free { .. } => None,
623 };
624 if let Some(inner) = next {
625 return Cow::Owned(resolve_row(&inner).into_owned());
626 }
627 }
628 Cow::Borrowed(row)
629}
630
631// ============================================================================
632// Polymorphic types and level-based generalization
633// ============================================================================
634
635/// A type scheme: a monomorphic body plus the set of that body's free
636/// variables which are quantified over it.
637///
638/// **Deviation from v0.0.6**: v0.0.6 (types.cppo.ml:351-364) converts a
639/// generalized variable's `MonoFree` cell into a `PolyBound` id, so the
640/// same physical type reads differently as "a mono type" vs "a poly type",
641/// and instantiating rebuilds `PolyBound` occurrences into fresh
642/// `MonoFree` cells. This port instead keeps quantified variables as
643/// ordinary (still-`Free`) `TyVarRef`/`RowVarRef` cells and just remembers
644/// which ones they are (`vars`/`row_vars` below); `instantiate` deep-copies
645/// `body`, replacing each remembered variable (by pointer identity) with a
646/// fresh one and leaving everything else shared untouched. This is the
647/// standard "generalization via levels" technique, and it replaces
648/// v0.0.6's `quantifiability` flag (`Quantifiable`/`Unquantifiable`,
649/// types.cppo.ml:54) — which guards against generalizing a variable
650/// unification already linked outside the current let binding — with a
651/// consequence of levels instead: a variable unification touches from an
652/// outer scope gets its level lowered (`unify::occurs_var`/
653/// `occurs_var_in_row`), so by the time `generalize` runs it no longer
654/// looks "deep enough" to quantify.
655#[derive(Clone, Debug)]
656pub struct PolyType {
657 vars: Vec<TyVarRef>,
658 row_vars: Vec<RowVarRef>,
659 body: MonoType,
660}
661
662impl PolyType {
663 /// A trivial scheme with no quantified variables at all.
664 pub fn mono(ty: MonoType) -> PolyType {
665 PolyType {
666 vars: Vec::new(),
667 row_vars: Vec::new(),
668 body: ty,
669 }
670 }
671
672 /// Build a scheme by hand, explicitly naming which variables (which
673 /// must occur free in `body`) are quantified. Used by `prim_types`,
674 /// which constructs polymorphic primitive signatures (`::`, `!`)
675 /// directly rather than via `generalize` (there is no enclosing
676 /// inference level to generalize *from* at primitive-table
677 /// construction time).
678 pub(crate) fn from_vars(
679 vars: Vec<TyVarRef>,
680 row_vars: Vec<RowVarRef>,
681 body: MonoType,
682 ) -> PolyType {
683 PolyType {
684 vars,
685 row_vars,
686 body,
687 }
688 }
689
690 /// The scheme's body, before instantiation — exposed for inspection
691 /// (e.g. arity-checking) without needing to mint fresh variables.
692 pub fn body(&self) -> &MonoType {
693 &self.body
694 }
695
696 pub fn is_monomorphic(&self) -> bool {
697 self.vars.is_empty() && self.row_vars.is_empty()
698 }
699}
700
701impl fmt::Display for PolyType {
702 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
703 fmt::Display::fmt(&self.body, f)
704 }
705}
706
707/// Per-inference-run state: the level stack for generalization, and a
708/// counter for fresh variable ids (see `FRESH_ID`'s doc comment for why
709/// `instantiate`/`unify` use a *different* counter than this one — the two
710/// never need to agree, since identity is always by pointer).
711pub struct TypeContext {
712 next_id: u64,
713 level: u32,
714}
715
716impl TypeContext {
717 pub fn new() -> Self {
718 TypeContext {
719 next_id: 0,
720 level: 0,
721 }
722 }
723
724 fn next_id(&mut self) -> u64 {
725 let id = self.next_id;
726 self.next_id += 1;
727 id
728 }
729
730 pub fn level(&self) -> u32 {
731 self.level
732 }
733
734 /// Enter a new `let`-nesting level. Call before inferring the
735 /// right-hand side of a `let`.
736 pub fn enter_level(&mut self) {
737 self.level += 1;
738 }
739
740 /// Leave the current level. Call after inferring the right-hand side
741 /// of a `let`, before calling `generalize`.
742 pub fn leave_level(&mut self) {
743 self.level -= 1;
744 }
745
746 pub fn fresh_var(&mut self) -> TyVarRef {
747 self.fresh_var_with_kind(Kind::Universal)
748 }
749
750 pub fn fresh_var_with_kind(&mut self, kind: Kind) -> TyVarRef {
751 TyVarRef::new(self.next_id(), self.level, kind)
752 }
753
754 pub fn fresh_row_var(&mut self) -> RowVarRef {
755 self.fresh_row_var_with_kind(BTreeSet::new())
756 }
757
758 pub fn fresh_row_var_with_kind(&mut self, kind: BTreeSet<String>) -> RowVarRef {
759 RowVarRef::new(self.next_id(), self.level, kind)
760 }
761}
762
763impl Default for TypeContext {
764 fn default() -> Self {
765 Self::new()
766 }
767}
768
769/// Quantify every free variable in `ty` whose level is deeper than `level`
770/// (i.e. was created after entering the let binding being generalized).
771/// Typical usage:
772///
773/// ```ignore
774/// ctx.enter_level();
775/// let ty = infer(ctx, rhs)?;
776/// ctx.leave_level();
777/// let scheme = generalize(ctx.level(), &ty);
778/// ```
779pub fn generalize(level: u32, ty: &MonoType) -> PolyType {
780 let mut vars = Vec::new();
781 let mut row_vars = Vec::new();
782 collect_generalizable(level, ty, &mut vars, &mut row_vars);
783 PolyType {
784 vars,
785 row_vars,
786 body: ty.clone(),
787 }
788}
789
790fn collect_generalizable(
791 level: u32,
792 ty: &MonoType,
793 vars: &mut Vec<TyVarRef>,
794 row_vars: &mut Vec<RowVarRef>,
795) {
796 match &*resolve(ty) {
797 MonoType::Var(v) => {
798 if let Some(lv) = v.level() {
799 if lv > level && !vars.iter().any(|x| x.same(v)) {
800 vars.push(v.clone());
801 }
802 }
803 }
804 MonoType::Base(_) => {}
805 MonoType::Func(row, a, b) => {
806 collect_generalizable_row(level, &row, vars, row_vars);
807 collect_generalizable(level, &a, vars, row_vars);
808 collect_generalizable(level, &b, vars, row_vars);
809 }
810 MonoType::Product(ts) => {
811 for t in ts {
812 collect_generalizable(level, t, vars, row_vars);
813 }
814 }
815 MonoType::List(t) | MonoType::Ref(t) | MonoType::Code(t) => {
816 collect_generalizable(level, &t, vars, row_vars)
817 }
818 MonoType::Record(row) => collect_generalizable_row(level, &row, vars, row_vars),
819 MonoType::Variant(_, args) => {
820 for t in args {
821 collect_generalizable(level, t, vars, row_vars);
822 }
823 }
824 MonoType::InlineCmd(cs) | MonoType::BlockCmd(cs) | MonoType::MathCmd(cs) => {
825 for c in cs {
826 collect_generalizable(level, &c.ty, vars, row_vars);
827 for (_, lty) in &c.opt_labels {
828 collect_generalizable(level, lty, vars, row_vars);
829 }
830 }
831 }
832 }
833}
834
835fn collect_generalizable_row(
836 level: u32,
837 row: &Row,
838 vars: &mut Vec<TyVarRef>,
839 row_vars: &mut Vec<RowVarRef>,
840) {
841 match &*resolve_row(row) {
842 Row::Empty => {}
843 Row::Var(v) => {
844 if let Some(lv) = v.level() {
845 if lv > level && !row_vars.iter().any(|x| x.same(v)) {
846 row_vars.push(v.clone());
847 }
848 }
849 }
850 Row::Cons(_, t, rest) => {
851 collect_generalizable(level, &t, vars, row_vars);
852 collect_generalizable_row(level, &rest, vars, row_vars);
853 }
854 }
855}
856
857/// Instantiate a scheme: replace every quantified variable with a fresh
858/// one at `level`, leaving everything else in the body shared as-is.
859///
860/// This takes no `&mut TypeContext` (per this module's contract) — see
861/// `FRESH_ID`'s doc comment for how it still mints fresh, correctly
862/// leveled variables.
863pub fn instantiate(poly: &PolyType, level: u32) -> MonoType {
864 let mut var_map: HashMap<usize, MonoType> = HashMap::new();
865 for v in &poly.vars {
866 let fresh = TyVarRef::new(fresh_id(), level, v.kind());
867 var_map.insert(v.ptr_key(), MonoType::Var(fresh));
868 }
869 let mut row_map: HashMap<usize, Row> = HashMap::new();
870 for v in &poly.row_vars {
871 let fresh = RowVarRef::new(fresh_id(), level, v.kind());
872 row_map.insert(v.ptr_key(), Row::Var(fresh));
873 }
874 substitute(&poly.body, &var_map, &row_map)
875}
876
877/// Deep-copy `ty`, replacing any (resolved) variable found in `var_map`/
878/// `row_map` by pointer identity with its mapped replacement, and cloning
879/// everything else structurally. Shared by `instantiate` (mapping
880/// quantified variables to fresh ones) and by `prim_types::VariantDecl`
881/// (mapping a declaration's parameter placeholders to the concrete
882/// arguments of one particular constructor application).
883pub(crate) fn substitute(
884 ty: &MonoType,
885 var_map: &HashMap<usize, MonoType>,
886 row_map: &HashMap<usize, Row>,
887) -> MonoType {
888 match &*resolve(ty) {
889 MonoType::Var(v) => var_map
890 .get(&v.ptr_key())
891 .cloned()
892 .unwrap_or_else(|| MonoType::Var(v.clone())),
893 MonoType::Base(b) => MonoType::Base(*b),
894 MonoType::Func(row, a, b) => MonoType::Func(
895 Box::new(substitute_row(&row, var_map, row_map)),
896 Box::new(substitute(&a, var_map, row_map)),
897 Box::new(substitute(&b, var_map, row_map)),
898 ),
899 MonoType::Product(ts) => {
900 MonoType::Product(ts.iter().map(|t| substitute(t, var_map, row_map)).collect())
901 }
902 MonoType::List(t) => MonoType::List(Box::new(substitute(&t, var_map, row_map))),
903 MonoType::Ref(t) => MonoType::Ref(Box::new(substitute(&t, var_map, row_map))),
904 MonoType::Code(t) => MonoType::Code(Box::new(substitute(&t, var_map, row_map))),
905 MonoType::Record(row) => MonoType::Record(substitute_row(&row, var_map, row_map)),
906 MonoType::Variant(name, args) => MonoType::Variant(
907 name.clone(),
908 args.iter()
909 .map(|t| substitute(t, var_map, row_map))
910 .collect(),
911 ),
912 MonoType::InlineCmd(cs) => MonoType::InlineCmd(substitute_cmd_args(&cs, var_map, row_map)),
913 MonoType::BlockCmd(cs) => MonoType::BlockCmd(substitute_cmd_args(&cs, var_map, row_map)),
914 MonoType::MathCmd(cs) => MonoType::MathCmd(substitute_cmd_args(&cs, var_map, row_map)),
915 }
916}
917
918pub(crate) fn substitute_row(
919 row: &Row,
920 var_map: &HashMap<usize, MonoType>,
921 row_map: &HashMap<usize, Row>,
922) -> Row {
923 match &*resolve_row(row) {
924 Row::Empty => Row::Empty,
925 Row::Var(v) => row_map
926 .get(&v.ptr_key())
927 .cloned()
928 .unwrap_or_else(|| Row::Var(v.clone())),
929 Row::Cons(label, t, rest) => Row::Cons(
930 label.clone(),
931 Box::new(substitute(&t, var_map, row_map)),
932 Box::new(substitute_row(&rest, var_map, row_map)),
933 ),
934 }
935}
936
937fn substitute_cmd_args(
938 cs: &[CmdArgType],
939 var_map: &HashMap<usize, MonoType>,
940 row_map: &HashMap<usize, Row>,
941) -> Vec<CmdArgType> {
942 cs.iter()
943 .map(|c| CmdArgType {
944 optional: c.optional,
945 opt_labels: c
946 .opt_labels
947 .iter()
948 .map(|(l, t)| (l.clone(), substitute(t, var_map, row_map)))
949 .collect(),
950 ty: substitute(&c.ty, var_map, row_map),
951 })
952 .collect()
953}
954
955pub(crate) fn ptr_key(v: &TyVarRef) -> usize {
956 v.ptr_key()
957}
958
959// ============================================================================
960// Display — a SATySFi-syntax-ish pretty printer for error messages.
961//
962// Intentionally not byte-for-byte identical to v0.0.6's own printer
963// (`display.ml`); it exists to make unification errors readable, with a
964// simple parenthesization convention: atoms never need parens;
965// `list`/`ref`/single-argument variants are postfix and only parenthesize
966// a compound (function/product) operand; a function's codomain is
967// parenthesized whenever it isn't itself an atom — so `int -> (string
968// list)` gets parens around the list even though `list` binds tighter
969// than `->` (there's no source-level ambiguity; it's purely for
970// readability).
971// ============================================================================
972
973struct VarNamer {
974 names: HashMap<usize, String>,
975 next: usize,
976}
977
978impl VarNamer {
979 fn new() -> Self {
980 VarNamer {
981 names: HashMap::new(),
982 next: 0,
983 }
984 }
985
986 fn name_for(&mut self, key: usize) -> String {
987 if let Some(n) = self.names.get(&key) {
988 return n.clone();
989 }
990 let n = Self::letter(self.next);
991 self.next += 1;
992 self.names.insert(key, n.clone());
993 n
994 }
995
996 fn letter(i: usize) -> String {
997 let letter = (b'a' + (i % 26) as u8) as char;
998 let suffix = i / 26;
999 if suffix == 0 {
1000 format!("'{letter}")
1001 } else {
1002 format!("'{letter}{suffix}")
1003 }
1004 }
1005}
1006
1007fn is_atomic(ty: &MonoType) -> bool {
1008 match ty {
1009 MonoType::Base(_) | MonoType::Var(_) | MonoType::Record(_) => true,
1010 MonoType::Variant(_, args) => args.is_empty(),
1011 MonoType::Func(_, _, _)
1012 | MonoType::Product(_)
1013 | MonoType::List(_)
1014 | MonoType::Ref(_)
1015 | MonoType::Code(_)
1016 | MonoType::InlineCmd(_)
1017 | MonoType::BlockCmd(_)
1018 | MonoType::MathCmd(_) => false,
1019 }
1020}
1021
1022fn needs_parens_as_operand(ty: &MonoType) -> bool {
1023 matches!(ty, MonoType::Func(_, _, _) | MonoType::Product(_))
1024}
1025
1026fn fmt_operand(ty: &MonoType, f: &mut fmt::Formatter<'_>, namer: &mut VarNamer) -> fmt::Result {
1027 if needs_parens_as_operand(&resolve(ty)) {
1028 f.write_str("(")?;
1029 fmt_mono(ty, f, namer)?;
1030 f.write_str(")")
1031 } else {
1032 fmt_mono(ty, f, namer)
1033 }
1034}
1035
1036fn fmt_mono(ty: &MonoType, f: &mut fmt::Formatter<'_>, namer: &mut VarNamer) -> fmt::Result {
1037 let ty = resolve(ty);
1038 match &*ty {
1039 MonoType::Var(v) => write!(f, "{}", namer.name_for(v.ptr_key())),
1040 MonoType::Base(b) => write!(f, "{b}"),
1041 MonoType::Func(row, dom, cod) => {
1042 fmt_func_row(row, f, namer)?;
1043 fmt_operand(dom, f, namer)?;
1044 f.write_str(" -> ")?;
1045 let rcod = resolve(cod);
1046 if is_atomic(&rcod) {
1047 fmt_mono(cod, f, namer)
1048 } else {
1049 f.write_str("(")?;
1050 fmt_mono(cod, f, namer)?;
1051 f.write_str(")")
1052 }
1053 }
1054 MonoType::Product(ts) => {
1055 for (i, t) in ts.iter().enumerate() {
1056 if i > 0 {
1057 f.write_str(" * ")?;
1058 }
1059 fmt_operand(t, f, namer)?;
1060 }
1061 Ok(())
1062 }
1063 MonoType::List(t) => fmt_postfix(t, "list", f, namer),
1064 MonoType::Ref(t) => fmt_postfix(t, "ref", f, namer),
1065 MonoType::Code(t) => fmt_postfix(t, "code", f, namer),
1066 MonoType::Record(row) => fmt_row(row, f, namer),
1067 MonoType::Variant(name, args) => match args.as_slice() {
1068 [] => write!(f, "{name}"),
1069 [one] => fmt_postfix(one, name, f, namer),
1070 many => {
1071 f.write_str("(")?;
1072 for (i, t) in many.iter().enumerate() {
1073 if i > 0 {
1074 f.write_str(", ")?;
1075 }
1076 fmt_mono(t, f, namer)?;
1077 }
1078 write!(f, ") {name}")
1079 }
1080 },
1081 MonoType::InlineCmd(cs) => fmt_cmd(cs, "inline-cmd", f, namer),
1082 MonoType::BlockCmd(cs) => fmt_cmd(cs, "block-cmd", f, namer),
1083 MonoType::MathCmd(cs) => fmt_cmd(cs, "math-cmd", f, namer),
1084 }
1085}
1086
1087fn fmt_postfix(
1088 operand: &MonoType,
1089 suffix: &str,
1090 f: &mut fmt::Formatter<'_>,
1091 namer: &mut VarNamer,
1092) -> fmt::Result {
1093 fmt_operand(operand, f, namer)?;
1094 write!(f, " {suffix}")
1095}
1096
1097fn fmt_cmd(
1098 cs: &[CmdArgType],
1099 suffix: &str,
1100 f: &mut fmt::Formatter<'_>,
1101 namer: &mut VarNamer,
1102) -> fmt::Result {
1103 f.write_str("[")?;
1104 for (i, c) in cs.iter().enumerate() {
1105 if i > 0 {
1106 f.write_str("; ")?;
1107 }
1108 fmt_opt_labels(&c.opt_labels, f, namer)?;
1109 fmt_mono(&c.ty, f, namer)?;
1110 if c.optional {
1111 f.write_str("?")?;
1112 }
1113 }
1114 write!(f, "] {suffix}")
1115}
1116
1117/// Prefix-print a command argument slot's closed optional-label map (0.1's
1118/// `CmdArgType.opt_labels`): `?(l : τ, …) ` before the slot's mandatory `ty`,
1119/// or nothing at all when the map is empty (guaranteeing byte-identical
1120/// output for every 0.0.6-reachable `CmdArgType`, since those are always
1121/// `opt_labels == []`) — the command-type analogue of `fmt_func_row`, minus
1122/// the row-variable tail (command optional maps are closed, never open).
1123fn fmt_opt_labels(
1124 labels: &[(String, MonoType)],
1125 f: &mut fmt::Formatter<'_>,
1126 namer: &mut VarNamer,
1127) -> fmt::Result {
1128 if labels.is_empty() {
1129 return Ok(());
1130 }
1131 let mut fields = labels.to_vec();
1132 fields.sort_by(|a, b| a.0.cmp(&b.0));
1133 f.write_str("?(")?;
1134 for (i, (label, ty)) in fields.iter().enumerate() {
1135 if i > 0 {
1136 f.write_str(", ")?;
1137 }
1138 write!(f, "{label} : ")?;
1139 fmt_mono(ty, f, namer)?;
1140 }
1141 f.write_str(") ")
1142}
1143
1144/// Prefix-print a function type's optional-argument row: nothing at all for
1145/// an empty (0.0.6) row — guaranteeing byte-identical output — or `?(l : τ,
1146/// …) ` (a free-var tail adding `| ?'rN`) for a non-empty 0.1 row.
1147fn fmt_func_row(row: &Row, f: &mut fmt::Formatter<'_>, namer: &mut VarNamer) -> fmt::Result {
1148 let mut fields: Vec<(String, MonoType)> = Vec::new();
1149 let mut cur = resolve_row(row).into_owned();
1150 let tail_name = loop {
1151 match cur {
1152 Row::Empty => break None,
1153 Row::Var(v) => break Some(namer.name_for(v.ptr_key())),
1154 Row::Cons(label, ty, rest) => {
1155 fields.push((label, *ty));
1156 cur = resolve_row(&rest).into_owned();
1157 }
1158 }
1159 };
1160 if fields.is_empty() && tail_name.is_none() {
1161 return Ok(());
1162 }
1163 fields.sort_by(|a, b| a.0.cmp(&b.0));
1164 f.write_str("?(")?;
1165 for (i, (label, ty)) in fields.iter().enumerate() {
1166 if i > 0 {
1167 f.write_str(", ")?;
1168 }
1169 write!(f, "{label} : ")?;
1170 fmt_mono(ty, f, namer)?;
1171 }
1172 if let Some(name) = tail_name {
1173 if !fields.is_empty() {
1174 f.write_str(" ")?;
1175 }
1176 write!(f, "| ?{name}")?;
1177 }
1178 f.write_str(") ")
1179}
1180
1181fn fmt_row(row: &Row, f: &mut fmt::Formatter<'_>, namer: &mut VarNamer) -> fmt::Result {
1182 let mut fields: Vec<(String, MonoType)> = Vec::new();
1183 let mut cur = resolve_row(row).into_owned();
1184 let tail_name = loop {
1185 match cur {
1186 Row::Empty => break None,
1187 Row::Var(v) => break Some(namer.name_for(v.ptr_key())),
1188 Row::Cons(label, ty, rest) => {
1189 fields.push((label, *ty));
1190 cur = resolve_row(&rest).into_owned();
1191 }
1192 }
1193 };
1194 fields.sort_by(|a, b| a.0.cmp(&b.0));
1195 f.write_str("(| ")?;
1196 for (i, (label, ty)) in fields.iter().enumerate() {
1197 if i > 0 {
1198 f.write_str("; ")?;
1199 }
1200 write!(f, "{label} : ")?;
1201 fmt_mono(ty, f, namer)?;
1202 }
1203 if let Some(name) = tail_name {
1204 if !fields.is_empty() {
1205 f.write_str(" ")?;
1206 }
1207 write!(f, "| {name}")?;
1208 }
1209 f.write_str(" |)")
1210}
1211
1212impl fmt::Display for MonoType {
1213 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1214 let mut namer = VarNamer::new();
1215 fmt_mono(self, f, &mut namer)
1216 }
1217}
1218
1219impl fmt::Display for Row {
1220 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1221 let mut namer = VarNamer::new();
1222 fmt_row(self, f, &mut namer)
1223 }
1224}