Skip to main content

luaur_analysis/methods/
subtyping_check_generic_bounds.rs

1use crate::enums::normalization_result::NormalizationResult;
2use crate::enums::subtyping_suppression_policy::SubtypingSuppressionPolicy;
3use crate::records::generic_bounds::GenericBounds;
4use crate::records::generic_bounds_mismatch::GenericBoundsMismatch;
5use crate::records::intersection_builder::IntersectionBuilder;
6use crate::records::scope::Scope;
7use crate::records::subtyping::Subtyping;
8use crate::records::subtyping_environment::SubtypingEnvironment;
9use crate::records::subtyping_result::SubtypingResult;
10use crate::records::union_builder::UnionBuilder;
11
12use luaur_common::macros::luau_assert::LUAU_ASSERT;
13
14impl Subtyping {
15    pub fn subtyping_check_generic_bounds(
16        &mut self,
17        bounds: &GenericBounds,
18        env: &mut SubtypingEnvironment,
19        scope: *mut Scope,
20        generic_name: &str,
21    ) -> SubtypingResult {
22        let mut result = SubtypingResult {
23            is_subtype: true,
24            normalization_too_complex: false,
25            is_cacheable: true,
26            is_error_suppressing: false,
27            errors: Default::default(),
28            reasoning: Default::default(),
29            assumed_constraints: Default::default(),
30            generic_bounds_mismatches: Default::default(),
31        };
32
33        let mut aggregate_lower_bound = UnionBuilder::union_builder(self.arena, self.builtin_types);
34        aggregate_lower_bound.reserve(bounds.lower_bound.size());
35        for &t in &bounds.lower_bound.order {
36            if let Some(mapped_bounds) = env.mapped_generics.find(&t) {
37                if mapped_bounds.is_empty() {
38                    continue;
39                }
40            }
41            aggregate_lower_bound.add(t);
42        }
43        let mut lower_bound = aggregate_lower_bound.build();
44
45        let mut aggregate_upper_bound =
46            IntersectionBuilder::intersection_builder(self.arena, self.builtin_types);
47        aggregate_upper_bound.reserve(bounds.upper_bound.size());
48        for &t in &bounds.upper_bound.order {
49            if let Some(mapped_bounds) = env.mapped_generics.find(&t) {
50                if mapped_bounds.is_empty() {
51                    continue;
52                }
53            }
54            aggregate_upper_bound.add(t);
55        }
56        let mut upper_bound = aggregate_upper_bound.build();
57
58        if let Some(subst_lower_bound) = env.apply_mapped_generics(
59            self.builtin_types,
60            self.arena,
61            lower_bound,
62            self.ice_reporter,
63        ) {
64            lower_bound = subst_lower_bound;
65        }
66
67        if let Some(subst_upper_bound) = env.apply_mapped_generics(
68            self.builtin_types,
69            self.arena,
70            upper_bound,
71            self.ice_reporter,
72        ) {
73            upper_bound = subst_upper_bound;
74        }
75
76        // `Normalizer::normalize` here returns a non-nullable `Arc<NormalizedType>`,
77        // so the C++ `!nt` (normalization-failed → nullptr) branch is not
78        // representable through this signature; the value is always present, and
79        // `res` is therefore always the inhabitedness result.
80        let nt = unsafe { (*self.normalizer).normalize(upper_bound) };
81        let res = unsafe { (*self.normalizer).is_inhabited_normalized_type(&nt) };
82
83        if res == NormalizationResult::HitLimits {
84            result.normalization_too_complex = true;
85        } else if res == NormalizationResult::False {
86            result.is_subtype = false;
87        }
88
89        let mut bounds_env = SubtypingEnvironment {
90            parent: env as *mut SubtypingEnvironment,
91            mapped_generics: luaur_common::records::dense_hash_map::DenseHashMap::new(
92                core::ptr::null(),
93            ),
94            mapped_generic_packs:
95                crate::records::mapped_generic_environment::MappedGenericEnvironment {
96                    frames: alloc::vec::Vec::new(),
97                    current_scope_index: None,
98                },
99            substitutions: luaur_common::records::dense_hash_map::DenseHashMap::new(
100                core::ptr::null(),
101            ),
102            seen_set_cache: luaur_common::records::dense_hash_map::DenseHashMap::new((
103                core::ptr::null(),
104                core::ptr::null(),
105            )),
106            iteration_count: 0,
107        };
108        let mut bounds_result = self
109            .is_covariant_with_subtyping_environment_type_id_type_id_not_null_scope(
110                &mut bounds_env,
111                lower_bound,
112                upper_bound,
113                scope,
114            );
115        bounds_result.reasoning.clear();
116
117        if res == NormalizationResult::False {
118            result
119                .generic_bounds_mismatches
120                .push(GenericBoundsMismatch::new(
121                    generic_name,
122                    bounds.lower_bound.clone(),
123                    bounds.upper_bound.clone(),
124                ));
125        } else if !bounds_result.is_subtype {
126            // Check if the bounds are error suppressing before reporting a mismatch
127            let lower_error_suppression = unsafe {
128                crate::functions::should_suppress_errors_type_utils::should_suppress_errors(
129                    self.normalizer,
130                    lower_bound,
131                )
132            };
133            let upper_error_suppression = unsafe {
134                crate::functions::should_suppress_errors_type_utils::should_suppress_errors(
135                    self.normalizer,
136                    upper_bound,
137                )
138            };
139            match lower_error_suppression
140                .or_else(&upper_error_suppression)
141                .value
142            {
143                crate::enums::value::Value::Suppress => {}
144                crate::enums::value::Value::NormalizationFailed => {
145                    result
146                        .generic_bounds_mismatches
147                        .push(GenericBoundsMismatch::new(
148                            generic_name,
149                            bounds.lower_bound.clone(),
150                            bounds.upper_bound.clone(),
151                        ));
152                }
153                crate::enums::value::Value::DoNotSuppress => {
154                    result
155                        .generic_bounds_mismatches
156                        .push(GenericBoundsMismatch::new(
157                            generic_name,
158                            bounds.lower_bound.clone(),
159                            bounds.upper_bound.clone(),
160                        ));
161                }
162                _ => {
163                    LUAU_ASSERT!(false);
164                }
165            }
166        }
167
168        result.and_also(bounds_result, SubtypingSuppressionPolicy::Any);
169
170        result
171    }
172}