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