Skip to main content

mago_codex/ttype/template/
variance.rs

1use mago_phpdoc_syntax::cst::TemplateTagValueVariance;
2use mago_phpdoc_syntax::cst::r#type::GenericParameterVariance;
3
4#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]
5#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
6pub enum Variance {
7    Invariant,
8    Covariant,
9    Contravariant,
10    Bivariant,
11}
12
13impl From<GenericParameterVariance<'_>> for Variance {
14    fn from(variance: GenericParameterVariance<'_>) -> Self {
15        match variance {
16            GenericParameterVariance::Covariant(_) => Variance::Covariant,
17            GenericParameterVariance::Contravariant(_) => Variance::Contravariant,
18        }
19    }
20}
21
22impl From<TemplateTagValueVariance> for Variance {
23    fn from(variance: TemplateTagValueVariance) -> Self {
24        match variance {
25            TemplateTagValueVariance::Invariant => Variance::Invariant,
26            TemplateTagValueVariance::Covariant => Variance::Covariant,
27            TemplateTagValueVariance::Contravariant => Variance::Contravariant,
28        }
29    }
30}
31
32impl Variance {
33    #[inline]
34    #[must_use]
35    pub const fn is_invariant(&self) -> bool {
36        matches!(self, Variance::Invariant)
37    }
38
39    #[inline]
40    #[must_use]
41    pub const fn is_covariant(&self) -> bool {
42        matches!(self, Variance::Covariant)
43    }
44
45    #[inline]
46    #[must_use]
47    pub const fn is_contravariant(&self) -> bool {
48        matches!(self, Variance::Contravariant)
49    }
50
51    #[inline]
52    #[must_use]
53    pub const fn is_bivariant(&self) -> bool {
54        matches!(self, Variance::Bivariant)
55    }
56
57    #[inline]
58    #[must_use]
59    pub const fn flip(self) -> Self {
60        match self {
61            Variance::Covariant => Variance::Contravariant,
62            Variance::Contravariant => Variance::Covariant,
63            other => other,
64        }
65    }
66
67    #[inline]
68    #[must_use]
69    pub const fn is_readonly(&self) -> bool {
70        matches!(self, Variance::Covariant | Variance::Invariant)
71    }
72
73    /// Combines an outer variance context with an inner variance context.
74    ///
75    /// This is used when resolving nested templates, e.g., `Outer<Inner<T>>`.
76    /// The variance of `T` relative to the outermost context depends on both
77    /// the variance of `T` within `Inner` and the variance of `Inner` within `Outer`.
78    ///
79    /// Rules:
80    ///
81    /// - Anything combined with Invariant results in Invariant.
82    /// - Covariant + Covariant = Covariant
83    /// - Contravariant + Contravariant = Covariant
84    /// - Covariant + Contravariant = Contravariant
85    /// - Contravariant + Covariant = Contravariant
86    #[inline]
87    #[must_use]
88    pub const fn combine(outer_variance: Self, inner_variance: Self) -> Self {
89        match (outer_variance, inner_variance) {
90            (Variance::Bivariant, _) | (_, Variance::Bivariant) => Variance::Bivariant,
91            // If either is invariant, the result is invariant
92            (Variance::Invariant, _) | (_, Variance::Invariant) => Variance::Invariant,
93            // Co + Co = Co
94            (Variance::Covariant, Variance::Covariant) => Variance::Covariant,
95            // Contra + Contra = Co (double negative flips back)
96            (Variance::Contravariant, Variance::Contravariant) => Variance::Covariant,
97            // Co + Contra = Contra
98            (Variance::Covariant, Variance::Contravariant) => Variance::Contravariant,
99            // Contra + Co = Contra
100            (Variance::Contravariant, Variance::Covariant) => Variance::Contravariant,
101        }
102    }
103
104    #[inline]
105    #[must_use]
106    pub const fn project(self, polarity: Variance) -> Option<bool> {
107        let reads = matches!(self, Variance::Covariant | Variance::Invariant);
108        let writes = matches!(self, Variance::Contravariant | Variance::Invariant);
109
110        match polarity {
111            Variance::Contravariant => {
112                if writes {
113                    Some(true)
114                } else {
115                    None
116                }
117            }
118            _ => {
119                if reads {
120                    Some(true)
121                } else {
122                    Some(false)
123                }
124            }
125        }
126    }
127}
128
129impl std::fmt::Display for Variance {
130    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
131        match self {
132            Variance::Invariant => write!(f, "invariant"),
133            Variance::Covariant => write!(f, "covariant"),
134            Variance::Contravariant => write!(f, "contravariant"),
135            Variance::Bivariant => write!(f, "*"),
136        }
137    }
138}