Skip to main content

luaur_analysis/methods/
subtyping_is_contravariant_with_subtyping.rs

1use crate::enums::subtyping_variance::SubtypingVariance;
2use crate::functions::assert_reasoning_valid_subtyping::assert_reasoning_valid;
3use crate::functions::merge_reasonings::k_empty_reasoning;
4use crate::records::scope::Scope;
5use crate::records::subtyping::Subtyping;
6use crate::records::subtyping_environment::SubtypingEnvironment;
7use crate::records::subtyping_reasoning::SubtypingReasoning;
8use crate::records::subtyping_result::SubtypingResult;
9use crate::records::table_indexer::TableIndexer;
10use crate::type_aliases::path::Path;
11use crate::type_aliases::subtyping_reasonings::SubtypingReasonings;
12use crate::type_aliases::type_id::TypeId;
13
14/// The C++ `isContravariantWith`/`isInvariantWith` are
15/// `template<typename SubTy, typename SuperTy>` and resolve the inner
16/// `isCovariantWith` against the C++ overload set per instantiation. The live
17/// instantiations are `TypeId/TypeId` (most calls), the dead `TryPair`
18/// forwarders (`const T*/const T*`), and `TableIndexer/TableIndexer`
19/// (`LuauReadOnlyIndexers`-off indexer path). We model that overload resolution
20/// with a runtime tag produced by `IntoCovOperand` and dispatch to the matching
21/// `isCovariantWith` overload.
22pub(crate) enum CovOperand {
23    Type(TypeId),
24    Indexer(TableIndexer),
25}
26
27pub(crate) trait IntoCovOperand: Copy {
28    fn into_cov_operand(self) -> CovOperand;
29}
30
31impl<T> IntoCovOperand for *const T {
32    #[inline]
33    fn into_cov_operand(self) -> CovOperand {
34        CovOperand::Type(self as TypeId)
35    }
36}
37
38impl IntoCovOperand for TableIndexer {
39    #[inline]
40    fn into_cov_operand(self) -> CovOperand {
41        CovOperand::Indexer(self)
42    }
43}
44
45impl Subtyping {
46    /// Dispatch `isCovariantWith(env, sub, super, scope)` to the concrete overload
47    /// that matches the runtime operand tags (mirrors C++ template overload
48    /// resolution for the generic variance helpers).
49    pub(crate) fn covariant_dispatch(
50        &mut self,
51        env: &mut SubtypingEnvironment,
52        sub: CovOperand,
53        sup: CovOperand,
54        scope: *mut Scope,
55    ) -> SubtypingResult {
56        match (sub, sup) {
57            (CovOperand::Type(a), CovOperand::Type(b)) => self
58                .is_covariant_with_subtyping_environment_type_id_type_id_not_null_scope(
59                    env, a, b, scope,
60                ),
61            (CovOperand::Indexer(a), CovOperand::Indexer(b)) => self
62                .is_covariant_with_subtyping_environment_table_indexer_table_indexer_not_null_scope(
63                    env, &a, &b, scope,
64                ),
65            // Mixed operand kinds never occur in any real instantiation.
66            _ => unreachable!("isCovariantWith dispatch with mismatched operand kinds"),
67        }
68    }
69
70    pub fn is_contravariant_with_subtyping_environment_sub_ty_super_ty_not_null_scope<
71        SubTy,
72        SuperTy,
73    >(
74        &mut self,
75        env: &mut SubtypingEnvironment,
76        sub_ty: SubTy,
77        super_ty: SuperTy,
78        scope: *mut Scope,
79    ) -> SubtypingResult
80    where
81        SubTy: IntoCovOperand,
82        SuperTy: IntoCovOperand,
83    {
84        // C++: isCovariantWith(env, superTy, subTy, scope) — note the swap.
85        let mut result = self.covariant_dispatch(
86            env,
87            super_ty.into_cov_operand(),
88            sub_ty.into_cov_operand(),
89            scope,
90        );
91
92        if result.reasoning.empty() {
93            result.reasoning.insert(SubtypingReasoning {
94                sub_path: Path::default(),
95                super_path: Path::default(),
96                variance: SubtypingVariance::Contravariant,
97                is_property_modifier_violation: false,
98            });
99        } else {
100            // If we don't swap the paths here, we will end up producing an invalid
101            // path whenever we involve contravariance. We'll end up appending path
102            // components that should belong to the supertype to the subtype, and vice
103            // versa.
104            let mut updated = SubtypingReasonings::new(k_empty_reasoning());
105            for r in result.reasoning.iter() {
106                let mut r = r.clone();
107                core::mem::swap(&mut r.sub_path, &mut r.super_path);
108
109                // Also swap covariant/contravariant, since those are also the other
110                // way around.
111                if r.variance == SubtypingVariance::Covariant {
112                    r.variance = SubtypingVariance::Contravariant;
113                } else if r.variance == SubtypingVariance::Contravariant {
114                    r.variance = SubtypingVariance::Covariant;
115                }
116                updated.insert(r);
117            }
118            result.reasoning = updated;
119        }
120
121        // `assertReasoningValid(subTy, superTy, ...)` is a debug-only no-op (its body
122        // is gated behind DebugLuauSubtypingCheckPathValidity and elided). The Rust
123        // helper takes a single `TID`; `SubTy`/`SuperTy` are distinct type params here
124        // (always the same kind in practice), so we pass `sub_ty` for both to satisfy
125        // the unified type parameter without changing behaviour.
126        assert_reasoning_valid(sub_ty, sub_ty, &result, self.builtin_types, self.arena);
127
128        result
129    }
130}