Skip to main content

lisette_syntax/
types.rs

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