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}
40
41#[derive(Clone, Debug, PartialEq, Eq, Hash)]
42pub struct TemplateBound {
43    pub bound_type: TUnion,
44    pub appearance_depth: usize,
45    pub argument_offset: Option<usize>,
46    pub equality_bound_classlike: Option<Word>,
47    pub span: Option<Span>,
48}
49
50impl GenericTemplate {
51    /// Creates a new `GenericTemplate` with the given source and constraint type.
52    #[must_use]
53    pub fn new(template_source: GenericParent, template_type: TUnion) -> Self {
54        Self { defining_entity: template_source, constraint: template_type, default: None }
55    }
56
57    /// Returns the same `GenericTemplate` with the given default type set.
58    #[must_use]
59    pub fn with_default(mut self, default: Option<TUnion>) -> Self {
60        self.default = default;
61        self
62    }
63}
64
65impl TemplateResult {
66    #[must_use]
67    pub fn new(
68        template_types: IndexMap<Word, Vec<GenericTemplate>, RandomState>,
69        lower_bounds: HashMap<Word, HashMap<GenericParent, TUnion>>,
70    ) -> TemplateResult {
71        let mut new_lower_bounds = HashMap::default();
72
73        for (k, v) in lower_bounds {
74            let mut th = HashMap::default();
75
76            for (vk, vv) in v {
77                th.insert(vk, vec![TemplateBound::new(vv, 0, None, None)]);
78            }
79
80            new_lower_bounds.insert(k, th);
81        }
82
83        TemplateResult {
84            template_types,
85            lower_bounds: new_lower_bounds,
86            upper_bounds: HashMap::default(),
87            readonly: false,
88            upper_bounds_unintersectable_types: Vec::new(),
89        }
90    }
91
92    #[must_use]
93    pub fn has_template_types(&self) -> bool {
94        !self.template_types.is_empty()
95    }
96
97    pub fn add_lower_bounds(&mut self, lower_bounds: HashMap<Word, HashMap<GenericParent, TUnion>>) {
98        for (k, v) in lower_bounds {
99            let mut th = HashMap::default();
100
101            for (vk, vv) in v {
102                th.insert(vk, vec![TemplateBound::new(vv, 0, None, None)]);
103            }
104
105            self.lower_bounds.insert(k, th);
106        }
107    }
108
109    pub fn add_lower_bound(&mut self, parameter_name: Word, generic_parent: GenericParent, bound: TUnion) {
110        let entry = self.lower_bounds.entry(parameter_name).or_default();
111
112        entry.entry(generic_parent).or_default().push(TemplateBound::new(bound, 0, None, None));
113    }
114
115    pub fn add_template_type(&mut self, parameter_name: Word, generic_parent: GenericParent, constraint: TUnion) {
116        let entry = self.template_types.entry(parameter_name).or_default();
117        entry.push(GenericTemplate::new(generic_parent, constraint));
118    }
119
120    pub fn add_upper_bound(&mut self, parameter_name: Word, generic_parent: GenericParent, bound: TemplateBound) {
121        let entry = self.upper_bounds.entry(parameter_name).or_default();
122        entry.insert(generic_parent, bound);
123    }
124
125    pub fn add_upper_bound_unintersectable_type(&mut self, bound: TUnion) {
126        self.upper_bounds_unintersectable_types.push(bound);
127    }
128
129    #[must_use]
130    pub fn has_lower_bound(&self, parameter_name: Word, generic_parent: &GenericParent) -> bool {
131        self.lower_bounds
132            .get(&parameter_name)
133            .and_then(|bounds| bounds.get(generic_parent))
134            .is_some_and(|bounds| !bounds.is_empty())
135    }
136
137    #[must_use]
138    pub fn has_lower_bound_for_class_like(&self, parameter_name: Word, classlike_name: &Word) -> bool {
139        self.has_lower_bound(parameter_name, &GenericParent::ClassLike(*classlike_name))
140    }
141
142    #[must_use]
143    pub fn get_lower_bounds_for_class_like(
144        &self,
145        parameter_name: Word,
146        classlike_name: Word,
147    ) -> Option<&Vec<TemplateBound>> {
148        self.lower_bounds.get(&parameter_name).and_then(|bounds| bounds.get(&GenericParent::ClassLike(classlike_name)))
149    }
150}
151
152impl TemplateBound {
153    #[must_use]
154    pub fn new(
155        bound_type: TUnion,
156        appearance_depth: usize,
157        argument_offset: Option<usize>,
158        equality_bound_classlike: Option<Word>,
159    ) -> Self {
160        Self { bound_type, appearance_depth, argument_offset, equality_bound_classlike, span: None }
161    }
162
163    #[must_use]
164    pub fn of_type(bound_type: TUnion) -> Self {
165        Self { bound_type, appearance_depth: 0, argument_offset: None, equality_bound_classlike: None, span: None }
166    }
167}