Skip to main content

mago_codex/ttype/template/
mod.rs

1use ahash::HashMap;
2use ahash::RandomState;
3use indexmap::IndexMap;
4use serde::Deserialize;
5use serde::Serialize;
6
7use mago_atom::Atom;
8use mago_span::Span;
9
10use crate::misc::GenericParent;
11use crate::ttype::union::TUnion;
12
13pub mod inferred_type_replacer;
14pub mod standin_type_replacer;
15pub mod variance;
16
17/// Represents a template parameter definition with its source and constraint type.
18///
19/// This struct pairs a `GenericParent` (identifying where the template is defined)
20/// with a `TUnion` (the constraint type for the template parameter).
21#[derive(Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
22pub struct GenericTemplate {
23    /// The entity (class or function) where this template parameter is defined.
24    pub defining_entity: GenericParent,
25    /// The constraint type for this template parameter (e.g., `object` for `@template T of object`).
26    pub constraint: TUnion,
27}
28
29#[derive(Clone, Debug, Default)]
30pub struct TemplateResult {
31    pub template_types: IndexMap<Atom, Vec<GenericTemplate>, RandomState>,
32    pub lower_bounds: HashMap<Atom, HashMap<GenericParent, Vec<TemplateBound>>>,
33    pub upper_bounds: HashMap<Atom, HashMap<GenericParent, TemplateBound>>,
34    pub readonly: bool,
35    pub upper_bounds_unintersectable_types: Vec<TUnion>,
36}
37
38#[derive(Clone, Debug, PartialEq, Eq, Hash)]
39pub struct TemplateBound {
40    pub bound_type: TUnion,
41    pub appearance_depth: usize,
42    pub argument_offset: Option<usize>,
43    pub equality_bound_classlike: Option<Atom>,
44    pub span: Option<Span>,
45}
46
47impl GenericTemplate {
48    /// Creates a new `GenericTemplate` with the given source and constraint type.
49    #[must_use]
50    pub fn new(template_source: GenericParent, template_type: TUnion) -> Self {
51        Self { defining_entity: template_source, constraint: template_type }
52    }
53}
54
55impl TemplateResult {
56    #[must_use]
57    pub fn new(
58        template_types: IndexMap<Atom, Vec<GenericTemplate>, RandomState>,
59        lower_bounds: HashMap<Atom, HashMap<GenericParent, TUnion>>,
60    ) -> TemplateResult {
61        let mut new_lower_bounds = HashMap::default();
62
63        for (k, v) in lower_bounds {
64            let mut th = HashMap::default();
65
66            for (vk, vv) in v {
67                th.insert(vk, vec![TemplateBound::new(vv, 0, None, None)]);
68            }
69
70            new_lower_bounds.insert(k, th);
71        }
72
73        TemplateResult {
74            template_types,
75            lower_bounds: new_lower_bounds,
76            upper_bounds: HashMap::default(),
77            readonly: false,
78            upper_bounds_unintersectable_types: Vec::new(),
79        }
80    }
81
82    #[must_use]
83    pub fn has_template_types(&self) -> bool {
84        !self.template_types.is_empty()
85    }
86
87    pub fn add_lower_bounds(&mut self, lower_bounds: HashMap<Atom, HashMap<GenericParent, TUnion>>) {
88        for (k, v) in lower_bounds {
89            let mut th = HashMap::default();
90
91            for (vk, vv) in v {
92                th.insert(vk, vec![TemplateBound::new(vv, 0, None, None)]);
93            }
94
95            self.lower_bounds.insert(k, th);
96        }
97    }
98
99    pub fn add_lower_bound(&mut self, parameter_name: Atom, generic_parent: GenericParent, bound: TUnion) {
100        let entry = self.lower_bounds.entry(parameter_name).or_default();
101
102        entry.entry(generic_parent).or_default().push(TemplateBound::new(bound, 0, None, None));
103    }
104
105    pub fn add_template_type(&mut self, parameter_name: Atom, generic_parent: GenericParent, constraint: TUnion) {
106        let entry = self.template_types.entry(parameter_name).or_default();
107        entry.push(GenericTemplate::new(generic_parent, constraint));
108    }
109
110    pub fn add_upper_bound(&mut self, parameter_name: Atom, generic_parent: GenericParent, bound: TemplateBound) {
111        let entry = self.upper_bounds.entry(parameter_name).or_default();
112        entry.insert(generic_parent, bound);
113    }
114
115    pub fn add_upper_bound_unintersectable_type(&mut self, bound: TUnion) {
116        self.upper_bounds_unintersectable_types.push(bound);
117    }
118
119    #[must_use]
120    pub fn has_lower_bound(&self, parameter_name: &Atom, generic_parent: &GenericParent) -> bool {
121        self.lower_bounds
122            .get(parameter_name)
123            .and_then(|bounds| bounds.get(generic_parent))
124            .is_some_and(|bounds| !bounds.is_empty())
125    }
126
127    #[must_use]
128    pub fn has_lower_bound_for_class_like(&self, parameter_name: &Atom, classlike_name: &Atom) -> bool {
129        self.has_lower_bound(parameter_name, &GenericParent::ClassLike(*classlike_name))
130    }
131
132    #[must_use]
133    pub fn get_lower_bounds_for_class_like(
134        &self,
135        parameter_name: &Atom,
136        classlike_name: &Atom,
137    ) -> Option<&Vec<TemplateBound>> {
138        self.lower_bounds.get(parameter_name).and_then(|bounds| bounds.get(&GenericParent::ClassLike(*classlike_name)))
139    }
140}
141
142impl TemplateBound {
143    #[must_use]
144    pub fn new(
145        bound_type: TUnion,
146        appearance_depth: usize,
147        argument_offset: Option<usize>,
148        equality_bound_classlike: Option<Atom>,
149    ) -> Self {
150        Self { bound_type, appearance_depth, argument_offset, equality_bound_classlike, span: None }
151    }
152
153    #[must_use]
154    pub fn of_type(bound_type: TUnion) -> Self {
155        Self { bound_type, appearance_depth: 0, argument_offset: None, equality_bound_classlike: None, span: None }
156    }
157}