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