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