Skip to main content

lisette_syntax/
types.rs

1use rustc_hash::{FxHashMap as HashMap, FxHashSet as HashSet};
2use std::cell::{OnceCell, RefCell};
3use std::rc::Rc;
4
5use ecow::EcoString;
6
7/// Extract the unqualified name from a dot-qualified identifier.
8///
9/// `"prelude.Option"` → `"Option"`, `"**nominal.int"` → `"int"`, `"foo"` → `"foo"`
10pub fn unqualified_name(id: &str) -> &str {
11    id.rsplit('.').next().unwrap_or(id)
12}
13
14/// type param name -> type variable
15pub type SubstitutionMap = HashMap<EcoString, Type>;
16
17pub fn substitute(ty: &Type, map: &HashMap<EcoString, Type>) -> Type {
18    if map.is_empty() {
19        return ty.clone();
20    }
21    match ty {
22        Type::Parameter(name) => map.get(name).cloned().unwrap_or_else(|| ty.clone()),
23        Type::Constructor {
24            id,
25            params,
26            underlying_ty: underlying,
27        } => Type::Constructor {
28            id: id.clone(),
29            params: params.iter().map(|p| substitute(p, map)).collect(),
30            underlying_ty: underlying.as_ref().map(|u| Box::new(substitute(u, map))),
31        },
32        Type::Function {
33            params,
34            param_mutability,
35            bounds,
36            return_type,
37        } => Type::Function {
38            params: params.iter().map(|p| substitute(p, map)).collect(),
39            param_mutability: param_mutability.clone(),
40            bounds: bounds
41                .iter()
42                .map(|b| Bound {
43                    param_name: b.param_name.clone(),
44                    generic: substitute(&b.generic, map),
45                    ty: substitute(&b.ty, map),
46                })
47                .collect(),
48            return_type: Box::new(substitute(return_type, map)),
49        },
50        Type::Variable(_) | Type::Error => ty.clone(),
51        Type::Forall { vars, body } => {
52            let has_overlap = map.keys().any(|k| vars.contains(k));
53            let substituted_body = if has_overlap {
54                let filtered_map: HashMap<EcoString, Type> = map
55                    .iter()
56                    .filter(|(k, _)| !vars.contains(*k))
57                    .map(|(k, v)| (k.clone(), v.clone()))
58                    .collect();
59                substitute(body, &filtered_map)
60            } else {
61                substitute(body, map)
62            };
63            Type::Forall {
64                vars: vars.clone(),
65                body: Box::new(substituted_body),
66            }
67        }
68        Type::Tuple(elements) => Type::Tuple(elements.iter().map(|e| substitute(e, map)).collect()),
69        Type::Never => ty.clone(),
70    }
71}
72
73#[derive(Debug, Clone, PartialEq)]
74pub struct Bound {
75    pub param_name: EcoString,
76    pub generic: Type,
77    pub ty: Type,
78}
79
80#[derive(Clone)]
81pub enum TypeVariableState {
82    Unbound { id: i32, hint: Option<EcoString> },
83    Link(Type),
84}
85
86impl TypeVariableState {
87    pub fn is_unbound(&self) -> bool {
88        matches!(self, TypeVariableState::Unbound { .. })
89    }
90}
91
92impl std::fmt::Debug for TypeVariableState {
93    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
94        match self {
95            TypeVariableState::Unbound { id, hint } => match hint {
96                Some(name) => write!(f, "{}", name),
97                None => write!(f, "{}", id),
98            },
99            TypeVariableState::Link(ty) => write!(f, "{:?}", ty),
100        }
101    }
102}
103
104impl PartialEq for TypeVariableState {
105    fn eq(&self, other: &Self) -> bool {
106        match (self, other) {
107            (
108                TypeVariableState::Unbound { id: id1, .. },
109                TypeVariableState::Unbound { id: id2, .. },
110            ) => id1 == id2,
111            (TypeVariableState::Link(ty1), TypeVariableState::Link(ty2)) => ty1 == ty2,
112            _ => false,
113        }
114    }
115}
116
117#[derive(Clone)]
118pub enum Type {
119    Constructor {
120        id: EcoString,
121        params: Vec<Type>,
122        underlying_ty: Option<Box<Type>>,
123    },
124
125    Function {
126        params: Vec<Type>,
127        param_mutability: Vec<bool>,
128        bounds: Vec<Bound>,
129        return_type: Box<Type>,
130    },
131
132    Variable(Rc<RefCell<TypeVariableState>>),
133
134    Forall {
135        vars: Vec<EcoString>,
136        body: Box<Type>,
137    },
138
139    Parameter(EcoString),
140
141    Never,
142
143    Tuple(Vec<Type>),
144
145    /// Poison type returned after an error has been reported.
146    /// Unifies with everything silently, preventing cascading diagnostics.
147    Error,
148}
149
150impl std::fmt::Debug for Type {
151    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
152        match self {
153            Type::Constructor { id, params, .. } => f
154                .debug_struct("Constructor")
155                .field("id", id)
156                .field("params", params)
157                .finish(),
158            Type::Function {
159                params,
160                param_mutability,
161                bounds,
162                return_type,
163            } => {
164                let mut s = f.debug_struct("Function");
165                s.field("params", params);
166                if param_mutability.iter().any(|m| *m) {
167                    s.field("param_mutability", param_mutability);
168                }
169                s.field("bounds", bounds)
170                    .field("return_type", return_type)
171                    .finish()
172            }
173            Type::Variable(type_var) => f
174                .debug_tuple("Variable")
175                .field(&*type_var.borrow())
176                .finish(),
177            Type::Forall { vars, body } => f
178                .debug_struct("Forall")
179                .field("vars", vars)
180                .field("body", body)
181                .finish(),
182            Type::Parameter(name) => f.debug_tuple("Parameter").field(name).finish(),
183            Type::Never => write!(f, "Never"),
184            Type::Tuple(elements) => f.debug_tuple("Tuple").field(elements).finish(),
185            Type::Error => write!(f, "Error"),
186        }
187    }
188}
189
190impl PartialEq for Type {
191    fn eq(&self, other: &Self) -> bool {
192        match (self, other) {
193            (
194                Type::Constructor {
195                    id: id1,
196                    params: params1,
197                    ..
198                },
199                Type::Constructor {
200                    id: id2,
201                    params: params2,
202                    ..
203                },
204            ) => id1 == id2 && params1 == params2,
205            (
206                Type::Function {
207                    params: p1,
208                    param_mutability: m1,
209                    bounds: b1,
210                    return_type: r1,
211                },
212                Type::Function {
213                    params: p2,
214                    param_mutability: m2,
215                    bounds: b2,
216                    return_type: r2,
217                },
218            ) => p1 == p2 && m1 == m2 && b1 == b2 && r1 == r2,
219            (Type::Variable(v1), Type::Variable(v2)) => {
220                Rc::ptr_eq(v1, v2) || *v1.borrow() == *v2.borrow()
221            }
222            (
223                Type::Forall {
224                    vars: vars1,
225                    body: body1,
226                },
227                Type::Forall {
228                    vars: vars2,
229                    body: body2,
230                },
231            ) => vars1 == vars2 && body1 == body2,
232            (Type::Parameter(name1), Type::Parameter(name2)) => name1 == name2,
233            (Type::Never, Type::Never) => true,
234            (Type::Tuple(elems1), Type::Tuple(elems2)) => elems1 == elems2,
235            _ => false,
236        }
237    }
238}
239
240thread_local! {
241    static INTERNED_INT: OnceCell<Type> = const { OnceCell::new() };
242    static INTERNED_STRING: OnceCell<Type> = const { OnceCell::new() };
243    static INTERNED_BOOL: OnceCell<Type> = const { OnceCell::new() };
244    static INTERNED_UNIT: OnceCell<Type> = const { OnceCell::new() };
245    static INTERNED_FLOAT64: OnceCell<Type> = const { OnceCell::new() };
246    static INTERNED_RUNE: OnceCell<Type> = const { OnceCell::new() };
247}
248
249impl Type {
250    pub fn int() -> Type {
251        INTERNED_INT.with(|cell| cell.get_or_init(|| Self::nominal("int")).clone())
252    }
253
254    pub fn string() -> Type {
255        INTERNED_STRING.with(|cell| cell.get_or_init(|| Self::nominal("string")).clone())
256    }
257
258    pub fn bool() -> Type {
259        INTERNED_BOOL.with(|cell| cell.get_or_init(|| Self::nominal("bool")).clone())
260    }
261
262    pub fn unit() -> Type {
263        INTERNED_UNIT.with(|cell| cell.get_or_init(|| Self::nominal("Unit")).clone())
264    }
265
266    pub fn float64() -> Type {
267        INTERNED_FLOAT64.with(|cell| cell.get_or_init(|| Self::nominal("float64")).clone())
268    }
269
270    pub fn rune() -> Type {
271        INTERNED_RUNE.with(|cell| cell.get_or_init(|| Self::nominal("rune")).clone())
272    }
273}
274
275impl Type {
276    const UNINFERRED_ID: i32 = -1;
277    const IGNORED_ID: i32 = -333;
278
279    pub fn nominal(name: &str) -> Self {
280        Self::Constructor {
281            id: format!("**nominal.{}", name).into(),
282            params: vec![],
283            underlying_ty: None,
284        }
285    }
286
287    pub fn uninferred() -> Self {
288        Self::Variable(Rc::new(RefCell::new(TypeVariableState::Unbound {
289            id: Self::UNINFERRED_ID,
290            hint: None,
291        })))
292    }
293
294    pub fn ignored() -> Self {
295        Self::Variable(Rc::new(RefCell::new(TypeVariableState::Unbound {
296            id: Self::IGNORED_ID,
297            hint: None,
298        })))
299    }
300
301    pub fn get_type_params(&self) -> Option<&[Type]> {
302        match self {
303            Type::Constructor { params, .. } => Some(params),
304            _ => None,
305        }
306    }
307}
308
309const ARITHMETIC_TYPES: &[&str] = &[
310    "byte",
311    "complex128",
312    "complex64",
313    "float32",
314    "float64",
315    "int",
316    "int16",
317    "int32",
318    "int64",
319    "int8",
320    "rune",
321    "uint",
322    "uint16",
323    "uint32",
324    "uint64",
325    "uint8",
326];
327
328const ORDERED_TYPES: &[&str] = &[
329    "byte", "float32", "float64", "int", "int16", "int32", "int64", "int8", "rune", "uint",
330    "uint16", "uint32", "uint64", "uint8",
331];
332
333const UNSIGNED_INT_TYPES: &[&str] = &["byte", "uint", "uint8", "uint16", "uint32", "uint64"];
334
335impl Type {
336    pub fn get_function_ret(&self) -> Option<&Type> {
337        match self {
338            Type::Function { return_type, .. } => Some(return_type),
339            _ => None,
340        }
341    }
342
343    pub fn has_name(&self, name: &str) -> bool {
344        match self {
345            Type::Constructor { id, .. } => unqualified_name(id) == name,
346            _ => false,
347        }
348    }
349
350    pub fn get_qualified_id(&self) -> Option<&str> {
351        match self {
352            Type::Constructor { id, .. } => Some(id.as_str()),
353            _ => None,
354        }
355    }
356
357    pub fn get_underlying(&self) -> Option<&Type> {
358        match self {
359            Type::Constructor {
360                underlying_ty: underlying,
361                ..
362            } => underlying.as_deref(),
363            _ => None,
364        }
365    }
366
367    pub fn is_result(&self) -> bool {
368        self.has_name("Result")
369    }
370
371    pub fn is_option(&self) -> bool {
372        self.has_name("Option")
373    }
374
375    pub fn is_unit(&self) -> bool {
376        matches!(self.resolve(), Type::Constructor { ref id, .. } if id.as_ref() == "**nominal.Unit")
377    }
378
379    pub fn tuple_arity(&self) -> Option<usize> {
380        match self {
381            Type::Tuple(elements) => Some(elements.len()),
382            _ => None,
383        }
384    }
385
386    pub fn is_tuple(&self) -> bool {
387        matches!(self, Type::Tuple(_))
388    }
389
390    pub fn is_ref(&self) -> bool {
391        self.has_name("Ref")
392    }
393
394    pub fn is_receiver_placeholder(&self) -> bool {
395        self.has_name("__receiver__")
396    }
397
398    pub fn is_unknown(&self) -> bool {
399        self.has_name("Unknown")
400    }
401
402    pub fn is_receiver(&self) -> bool {
403        self.has_name("Receiver")
404    }
405
406    pub fn is_ignored(&self) -> bool {
407        match self {
408            Type::Variable(var) => {
409                matches!(&*var.borrow(), TypeVariableState::Unbound { id, .. } if *id == Self::IGNORED_ID)
410            }
411            _ => false,
412        }
413    }
414
415    pub fn is_variadic(&self) -> Option<Type> {
416        let args = self.get_function_params()?;
417        let last = args.last()?;
418
419        if last.get_name()? == "VarArgs" {
420            return last.inner();
421        }
422
423        None
424    }
425
426    pub fn is_string(&self) -> bool {
427        self.has_name("string")
428    }
429
430    pub fn is_slice_of(&self, element_name: &str) -> bool {
431        match self {
432            Type::Constructor { id, params, .. } => {
433                if unqualified_name(id) != "Slice" || params.len() != 1 {
434                    return false;
435                }
436                params[0].resolve().has_name(element_name)
437            }
438            _ => false,
439        }
440    }
441
442    pub fn is_byte_slice(&self) -> bool {
443        self.is_slice_of("byte") || self.is_slice_of("uint8")
444    }
445
446    pub fn is_rune_slice(&self) -> bool {
447        self.is_slice_of("rune")
448    }
449
450    pub fn is_byte_or_rune_slice(&self) -> bool {
451        self.is_byte_slice() || self.is_rune_slice()
452    }
453
454    pub fn has_byte_or_rune_slice_underlying(&self) -> bool {
455        if self.is_byte_or_rune_slice() {
456            return true;
457        }
458        match self {
459            Type::Constructor { underlying_ty, .. } => underlying_ty
460                .as_deref()
461                .is_some_and(|u| u.has_byte_or_rune_slice_underlying()),
462            _ => false,
463        }
464    }
465
466    pub fn is_boolean(&self) -> bool {
467        self.has_name("bool")
468    }
469
470    pub fn is_rune(&self) -> bool {
471        self.has_name("rune")
472    }
473
474    pub fn is_float64(&self) -> bool {
475        self.has_name("float64")
476    }
477
478    pub fn is_float32(&self) -> bool {
479        self.has_name("float32")
480    }
481
482    pub fn is_float(&self) -> bool {
483        self.is_float64() || self.is_float32()
484    }
485
486    pub fn is_variable(&self) -> bool {
487        matches!(self, Type::Variable(_))
488    }
489
490    pub fn is_unbound_variable(&self) -> bool {
491        matches!(self, Type::Variable(cell) if cell.borrow().is_unbound())
492    }
493
494    pub fn is_numeric(&self) -> bool {
495        match self {
496            Type::Constructor { id, .. } => ARITHMETIC_TYPES.contains(&unqualified_name(id)),
497            _ => false,
498        }
499    }
500
501    pub fn is_ordered(&self) -> bool {
502        match self {
503            Type::Constructor { id, .. } => ORDERED_TYPES.contains(&unqualified_name(id)),
504            _ => false,
505        }
506    }
507
508    pub fn is_complex(&self) -> bool {
509        match self {
510            Type::Constructor { id, .. } => {
511                matches!(unqualified_name(id), "complex128" | "complex64")
512            }
513            _ => false,
514        }
515    }
516
517    pub fn is_unsigned_int(&self) -> bool {
518        match self {
519            Type::Constructor { id, .. } => UNSIGNED_INT_TYPES.contains(&unqualified_name(id)),
520            _ => false,
521        }
522    }
523
524    pub fn is_never(&self) -> bool {
525        matches!(self.shallow_resolve(), Type::Never)
526    }
527
528    pub fn is_error(&self) -> bool {
529        matches!(self.shallow_resolve(), Type::Error)
530    }
531
532    pub fn has_unbound_variables(&self) -> bool {
533        match self {
534            Type::Variable(type_var) => match &*type_var.borrow() {
535                TypeVariableState::Unbound { hint, .. } => hint.is_some(),
536                TypeVariableState::Link(ty) => ty.has_unbound_variables(),
537            },
538            Type::Constructor { params, .. } => params.iter().any(|p| p.has_unbound_variables()),
539            Type::Function {
540                params,
541                return_type,
542                ..
543            } => {
544                params.iter().any(|p| p.has_unbound_variables())
545                    || return_type.has_unbound_variables()
546            }
547            Type::Forall { body, .. } => body.has_unbound_variables(),
548            Type::Tuple(elements) => elements.iter().any(|e| e.has_unbound_variables()),
549            Type::Parameter(_) | Type::Never | Type::Error => false,
550        }
551    }
552
553    pub fn remove_found_type_names(&self, names: &mut HashSet<EcoString>) {
554        if names.is_empty() {
555            return;
556        }
557
558        match self {
559            Type::Constructor { id, params, .. } => {
560                names.remove(unqualified_name(id));
561                for param in params {
562                    param.remove_found_type_names(names);
563                }
564            }
565            Type::Function {
566                params,
567                return_type,
568                bounds,
569                ..
570            } => {
571                for param in params {
572                    param.remove_found_type_names(names);
573                }
574                return_type.remove_found_type_names(names);
575                for bound in bounds {
576                    bound.generic.remove_found_type_names(names);
577                    bound.ty.remove_found_type_names(names);
578                }
579            }
580            Type::Forall { body, .. } => {
581                body.remove_found_type_names(names);
582            }
583            Type::Variable(type_var) => {
584                if let TypeVariableState::Link(ty) = &*type_var.borrow() {
585                    ty.remove_found_type_names(names);
586                }
587            }
588            Type::Parameter(name) => {
589                names.remove(name);
590            }
591            Type::Tuple(elements) => {
592                for element in elements {
593                    element.remove_found_type_names(names);
594                }
595            }
596            Type::Never | Type::Error => {}
597        }
598    }
599}
600
601impl Type {
602    pub fn get_name(&self) -> Option<&str> {
603        match self {
604            Type::Constructor { id, params, .. } => {
605                let name = unqualified_name(id);
606                if name == "Ref" {
607                    return params.first().and_then(|inner| inner.get_name());
608                }
609                if let Some(module_path) = id.strip_prefix("@import/") {
610                    let path = module_path.strip_prefix("go:").unwrap_or(module_path);
611                    return path.rsplit('/').next();
612                }
613                Some(name)
614            }
615            _ => None,
616        }
617    }
618
619    pub fn wraps(&self, name: &str, inner: &Type) -> bool {
620        self.get_name().is_some_and(|n| n == name)
621            && self
622                .get_type_params()
623                .and_then(|p| p.first())
624                .is_some_and(|first| *first == *inner)
625    }
626
627    pub fn get_function_params(&self) -> Option<&[Type]> {
628        match self {
629            Type::Function { params, .. } => Some(params),
630            Type::Constructor {
631                underlying_ty: Some(inner),
632                ..
633            } => inner.get_function_params(),
634            _ => None,
635        }
636    }
637
638    pub fn param_count(&self) -> usize {
639        match self {
640            Type::Function { params, .. } => params.len(),
641            _ => 0,
642        }
643    }
644
645    pub fn get_param_mutability(&self) -> &[bool] {
646        match self {
647            Type::Function {
648                param_mutability, ..
649            } => param_mutability,
650            _ => &[],
651        }
652    }
653
654    pub fn with_replaced_first_param(&self, new_first: &Type) -> Type {
655        match self {
656            Type::Function {
657                params,
658                param_mutability,
659                bounds,
660                return_type,
661            } => {
662                if params.is_empty() {
663                    return self.clone();
664                }
665                let mut new_params = params.clone();
666                new_params[0] = new_first.clone();
667                Type::Function {
668                    params: new_params,
669                    param_mutability: param_mutability.clone(),
670                    bounds: bounds.clone(),
671                    return_type: return_type.clone(),
672                }
673            }
674            Type::Forall { vars, body } => Type::Forall {
675                vars: vars.clone(),
676                body: Box::new(body.with_replaced_first_param(new_first)),
677            },
678            _ => self.clone(),
679        }
680    }
681
682    pub fn get_bounds(&self) -> &[Bound] {
683        match self {
684            Type::Function { bounds, .. } => bounds,
685            Type::Forall { body, .. } => body.get_bounds(),
686            _ => &[],
687        }
688    }
689
690    pub fn get_qualified_name(&self) -> EcoString {
691        match self.strip_refs() {
692            Type::Constructor { id, .. } => id,
693            _ => panic!("called get_qualified_name on {:#?}", self),
694        }
695    }
696
697    pub fn inner(&self) -> Option<Type> {
698        self.get_type_params()
699            .and_then(|args| args.first().cloned())
700    }
701
702    pub fn ok_type(&self) -> Type {
703        debug_assert!(
704            self.is_result() || self.is_option(),
705            "ok_type called on non-Result/Option type"
706        );
707        self.inner().expect("Result/Option should have inner type")
708    }
709
710    pub fn err_type(&self) -> Type {
711        debug_assert!(self.is_result(), "err_type called on non-Result type");
712        self.get_type_params()
713            .and_then(|args| args.get(1).cloned())
714            .expect("Result should have error type")
715    }
716}
717
718impl Type {
719    pub fn unwrap_forall(&self) -> &Type {
720        match self {
721            Type::Forall { body, .. } => body.as_ref(),
722            other => other,
723        }
724    }
725
726    pub fn strip_refs(&self) -> Type {
727        if self.is_ref() {
728            return self.inner().expect("ref type must have inner").strip_refs();
729        }
730
731        self.clone()
732    }
733
734    pub fn with_receiver_placeholder(self) -> Type {
735        match self {
736            Type::Function {
737                params,
738                param_mutability,
739                bounds,
740                return_type,
741            } => {
742                let mut new_params = vec![Type::nominal("__receiver__")];
743                new_params.extend(params);
744
745                let mut new_mutability = vec![false];
746                new_mutability.extend(param_mutability);
747
748                Type::Function {
749                    params: new_params,
750                    param_mutability: new_mutability,
751                    bounds,
752                    return_type,
753                }
754            }
755            _ => unreachable!(
756                "with_receiver_placeholder called on non-function type: {:?}",
757                self
758            ),
759        }
760    }
761
762    pub fn remove_vars(types: &[&Type]) -> (Vec<Type>, Vec<EcoString>) {
763        let mut vars = HashMap::default();
764        let types = types
765            .iter()
766            .map(|v| Self::remove_vars_impl(v, &mut vars))
767            .collect();
768
769        (types, vars.into_values().collect())
770    }
771
772    fn remove_vars_impl(ty: &Type, vars: &mut HashMap<i32, EcoString>) -> Type {
773        match ty {
774            Type::Constructor {
775                id: name,
776                params: args,
777                underlying_ty: underlying,
778            } => Type::Constructor {
779                id: name.clone(),
780                params: args
781                    .iter()
782                    .map(|a| Self::remove_vars_impl(a, vars))
783                    .collect(),
784                underlying_ty: underlying
785                    .as_ref()
786                    .map(|u| Box::new(Self::remove_vars_impl(u, vars))),
787            },
788
789            Type::Function {
790                params: args,
791                param_mutability,
792                bounds,
793                return_type,
794            } => Type::Function {
795                params: args
796                    .iter()
797                    .map(|a| Self::remove_vars_impl(a, vars))
798                    .collect(),
799                param_mutability: param_mutability.clone(),
800                bounds: bounds
801                    .iter()
802                    .map(|b| Bound {
803                        param_name: b.param_name.clone(),
804                        generic: Self::remove_vars_impl(&b.generic, vars),
805                        ty: Self::remove_vars_impl(&b.ty, vars),
806                    })
807                    .collect(),
808                return_type: Self::remove_vars_impl(return_type, vars).into(),
809            },
810
811            Type::Variable(type_var) => match &*type_var.borrow() {
812                TypeVariableState::Unbound { id, hint } => match vars.get(id) {
813                    Some(g) => Self::nominal(g),
814                    None => {
815                        let name: EcoString = hint.clone().unwrap_or_else(|| {
816                            char::from_digit(
817                                (vars.len() + 10)
818                                    .try_into()
819                                    .expect("type var count fits in u32"),
820                                16,
821                            )
822                            .expect("type var index is valid hex digit")
823                            .to_uppercase()
824                            .to_string()
825                            .into()
826                        });
827
828                        vars.insert(*id, name.clone());
829                        Self::nominal(&name)
830                    }
831                },
832                TypeVariableState::Link(ty) => Self::remove_vars_impl(ty, vars),
833            },
834
835            Type::Forall { body, .. } => Self::remove_vars_impl(body, vars),
836            Type::Tuple(elements) => Type::Tuple(
837                elements
838                    .iter()
839                    .map(|e| Self::remove_vars_impl(e, vars))
840                    .collect(),
841            ),
842            Type::Parameter(name) => Type::Parameter(name.clone()),
843            Type::Never | Type::Error => ty.clone(),
844        }
845    }
846
847    pub fn contains_type(&self, target: &Type) -> bool {
848        if *self == *target {
849            return true;
850        }
851        match self {
852            Type::Constructor { params, .. } => params.iter().any(|p| p.contains_type(target)),
853            Type::Function {
854                params,
855                return_type,
856                ..
857            } => {
858                params.iter().any(|p| p.contains_type(target)) || return_type.contains_type(target)
859            }
860            Type::Variable(var) => {
861                if let TypeVariableState::Link(linked) = &*var.borrow() {
862                    linked.contains_type(target)
863                } else {
864                    false
865                }
866            }
867            Type::Forall { body, .. } => body.contains_type(target),
868            Type::Tuple(elements) => elements.iter().any(|e| e.contains_type(target)),
869            Type::Parameter(_) | Type::Never | Type::Error => false,
870        }
871    }
872
873    /// Follow Variable::Link chains to the outermost non-variable type.
874    /// Does NOT recurse into Constructor params, Function params, etc.
875    /// Use this when you only need the outermost type (e.g. is_never, is_unknown, has_name).
876    pub fn shallow_resolve(&self) -> Type {
877        match self {
878            Type::Variable(type_var) => {
879                let state = type_var.borrow();
880                match &*state {
881                    TypeVariableState::Unbound { .. } => self.clone(),
882                    TypeVariableState::Link(linked) => linked.shallow_resolve(),
883                }
884            }
885            _ => self.clone(),
886        }
887    }
888
889    pub fn resolve(&self) -> Type {
890        match self {
891            Type::Variable(type_var) => {
892                let state = type_var.borrow();
893                match &*state {
894                    TypeVariableState::Unbound { .. } => self.clone(),
895                    TypeVariableState::Link(linked) => {
896                        let resolved = linked.resolve();
897                        drop(state);
898                        *type_var.borrow_mut() = TypeVariableState::Link(resolved.clone());
899                        resolved
900                    }
901                }
902            }
903            Type::Constructor {
904                id,
905                params,
906                underlying_ty: underlying,
907            } => Type::Constructor {
908                id: id.clone(),
909                params: params.iter().map(|p| p.resolve()).collect(),
910                underlying_ty: underlying.as_ref().map(|u| Box::new(u.resolve())),
911            },
912            Type::Function {
913                params,
914                param_mutability,
915                bounds,
916                return_type,
917            } => Type::Function {
918                params: params.iter().map(|p| p.resolve()).collect(),
919                param_mutability: param_mutability.clone(),
920                bounds: bounds
921                    .iter()
922                    .map(|b| Bound {
923                        param_name: b.param_name.clone(),
924                        generic: b.generic.resolve(),
925                        ty: b.ty.resolve(),
926                    })
927                    .collect(),
928                return_type: Box::new(return_type.resolve()),
929            },
930            Type::Forall { .. } => {
931                unreachable!("Forall types are always instantiated before resolve")
932            }
933            Type::Tuple(elements) => Type::Tuple(elements.iter().map(|e| e.resolve()).collect()),
934            Type::Parameter(_) | Type::Error => self.clone(),
935            Type::Never => Type::Never,
936        }
937    }
938}
939
940#[derive(Debug, Clone, Copy, PartialEq, Eq)]
941pub enum NumericFamily {
942    SignedInt,
943    UnsignedInt,
944    Float,
945}
946
947const SIGNED_INT_TYPES: &[&str] = &["int", "int8", "int16", "int32", "int64", "rune"];
948const FLOAT_TYPES: &[&str] = &["float32", "float64"];
949
950impl Type {
951    pub fn underlying_numeric_type(&self) -> Option<Type> {
952        self.underlying_numeric_type_recursive(&mut HashSet::default())
953    }
954
955    pub fn has_underlying_numeric_type(&self) -> bool {
956        self.underlying_numeric_type().is_some()
957    }
958
959    fn underlying_numeric_type_recursive(&self, visited: &mut HashSet<EcoString>) -> Option<Type> {
960        match self {
961            Type::Constructor {
962                id,
963                underlying_ty: underlying,
964                ..
965            } => {
966                if self.is_numeric() {
967                    return Some(self.clone());
968                }
969
970                if !visited.insert(id.clone()) {
971                    return None;
972                }
973
974                underlying
975                    .as_ref()?
976                    .underlying_numeric_type_recursive(visited)
977            }
978            _ => None,
979        }
980    }
981
982    pub fn numeric_family(&self) -> Option<NumericFamily> {
983        let name = match self {
984            Type::Constructor { id, .. } => unqualified_name(id),
985            _ => return None,
986        };
987
988        if SIGNED_INT_TYPES.contains(&name) {
989            Some(NumericFamily::SignedInt)
990        } else if UNSIGNED_INT_TYPES.contains(&name) {
991            Some(NumericFamily::UnsignedInt)
992        } else if FLOAT_TYPES.contains(&name) {
993            Some(NumericFamily::Float)
994        } else {
995            None
996        }
997    }
998
999    pub fn is_numeric_compatible_with(&self, other: &Type) -> bool {
1000        let self_underlying_ty = self.underlying_numeric_type();
1001        let other_underlying_ty = other.underlying_numeric_type();
1002
1003        match (self_underlying_ty, other_underlying_ty) {
1004            (Some(s), Some(o)) => s.numeric_family() == o.numeric_family(),
1005            _ => false,
1006        }
1007    }
1008
1009    pub fn is_aliased_numeric_type(&self) -> bool {
1010        match self {
1011            Type::Constructor { underlying_ty, .. } => {
1012                underlying_ty.is_some() && !self.is_numeric()
1013            }
1014            _ => false,
1015        }
1016    }
1017}