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