Skip to main content

mago_codex/ttype/template/
mod.rs

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