Skip to main content

mago_codex/ttype/template/
mod.rs

1use foldhash::HashMap;
2use foldhash::fast::RandomState;
3use indexmap::IndexMap;
4
5use mago_word::Word;
6
7use crate::misc::GenericParent;
8use crate::ttype::union::TUnion;
9
10pub mod bounds;
11pub mod definition_type_replacer;
12pub mod inferred_type_replacer;
13pub mod variance;
14
15/// Represents a template parameter definition with its source and constraint type.
16///
17/// This struct pairs a `GenericParent` (identifying where the template is defined)
18/// with a `TUnion` (the constraint type for the template parameter).
19#[derive(Clone, Debug, PartialEq, Eq, Hash)]
20#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
21pub struct GenericTemplate {
22    /// The entity (class or function) where this template parameter is defined.
23    pub defining_entity: GenericParent,
24    /// The constraint type for this template parameter (e.g., `object` for `@template T of object`).
25    pub constraint: TUnion,
26    /// The default type used when no explicit generic argument is provided
27    /// (e.g., `string` for `@template T of int|string = string`).
28    pub default: Option<TUnion>,
29}
30
31#[derive(Clone, Debug, Default)]
32pub struct TemplateResult {
33    pub template_types: IndexMap<Word, Vec<GenericTemplate>, RandomState>,
34    pub lower_bounds: HashMap<Word, HashMap<GenericParent, Vec<TemplateBound>>>,
35    pub projections: HashMap<Word, crate::ttype::template::variance::Variance>,
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}
44
45impl GenericTemplate {
46    /// Creates a new `GenericTemplate` with the given source and constraint type.
47    #[must_use]
48    pub fn new(template_source: GenericParent, template_type: TUnion) -> Self {
49        Self { defining_entity: template_source, constraint: template_type, default: None }
50    }
51
52    /// Returns the same `GenericTemplate` with the given default type set.
53    #[must_use]
54    pub fn with_default(mut self, default: Option<TUnion>) -> Self {
55        self.default = default;
56        self
57    }
58}
59
60impl TemplateResult {
61    #[must_use]
62    pub fn new(
63        template_types: IndexMap<Word, Vec<GenericTemplate>, RandomState>,
64        lower_bounds: HashMap<Word, HashMap<GenericParent, TUnion>>,
65    ) -> TemplateResult {
66        let mut new_lower_bounds = HashMap::default();
67
68        for (k, v) in lower_bounds {
69            let mut th = HashMap::default();
70
71            for (vk, vv) in v {
72                th.insert(vk, vec![TemplateBound::new(vv, 0, None)]);
73            }
74
75            new_lower_bounds.insert(k, th);
76        }
77
78        TemplateResult { template_types, lower_bounds: new_lower_bounds, projections: HashMap::default() }
79    }
80
81    #[must_use]
82    pub fn has_template_types(&self) -> bool {
83        !self.template_types.is_empty()
84    }
85
86    pub fn add_lower_bounds(&mut self, lower_bounds: HashMap<Word, HashMap<GenericParent, TUnion>>) {
87        for (k, v) in lower_bounds {
88            let mut th = HashMap::default();
89
90            for (vk, vv) in v {
91                th.insert(vk, vec![TemplateBound::new(vv, 0, None)]);
92            }
93
94            self.lower_bounds.insert(k, th);
95        }
96    }
97
98    pub fn add_lower_bound(&mut self, parameter_name: Word, generic_parent: GenericParent, bound: TUnion) {
99        let entry = self.lower_bounds.entry(parameter_name).or_default();
100
101        entry.entry(generic_parent).or_default().push(TemplateBound::new(bound, 0, None));
102    }
103
104    pub fn add_template_type(&mut self, parameter_name: Word, generic_parent: GenericParent, constraint: TUnion) {
105        let entry = self.template_types.entry(parameter_name).or_default();
106        entry.push(GenericTemplate::new(generic_parent, constraint));
107    }
108
109    #[must_use]
110    pub fn has_lower_bound(&self, parameter_name: Word, generic_parent: &GenericParent) -> bool {
111        self.lower_bounds
112            .get(&parameter_name)
113            .and_then(|bounds| bounds.get(generic_parent))
114            .is_some_and(|bounds| !bounds.is_empty())
115    }
116
117    #[must_use]
118    pub fn get_lower_bounds_for_class_like(
119        &self,
120        parameter_name: Word,
121        classlike_name: Word,
122    ) -> Option<&Vec<TemplateBound>> {
123        self.lower_bounds.get(&parameter_name).and_then(|bounds| bounds.get(&GenericParent::ClassLike(classlike_name)))
124    }
125}
126
127impl TemplateBound {
128    #[must_use]
129    pub fn new(bound_type: TUnion, appearance_depth: usize, argument_offset: Option<usize>) -> Self {
130        Self { bound_type, appearance_depth, argument_offset }
131    }
132}