Skip to main content

luaur_analysis/methods/
subtyping_is_covariant_with_subtyping.rs

1use crate::enums::subtyping_suppression_policy::SubtypingSuppressionPolicy;
2use crate::enums::table_state::TableState;
3use crate::enums::type_field::TypeField;
4use crate::functions::assert_reasoning_valid_subtyping::assert_reasoning_valid;
5use crate::functions::follow_type::follow_type_id;
6use crate::functions::get_2::get2;
7use crate::functions::get_type_alt_j::get_type_id;
8use crate::functions::subsumes_scope::subsumes;
9use crate::methods::subtyping_bind_generic::dense_hash_map_find_no_default;
10use crate::records::any_type::AnyType;
11use crate::records::blocked_type::BlockedType;
12use crate::records::error_type::ErrorType;
13use crate::records::extern_type::ExternType;
14use crate::records::free_type::FreeType;
15use crate::records::function_type::FunctionType;
16use crate::records::generic_type::GenericType;
17use crate::records::intersection_type::IntersectionType;
18use crate::records::metatable_type::MetatableType;
19use crate::records::negation_type::NegationType;
20use crate::records::never_type::NeverType;
21use crate::records::primitive_type::{PrimitiveType, Type as PrimType};
22use crate::records::scope::Scope;
23use crate::records::singleton_type::SingletonType;
24use crate::records::subtype_constraint::SubtypeConstraint;
25use crate::records::subtyping::Subtyping;
26use crate::records::subtyping_environment::SubtypingEnvironment;
27use crate::records::subtyping_result::SubtypingResult;
28use crate::records::table_type::TableType;
29use crate::records::type_function_instance_type::TypeFunctionInstanceType;
30use crate::records::union_type::UnionType;
31use crate::records::unknown_type::UnknownType;
32use crate::type_aliases::component::Component;
33use crate::type_aliases::constraint_v::ConstraintV;
34use crate::type_aliases::type_id::TypeId;
35
36impl Subtyping {
37    pub fn is_covariant_with_subtyping_environment_type_id_type_id_not_null_scope(
38        &mut self,
39        env: &mut SubtypingEnvironment,
40        sub_ty: TypeId,
41        super_ty: TypeId,
42        scope: *mut Scope,
43    ) -> SubtypingResult {
44        // NonExceptionalRecursionLimiter nerl(&normalizer->sharedState->counters.recursionCount);
45        let recursion_count_ptr =
46            unsafe { &mut (*(*self.normalizer).shared_state).counters.recursion_count as *mut i32 };
47        let mut nerl =
48            crate::records::non_exceptional_recursion_limiter::NonExceptionalRecursionLimiter {
49                base: unsafe { core::mem::zeroed() },
50                native_stack_guard: unsafe { core::mem::zeroed() },
51            };
52        nerl.non_exceptional_recursion_limiter_non_exceptional_recursion_limiter(
53            recursion_count_ptr as *mut core::ffi::c_int,
54        );
55        if !nerl.is_ok(luaur_common::DFInt::LuauSubtypingRecursionLimit.get() as core::ffi::c_int) {
56            return SubtypingResult {
57                is_subtype: false,
58                normalization_too_complex: true,
59                ..Default::default()
60            };
61        }
62        let _nerl = nerl;
63
64        env.iteration_count += 1;
65        let iteration_limit = luaur_common::FInt::LuauSubtypingIterationLimit.get() as i32;
66        if iteration_limit > 0 && env.iteration_count >= iteration_limit {
67            return SubtypingResult {
68                is_subtype: false,
69                normalization_too_complex: true,
70                ..Default::default()
71            };
72        }
73
74        let mut sub_ty = unsafe { follow_type_id(sub_ty) };
75        let mut super_ty = unsafe { follow_type_id(super_ty) };
76
77        if let Some(sub_it) = env.try_find_substitution(sub_ty) {
78            if !sub_it.is_null() {
79                sub_ty = sub_it;
80            }
81        }
82
83        if let Some(super_it) = env.try_find_substitution(super_ty) {
84            if !super_it.is_null() {
85                // NOTE: This faithfully mirrors the C++, which assigns the
86                // super substitution back into `subTy` (apparent upstream typo).
87                sub_ty = super_it;
88            }
89        }
90
91        if let Some(cached_result) = self.result_cache.find(&(sub_ty, super_ty)) {
92            return cached_result.clone();
93        }
94
95        if let Some(cached_result) = env.try_find_subtyping_result((sub_ty, super_ty)) {
96            return cached_result.clone();
97        }
98
99        // TODO: Do we care about returning a proof that this is error-suppressing?
100        if sub_ty == super_ty {
101            return SubtypingResult {
102                is_subtype: true,
103                ..Default::default()
104            };
105        }
106
107        let type_pair = (sub_ty, super_ty);
108        // seenTypes.insert(typePair) — Luau::Set semantics over a DenseHashMap<_, bool>.
109        let fresh = {
110            let entry = self.seen_types.get_or_insert(type_pair);
111            let fresh = !*entry;
112            if fresh {
113                *entry = true;
114            }
115            fresh
116        };
117        if !fresh {
118            // We've encountered a cycle; conservatively assume subtype and refuse to
119            // cache anything that touches the cycle.
120            let mut res = SubtypingResult::default();
121            res.is_subtype = true;
122            res.is_cacheable = false;
123
124            *env.seen_set_cache.get_or_insert(type_pair) = res.clone();
125
126            return res;
127        }
128
129        // ScopedSeenSet ssp{seenTypes, typePair}; — re-insert (no-op) and erase on
130        // every exit below. With no `erase`, mirror `Luau::Set::erase` by clearing
131        // the slot's bool before each return point.
132
133        let mut result = SubtypingResult::default();
134
135        let pair_ff = get2::<FreeType, FreeType, _>(sub_ty, super_ty);
136        if !pair_ff.first.is_null() {
137            // Any two free types are potentially subtypes of one another because
138            // both of them could be narrowed to never.
139            result = SubtypingResult {
140                is_subtype: true,
141                ..Default::default()
142            };
143            result.with_assumed_constraint(ConstraintV::Subtype(SubtypeConstraint {
144                sub_type: sub_ty,
145                super_type: super_ty,
146            }));
147        } else if let Some(super_free) = unsafe { get_type_id::<FreeType>(super_ty).as_ref() } {
148            // FIXME CLI-185582.
149            result = self.is_covariant_with_subtyping_environment_type_id_type_id_not_null_scope(
150                env,
151                sub_ty,
152                super_free.upper_bound,
153                scope,
154            );
155
156            if result.is_subtype {
157                result.with_assumed_constraint(ConstraintV::Subtype(SubtypeConstraint {
158                    sub_type: sub_ty,
159                    super_type: super_ty,
160                }));
161            }
162        } else if let Some(sub_free) = unsafe { get_type_id::<FreeType>(sub_ty).as_ref() } {
163            // FIXME CLI-185582.
164            if self
165                .is_covariant_with_subtyping_environment_type_id_type_id_not_null_scope(
166                    env,
167                    sub_free.lower_bound,
168                    super_ty,
169                    scope,
170                )
171                .is_subtype
172            {
173                result = SubtypingResult {
174                    is_subtype: true,
175                    ..Default::default()
176                };
177                result.with_assumed_constraint(ConstraintV::Subtype(SubtypeConstraint {
178                    sub_type: sub_ty,
179                    super_type: super_ty,
180                }));
181            } else {
182                result = SubtypingResult {
183                    is_subtype: false,
184                    ..Default::default()
185                };
186            }
187        } else if unsafe {
188            !get_type_id::<BlockedType>(sub_ty).is_null()
189                || !get_type_id::<BlockedType>(super_ty).is_null()
190        } {
191            result = SubtypingResult {
192                is_subtype: true,
193                ..Default::default()
194            };
195            result.with_assumed_constraint(ConstraintV::Subtype(SubtypeConstraint {
196                sub_type: sub_ty,
197                super_type: super_ty,
198            }));
199        }
200        // TODO: These branches are entirely incorrect (per upstream).
201        else if let Some(sub_generic) = unsafe { get_type_id::<GenericType>(sub_ty).as_ref() }
202            .filter(|g| subsumes(g.scope, scope))
203        {
204            let _ = sub_generic;
205            self.seen_set_erase(type_pair);
206            return self.is_covariant_with_subtyping_environment_type_id_type_id_not_null_scope(
207                env,
208                unsafe { (*self.builtin_types).neverType },
209                super_ty,
210                scope,
211            );
212        } else if let Some(super_generic) = unsafe { get_type_id::<GenericType>(super_ty).as_ref() }
213            .filter(|g| subsumes(g.scope, scope))
214        {
215            let _ = super_generic;
216            self.seen_set_erase(type_pair);
217            return self.is_covariant_with_subtyping_environment_type_id_type_id_not_null_scope(
218                env,
219                sub_ty,
220                unsafe { (*self.builtin_types).unknownType },
221                scope,
222            );
223        } else if unsafe { !get_type_id::<AnyType>(super_ty).is_null() } {
224            result = SubtypingResult {
225                is_subtype: true,
226                ..Default::default()
227            };
228        }
229        // any = err | unknown.
230        else if unsafe {
231            !get_type_id::<AnyType>(sub_ty).is_null()
232                && !get_type_id::<UnknownType>(super_ty).is_null()
233        } {
234            result = SubtypingResult {
235                is_subtype: true,
236                ..Default::default()
237            };
238        } else if unsafe { !get_type_id::<AnyType>(sub_ty).is_null() } {
239            // any = unknown | error, so we rewrite this to match.
240            result = self.is_covariant_with_subtyping_environment_type_id_type_id_not_null_scope(
241                env,
242                unsafe { (*self.builtin_types).unknownType },
243                super_ty,
244                scope,
245            );
246            let err_part = self
247                .is_covariant_with_subtyping_environment_type_id_type_id_not_null_scope(
248                    env,
249                    unsafe { (*self.builtin_types).errorType },
250                    super_ty,
251                    scope,
252                );
253            result.and_also(err_part, SubtypingSuppressionPolicy::Any);
254            result.is_error_suppressing = true;
255        } else if unsafe {
256            !get_type_id::<UnknownType>(super_ty).is_null()
257                && get_type_id::<UnionType>(sub_ty).is_null()
258                && get_type_id::<IntersectionType>(sub_ty).is_null()
259        } {
260            let error_suppressing = unsafe { !get_type_id::<ErrorType>(sub_ty).is_null() };
261            result.is_subtype = !error_suppressing;
262            result.is_error_suppressing = error_suppressing;
263        } else if unsafe { !get_type_id::<NeverType>(sub_ty).is_null() } {
264            result = SubtypingResult {
265                is_subtype: true,
266                ..Default::default()
267            };
268        } else if unsafe { !get_type_id::<ErrorType>(super_ty).is_null() } {
269            result = SubtypingResult {
270                is_subtype: false,
271                ..Default::default()
272            };
273        } else if unsafe { !get_type_id::<ErrorType>(sub_ty).is_null() } {
274            result = SubtypingResult {
275                is_subtype: true,
276                ..Default::default()
277            };
278            result.is_error_suppressing = true;
279        } else if unsafe { !get_type_id::<TypeFunctionInstanceType>(sub_ty).is_null() } {
280            let mut sub_type_function_instance =
281                unsafe { get_type_id::<TypeFunctionInstanceType>(sub_ty) };
282            let mut mapped_generics_applied = false;
283            if let Some(subst_sub_ty) =
284                env.apply_mapped_generics(self.builtin_types, self.arena, sub_ty, self.ice_reporter)
285            {
286                mapped_generics_applied = subst_sub_ty != sub_ty;
287                sub_type_function_instance =
288                    unsafe { get_type_id::<TypeFunctionInstanceType>(subst_sub_ty) };
289            }
290
291            result = self
292                .is_covariant_with_subtyping_environment_type_function_instance_type_type_id_not_null_scope(
293                    env,
294                    unsafe { &*sub_type_function_instance },
295                    super_ty,
296                    scope,
297                );
298            result.is_cacheable = !mapped_generics_applied;
299        } else if unsafe { !get_type_id::<TypeFunctionInstanceType>(super_ty).is_null() } {
300            let mut super_type_function_instance =
301                unsafe { get_type_id::<TypeFunctionInstanceType>(super_ty) };
302            let mut mapped_generics_applied = false;
303            if let Some(subst_super_ty) = env.apply_mapped_generics(
304                self.builtin_types,
305                self.arena,
306                super_ty,
307                self.ice_reporter,
308            ) {
309                mapped_generics_applied = subst_super_ty != super_ty;
310                super_type_function_instance =
311                    unsafe { get_type_id::<TypeFunctionInstanceType>(subst_super_ty) };
312            }
313
314            result = self
315                .is_covariant_with_subtyping_environment_type_id_type_function_instance_type_not_null_scope(
316                    env,
317                    sub_ty,
318                    unsafe { &*super_type_function_instance },
319                    scope,
320                );
321            result.is_cacheable = !mapped_generics_applied;
322        } else if unsafe {
323            !get_type_id::<GenericType>(sub_ty).is_null()
324                || !get_type_id::<GenericType>(super_ty).is_null()
325        } {
326            let sub_has_bounds = dense_hash_map_find_no_default(&env.mapped_generics, &sub_ty)
327                .map_or(false, |b| !b.is_empty());
328            let super_has_bounds = dense_hash_map_find_no_default(&env.mapped_generics, &super_ty)
329                .map_or(false, |b| !b.is_empty());
330            if sub_has_bounds {
331                let ok = self.bind_generic(env, sub_ty, super_ty);
332                result.is_subtype = ok;
333                result.is_cacheable = false;
334            } else if super_has_bounds {
335                let ok = self.bind_generic(env, sub_ty, super_ty);
336                result.is_subtype = ok;
337                result.is_cacheable = false;
338            }
339        } else if let Some(sub_union) = unsafe { get_type_id::<UnionType>(sub_ty).as_ref() } {
340            result = self
341                .is_covariant_with_subtyping_environment_union_type_type_id_not_null_scope(
342                    env, sub_union, super_ty, scope,
343                );
344        } else if let Some(super_union) = unsafe { get_type_id::<UnionType>(super_ty).as_ref() } {
345            result = self
346                .is_covariant_with_subtyping_environment_type_id_union_type_not_null_scope(
347                    env,
348                    sub_ty,
349                    super_union,
350                    scope,
351                );
352            if !result.is_subtype && !result.normalization_too_complex {
353                result = self.try_semantic_subtyping(env, sub_ty, super_ty, scope, &mut result);
354            }
355        } else if let Some(super_intersection) =
356            unsafe { get_type_id::<IntersectionType>(super_ty).as_ref() }
357        {
358            result = self
359                .is_covariant_with_subtyping_environment_type_id_intersection_type_not_null_scope(
360                    env,
361                    sub_ty,
362                    super_intersection,
363                    scope,
364                );
365        } else if let Some(sub_intersection) =
366            unsafe { get_type_id::<IntersectionType>(sub_ty).as_ref() }
367        {
368            result = self
369                .is_covariant_with_subtyping_environment_intersection_type_type_id_not_null_scope(
370                    env,
371                    sub_intersection,
372                    super_ty,
373                    scope,
374                );
375            if !result.is_subtype && !result.normalization_too_complex {
376                result = self.try_semantic_subtyping(env, sub_ty, super_ty, scope, &mut result);
377            }
378        } else if {
379            let p = get2::<NegationType, NegationType, _>(sub_ty, super_ty);
380            !p.first.is_null()
381        } {
382            let p = get2::<NegationType, NegationType, _>(sub_ty, super_ty);
383            // We use `isContravariantWith` here in order to make sure that the
384            // type paths still look coherent.
385            result = self
386                .is_contravariant_with_subtyping_environment_sub_ty_super_ty_not_null_scope(
387                    env,
388                    unsafe { (*p.first).ty },
389                    unsafe { (*p.second).ty },
390                    scope,
391                );
392            result.with_both_component(Component::TypeField(TypeField::Negated));
393        } else if let Some(sub_negation) = unsafe { get_type_id::<NegationType>(sub_ty).as_ref() } {
394            result = self
395                .is_covariant_with_subtyping_environment_negation_type_type_id_not_null_scope(
396                    env,
397                    sub_negation,
398                    super_ty,
399                    scope,
400                );
401            if !result.is_subtype && !result.normalization_too_complex {
402                result = self.try_semantic_subtyping(env, sub_ty, super_ty, scope, &mut result);
403            }
404        } else if let Some(super_negation) =
405            unsafe { get_type_id::<NegationType>(super_ty).as_ref() }
406        {
407            result = self
408                .is_covariant_with_subtyping_environment_type_id_negation_type_not_null_scope(
409                    env,
410                    sub_ty,
411                    super_negation,
412                    scope,
413                );
414            if !result.is_subtype && !result.normalization_too_complex {
415                result = self.try_semantic_subtyping(env, sub_ty, super_ty, scope, &mut result);
416            }
417        } else if {
418            let p = get2::<PrimitiveType, PrimitiveType, _>(sub_ty, super_ty);
419            !p.first.is_null()
420        } {
421            let p = get2::<PrimitiveType, PrimitiveType, _>(sub_ty, super_ty);
422            result = self
423                .is_covariant_with_subtyping_environment_primitive_type_primitive_type_not_null_scope(
424                    env,
425                    unsafe { &*p.first },
426                    unsafe { &*p.second },
427                    scope,
428                );
429        } else if {
430            let p = get2::<SingletonType, PrimitiveType, _>(sub_ty, super_ty);
431            !p.first.is_null()
432        } {
433            let p = get2::<SingletonType, PrimitiveType, _>(sub_ty, super_ty);
434            result = self
435                .is_covariant_with_subtyping_environment_singleton_type_primitive_type_not_null_scope(
436                    env,
437                    unsafe { &*p.first },
438                    unsafe { &*p.second },
439                    scope,
440                );
441        } else if {
442            let p = get2::<SingletonType, SingletonType, _>(sub_ty, super_ty);
443            !p.first.is_null()
444        } {
445            let p = get2::<SingletonType, SingletonType, _>(sub_ty, super_ty);
446            result = self
447                .is_covariant_with_subtyping_environment_singleton_type_singleton_type_not_null_scope(
448                    env,
449                    unsafe { &*p.first },
450                    unsafe { &*p.second },
451                    scope,
452                );
453        } else if {
454            let p = get2::<FunctionType, PrimitiveType, _>(sub_ty, super_ty);
455            !p.first.is_null()
456        } {
457            let p = get2::<FunctionType, PrimitiveType, _>(sub_ty, super_ty);
458            let _sub_function = p.first;
459            let super_primitive = p.second;
460            result.is_subtype = unsafe { (*super_primitive).r#type == PrimType::Function };
461        } else if {
462            let p = get2::<FunctionType, FunctionType, _>(sub_ty, super_ty);
463            !p.first.is_null()
464        } {
465            let p = get2::<FunctionType, FunctionType, _>(sub_ty, super_ty);
466            result = self
467                .is_covariant_with_subtyping_environment_function_type_function_type_not_null_scope(
468                    env,
469                    unsafe { &*p.first },
470                    unsafe { &*p.second },
471                    scope,
472                );
473        } else if {
474            let p = get2::<TableType, TableType, _>(sub_ty, super_ty);
475            !p.first.is_null()
476        } {
477            let p = get2::<TableType, TableType, _>(sub_ty, super_ty);
478            let force_covariant_test =
479                !self.unique_types.is_null() && unsafe { (*self.unique_types).contains(&sub_ty) };
480            result = if luaur_common::FFlag::LuauSubtypingTablesHasBetterErrorSuppression.get() {
481                self.is_covariant_with_subtyping_environment_table_type_table_type_bool_not_null_scope(
482                    env,
483                    unsafe { &*p.first },
484                    unsafe { &*p.second },
485                    force_covariant_test,
486                    scope,
487                )
488            } else {
489                self.is_covariant_with_deprecated(
490                    env,
491                    unsafe { &*p.first },
492                    unsafe { &*p.second },
493                    force_covariant_test,
494                    scope,
495                )
496            };
497            if result.is_subtype
498                && unsafe { (*p.first).indexer.is_none() }
499                && unsafe { (*p.second).indexer.is_some() }
500                && unsafe { (*p.first).state != TableState::Sealed }
501            {
502                // FIXME CLI-182960.
503                result.with_assumed_constraint(ConstraintV::Subtype(SubtypeConstraint {
504                    sub_type: sub_ty,
505                    super_type: super_ty,
506                }));
507            }
508        } else if {
509            let p = get2::<MetatableType, MetatableType, _>(sub_ty, super_ty);
510            !p.first.is_null()
511        } {
512            let p = get2::<MetatableType, MetatableType, _>(sub_ty, super_ty);
513            result = self
514                .is_covariant_with_subtyping_environment_metatable_type_metatable_type_not_null_scope(
515                    env,
516                    unsafe { &*p.first },
517                    unsafe { &*p.second },
518                    scope,
519                );
520        } else if {
521            let p = get2::<MetatableType, TableType, _>(sub_ty, super_ty);
522            !p.first.is_null()
523        } {
524            let p = get2::<MetatableType, TableType, _>(sub_ty, super_ty);
525            result = self
526                .is_covariant_with_subtyping_environment_metatable_type_table_type_not_null_scope(
527                    env,
528                    unsafe { &*p.first },
529                    unsafe { &*p.second },
530                    scope,
531                );
532        } else if luaur_common::FFlag::LuauTableFreezeCheckIsSubtype.get() && {
533            let p = get2::<MetatableType, PrimitiveType, _>(sub_ty, super_ty);
534            !p.first.is_null()
535        } {
536            let p = get2::<MetatableType, PrimitiveType, _>(sub_ty, super_ty);
537            result = self
538                .is_covariant_with_subtyping_environment_metatable_type_primitive_type_not_null_scope(
539                    env,
540                    unsafe { &*p.first },
541                    unsafe { &*p.second },
542                    scope,
543                );
544        } else if {
545            let p = get2::<ExternType, ExternType, _>(sub_ty, super_ty);
546            !p.first.is_null()
547        } {
548            let p = get2::<ExternType, ExternType, _>(sub_ty, super_ty);
549            result = self
550                .is_covariant_with_subtyping_environment_extern_type_extern_type_not_null_scope(
551                    env,
552                    unsafe { &*p.first },
553                    unsafe { &*p.second },
554                    scope,
555                );
556        } else if {
557            let p = get2::<ExternType, TableType, _>(sub_ty, super_ty);
558            !p.first.is_null()
559        } {
560            let p = get2::<ExternType, TableType, _>(sub_ty, super_ty);
561            result = self
562                .is_covariant_with_subtyping_environment_type_id_extern_type_type_id_table_type_not_null_scope(
563                    env,
564                    sub_ty,
565                    unsafe { &*p.first },
566                    super_ty,
567                    unsafe { &*p.second },
568                    scope,
569                );
570        } else if {
571            let p = get2::<TableType, PrimitiveType, _>(sub_ty, super_ty);
572            !p.first.is_null()
573        } {
574            let p = get2::<TableType, PrimitiveType, _>(sub_ty, super_ty);
575            result = self
576                .is_covariant_with_subtyping_environment_table_type_primitive_type_not_null_scope(
577                    env,
578                    unsafe { &*p.first },
579                    unsafe { &*p.second },
580                    scope,
581                );
582        } else if {
583            let p = get2::<PrimitiveType, TableType, _>(sub_ty, super_ty);
584            !p.first.is_null()
585        } {
586            let p = get2::<PrimitiveType, TableType, _>(sub_ty, super_ty);
587            result = self
588                .is_covariant_with_subtyping_environment_primitive_type_table_type_not_null_scope(
589                    env,
590                    unsafe { &*p.first },
591                    unsafe { &*p.second },
592                    scope,
593                );
594        } else if {
595            let p = get2::<SingletonType, TableType, _>(sub_ty, super_ty);
596            !p.first.is_null()
597        } {
598            let p = get2::<SingletonType, TableType, _>(sub_ty, super_ty);
599            result = self
600                .is_covariant_with_subtyping_environment_singleton_type_table_type_not_null_scope(
601                    env,
602                    unsafe { &*p.first },
603                    unsafe { &*p.second },
604                    scope,
605                );
606        }
607
608        assert_reasoning_valid(sub_ty, super_ty, &result, self.builtin_types, self.arena);
609
610        // ScopedSeenSet destructor — erase the pair before returning.
611        self.seen_set_erase(type_pair);
612
613        self.cache(env, result, sub_ty, super_ty)
614    }
615
616    /// Mirror `Luau::Set::erase` over `seen_types` (a `DenseHashMap<_, bool>`): set
617    /// the slot's value to `false` rather than removing the key.
618    #[inline]
619    fn seen_set_erase(&mut self, type_pair: (TypeId, TypeId)) {
620        let entry = self.seen_types.get_or_insert(type_pair);
621        if *entry {
622            *entry = false;
623        }
624    }
625}