Skip to main content

lisette_syntax/
types.rs

1use rustc_hash::{FxHashMap as HashMap, FxHashSet as HashSet};
2use std::borrow::Borrow;
3use std::cell::OnceCell;
4
5use ecow::EcoString;
6
7/// Dot-qualified identifier for a named type, method, value, or variant.
8///
9/// Wraps the qualified name (`"main.Point.sum"`, `"prelude.Option"`,
10/// `"go:net/http.Handler"`) as a single `EcoString` and exposes structured
11/// accessors. Centralizes the join/split logic that used to live in ad-hoc
12/// `format!("{}.{}", ..)` and `split_once('.')` call sites.
13#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
14#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
15pub struct Symbol(EcoString);
16
17impl Symbol {
18    /// Joins a module id and a local (possibly multi-segment) name.
19    ///
20    /// `Symbol::from_parts("main", "Point.sum")` → `"main.Point.sum"`.
21    pub fn from_parts(module: &str, local: &str) -> Self {
22        let mut s = String::with_capacity(module.len() + 1 + local.len());
23        s.push_str(module);
24        s.push('.');
25        s.push_str(local);
26        Self(EcoString::from(s))
27    }
28
29    /// Appends an additional dot-segment to an already-qualified symbol.
30    ///
31    /// `Symbol::from_raw("main.Shape").with_segment("Circle")` →
32    /// `"main.Shape.Circle"`.
33    pub fn with_segment(&self, segment: &str) -> Self {
34        let mut s = String::with_capacity(self.0.len() + 1 + segment.len());
35        s.push_str(&self.0);
36        s.push('.');
37        s.push_str(segment);
38        Self(EcoString::from(s))
39    }
40
41    /// Wraps an already-constructed qualified string. Prefer `from_parts`
42    /// when the module id and local name are available separately.
43    pub fn from_raw(qualified: impl Into<EcoString>) -> Self {
44        Self(qualified.into())
45    }
46
47    pub fn as_str(&self) -> &str {
48        &self.0
49    }
50
51    pub fn as_eco(&self) -> &EcoString {
52        &self.0
53    }
54
55    /// Last dot-separated segment. `"main.Point.sum"` → `"sum"`.
56    pub fn last_segment(&self) -> &str {
57        self.0.rsplit('.').next().unwrap_or(&self.0)
58    }
59
60    /// Strips the last dot-separated segment. `"main.Point.sum"` → `"main.Point"`.
61    /// Returns `None` if the symbol has no dot.
62    pub fn without_last_segment(&self) -> Option<&str> {
63        self.0.rsplit_once('.').map(|(rest, _)| rest)
64    }
65
66    /// Naive first segment (first dot split). Correct for user modules; for
67    /// `go:net/http.Handler`-style symbols, resolve via
68    /// `Store::module_for_qualified_name` instead.
69    pub fn simple_module_part(&self) -> Option<&str> {
70        self.0.split_once('.').map(|(m, _)| m)
71    }
72}
73
74impl Borrow<str> for Symbol {
75    fn borrow(&self) -> &str {
76        &self.0
77    }
78}
79
80impl AsRef<str> for Symbol {
81    fn as_ref(&self) -> &str {
82        &self.0
83    }
84}
85
86impl std::ops::Deref for Symbol {
87    type Target = str;
88
89    fn deref(&self) -> &str {
90        &self.0
91    }
92}
93
94impl From<&Symbol> for EcoString {
95    fn from(s: &Symbol) -> Self {
96        s.0.clone()
97    }
98}
99
100impl std::fmt::Display for Symbol {
101    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
102        self.0.fmt(f)
103    }
104}
105
106impl From<EcoString> for Symbol {
107    fn from(s: EcoString) -> Self {
108        Self(s)
109    }
110}
111
112impl From<Symbol> for EcoString {
113    fn from(s: Symbol) -> Self {
114        s.0
115    }
116}
117
118impl From<&str> for Symbol {
119    fn from(s: &str) -> Self {
120        Self(EcoString::from(s))
121    }
122}
123
124impl From<String> for Symbol {
125    fn from(s: String) -> Self {
126        Self(EcoString::from(s))
127    }
128}
129
130impl PartialEq<str> for Symbol {
131    fn eq(&self, other: &str) -> bool {
132        self.0.as_str() == other
133    }
134}
135
136impl PartialEq<&str> for Symbol {
137    fn eq(&self, other: &&str) -> bool {
138        self.0.as_str() == *other
139    }
140}
141
142/// Extract the unqualified name from a dot-qualified identifier.
143///
144/// `"prelude.Option"` → `"Option"`, `"**nominal.int"` → `"int"`, `"foo"` → `"foo"`
145pub fn unqualified_name(id: &str) -> &str {
146    id.rsplit('.').next().unwrap_or(id)
147}
148
149/// Extract the module part of a dot-qualified identifier — the first
150/// segment, before any dot.
151///
152/// `"main.Point.sum"` → `"main"`, `"prelude.Option"` → `"prelude"`,
153/// `"foo"` → `"foo"`. For `go:net/http.Handler`-style ids, returns
154/// `"go:net/http"`. When the id has no dot at all, returns the id
155/// itself (the caller is responsible for handling the no-module case).
156pub fn module_part(id: &str) -> &str {
157    id.split('.').next().unwrap_or(id)
158}
159
160/// type param name -> type variable
161pub type SubstitutionMap = HashMap<EcoString, Type>;
162
163pub fn substitute(ty: &Type, map: &HashMap<EcoString, Type>) -> Type {
164    if map.is_empty() {
165        return ty.clone();
166    }
167    match ty {
168        Type::Parameter(name) => map.get(name).cloned().unwrap_or_else(|| ty.clone()),
169        Type::Nominal {
170            id,
171            params,
172            underlying_ty: underlying,
173        } => Type::Nominal {
174            id: id.clone(),
175            params: params.iter().map(|p| substitute(p, map)).collect(),
176            underlying_ty: underlying.as_ref().map(|u| Box::new(substitute(u, map))),
177        },
178        Type::Function {
179            params,
180            param_mutability,
181            bounds,
182            return_type,
183        } => Type::Function {
184            params: params.iter().map(|p| substitute(p, map)).collect(),
185            param_mutability: param_mutability.clone(),
186            bounds: bounds
187                .iter()
188                .map(|b| Bound {
189                    param_name: b.param_name.clone(),
190                    generic: substitute(&b.generic, map),
191                    ty: substitute(&b.ty, map),
192                })
193                .collect(),
194            return_type: Box::new(substitute(return_type, map)),
195        },
196        Type::Var { .. } | Type::Error => ty.clone(),
197        Type::Forall { vars, body } => {
198            let has_overlap = map.keys().any(|k| vars.contains(k));
199            let substituted_body = if has_overlap {
200                let filtered_map: HashMap<EcoString, Type> = map
201                    .iter()
202                    .filter(|(k, _)| !vars.contains(*k))
203                    .map(|(k, v)| (k.clone(), v.clone()))
204                    .collect();
205                substitute(body, &filtered_map)
206            } else {
207                substitute(body, map)
208            };
209            Type::Forall {
210                vars: vars.clone(),
211                body: Box::new(substituted_body),
212            }
213        }
214        Type::Tuple(elements) => Type::Tuple(elements.iter().map(|e| substitute(e, map)).collect()),
215        Type::Compound { kind, args } => Type::Compound {
216            kind: *kind,
217            args: args.iter().map(|a| substitute(a, map)).collect(),
218        },
219        Type::Simple(_) | Type::Never | Type::ImportNamespace(_) | Type::ReceiverPlaceholder => {
220            ty.clone()
221        }
222    }
223}
224
225#[derive(Debug, Clone, PartialEq)]
226#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
227pub struct Bound {
228    pub param_name: EcoString,
229    pub generic: Type,
230    pub ty: Type,
231}
232
233/// A unique handle identifying a type variable. The binding state (Unbound /
234/// Bound-to-a-Type) lives in a `TypeEnv` owned by the checker; the handle is
235/// a plain id so `Type` stays a pure value (Clone, Eq, Hash, Serialize).
236#[derive(Clone, Copy, PartialEq, Eq, Hash)]
237#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
238pub struct TypeVarId(pub u32);
239
240impl TypeVarId {
241    pub const IGNORED: TypeVarId = TypeVarId(u32::MAX);
242    pub const UNINFERRED: TypeVarId = TypeVarId(u32::MAX - 1);
243
244    pub fn is_reserved(self) -> bool {
245        self == Self::IGNORED || self == Self::UNINFERRED
246    }
247
248    pub fn as_u32(self) -> u32 {
249        self.0
250    }
251}
252
253impl std::fmt::Debug for TypeVarId {
254    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
255        match *self {
256            Self::IGNORED => write!(f, "ignored"),
257            Self::UNINFERRED => write!(f, "uninferred"),
258            TypeVarId(n) => write!(f, "#{}", n),
259        }
260    }
261}
262
263#[derive(Clone)]
264#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
265pub enum Type {
266    Simple(SimpleKind),
267
268    Compound {
269        kind: CompoundKind,
270        args: Vec<Type>,
271    },
272
273    Nominal {
274        id: Symbol,
275        params: Vec<Type>,
276        underlying_ty: Option<Box<Type>>,
277    },
278
279    /// Module namespace handle. Produced by imports (e.g. `import http "net/http"`
280    /// produces an `ImportNamespace("go:net/http")` on the local identifier).
281    /// Dot-access on this type resolves to the module's exports.
282    ImportNamespace(EcoString),
283
284    Function {
285        params: Vec<Type>,
286        param_mutability: Vec<bool>,
287        bounds: Vec<Bound>,
288        return_type: Box<Type>,
289    },
290
291    /// Type variable handle. Binding state lives in a `TypeEnv` owned by the
292    /// checker; the inline `hint` is display metadata set at allocation time
293    /// so `Display`/`Debug` work without env access.
294    Var {
295        id: TypeVarId,
296        hint: Option<EcoString>,
297    },
298
299    Forall {
300        vars: Vec<EcoString>,
301        body: Box<Type>,
302    },
303
304    Parameter(EcoString),
305
306    Never,
307
308    Tuple(Vec<Type>),
309
310    /// Poison type returned after an error has been reported.
311    /// Unifies with everything silently, preventing cascading diagnostics.
312    Error,
313
314    /// Sentinel occupying the `self` slot of an interface method type.
315    /// Unifies silently so an implementing type's receiver does not conflict
316    /// with the abstract method shape. Previously encoded as
317    /// `Constructor { id: "**nominal.__receiver__" }`.
318    ReceiverPlaceholder,
319}
320
321impl std::fmt::Debug for Type {
322    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
323        match self {
324            Type::Nominal { id, params, .. } => f
325                .debug_struct("Nominal")
326                .field("id", id)
327                .field("params", params)
328                .finish(),
329            Type::Function {
330                params,
331                param_mutability,
332                bounds,
333                return_type,
334            } => {
335                let mut s = f.debug_struct("Function");
336                s.field("params", params);
337                if param_mutability.iter().any(|m| *m) {
338                    s.field("param_mutability", param_mutability);
339                }
340                s.field("bounds", bounds)
341                    .field("return_type", return_type)
342                    .finish()
343            }
344            Type::Var { id, hint } => {
345                let mut s = f.debug_struct("Var");
346                s.field("id", id);
347                if let Some(h) = hint {
348                    s.field("hint", h);
349                }
350                s.finish()
351            }
352            Type::Forall { vars, body } => f
353                .debug_struct("Forall")
354                .field("vars", vars)
355                .field("body", body)
356                .finish(),
357            Type::Parameter(name) => f.debug_tuple("Parameter").field(name).finish(),
358            Type::Never => write!(f, "Never"),
359            Type::Tuple(elements) => f.debug_tuple("Tuple").field(elements).finish(),
360            Type::Error => write!(f, "Error"),
361            Type::ImportNamespace(module_id) => {
362                f.debug_tuple("ImportNamespace").field(module_id).finish()
363            }
364            Type::ReceiverPlaceholder => write!(f, "ReceiverPlaceholder"),
365            Type::Simple(kind) => f.debug_tuple("Simple").field(kind).finish(),
366            Type::Compound { kind, args } => f
367                .debug_struct("Compound")
368                .field("kind", kind)
369                .field("args", args)
370                .finish(),
371        }
372    }
373}
374
375impl PartialEq for Type {
376    fn eq(&self, other: &Self) -> bool {
377        match (self, other) {
378            (
379                Type::Nominal {
380                    id: id1,
381                    params: params1,
382                    ..
383                },
384                Type::Nominal {
385                    id: id2,
386                    params: params2,
387                    ..
388                },
389            ) => id1 == id2 && params1 == params2,
390            (
391                Type::Function {
392                    params: p1,
393                    param_mutability: m1,
394                    bounds: b1,
395                    return_type: r1,
396                },
397                Type::Function {
398                    params: p2,
399                    param_mutability: m2,
400                    bounds: b2,
401                    return_type: r2,
402                },
403            ) => p1 == p2 && m1 == m2 && b1 == b2 && r1 == r2,
404            (Type::Var { id: id1, .. }, Type::Var { id: id2, .. }) => id1 == id2,
405            (
406                Type::Forall {
407                    vars: vars1,
408                    body: body1,
409                },
410                Type::Forall {
411                    vars: vars2,
412                    body: body2,
413                },
414            ) => vars1 == vars2 && body1 == body2,
415            (Type::Parameter(name1), Type::Parameter(name2)) => name1 == name2,
416            (Type::Never, Type::Never) => true,
417            (Type::Tuple(elems1), Type::Tuple(elems2)) => elems1 == elems2,
418            (Type::ImportNamespace(m1), Type::ImportNamespace(m2)) => m1 == m2,
419            (Type::ReceiverPlaceholder, Type::ReceiverPlaceholder) => true,
420            (Type::Simple(k1), Type::Simple(k2)) => k1 == k2,
421            (Type::Compound { kind: k1, args: a1 }, Type::Compound { kind: k2, args: a2 }) => {
422                k1 == k2 && a1 == a2
423            }
424            _ => false,
425        }
426    }
427}
428
429thread_local! {
430    static INTERNED_INT: OnceCell<Type> = const { OnceCell::new() };
431    static INTERNED_STRING: OnceCell<Type> = const { OnceCell::new() };
432    static INTERNED_BOOL: OnceCell<Type> = const { OnceCell::new() };
433    static INTERNED_UNIT: OnceCell<Type> = const { OnceCell::new() };
434    static INTERNED_FLOAT64: OnceCell<Type> = const { OnceCell::new() };
435    static INTERNED_RUNE: OnceCell<Type> = const { OnceCell::new() };
436    static INTERNED_BYTE: OnceCell<Type> = const { OnceCell::new() };
437}
438
439impl Type {
440    pub fn simple(kind: SimpleKind) -> Type {
441        Self::Simple(kind)
442    }
443
444    pub fn compound(kind: CompoundKind, args: Vec<Type>) -> Type {
445        Self::Compound { kind, args }
446    }
447
448    pub fn int() -> Type {
449        INTERNED_INT.with(|cell| cell.get_or_init(|| Self::simple(SimpleKind::Int)).clone())
450    }
451
452    pub fn string() -> Type {
453        INTERNED_STRING.with(|cell| {
454            cell.get_or_init(|| Self::simple(SimpleKind::String))
455                .clone()
456        })
457    }
458
459    pub fn bool() -> Type {
460        INTERNED_BOOL.with(|cell| cell.get_or_init(|| Self::simple(SimpleKind::Bool)).clone())
461    }
462
463    pub fn unit() -> Type {
464        INTERNED_UNIT.with(|cell| cell.get_or_init(|| Self::simple(SimpleKind::Unit)).clone())
465    }
466
467    pub fn float64() -> Type {
468        INTERNED_FLOAT64.with(|cell| {
469            cell.get_or_init(|| Self::simple(SimpleKind::Float64))
470                .clone()
471        })
472    }
473
474    pub fn rune() -> Type {
475        INTERNED_RUNE.with(|cell| cell.get_or_init(|| Self::simple(SimpleKind::Rune)).clone())
476    }
477
478    pub fn byte() -> Type {
479        INTERNED_BYTE.with(|cell| cell.get_or_init(|| Self::simple(SimpleKind::Byte)).clone())
480    }
481}
482
483impl Type {
484    pub fn uninferred() -> Self {
485        Self::Var {
486            id: TypeVarId::UNINFERRED,
487            hint: None,
488        }
489    }
490
491    pub fn ignored() -> Self {
492        Self::Var {
493            id: TypeVarId::IGNORED,
494            hint: None,
495        }
496    }
497
498    pub fn get_type_params(&self) -> Option<&[Type]> {
499        match self {
500            Type::Nominal { params, .. } => Some(params),
501            Type::Compound { args, .. } => Some(args),
502            _ => None,
503        }
504    }
505
506    /// Direct child types, for read-only walks. Excludes `Function.bounds`.
507    pub fn children(&self) -> Vec<&Type> {
508        match self {
509            Type::Nominal {
510                params,
511                underlying_ty,
512                ..
513            } => {
514                let mut c: Vec<&Type> = params.iter().collect();
515                if let Some(u) = underlying_ty {
516                    c.push(u);
517                }
518                c
519            }
520            Type::Compound { args, .. } => args.iter().collect(),
521            Type::Function {
522                params,
523                return_type,
524                ..
525            } => {
526                let mut c: Vec<&Type> = params.iter().collect();
527                c.push(return_type);
528                c
529            }
530            Type::Tuple(elements) => elements.iter().collect(),
531            Type::Forall { body, .. } => vec![body],
532            _ => vec![],
533        }
534    }
535}
536
537#[derive(Debug, Clone, Copy, PartialEq, Eq)]
538pub enum NumericFamily {
539    SignedInt,
540    UnsignedInt,
541    Float,
542}
543
544#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
545#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
546pub enum CompoundKind {
547    Ref,
548    Slice,
549    EnumeratedSlice,
550    Map,
551    Channel,
552    Sender,
553    Receiver,
554    VarArgs,
555}
556
557impl CompoundKind {
558    pub fn leaf_name(self) -> &'static str {
559        match self {
560            CompoundKind::Ref => "Ref",
561            CompoundKind::Slice => "Slice",
562            CompoundKind::EnumeratedSlice => "EnumeratedSlice",
563            CompoundKind::Map => "Map",
564            CompoundKind::Channel => "Channel",
565            CompoundKind::Sender => "Sender",
566            CompoundKind::Receiver => "Receiver",
567            CompoundKind::VarArgs => "VarArgs",
568        }
569    }
570
571    pub fn from_name(name: &str) -> Option<CompoundKind> {
572        Some(match name {
573            "Ref" => CompoundKind::Ref,
574            "Slice" => CompoundKind::Slice,
575            "EnumeratedSlice" => CompoundKind::EnumeratedSlice,
576            "Map" => CompoundKind::Map,
577            "Channel" => CompoundKind::Channel,
578            "Sender" => CompoundKind::Sender,
579            "Receiver" => CompoundKind::Receiver,
580            "VarArgs" => CompoundKind::VarArgs,
581            _ => return None,
582        })
583    }
584}
585
586#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
587#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
588pub enum SimpleKind {
589    Int,
590    Int8,
591    Int16,
592    Int32,
593    Int64,
594    Uint,
595    Uint8,
596    Uint16,
597    Uint32,
598    Uint64,
599    Uintptr,
600    Byte,
601    Float32,
602    Float64,
603    Complex64,
604    Complex128,
605    Rune,
606    Bool,
607    String,
608    Unit,
609}
610
611impl SimpleKind {
612    pub fn leaf_name(self) -> &'static str {
613        match self {
614            SimpleKind::Int => "int",
615            SimpleKind::Int8 => "int8",
616            SimpleKind::Int16 => "int16",
617            SimpleKind::Int32 => "int32",
618            SimpleKind::Int64 => "int64",
619            SimpleKind::Uint => "uint",
620            SimpleKind::Uint8 => "uint8",
621            SimpleKind::Uint16 => "uint16",
622            SimpleKind::Uint32 => "uint32",
623            SimpleKind::Uint64 => "uint64",
624            SimpleKind::Uintptr => "uintptr",
625            SimpleKind::Byte => "byte",
626            SimpleKind::Float32 => "float32",
627            SimpleKind::Float64 => "float64",
628            SimpleKind::Complex64 => "complex64",
629            SimpleKind::Complex128 => "complex128",
630            SimpleKind::Rune => "rune",
631            SimpleKind::Bool => "bool",
632            SimpleKind::String => "string",
633            SimpleKind::Unit => "Unit",
634        }
635    }
636
637    pub fn from_name(name: &str) -> Option<SimpleKind> {
638        Some(match name {
639            "int" => SimpleKind::Int,
640            "int8" => SimpleKind::Int8,
641            "int16" => SimpleKind::Int16,
642            "int32" => SimpleKind::Int32,
643            "int64" => SimpleKind::Int64,
644            "uint" => SimpleKind::Uint,
645            "uint8" => SimpleKind::Uint8,
646            "uint16" => SimpleKind::Uint16,
647            "uint32" => SimpleKind::Uint32,
648            "uint64" => SimpleKind::Uint64,
649            "uintptr" => SimpleKind::Uintptr,
650            "byte" => SimpleKind::Byte,
651            "float32" => SimpleKind::Float32,
652            "float64" => SimpleKind::Float64,
653            "complex64" => SimpleKind::Complex64,
654            "complex128" => SimpleKind::Complex128,
655            "rune" => SimpleKind::Rune,
656            "bool" => SimpleKind::Bool,
657            "string" => SimpleKind::String,
658            "Unit" => SimpleKind::Unit,
659            _ => return None,
660        })
661    }
662
663    pub fn is_arithmetic(self) -> bool {
664        !matches!(
665            self,
666            SimpleKind::Bool | SimpleKind::String | SimpleKind::Unit | SimpleKind::Uintptr
667        )
668    }
669
670    pub fn is_ordered(self) -> bool {
671        self.is_arithmetic() && !matches!(self, SimpleKind::Complex64 | SimpleKind::Complex128)
672    }
673
674    pub fn is_unsigned_int(self) -> bool {
675        matches!(
676            self,
677            SimpleKind::Byte
678                | SimpleKind::Uint
679                | SimpleKind::Uint8
680                | SimpleKind::Uint16
681                | SimpleKind::Uint32
682                | SimpleKind::Uint64
683        )
684    }
685
686    pub fn is_signed_int(self) -> bool {
687        matches!(
688            self,
689            SimpleKind::Int
690                | SimpleKind::Int8
691                | SimpleKind::Int16
692                | SimpleKind::Int32
693                | SimpleKind::Int64
694                | SimpleKind::Rune
695        )
696    }
697
698    pub fn is_float(self) -> bool {
699        matches!(self, SimpleKind::Float32 | SimpleKind::Float64)
700    }
701
702    pub fn is_complex(self) -> bool {
703        matches!(self, SimpleKind::Complex64 | SimpleKind::Complex128)
704    }
705
706    pub fn numeric_family(self) -> Option<NumericFamily> {
707        if self.is_signed_int() {
708            Some(NumericFamily::SignedInt)
709        } else if self.is_unsigned_int() {
710            Some(NumericFamily::UnsignedInt)
711        } else if self.is_float() {
712            Some(NumericFamily::Float)
713        } else {
714            None
715        }
716    }
717}
718
719impl Type {
720    pub fn get_function_ret(&self) -> Option<&Type> {
721        match self {
722            Type::Function { return_type, .. } => Some(return_type),
723            _ => None,
724        }
725    }
726
727    pub fn has_name(&self, name: &str) -> bool {
728        match self {
729            Type::Nominal { id, .. } => id.last_segment() == name,
730            Type::Simple(kind) => kind.leaf_name() == name,
731            Type::Compound { kind, .. } => kind.leaf_name() == name,
732            _ => false,
733        }
734    }
735
736    pub fn get_qualified_id(&self) -> Option<&str> {
737        match self {
738            Type::Nominal { id, .. } => Some(id.as_str()),
739            _ => None,
740        }
741    }
742
743    pub fn get_underlying(&self) -> Option<&Type> {
744        match self {
745            Type::Nominal {
746                underlying_ty: underlying,
747                ..
748            } => underlying.as_deref(),
749            _ => None,
750        }
751    }
752
753    pub fn is_result(&self) -> bool {
754        self.has_qualified_id("prelude.Result")
755    }
756
757    pub fn is_option(&self) -> bool {
758        self.has_qualified_id("prelude.Option")
759    }
760
761    pub fn is_partial(&self) -> bool {
762        self.has_qualified_id("prelude.Partial")
763    }
764
765    fn has_qualified_id(&self, qualified_id: &str) -> bool {
766        matches!(self, Type::Nominal { id, .. } if id.as_str() == qualified_id)
767    }
768
769    pub fn is_unit(&self) -> bool {
770        self.is_simple(SimpleKind::Unit)
771    }
772
773    pub fn tuple_arity(&self) -> Option<usize> {
774        match self {
775            Type::Tuple(elements) => Some(elements.len()),
776            _ => None,
777        }
778    }
779
780    pub fn is_tuple(&self) -> bool {
781        matches!(self, Type::Tuple(_))
782    }
783
784    pub fn as_import_namespace(&self) -> Option<&str> {
785        match self {
786            Type::ImportNamespace(module_id) => Some(module_id),
787            _ => None,
788        }
789    }
790
791    pub fn as_compound(&self) -> Option<(CompoundKind, &[Type])> {
792        match self {
793            Type::Compound { kind, args } => Some((*kind, args.as_slice())),
794            Type::Nominal { id, params, .. } => {
795                CompoundKind::from_name(id.last_segment()).map(|k| (k, params.as_slice()))
796            }
797            _ => None,
798        }
799    }
800
801    pub fn is_native(&self, kind: CompoundKind) -> bool {
802        self.as_compound().is_some_and(|(k, _)| k == kind)
803    }
804
805    pub fn is_ref(&self) -> bool {
806        self.is_native(CompoundKind::Ref)
807    }
808
809    pub fn is_slice(&self) -> bool {
810        self.is_native(CompoundKind::Slice)
811    }
812
813    pub fn is_map(&self) -> bool {
814        self.is_native(CompoundKind::Map)
815    }
816
817    pub fn is_channel(&self) -> bool {
818        self.is_native(CompoundKind::Channel)
819    }
820
821    pub fn is_receiver_placeholder(&self) -> bool {
822        matches!(self, Type::ReceiverPlaceholder)
823    }
824
825    pub fn is_unknown(&self) -> bool {
826        self.has_name("Unknown")
827    }
828
829    pub fn resolves_to_unknown(&self) -> bool {
830        peel_alias(self, |_| true).is_unknown()
831    }
832
833    pub fn contains_unknown(&self) -> bool {
834        let peeled = peel_alias(self, |_| true);
835        if peeled.is_unknown() {
836            return true;
837        }
838        match &peeled {
839            Type::Compound { args, .. } => args.iter().any(|a| a.contains_unknown()),
840            Type::Function {
841                params,
842                return_type,
843                ..
844            } => params.iter().any(|p| p.contains_unknown()) || return_type.contains_unknown(),
845            Type::Tuple(elements) => elements.iter().any(|e| e.contains_unknown()),
846            Type::Nominal { params, .. } => params.iter().any(|p| p.contains_unknown()),
847            Type::Forall { body, .. } => body.contains_unknown(),
848            _ => false,
849        }
850    }
851
852    pub fn is_receiver(&self) -> bool {
853        self.is_native(CompoundKind::Receiver)
854    }
855
856    pub fn is_ignored(&self) -> bool {
857        matches!(self, Type::Var { id, .. } if *id == TypeVarId::IGNORED)
858    }
859
860    pub fn is_variadic(&self) -> Option<Type> {
861        let last = self.get_function_params()?.last()?;
862        match last.as_compound()? {
863            (CompoundKind::VarArgs, _) => last.inner(),
864            _ => None,
865        }
866    }
867
868    pub fn is_string(&self) -> bool {
869        self.is_simple(SimpleKind::String)
870    }
871
872    pub fn is_slice_of_simple(&self, element: SimpleKind) -> bool {
873        match self.as_compound() {
874            Some((CompoundKind::Slice, [elem])) => elem.is_simple(element),
875            _ => false,
876        }
877    }
878
879    pub fn is_slice_of(&self, element_name: &str) -> bool {
880        match self.as_compound() {
881            Some((CompoundKind::Slice, [elem])) => elem.has_name(element_name),
882            _ => false,
883        }
884    }
885
886    pub fn is_byte_slice(&self) -> bool {
887        self.is_slice_of_simple(SimpleKind::Byte) || self.is_slice_of_simple(SimpleKind::Uint8)
888    }
889
890    pub fn is_rune_slice(&self) -> bool {
891        self.is_slice_of_simple(SimpleKind::Rune)
892    }
893
894    pub fn is_byte_or_rune_slice(&self) -> bool {
895        self.is_byte_slice() || self.is_rune_slice()
896    }
897
898    pub fn has_byte_or_rune_slice_underlying(&self) -> bool {
899        if self.is_byte_or_rune_slice() {
900            return true;
901        }
902        match self {
903            Type::Nominal { underlying_ty, .. } => underlying_ty
904                .as_deref()
905                .is_some_and(|u| u.has_byte_or_rune_slice_underlying()),
906            _ => false,
907        }
908    }
909
910    pub fn as_simple(&self) -> Option<SimpleKind> {
911        match self {
912            Type::Simple(kind) => Some(*kind),
913            Type::Nominal { id, .. } => SimpleKind::from_name(id.last_segment()),
914            _ => None,
915        }
916    }
917
918    pub fn is_simple(&self, kind: SimpleKind) -> bool {
919        self.as_simple() == Some(kind)
920    }
921
922    pub fn is_boolean(&self) -> bool {
923        self.is_simple(SimpleKind::Bool)
924    }
925
926    pub fn is_rune(&self) -> bool {
927        self.is_simple(SimpleKind::Rune)
928    }
929
930    pub fn is_float64(&self) -> bool {
931        self.is_simple(SimpleKind::Float64)
932    }
933
934    pub fn is_float32(&self) -> bool {
935        self.is_simple(SimpleKind::Float32)
936    }
937
938    pub fn is_float(&self) -> bool {
939        self.as_simple().is_some_and(SimpleKind::is_float)
940    }
941
942    pub fn is_variable(&self) -> bool {
943        matches!(self, Type::Var { .. })
944    }
945
946    pub fn is_type_var(&self) -> bool {
947        matches!(self, Type::Var { .. })
948    }
949
950    pub fn is_numeric(&self) -> bool {
951        self.as_simple().is_some_and(SimpleKind::is_arithmetic)
952    }
953
954    pub fn is_ordered(&self) -> bool {
955        self.as_simple().is_some_and(SimpleKind::is_ordered)
956    }
957
958    /// True for Go's `cmp.Ordered` set: ints, floats, strings, and named aliases over them.
959    pub fn satisfies_ordered_constraint(&self) -> bool {
960        if let Some(kind) = self.as_simple() {
961            return matches!(
962                kind,
963                SimpleKind::Int
964                    | SimpleKind::Int8
965                    | SimpleKind::Int16
966                    | SimpleKind::Int32
967                    | SimpleKind::Int64
968                    | SimpleKind::Uint
969                    | SimpleKind::Uint8
970                    | SimpleKind::Uint16
971                    | SimpleKind::Uint32
972                    | SimpleKind::Uint64
973                    | SimpleKind::Uintptr
974                    | SimpleKind::Byte
975                    | SimpleKind::Rune
976                    | SimpleKind::Float32
977                    | SimpleKind::Float64
978                    | SimpleKind::String
979            );
980        }
981        match self {
982            Type::Nominal { underlying_ty, .. } => underlying_ty
983                .as_deref()
984                .is_some_and(Type::satisfies_ordered_constraint),
985            Type::Parameter(_) => true,
986            _ => false,
987        }
988    }
989
990    pub fn is_complex(&self) -> bool {
991        self.as_simple().is_some_and(SimpleKind::is_complex)
992    }
993
994    pub fn is_unsigned_int(&self) -> bool {
995        self.as_simple().is_some_and(SimpleKind::is_unsigned_int)
996    }
997
998    pub fn is_never(&self) -> bool {
999        matches!(self, Type::Never)
1000    }
1001
1002    pub fn is_error(&self) -> bool {
1003        matches!(self, Type::Error)
1004    }
1005
1006    pub fn contains_error(&self) -> bool {
1007        match self {
1008            Type::Error => true,
1009            Type::Nominal {
1010                params,
1011                underlying_ty,
1012                ..
1013            } => {
1014                params.iter().any(Type::contains_error)
1015                    || underlying_ty.as_deref().is_some_and(Type::contains_error)
1016            }
1017            Type::Compound { args, .. } => args.iter().any(Type::contains_error),
1018            Type::Function {
1019                params,
1020                return_type,
1021                ..
1022            } => params.iter().any(Type::contains_error) || return_type.contains_error(),
1023            Type::Tuple(elements) => elements.iter().any(Type::contains_error),
1024            Type::Forall { body, .. } => body.contains_error(),
1025            _ => false,
1026        }
1027    }
1028
1029    pub fn has_unbound_variables(&self) -> bool {
1030        match self {
1031            Type::Var { hint, .. } => hint.is_some(),
1032            Type::Nominal { params, .. } => params.iter().any(|p| p.has_unbound_variables()),
1033            Type::Function {
1034                params,
1035                return_type,
1036                ..
1037            } => {
1038                params.iter().any(|p| p.has_unbound_variables())
1039                    || return_type.has_unbound_variables()
1040            }
1041            Type::Forall { body, .. } => body.has_unbound_variables(),
1042            Type::Tuple(elements) => elements.iter().any(|e| e.has_unbound_variables()),
1043            Type::Compound { args, .. } => args.iter().any(|a| a.has_unbound_variables()),
1044            Type::Simple(_)
1045            | Type::Parameter(_)
1046            | Type::Never
1047            | Type::Error
1048            | Type::ImportNamespace(_)
1049            | Type::ReceiverPlaceholder => false,
1050        }
1051    }
1052
1053    pub fn remove_found_type_names(&self, names: &mut HashSet<EcoString>) {
1054        if names.is_empty() {
1055            return;
1056        }
1057
1058        match self {
1059            Type::Nominal { id, params, .. } => {
1060                names.remove(id.last_segment());
1061                for param in params {
1062                    param.remove_found_type_names(names);
1063                }
1064            }
1065            Type::Function {
1066                params,
1067                return_type,
1068                bounds,
1069                ..
1070            } => {
1071                for param in params {
1072                    param.remove_found_type_names(names);
1073                }
1074                return_type.remove_found_type_names(names);
1075                for bound in bounds {
1076                    bound.generic.remove_found_type_names(names);
1077                    bound.ty.remove_found_type_names(names);
1078                }
1079            }
1080            Type::Forall { body, .. } => {
1081                body.remove_found_type_names(names);
1082            }
1083            Type::Var { .. } => {}
1084            Type::Parameter(name) => {
1085                names.remove(name);
1086            }
1087            Type::Tuple(elements) => {
1088                for element in elements {
1089                    element.remove_found_type_names(names);
1090                }
1091            }
1092            Type::Compound { kind, args } => {
1093                names.remove(kind.leaf_name());
1094                for arg in args {
1095                    arg.remove_found_type_names(names);
1096                }
1097            }
1098            Type::Simple(kind) => {
1099                names.remove(kind.leaf_name());
1100            }
1101            Type::Never | Type::Error | Type::ImportNamespace(_) | Type::ReceiverPlaceholder => {}
1102        }
1103    }
1104}
1105
1106impl Type {
1107    pub fn get_name(&self) -> Option<&str> {
1108        match self {
1109            Type::Simple(kind) => Some(kind.leaf_name()),
1110            Type::Compound { kind, args } => match kind {
1111                CompoundKind::Ref => args.first().and_then(|inner| inner.get_name()),
1112                _ => Some(kind.leaf_name()),
1113            },
1114            Type::Nominal { id, params, .. } => {
1115                let name = id.last_segment();
1116                if CompoundKind::from_name(name) == Some(CompoundKind::Ref) {
1117                    return params.first().and_then(|inner| inner.get_name());
1118                }
1119                Some(name)
1120            }
1121            Type::ImportNamespace(module_id) => {
1122                let path = module_id.strip_prefix("go:").unwrap_or(module_id);
1123                path.rsplit('/').next()
1124            }
1125            _ => None,
1126        }
1127    }
1128
1129    pub fn wraps(&self, name: &str, inner: &Type) -> bool {
1130        self.get_name().is_some_and(|n| n == name)
1131            && self
1132                .get_type_params()
1133                .and_then(|p| p.first())
1134                .is_some_and(|first| *first == *inner)
1135    }
1136
1137    pub fn get_function_params(&self) -> Option<&[Type]> {
1138        match self {
1139            Type::Function { params, .. } => Some(params),
1140            Type::Nominal {
1141                underlying_ty: Some(inner),
1142                ..
1143            } => inner.get_function_params(),
1144            _ => None,
1145        }
1146    }
1147
1148    pub fn param_count(&self) -> usize {
1149        match self {
1150            Type::Function { params, .. } => params.len(),
1151            _ => 0,
1152        }
1153    }
1154
1155    pub fn get_param_mutability(&self) -> &[bool] {
1156        match self {
1157            Type::Function {
1158                param_mutability, ..
1159            } => param_mutability,
1160            _ => &[],
1161        }
1162    }
1163
1164    pub fn with_replaced_first_param(&self, new_first: &Type) -> Type {
1165        match self {
1166            Type::Function {
1167                params,
1168                param_mutability,
1169                bounds,
1170                return_type,
1171            } => {
1172                if params.is_empty() {
1173                    return self.clone();
1174                }
1175                let mut new_params = params.clone();
1176                new_params[0] = new_first.clone();
1177                Type::Function {
1178                    params: new_params,
1179                    param_mutability: param_mutability.clone(),
1180                    bounds: bounds.clone(),
1181                    return_type: return_type.clone(),
1182                }
1183            }
1184            Type::Forall { vars, body } => Type::Forall {
1185                vars: vars.clone(),
1186                body: Box::new(body.with_replaced_first_param(new_first)),
1187            },
1188            _ => self.clone(),
1189        }
1190    }
1191
1192    pub fn get_bounds(&self) -> &[Bound] {
1193        match self {
1194            Type::Function { bounds, .. } => bounds,
1195            Type::Forall { body, .. } => body.get_bounds(),
1196            _ => &[],
1197        }
1198    }
1199
1200    pub fn get_qualified_name(&self) -> Symbol {
1201        match self.strip_refs() {
1202            Type::Nominal { id, .. } => id,
1203            Type::Simple(kind) => Symbol::from_parts("prelude", kind.leaf_name()),
1204            Type::Compound { kind, .. } => Symbol::from_parts("prelude", kind.leaf_name()),
1205            _ => panic!("called get_qualified_name on {:#?}", self),
1206        }
1207    }
1208
1209    pub fn inner(&self) -> Option<Type> {
1210        self.get_type_params()
1211            .and_then(|args| args.first().cloned())
1212    }
1213
1214    pub fn ok_type(&self) -> Type {
1215        debug_assert!(
1216            self.is_result() || self.is_option() || self.is_partial(),
1217            "ok_type called on non-Result/Option/Partial type"
1218        );
1219        self.inner()
1220            .expect("Result/Option/Partial should have inner type")
1221    }
1222
1223    pub fn err_type(&self) -> Type {
1224        debug_assert!(
1225            self.is_result() || self.is_partial(),
1226            "err_type called on non-Result/Partial type"
1227        );
1228        self.get_type_params()
1229            .and_then(|args| args.get(1).cloned())
1230            .expect("Result/Partial should have error type")
1231    }
1232}
1233
1234/// Walk an alias chain via `underlying_ty` (preserves substitution); cycle
1235/// guard defends against chains that slip past `circular_type_alias`.
1236pub fn peel_alias<F>(ty: &Type, is_alias: F) -> Type
1237where
1238    F: Fn(&str) -> bool,
1239{
1240    let mut current = ty.unwrap_forall().clone();
1241    let mut seen: Vec<String> = Vec::new();
1242    while let Type::Nominal {
1243        id,
1244        underlying_ty: Some(u),
1245        ..
1246    } = &current
1247    {
1248        if !is_alias(id.as_str()) {
1249            break;
1250        }
1251        if seen.iter().any(|s| s == id.as_str()) {
1252            break;
1253        }
1254        seen.push(id.to_string());
1255        current = u.unwrap_forall().clone();
1256    }
1257    current
1258}
1259
1260/// Walk an alias chain by id alone; used when no `Type` with
1261/// `underlying_ty` is available (e.g. Go-name resolution).
1262pub fn peel_alias_id<F>(id: &str, next_alias: F) -> String
1263where
1264    F: Fn(&str) -> Option<String>,
1265{
1266    let mut current = id.to_string();
1267    let mut seen: Vec<String> = Vec::new();
1268    loop {
1269        if seen.iter().any(|s| s == &current) {
1270            return current;
1271        }
1272        let Some(next) = next_alias(&current) else {
1273            return current;
1274        };
1275        seen.push(current);
1276        current = next;
1277    }
1278}
1279
1280impl Type {
1281    pub fn unwrap_forall(&self) -> &Type {
1282        match self {
1283            Type::Forall { body, .. } => body.as_ref(),
1284            other => other,
1285        }
1286    }
1287
1288    pub fn strip_refs(&self) -> Type {
1289        if self.is_ref() {
1290            return self.inner().expect("ref type must have inner").strip_refs();
1291        }
1292
1293        self.clone()
1294    }
1295
1296    pub fn with_receiver_placeholder(self) -> Type {
1297        match self {
1298            Type::Function {
1299                params,
1300                param_mutability,
1301                bounds,
1302                return_type,
1303            } => {
1304                let mut new_params = vec![Type::ReceiverPlaceholder];
1305                new_params.extend(params);
1306
1307                let mut new_mutability = vec![false];
1308                new_mutability.extend(param_mutability);
1309
1310                Type::Function {
1311                    params: new_params,
1312                    param_mutability: new_mutability,
1313                    bounds,
1314                    return_type,
1315                }
1316            }
1317            _ => unreachable!(
1318                "with_receiver_placeholder called on non-function type: {:?}",
1319                self
1320            ),
1321        }
1322    }
1323
1324    pub fn remove_vars(types: &[&Type]) -> (Vec<Type>, Vec<EcoString>) {
1325        let mut vars = HashMap::default();
1326        let types = types
1327            .iter()
1328            .map(|v| Self::remove_vars_impl(v, &mut vars))
1329            .collect();
1330
1331        (types, vars.into_values().collect())
1332    }
1333
1334    fn remove_vars_impl(ty: &Type, vars: &mut HashMap<u32, EcoString>) -> Type {
1335        match ty {
1336            Type::Nominal {
1337                id: name,
1338                params: args,
1339                underlying_ty: underlying,
1340            } => Type::Nominal {
1341                id: name.clone(),
1342                params: args
1343                    .iter()
1344                    .map(|a| Self::remove_vars_impl(a, vars))
1345                    .collect(),
1346                underlying_ty: underlying
1347                    .as_ref()
1348                    .map(|u| Box::new(Self::remove_vars_impl(u, vars))),
1349            },
1350
1351            Type::Function {
1352                params: args,
1353                param_mutability,
1354                bounds,
1355                return_type,
1356            } => Type::Function {
1357                params: args
1358                    .iter()
1359                    .map(|a| Self::remove_vars_impl(a, vars))
1360                    .collect(),
1361                param_mutability: param_mutability.clone(),
1362                bounds: bounds
1363                    .iter()
1364                    .map(|b| Bound {
1365                        param_name: b.param_name.clone(),
1366                        generic: Self::remove_vars_impl(&b.generic, vars),
1367                        ty: Self::remove_vars_impl(&b.ty, vars),
1368                    })
1369                    .collect(),
1370                return_type: Self::remove_vars_impl(return_type, vars).into(),
1371            },
1372
1373            Type::Var { id, hint } => match vars.get(&id.0) {
1374                Some(g) => Type::Parameter(g.clone()),
1375                None => {
1376                    let name: EcoString = hint
1377                        .clone()
1378                        .unwrap_or_else(|| alpha_index(vars.len()).into());
1379
1380                    vars.insert(id.0, name.clone());
1381                    Type::Parameter(name)
1382                }
1383            },
1384
1385            Type::Forall { body, .. } => Self::remove_vars_impl(body, vars),
1386            Type::Tuple(elements) => Type::Tuple(
1387                elements
1388                    .iter()
1389                    .map(|e| Self::remove_vars_impl(e, vars))
1390                    .collect(),
1391            ),
1392            Type::Compound { kind, args } => Type::Compound {
1393                kind: *kind,
1394                args: args
1395                    .iter()
1396                    .map(|a| Self::remove_vars_impl(a, vars))
1397                    .collect(),
1398            },
1399            Type::Simple(_) | Type::Parameter(_) => ty.clone(),
1400            Type::Never | Type::Error | Type::ImportNamespace(_) | Type::ReceiverPlaceholder => {
1401                ty.clone()
1402            }
1403        }
1404    }
1405
1406    pub fn contains_type(&self, target: &Type) -> bool {
1407        if *self == *target {
1408            return true;
1409        }
1410        match self {
1411            Type::Nominal { params, .. } => params.iter().any(|p| p.contains_type(target)),
1412            Type::Function {
1413                params,
1414                return_type,
1415                ..
1416            } => {
1417                params.iter().any(|p| p.contains_type(target)) || return_type.contains_type(target)
1418            }
1419            Type::Var { .. } => false,
1420            Type::Forall { body, .. } => body.contains_type(target),
1421            Type::Tuple(elements) => elements.iter().any(|e| e.contains_type(target)),
1422            Type::Compound { args, .. } => args.iter().any(|a| a.contains_type(target)),
1423            Type::Simple(_)
1424            | Type::Parameter(_)
1425            | Type::Never
1426            | Type::Error
1427            | Type::ImportNamespace(_)
1428            | Type::ReceiverPlaceholder => false,
1429        }
1430    }
1431}
1432
1433impl Type {
1434    pub fn underlying_numeric_type(&self) -> Option<Type> {
1435        self.underlying_numeric_type_recursive(&mut HashSet::default())
1436    }
1437
1438    pub fn has_underlying_numeric_type(&self) -> bool {
1439        self.underlying_numeric_type().is_some()
1440    }
1441
1442    fn underlying_numeric_type_recursive(&self, visited: &mut HashSet<Symbol>) -> Option<Type> {
1443        match self {
1444            Type::Simple(_) if self.is_numeric() => Some(self.clone()),
1445            Type::Nominal {
1446                id,
1447                underlying_ty: underlying,
1448                ..
1449            } => {
1450                if self.is_numeric() {
1451                    return Some(self.clone());
1452                }
1453
1454                if !visited.insert(id.clone()) {
1455                    return None;
1456                }
1457
1458                underlying
1459                    .as_ref()?
1460                    .underlying_numeric_type_recursive(visited)
1461            }
1462            _ => None,
1463        }
1464    }
1465
1466    pub fn numeric_family(&self) -> Option<NumericFamily> {
1467        self.as_simple()?.numeric_family()
1468    }
1469
1470    pub fn is_numeric_compatible_with(&self, other: &Type) -> bool {
1471        let self_underlying_ty = self.underlying_numeric_type();
1472        let other_underlying_ty = other.underlying_numeric_type();
1473
1474        match (self_underlying_ty, other_underlying_ty) {
1475            (Some(s), Some(o)) => s.numeric_family() == o.numeric_family(),
1476            _ => false,
1477        }
1478    }
1479
1480    pub fn is_aliased_numeric_type(&self) -> bool {
1481        match self {
1482            Type::Nominal { underlying_ty, .. } => {
1483                underlying_ty.is_some() && !self.is_numeric() && self.has_underlying_numeric_type()
1484            }
1485            _ => false,
1486        }
1487    }
1488}
1489
1490/// 0 → "A", 25 → "Z", 26 → "AA", 27 → "AB", ... (bijective base-26 over A-Z).
1491fn alpha_index(idx: usize) -> String {
1492    let mut s = String::new();
1493    let mut n = idx + 1;
1494    while n > 0 {
1495        n -= 1;
1496        s.insert(0, (b'A' + (n % 26) as u8) as char);
1497        n /= 26;
1498    }
1499    s
1500}
1501
1502#[cfg(test)]
1503mod tests {
1504    use super::*;
1505
1506    #[test]
1507    fn alpha_index_single() {
1508        assert_eq!(alpha_index(0), "A");
1509        assert_eq!(alpha_index(5), "F");
1510        assert_eq!(alpha_index(25), "Z");
1511    }
1512
1513    #[test]
1514    fn alpha_index_double() {
1515        assert_eq!(alpha_index(26), "AA");
1516        assert_eq!(alpha_index(27), "AB");
1517        assert_eq!(alpha_index(51), "AZ");
1518        assert_eq!(alpha_index(52), "BA");
1519        assert_eq!(alpha_index(701), "ZZ");
1520    }
1521
1522    #[test]
1523    fn alpha_index_triple() {
1524        assert_eq!(alpha_index(702), "AAA");
1525    }
1526
1527    fn unhinted_var(id: u32) -> Type {
1528        Type::Var {
1529            id: TypeVarId(id),
1530            hint: None,
1531        }
1532    }
1533
1534    #[test]
1535    fn remove_vars_handles_more_than_six_unhinted_vars() {
1536        let func = Type::Function {
1537            params: (0..6).map(unhinted_var).collect(),
1538            param_mutability: vec![false; 6],
1539            bounds: vec![],
1540            return_type: Box::new(unhinted_var(6)),
1541        };
1542
1543        let (resolved, generics) = Type::remove_vars(&[&func]);
1544
1545        assert_eq!(generics.len(), 7);
1546        let Type::Function {
1547            params,
1548            return_type,
1549            ..
1550        } = &resolved[0]
1551        else {
1552            panic!("expected function type");
1553        };
1554        let names: Vec<_> = params
1555            .iter()
1556            .chain(std::iter::once(return_type.as_ref()))
1557            .map(|p| match p {
1558                Type::Parameter(name) => name.to_string(),
1559                other => panic!("expected parameter, got {:?}", other),
1560            })
1561            .collect();
1562        assert_eq!(names, vec!["A", "B", "C", "D", "E", "F", "G"]);
1563    }
1564
1565    #[test]
1566    fn remove_vars_handles_dozens_of_unhinted_vars() {
1567        let params: Vec<Type> = (0..30).map(unhinted_var).collect();
1568        let func = Type::Function {
1569            params: params.clone(),
1570            param_mutability: vec![false; params.len()],
1571            bounds: vec![],
1572            return_type: Box::new(Type::Simple(SimpleKind::Unit)),
1573        };
1574        let (_, generics) = Type::remove_vars(&[&func]);
1575        assert_eq!(generics.len(), 30);
1576    }
1577}