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
619#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
620#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
621pub enum SimpleKind {
622    Int,
623    Int8,
624    Int16,
625    Int32,
626    Int64,
627    Uint,
628    Uint8,
629    Uint16,
630    Uint32,
631    Uint64,
632    Uintptr,
633    Byte,
634    Float32,
635    Float64,
636    Complex64,
637    Complex128,
638    Rune,
639    Bool,
640    String,
641    Unit,
642}
643
644impl SimpleKind {
645    pub fn leaf_name(self) -> &'static str {
646        match self {
647            SimpleKind::Int => "int",
648            SimpleKind::Int8 => "int8",
649            SimpleKind::Int16 => "int16",
650            SimpleKind::Int32 => "int32",
651            SimpleKind::Int64 => "int64",
652            SimpleKind::Uint => "uint",
653            SimpleKind::Uint8 => "uint8",
654            SimpleKind::Uint16 => "uint16",
655            SimpleKind::Uint32 => "uint32",
656            SimpleKind::Uint64 => "uint64",
657            SimpleKind::Uintptr => "uintptr",
658            SimpleKind::Byte => "byte",
659            SimpleKind::Float32 => "float32",
660            SimpleKind::Float64 => "float64",
661            SimpleKind::Complex64 => "complex64",
662            SimpleKind::Complex128 => "complex128",
663            SimpleKind::Rune => "rune",
664            SimpleKind::Bool => "bool",
665            SimpleKind::String => "string",
666            SimpleKind::Unit => "Unit",
667        }
668    }
669
670    pub fn from_name(name: &str) -> Option<SimpleKind> {
671        Some(match name {
672            "int" => SimpleKind::Int,
673            "int8" => SimpleKind::Int8,
674            "int16" => SimpleKind::Int16,
675            "int32" => SimpleKind::Int32,
676            "int64" => SimpleKind::Int64,
677            "uint" => SimpleKind::Uint,
678            "uint8" => SimpleKind::Uint8,
679            "uint16" => SimpleKind::Uint16,
680            "uint32" => SimpleKind::Uint32,
681            "uint64" => SimpleKind::Uint64,
682            "uintptr" => SimpleKind::Uintptr,
683            "byte" => SimpleKind::Byte,
684            "float32" => SimpleKind::Float32,
685            "float64" => SimpleKind::Float64,
686            "complex64" => SimpleKind::Complex64,
687            "complex128" => SimpleKind::Complex128,
688            "rune" => SimpleKind::Rune,
689            "bool" => SimpleKind::Bool,
690            "string" => SimpleKind::String,
691            "Unit" => SimpleKind::Unit,
692            _ => return None,
693        })
694    }
695
696    pub fn is_arithmetic(self) -> bool {
697        !matches!(
698            self,
699            SimpleKind::Bool | SimpleKind::String | SimpleKind::Unit | SimpleKind::Uintptr
700        )
701    }
702
703    pub fn is_ordered(self) -> bool {
704        self.is_arithmetic() && !matches!(self, SimpleKind::Complex64 | SimpleKind::Complex128)
705    }
706
707    pub fn is_unsigned_int(self) -> bool {
708        matches!(
709            self,
710            SimpleKind::Byte
711                | SimpleKind::Uint
712                | SimpleKind::Uint8
713                | SimpleKind::Uint16
714                | SimpleKind::Uint32
715                | SimpleKind::Uint64
716        )
717    }
718
719    pub fn is_signed_int(self) -> bool {
720        matches!(
721            self,
722            SimpleKind::Int
723                | SimpleKind::Int8
724                | SimpleKind::Int16
725                | SimpleKind::Int32
726                | SimpleKind::Int64
727                | SimpleKind::Rune
728        )
729    }
730
731    pub fn is_float(self) -> bool {
732        matches!(self, SimpleKind::Float32 | SimpleKind::Float64)
733    }
734
735    pub fn is_complex(self) -> bool {
736        matches!(self, SimpleKind::Complex64 | SimpleKind::Complex128)
737    }
738
739    pub fn numeric_family(self) -> Option<NumericFamily> {
740        if self.is_signed_int() {
741            Some(NumericFamily::SignedInt)
742        } else if self.is_unsigned_int() {
743            Some(NumericFamily::UnsignedInt)
744        } else if self.is_float() {
745            Some(NumericFamily::Float)
746        } else {
747            None
748        }
749    }
750}
751
752impl Type {
753    pub fn get_function_ret(&self) -> Option<&Type> {
754        match self {
755            Type::Function(f) => Some(&f.return_type),
756            _ => None,
757        }
758    }
759
760    pub fn is_stringer_signature(&self) -> bool {
761        let func = match self {
762            Type::Forall { body, .. } => body.as_ref(),
763            other => other,
764        };
765        matches!(
766            func,
767            Type::Function(f)
768                if f.params.len() == 1
769                    && matches!(f.return_type.as_ref(), Type::Simple(SimpleKind::String))
770        )
771    }
772
773    pub fn has_name(&self, name: &str) -> bool {
774        match self {
775            Type::Nominal { id, .. } => id.last_segment() == name,
776            Type::Simple(kind) => kind.leaf_name() == name,
777            Type::Compound { kind, .. } => kind.leaf_name() == name,
778            _ => false,
779        }
780    }
781
782    pub fn get_qualified_id(&self) -> Option<&str> {
783        match self {
784            Type::Nominal { id, .. } => Some(id.as_str()),
785            _ => None,
786        }
787    }
788
789    pub fn get_underlying(&self) -> Option<&Type> {
790        match self {
791            Type::Nominal {
792                underlying_ty: underlying,
793                ..
794            } => underlying.as_deref(),
795            _ => None,
796        }
797    }
798
799    pub fn is_result(&self) -> bool {
800        self.has_qualified_id("prelude.Result")
801    }
802
803    pub fn is_option(&self) -> bool {
804        self.has_qualified_id("prelude.Option")
805    }
806
807    pub fn is_partial(&self) -> bool {
808        self.has_qualified_id("prelude.Partial")
809    }
810
811    fn has_qualified_id(&self, qualified_id: &str) -> bool {
812        matches!(self, Type::Nominal { id, .. } if id.as_str() == qualified_id)
813    }
814
815    pub fn is_unit(&self) -> bool {
816        self.is_simple(SimpleKind::Unit)
817    }
818
819    pub fn tuple_arity(&self) -> Option<usize> {
820        match self {
821            Type::Tuple(elements) => Some(elements.len()),
822            _ => None,
823        }
824    }
825
826    pub fn is_tuple(&self) -> bool {
827        matches!(self, Type::Tuple(_))
828    }
829
830    pub fn as_import_namespace(&self) -> Option<&str> {
831        match self {
832            Type::ImportNamespace(module_id) => Some(module_id),
833            _ => None,
834        }
835    }
836
837    pub fn as_compound(&self) -> Option<(CompoundKind, &[Type])> {
838        match self {
839            Type::Compound { kind, args } => Some((*kind, args.as_slice())),
840            Type::Nominal { id, params, .. } => {
841                CompoundKind::from_name(id.last_segment()).map(|k| (k, params.as_slice()))
842            }
843            _ => None,
844        }
845    }
846
847    pub fn is_native(&self, kind: CompoundKind) -> bool {
848        self.as_compound().is_some_and(|(k, _)| k == kind)
849    }
850
851    pub fn is_ref(&self) -> bool {
852        self.is_native(CompoundKind::Ref)
853    }
854
855    pub fn is_slice(&self) -> bool {
856        self.is_native(CompoundKind::Slice)
857    }
858
859    pub fn is_map(&self) -> bool {
860        self.is_native(CompoundKind::Map)
861    }
862
863    pub fn is_channel(&self) -> bool {
864        self.is_native(CompoundKind::Channel)
865    }
866
867    pub fn is_receiver_placeholder(&self) -> bool {
868        matches!(self, Type::ReceiverPlaceholder)
869    }
870
871    pub fn is_unknown(&self) -> bool {
872        self.has_name("Unknown")
873    }
874
875    pub fn resolves_to_unknown(&self) -> bool {
876        peel_alias(self, |_| true).is_unknown()
877    }
878
879    pub fn contains_unknown(&self) -> bool {
880        let peeled = peel_alias(self, |_| true);
881        if peeled.is_unknown() {
882            return true;
883        }
884        match &peeled {
885            Type::Compound { args, .. } => args.iter().any(|a| a.contains_unknown()),
886            Type::Function(f) => {
887                f.params.iter().any(|p| p.contains_unknown()) || f.return_type.contains_unknown()
888            }
889            Type::Tuple(elements) => elements.iter().any(|e| e.contains_unknown()),
890            Type::Nominal { params, .. } => params.iter().any(|p| p.contains_unknown()),
891            Type::Forall { body, .. } => body.contains_unknown(),
892            _ => false,
893        }
894    }
895
896    pub fn is_receiver(&self) -> bool {
897        self.is_native(CompoundKind::Receiver)
898    }
899
900    pub fn is_ignored(&self) -> bool {
901        matches!(self, Type::Var { id, .. } if *id == TypeVarId::IGNORED)
902    }
903
904    pub fn is_variadic(&self) -> Option<Type> {
905        let last = self.get_function_params()?.last()?;
906        match last.as_compound()? {
907            (CompoundKind::VarArgs, _) => last.inner(),
908            _ => None,
909        }
910    }
911
912    pub fn is_string(&self) -> bool {
913        self.is_simple(SimpleKind::String)
914    }
915
916    pub fn is_slice_of_simple(&self, element: SimpleKind) -> bool {
917        match self.as_compound() {
918            Some((CompoundKind::Slice, [elem])) => elem.is_simple(element),
919            _ => false,
920        }
921    }
922
923    pub fn is_slice_of(&self, element_name: &str) -> bool {
924        match self.as_compound() {
925            Some((CompoundKind::Slice, [elem])) => elem.has_name(element_name),
926            _ => false,
927        }
928    }
929
930    pub fn is_byte_slice(&self) -> bool {
931        self.is_slice_of_simple(SimpleKind::Byte) || self.is_slice_of_simple(SimpleKind::Uint8)
932    }
933
934    pub fn is_rune_slice(&self) -> bool {
935        self.is_slice_of_simple(SimpleKind::Rune)
936    }
937
938    pub fn is_byte_or_rune_slice(&self) -> bool {
939        self.is_byte_slice() || self.is_rune_slice()
940    }
941
942    pub fn has_underlying_rune(&self) -> bool {
943        self.underlying_numeric_type().is_some_and(|t| t.is_rune())
944    }
945
946    pub fn has_underlying_byte(&self) -> bool {
947        self.underlying_numeric_type()
948            .is_some_and(|t| t.is_simple(SimpleKind::Byte) || t.is_simple(SimpleKind::Uint8))
949    }
950
951    pub fn has_byte_or_rune_slice_underlying(&self) -> bool {
952        if self.is_byte_or_rune_slice() {
953            return true;
954        }
955        match self {
956            Type::Nominal { underlying_ty, .. } => underlying_ty
957                .as_deref()
958                .is_some_and(|u| u.has_byte_or_rune_slice_underlying()),
959            _ => false,
960        }
961    }
962
963    pub fn as_simple(&self) -> Option<SimpleKind> {
964        match self {
965            Type::Simple(kind) => Some(*kind),
966            Type::Nominal { id, .. } => SimpleKind::from_name(id.last_segment()),
967            _ => None,
968        }
969    }
970
971    pub fn is_simple(&self, kind: SimpleKind) -> bool {
972        self.as_simple() == Some(kind)
973    }
974
975    pub fn is_boolean(&self) -> bool {
976        self.is_simple(SimpleKind::Bool)
977    }
978
979    pub fn is_rune(&self) -> bool {
980        self.is_simple(SimpleKind::Rune)
981    }
982
983    pub fn is_float64(&self) -> bool {
984        self.is_simple(SimpleKind::Float64)
985    }
986
987    pub fn is_float32(&self) -> bool {
988        self.is_simple(SimpleKind::Float32)
989    }
990
991    pub fn is_float(&self) -> bool {
992        self.as_simple().is_some_and(SimpleKind::is_float)
993    }
994
995    pub fn is_variable(&self) -> bool {
996        matches!(self, Type::Var { .. })
997    }
998
999    pub fn is_type_var(&self) -> bool {
1000        matches!(self, Type::Var { .. })
1001    }
1002
1003    pub fn is_numeric(&self) -> bool {
1004        self.as_simple().is_some_and(SimpleKind::is_arithmetic)
1005    }
1006
1007    pub fn is_ordered(&self) -> bool {
1008        self.as_simple().is_some_and(SimpleKind::is_ordered)
1009    }
1010
1011    /// True for Go's `cmp.Ordered` set: ints, floats, strings, and named aliases over them.
1012    pub fn satisfies_ordered_constraint(&self) -> bool {
1013        if let Some(kind) = self.as_simple() {
1014            return matches!(
1015                kind,
1016                SimpleKind::Int
1017                    | SimpleKind::Int8
1018                    | SimpleKind::Int16
1019                    | SimpleKind::Int32
1020                    | SimpleKind::Int64
1021                    | SimpleKind::Uint
1022                    | SimpleKind::Uint8
1023                    | SimpleKind::Uint16
1024                    | SimpleKind::Uint32
1025                    | SimpleKind::Uint64
1026                    | SimpleKind::Uintptr
1027                    | SimpleKind::Byte
1028                    | SimpleKind::Rune
1029                    | SimpleKind::Float32
1030                    | SimpleKind::Float64
1031                    | SimpleKind::String
1032            );
1033        }
1034        match self {
1035            Type::Nominal { underlying_ty, .. } => underlying_ty
1036                .as_deref()
1037                .is_some_and(Type::satisfies_ordered_constraint),
1038            Type::Parameter(_) => true,
1039            _ => false,
1040        }
1041    }
1042
1043    pub fn is_complex(&self) -> bool {
1044        self.as_simple().is_some_and(SimpleKind::is_complex)
1045    }
1046
1047    pub fn is_unsigned_int(&self) -> bool {
1048        self.as_simple().is_some_and(SimpleKind::is_unsigned_int)
1049    }
1050
1051    pub fn is_never(&self) -> bool {
1052        matches!(self, Type::Never)
1053    }
1054
1055    pub fn is_error(&self) -> bool {
1056        matches!(self, Type::Error)
1057    }
1058
1059    pub fn contains_error(&self) -> bool {
1060        match self {
1061            Type::Error => true,
1062            Type::Nominal {
1063                params,
1064                underlying_ty,
1065                ..
1066            } => {
1067                params.iter().any(Type::contains_error)
1068                    || underlying_ty.as_deref().is_some_and(Type::contains_error)
1069            }
1070            Type::Compound { args, .. } => args.iter().any(Type::contains_error),
1071            Type::Function(f) => {
1072                f.params.iter().any(Type::contains_error) || f.return_type.contains_error()
1073            }
1074            Type::Tuple(elements) => elements.iter().any(Type::contains_error),
1075            Type::Forall { body, .. } => body.contains_error(),
1076            _ => false,
1077        }
1078    }
1079
1080    pub fn has_unbound_variables(&self) -> bool {
1081        match self {
1082            Type::Var { hint, .. } => hint.is_some(),
1083            Type::Nominal { params, .. } => params.iter().any(|p| p.has_unbound_variables()),
1084            Type::Function(f) => {
1085                f.params.iter().any(|p| p.has_unbound_variables())
1086                    || f.return_type.has_unbound_variables()
1087            }
1088            Type::Forall { body, .. } => body.has_unbound_variables(),
1089            Type::Tuple(elements) => elements.iter().any(|e| e.has_unbound_variables()),
1090            Type::Compound { args, .. } => args.iter().any(|a| a.has_unbound_variables()),
1091            Type::Simple(_)
1092            | Type::Parameter(_)
1093            | Type::Never
1094            | Type::Error
1095            | Type::ImportNamespace(_)
1096            | Type::ReceiverPlaceholder => false,
1097        }
1098    }
1099
1100    pub fn remove_found_type_names(&self, names: &mut HashSet<EcoString>) {
1101        if names.is_empty() {
1102            return;
1103        }
1104
1105        match self {
1106            Type::Nominal { id, params, .. } => {
1107                names.remove(id.last_segment());
1108                for param in params {
1109                    param.remove_found_type_names(names);
1110                }
1111            }
1112            Type::Function(f) => {
1113                for param in &f.params {
1114                    param.remove_found_type_names(names);
1115                }
1116                f.return_type.remove_found_type_names(names);
1117                for bound in &f.bounds {
1118                    bound.generic.remove_found_type_names(names);
1119                    bound.ty.remove_found_type_names(names);
1120                }
1121            }
1122            Type::Forall { body, .. } => {
1123                body.remove_found_type_names(names);
1124            }
1125            Type::Var { .. } => {}
1126            Type::Parameter(name) => {
1127                names.remove(name);
1128            }
1129            Type::Tuple(elements) => {
1130                for element in elements {
1131                    element.remove_found_type_names(names);
1132                }
1133            }
1134            Type::Compound { kind, args } => {
1135                names.remove(kind.leaf_name());
1136                for arg in args {
1137                    arg.remove_found_type_names(names);
1138                }
1139            }
1140            Type::Simple(kind) => {
1141                names.remove(kind.leaf_name());
1142            }
1143            Type::Never | Type::Error | Type::ImportNamespace(_) | Type::ReceiverPlaceholder => {}
1144        }
1145    }
1146}
1147
1148impl Type {
1149    pub fn get_name(&self) -> Option<&str> {
1150        match self {
1151            Type::Simple(kind) => Some(kind.leaf_name()),
1152            Type::Compound { kind, args } => match kind {
1153                CompoundKind::Ref => args.first().and_then(|inner| inner.get_name()),
1154                _ => Some(kind.leaf_name()),
1155            },
1156            Type::Nominal { id, params, .. } => {
1157                let name = id.last_segment();
1158                if CompoundKind::from_name(name) == Some(CompoundKind::Ref) {
1159                    return params.first().and_then(|inner| inner.get_name());
1160                }
1161                Some(name)
1162            }
1163            Type::ImportNamespace(module_id) => {
1164                let path = module_id.strip_prefix("go:").unwrap_or(module_id);
1165                path.rsplit('/').next()
1166            }
1167            _ => None,
1168        }
1169    }
1170
1171    pub fn wraps(&self, name: &str, inner: &Type) -> bool {
1172        self.get_name().is_some_and(|n| n == name)
1173            && self
1174                .get_type_params()
1175                .and_then(|p| p.first())
1176                .is_some_and(|first| *first == *inner)
1177    }
1178
1179    pub fn get_function_params(&self) -> Option<&[Type]> {
1180        match self {
1181            Type::Function(f) => Some(&f.params),
1182            Type::Nominal {
1183                underlying_ty: Some(inner),
1184                ..
1185            } => inner.get_function_params(),
1186            _ => None,
1187        }
1188    }
1189
1190    pub fn param_count(&self) -> usize {
1191        match self {
1192            Type::Function(f) => f.params.len(),
1193            _ => 0,
1194        }
1195    }
1196
1197    pub fn get_param_mutability(&self) -> &[bool] {
1198        match self {
1199            Type::Function(f) => &f.param_mutability,
1200            _ => &[],
1201        }
1202    }
1203
1204    pub fn with_replaced_first_param(&self, new_first: &Type) -> Type {
1205        match self {
1206            Type::Function(f) => {
1207                if f.params.is_empty() {
1208                    return self.clone();
1209                }
1210                let mut new_params = f.params.clone();
1211                new_params[0] = new_first.clone();
1212                Type::function(
1213                    new_params,
1214                    f.param_mutability.clone(),
1215                    f.bounds.clone(),
1216                    f.return_type.clone(),
1217                )
1218            }
1219            Type::Forall { vars, body } => Type::Forall {
1220                vars: vars.clone(),
1221                body: Box::new(body.with_replaced_first_param(new_first)),
1222            },
1223            _ => self.clone(),
1224        }
1225    }
1226
1227    pub fn get_bounds(&self) -> &[Bound] {
1228        match self {
1229            Type::Function(f) => &f.bounds,
1230            Type::Forall { body, .. } => body.get_bounds(),
1231            _ => &[],
1232        }
1233    }
1234
1235    pub fn get_qualified_name(&self) -> Symbol {
1236        match self.strip_refs() {
1237            Type::Nominal { id, .. } => id,
1238            Type::Simple(kind) => Symbol::from_parts("prelude", kind.leaf_name()),
1239            Type::Compound { kind, .. } => Symbol::from_parts("prelude", kind.leaf_name()),
1240            _ => panic!("called get_qualified_name on {:#?}", self),
1241        }
1242    }
1243
1244    pub fn inner(&self) -> Option<Type> {
1245        self.get_type_params()
1246            .and_then(|args| args.first().cloned())
1247    }
1248
1249    pub fn ok_type(&self) -> Type {
1250        debug_assert!(
1251            self.is_result() || self.is_option() || self.is_partial(),
1252            "ok_type called on non-Result/Option/Partial type"
1253        );
1254        self.inner()
1255            .expect("Result/Option/Partial should have inner type")
1256    }
1257
1258    pub fn err_type(&self) -> Type {
1259        debug_assert!(
1260            self.is_result() || self.is_partial(),
1261            "err_type called on non-Result/Partial type"
1262        );
1263        self.get_type_params()
1264            .and_then(|args| args.get(1).cloned())
1265            .expect("Result/Partial should have error type")
1266    }
1267}
1268
1269/// Walk an alias chain via `underlying_ty` (preserves substitution); cycle
1270/// guard defends against chains that slip past `circular_type_alias`.
1271pub fn peel_alias<F>(ty: &Type, is_alias: F) -> Type
1272where
1273    F: Fn(&str) -> bool,
1274{
1275    let mut current = ty.unwrap_forall().clone();
1276    let mut seen: Vec<String> = Vec::new();
1277    while let Type::Nominal {
1278        id,
1279        underlying_ty: Some(u),
1280        ..
1281    } = &current
1282    {
1283        if !is_alias(id.as_str()) {
1284            break;
1285        }
1286        if seen.iter().any(|s| s == id.as_str()) {
1287            break;
1288        }
1289        seen.push(id.to_string());
1290        current = u.unwrap_forall().clone();
1291    }
1292    current
1293}
1294
1295/// Walk an alias chain by id alone; used when no `Type` with
1296/// `underlying_ty` is available (e.g. Go-name resolution).
1297pub fn peel_alias_id<F>(id: &str, next_alias: F) -> String
1298where
1299    F: Fn(&str) -> Option<String>,
1300{
1301    let mut current = id.to_string();
1302    let mut seen: Vec<String> = Vec::new();
1303    loop {
1304        if seen.iter().any(|s| s == &current) {
1305            return current;
1306        }
1307        let Some(next) = next_alias(&current) else {
1308            return current;
1309        };
1310        seen.push(current);
1311        current = next;
1312    }
1313}
1314
1315impl Type {
1316    pub fn unwrap_forall(&self) -> &Type {
1317        match self {
1318            Type::Forall { body, .. } => body.as_ref(),
1319            other => other,
1320        }
1321    }
1322
1323    pub fn strip_refs(&self) -> Type {
1324        if self.is_ref() {
1325            return self.inner().expect("ref type must have inner").strip_refs();
1326        }
1327
1328        self.clone()
1329    }
1330
1331    pub fn with_receiver_placeholder(self) -> Type {
1332        match self {
1333            Type::Function(f) => {
1334                let f = Arc::try_unwrap(f).unwrap_or_else(|arc| (*arc).clone());
1335                let mut new_params = vec![Type::ReceiverPlaceholder];
1336                new_params.extend(f.params);
1337
1338                let mut new_mutability = vec![false];
1339                new_mutability.extend(f.param_mutability);
1340
1341                Type::function(new_params, new_mutability, f.bounds, f.return_type)
1342            }
1343            _ => unreachable!(
1344                "with_receiver_placeholder called on non-function type: {:?}",
1345                self
1346            ),
1347        }
1348    }
1349
1350    pub fn remove_vars(types: &[&Type]) -> (Vec<Type>, Vec<EcoString>) {
1351        let mut vars = HashMap::default();
1352        let types = types
1353            .iter()
1354            .map(|v| Self::remove_vars_impl(v, &mut vars))
1355            .collect();
1356
1357        (types, vars.into_values().collect())
1358    }
1359
1360    fn remove_vars_impl(ty: &Type, vars: &mut HashMap<u32, EcoString>) -> Type {
1361        match ty {
1362            Type::Nominal {
1363                id: name,
1364                params: args,
1365                underlying_ty: underlying,
1366            } => Type::Nominal {
1367                id: name.clone(),
1368                params: args
1369                    .iter()
1370                    .map(|a| Self::remove_vars_impl(a, vars))
1371                    .collect(),
1372                underlying_ty: underlying
1373                    .as_ref()
1374                    .map(|u| Box::new(Self::remove_vars_impl(u, vars))),
1375            },
1376
1377            Type::Function(f) => Type::function(
1378                f.params
1379                    .iter()
1380                    .map(|a| Self::remove_vars_impl(a, vars))
1381                    .collect(),
1382                f.param_mutability.clone(),
1383                f.bounds
1384                    .iter()
1385                    .map(|b| Bound {
1386                        param_name: b.param_name.clone(),
1387                        generic: Self::remove_vars_impl(&b.generic, vars),
1388                        ty: Self::remove_vars_impl(&b.ty, vars),
1389                    })
1390                    .collect(),
1391                Self::remove_vars_impl(&f.return_type, vars).into(),
1392            ),
1393
1394            Type::Var { id, hint } => match vars.get(&id.0) {
1395                Some(g) => Type::Parameter(g.clone()),
1396                None => {
1397                    let name: EcoString = hint
1398                        .clone()
1399                        .unwrap_or_else(|| alpha_index(vars.len()).into());
1400
1401                    vars.insert(id.0, name.clone());
1402                    Type::Parameter(name)
1403                }
1404            },
1405
1406            Type::Forall { body, .. } => Self::remove_vars_impl(body, vars),
1407            Type::Tuple(elements) => Type::Tuple(
1408                elements
1409                    .iter()
1410                    .map(|e| Self::remove_vars_impl(e, vars))
1411                    .collect(),
1412            ),
1413            Type::Compound { kind, args } => Type::Compound {
1414                kind: *kind,
1415                args: args
1416                    .iter()
1417                    .map(|a| Self::remove_vars_impl(a, vars))
1418                    .collect(),
1419            },
1420            Type::Simple(_) | Type::Parameter(_) => ty.clone(),
1421            Type::Never | Type::Error | Type::ImportNamespace(_) | Type::ReceiverPlaceholder => {
1422                ty.clone()
1423            }
1424        }
1425    }
1426
1427    pub fn contains_type(&self, target: &Type) -> bool {
1428        if *self == *target {
1429            return true;
1430        }
1431        match self {
1432            Type::Nominal { params, .. } => params.iter().any(|p| p.contains_type(target)),
1433            Type::Function(f) => {
1434                f.params.iter().any(|p| p.contains_type(target))
1435                    || f.return_type.contains_type(target)
1436            }
1437            Type::Var { .. } => false,
1438            Type::Forall { body, .. } => body.contains_type(target),
1439            Type::Tuple(elements) => elements.iter().any(|e| e.contains_type(target)),
1440            Type::Compound { args, .. } => args.iter().any(|a| a.contains_type(target)),
1441            Type::Simple(_)
1442            | Type::Parameter(_)
1443            | Type::Never
1444            | Type::Error
1445            | Type::ImportNamespace(_)
1446            | Type::ReceiverPlaceholder => false,
1447        }
1448    }
1449}
1450
1451impl Type {
1452    pub fn underlying_numeric_type(&self) -> Option<Type> {
1453        self.underlying_numeric_type_recursive(&mut HashSet::default())
1454    }
1455
1456    pub fn has_underlying_numeric_type(&self) -> bool {
1457        self.underlying_numeric_type().is_some()
1458    }
1459
1460    pub fn literal_adaptation_target(&self) -> Option<Type> {
1461        if let Some(numeric) = self.underlying_numeric_type() {
1462            return Some(numeric);
1463        }
1464        match self {
1465            Type::Nominal { .. } if self.underlying_simple_kind() == Some(SimpleKind::Uintptr) => {
1466                Some(Type::Simple(SimpleKind::Uintptr))
1467            }
1468            _ => None,
1469        }
1470    }
1471
1472    pub fn underlying_simple_kind(&self) -> Option<SimpleKind> {
1473        self.underlying_simple_kind_recursive(&mut HashSet::default())
1474    }
1475
1476    fn underlying_simple_kind_recursive(
1477        &self,
1478        visited: &mut HashSet<Symbol>,
1479    ) -> Option<SimpleKind> {
1480        if let Some(kind) = self.as_simple() {
1481            return Some(kind);
1482        }
1483        match self {
1484            Type::Nominal {
1485                id,
1486                underlying_ty: Some(underlying),
1487                ..
1488            } => {
1489                if !visited.insert(id.clone()) {
1490                    return None;
1491                }
1492                underlying.underlying_simple_kind_recursive(visited)
1493            }
1494            _ => None,
1495        }
1496    }
1497
1498    fn underlying_numeric_type_recursive(&self, visited: &mut HashSet<Symbol>) -> Option<Type> {
1499        match self {
1500            Type::Simple(_) if self.is_numeric() => Some(self.clone()),
1501            Type::Nominal {
1502                id,
1503                underlying_ty: underlying,
1504                ..
1505            } => {
1506                if self.is_numeric() {
1507                    return Some(self.clone());
1508                }
1509
1510                if !visited.insert(id.clone()) {
1511                    return None;
1512                }
1513
1514                underlying
1515                    .as_ref()?
1516                    .underlying_numeric_type_recursive(visited)
1517            }
1518            _ => None,
1519        }
1520    }
1521
1522    pub fn numeric_family(&self) -> Option<NumericFamily> {
1523        self.as_simple()?.numeric_family()
1524    }
1525
1526    pub fn is_numeric_compatible_with(&self, other: &Type) -> bool {
1527        let self_underlying_ty = self.underlying_numeric_type();
1528        let other_underlying_ty = other.underlying_numeric_type();
1529
1530        match (self_underlying_ty, other_underlying_ty) {
1531            (Some(s), Some(o)) => s.numeric_family() == o.numeric_family(),
1532            _ => false,
1533        }
1534    }
1535
1536    pub fn is_aliased_numeric_type(&self) -> bool {
1537        match self {
1538            Type::Nominal { underlying_ty, .. } => {
1539                underlying_ty.is_some() && !self.is_numeric() && self.has_underlying_numeric_type()
1540            }
1541            _ => false,
1542        }
1543    }
1544}
1545
1546/// 0 → "A", 25 → "Z", 26 → "AA", 27 → "AB", ... (bijective base-26 over A-Z).
1547fn alpha_index(idx: usize) -> String {
1548    let mut s = String::new();
1549    let mut n = idx + 1;
1550    while n > 0 {
1551        n -= 1;
1552        s.insert(0, (b'A' + (n % 26) as u8) as char);
1553        n /= 26;
1554    }
1555    s
1556}
1557
1558#[cfg(test)]
1559mod tests {
1560    use super::*;
1561
1562    #[test]
1563    fn alpha_index_single() {
1564        assert_eq!(alpha_index(0), "A");
1565        assert_eq!(alpha_index(5), "F");
1566        assert_eq!(alpha_index(25), "Z");
1567    }
1568
1569    #[test]
1570    fn alpha_index_double() {
1571        assert_eq!(alpha_index(26), "AA");
1572        assert_eq!(alpha_index(27), "AB");
1573        assert_eq!(alpha_index(51), "AZ");
1574        assert_eq!(alpha_index(52), "BA");
1575        assert_eq!(alpha_index(701), "ZZ");
1576    }
1577
1578    #[test]
1579    fn alpha_index_triple() {
1580        assert_eq!(alpha_index(702), "AAA");
1581    }
1582
1583    fn unhinted_var(id: u32) -> Type {
1584        Type::Var {
1585            id: TypeVarId(id),
1586            hint: None,
1587        }
1588    }
1589
1590    #[test]
1591    fn remove_vars_handles_more_than_six_unhinted_vars() {
1592        let func = Type::function(
1593            (0..6).map(unhinted_var).collect(),
1594            vec![false; 6],
1595            vec![],
1596            Box::new(unhinted_var(6)),
1597        );
1598
1599        let (resolved, generics) = Type::remove_vars(&[&func]);
1600
1601        assert_eq!(generics.len(), 7);
1602        let Type::Function(f) = &resolved[0] else {
1603            panic!("expected function type");
1604        };
1605        let names: Vec<_> = f
1606            .params
1607            .iter()
1608            .chain(std::iter::once(f.return_type.as_ref()))
1609            .map(|p| match p {
1610                Type::Parameter(name) => name.to_string(),
1611                other => panic!("expected parameter, got {:?}", other),
1612            })
1613            .collect();
1614        assert_eq!(names, vec!["A", "B", "C", "D", "E", "F", "G"]);
1615    }
1616
1617    #[test]
1618    fn remove_vars_handles_dozens_of_unhinted_vars() {
1619        let params: Vec<Type> = (0..30).map(unhinted_var).collect();
1620        let func = Type::function(
1621            params.clone(),
1622            vec![false; params.len()],
1623            vec![],
1624            Box::new(Type::Simple(SimpleKind::Unit)),
1625        );
1626        let (_, generics) = Type::remove_vars(&[&func]);
1627        assert_eq!(generics.len(), 30);
1628    }
1629}