Skip to main content

mago_codex/ttype/template/
variance.rs

1use mago_phpdoc_syntax::cst::TemplateTagValueVariance;
2
3#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)]
4#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
5pub enum Variance {
6    Invariant,
7    Covariant,
8    Contravariant,
9}
10
11impl From<TemplateTagValueVariance> for Variance {
12    fn from(variance: TemplateTagValueVariance) -> Self {
13        match variance {
14            TemplateTagValueVariance::Invariant => Variance::Invariant,
15            TemplateTagValueVariance::Covariant => Variance::Covariant,
16            TemplateTagValueVariance::Contravariant => Variance::Contravariant,
17        }
18    }
19}
20
21impl Variance {
22    #[inline]
23    #[must_use]
24    pub const fn is_invariant(&self) -> bool {
25        matches!(self, Variance::Invariant)
26    }
27
28    #[inline]
29    #[must_use]
30    pub const fn is_covariant(&self) -> bool {
31        matches!(self, Variance::Covariant)
32    }
33
34    #[inline]
35    #[must_use]
36    pub const fn is_contravariant(&self) -> bool {
37        matches!(self, Variance::Contravariant)
38    }
39
40    #[inline]
41    #[must_use]
42    pub const fn is_readonly(&self) -> bool {
43        matches!(self, Variance::Covariant | Variance::Invariant)
44    }
45
46    /// Combines an outer variance context with an inner variance context.
47    ///
48    /// This is used when resolving nested templates, e.g., `Outer<Inner<T>>`.
49    /// The variance of `T` relative to the outermost context depends on both
50    /// the variance of `T` within `Inner` and the variance of `Inner` within `Outer`.
51    ///
52    /// Rules:
53    ///
54    /// - Anything combined with Invariant results in Invariant.
55    /// - Covariant + Covariant = Covariant
56    /// - Contravariant + Contravariant = Covariant
57    /// - Covariant + Contravariant = Contravariant
58    /// - Contravariant + Covariant = Contravariant
59    #[inline]
60    #[must_use]
61    pub const fn combine(outer_variance: Self, inner_variance: Self) -> Self {
62        match (outer_variance, inner_variance) {
63            // If either is invariant, the result is invariant
64            (Variance::Invariant, _) | (_, Variance::Invariant) => Variance::Invariant,
65            // Co + Co = Co
66            (Variance::Covariant, Variance::Covariant) => Variance::Covariant,
67            // Contra + Contra = Co (double negative flips back)
68            (Variance::Contravariant, Variance::Contravariant) => Variance::Covariant,
69            // Co + Contra = Contra
70            (Variance::Covariant, Variance::Contravariant) => Variance::Contravariant,
71            // Contra + Co = Contra
72            (Variance::Contravariant, Variance::Covariant) => Variance::Contravariant,
73        }
74    }
75}
76
77impl std::fmt::Display for Variance {
78    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
79        match self {
80            Variance::Invariant => write!(f, "invariant"),
81            Variance::Covariant => write!(f, "covariant"),
82            Variance::Contravariant => write!(f, "contravariant"),
83        }
84    }
85}