Skip to main content

luaur_analysis/methods/
constraint_solver_try_dispatch_has_indexer.rs

1use crate::enums::polarity::Polarity;
2use crate::enums::table_state::TableState;
3use crate::functions::as_mutable_type::as_mutable_type_id;
4use crate::functions::follow_type::follow_type_id;
5use crate::functions::fresh_type::fresh_type;
6use crate::functions::get_mutable_type::get_mutable_type_id;
7use crate::functions::get_type_alt_j::get_type_id;
8use crate::functions::track_interior_free_type::track_interior_free_type;
9use crate::records::any_type::AnyType;
10use crate::records::blocked_type::BlockedType;
11use crate::records::constraint::Constraint;
12use crate::records::constraint_solver::ConstraintSolver;
13use crate::records::extern_type::ExternType;
14use crate::records::free_type::FreeType;
15use crate::records::intersection_builder::IntersectionBuilder;
16use crate::records::intersection_type::IntersectionType;
17use crate::records::metatable_type::MetatableType;
18use crate::records::never_type::NeverType;
19use crate::records::set::Set;
20use crate::records::table_indexer::TableIndexer;
21use crate::records::table_type::TableType;
22use crate::records::type_level::TypeLevel;
23use crate::records::union_builder::UnionBuilder;
24use crate::records::union_type::UnionType;
25use crate::type_aliases::error_type::ErrorType;
26use crate::type_aliases::type_id::TypeId;
27use crate::type_aliases::type_variant::TypeVariant;
28use luaur_common::records::dense_hash_set::DenseHashSet;
29use luaur_common::FFlag;
30
31impl ConstraintSolver {
32    pub fn constraint_solver_try_dispatch_has_indexer(
33        &mut self,
34        _recursion_depth: &mut i32,
35        constraint: *const Constraint,
36        subject_type: TypeId,
37        index_type: TypeId,
38        mut result_type: TypeId,
39        _seen: &mut DenseHashSet<TypeId>,
40    ) -> bool {
41        let subject_type = unsafe { follow_type_id(subject_type) };
42        let index_type = unsafe { follow_type_id(index_type) };
43
44        if _seen.contains(&subject_type) {
45            return false;
46        }
47        _seen.insert(subject_type);
48
49        if unsafe { !get_type_id::<AnyType>(subject_type).is_null() } {
50            self.bind_not_null_constraint_type_id_type_id(constraint, result_type, unsafe {
51                (*self.builtin_types).anyType
52            });
53            return true;
54        }
55
56        let free_type = unsafe { get_mutable_type_id::<FreeType>(subject_type) };
57        if !free_type.is_null() {
58            let upper_bound = unsafe { follow_type_id((*free_type).upper_bound) };
59
60            if let Some(table) = unsafe { get_type_id::<TableType>(upper_bound).as_ref() } {
61                if let Some(indexer) = &table.indexer {
62                    self.constraint_solver_unify(constraint, index_type, indexer.index_type);
63                    self.bind_not_null_constraint_type_id_type_id(
64                        constraint,
65                        result_type,
66                        indexer.index_result_type,
67                    );
68                    return true;
69                }
70            } else if let Some(metatable) =
71                unsafe { get_type_id::<MetatableType>(upper_bound).as_ref() }
72            {
73                return self.constraint_solver_try_dispatch_has_indexer(
74                    _recursion_depth,
75                    constraint,
76                    metatable.table(),
77                    index_type,
78                    result_type,
79                    _seen,
80                );
81            }
82
83            let scope = unsafe { (*free_type).scope };
84            let free_result = unsafe {
85                fresh_type(
86                    &mut *self.arena,
87                    &*self.builtin_types,
88                    scope,
89                    Polarity::Mixed,
90                )
91            };
92            track_interior_free_type(scope, free_result);
93            self.bind_not_null_constraint_type_id_type_id(constraint, result_type, free_result);
94            result_type = free_result;
95
96            let mut table = TableType::table_type_table_state_type_level_scope(
97                TableState::Unsealed,
98                TypeLevel::default(),
99                scope,
100            );
101            table.indexer = Some(TableIndexer {
102                index_type,
103                index_result_type: free_result,
104                is_read_only: false,
105            });
106
107            let upper_bound = unsafe { (*self.arena).add_type(table) };
108            let simplified = self.simplify_intersection_not_null_scope_location_type_id_type_id(
109                unsafe { (*constraint).scope },
110                unsafe { (*constraint).location },
111                unsafe { (*free_type).upper_bound },
112                upper_bound,
113            );
114
115            if unsafe { !get_type_id::<NeverType>(simplified).is_null() } {
116                self.bind_not_null_constraint_type_id_type_id(constraint, result_type, unsafe {
117                    (*self.builtin_types).errorType
118                });
119            } else {
120                unsafe {
121                    (*free_type).upper_bound = simplified;
122                }
123            }
124
125            return true;
126        }
127
128        if let Some(table) = unsafe { get_mutable_type_id::<TableType>(subject_type).as_mut() } {
129            if let Some(indexer) = &table.indexer {
130                self.constraint_solver_unify(constraint, index_type, indexer.index_type);
131                self.bind_not_null_constraint_type_id_type_id(
132                    constraint,
133                    result_type,
134                    indexer.index_result_type,
135                );
136                return true;
137            }
138
139            if table.state == TableState::Unsealed {
140                let scope = table.scope;
141                let free_result = unsafe {
142                    fresh_type(
143                        &mut *self.arena,
144                        &*self.builtin_types,
145                        scope,
146                        Polarity::Mixed,
147                    )
148                };
149                track_interior_free_type(scope, free_result);
150                self.bind_not_null_constraint_type_id_type_id(constraint, result_type, free_result);
151                table.indexer = Some(TableIndexer {
152                    index_type,
153                    index_result_type: result_type,
154                    is_read_only: false,
155                });
156                return true;
157            }
158        }
159
160        if let Some(metatable) = unsafe { get_type_id::<MetatableType>(subject_type).as_ref() } {
161            return self.constraint_solver_try_dispatch_has_indexer(
162                _recursion_depth,
163                constraint,
164                metatable.table(),
165                index_type,
166                result_type,
167                _seen,
168            );
169        }
170
171        let mut extern_type = unsafe { get_type_id::<ExternType>(subject_type) as *mut ExternType };
172        while !extern_type.is_null() {
173            if let Some(indexer) = unsafe { &(*extern_type).indexer } {
174                self.constraint_solver_unify(constraint, index_type, indexer.index_type);
175                self.bind_not_null_constraint_type_id_type_id(
176                    constraint,
177                    result_type,
178                    indexer.index_result_type,
179                );
180                return true;
181            }
182
183            extern_type = if let Some(parent) = unsafe { (*extern_type).parent } {
184                unsafe { get_type_id::<ExternType>(parent) as *mut ExternType }
185            } else {
186                core::ptr::null_mut()
187            };
188        }
189
190        if let Some(it) = unsafe { get_type_id::<IntersectionType>(subject_type).as_ref() } {
191            // Indexing into an intersection of types is roughly akin to overload
192            // selection: for every type in the intersection where it is well typed
193            // to index into _that_ type, we construct an intersection of said result
194            // types.
195            if FFlag::LuauRemoveConstraintSolverEmplace.get() {
196                let mut ib =
197                    IntersectionBuilder::intersection_builder(self.arena, self.builtin_types);
198                let mut success = false;
199
200                let parts: alloc::vec::Vec<TypeId> = it.parts.clone();
201                for part in parts {
202                    let r = unsafe { (*self.arena).add_type(BlockedType::default()) };
203                    unsafe {
204                        (*(get_mutable_type_id::<BlockedType>(r))).set_owner(constraint);
205                    }
206
207                    let ok = self.constraint_solver_try_dispatch_has_indexer(
208                        _recursion_depth,
209                        constraint,
210                        part,
211                        index_type,
212                        r,
213                        _seen,
214                    );
215                    // If we've cut a recursive loop short, skip it.
216                    if !ok {
217                        continue;
218                    }
219
220                    let r = unsafe { follow_type_id(r) };
221                    if unsafe { get_type_id::<ErrorType>(r).is_null() } {
222                        success = true;
223                        ib.add(r);
224                    }
225                }
226
227                // We need to distinguish between the empty case (there
228                // were no valid indexable types) and the bottom type (one of the
229                // indexable result types was never). UnionBuilder will opt to
230                // only record that its seen a top type as an optimization. we
231                // add a flag to distinguish these cases.
232                if success {
233                    let built = ib.build();
234                    self.bind_not_null_constraint_type_id_type_id(constraint, result_type, built);
235                } else {
236                    self.bind_not_null_constraint_type_id_type_id(
237                        constraint,
238                        result_type,
239                        unsafe { (*self.builtin_types).errorType },
240                    );
241                }
242            } else {
243                let mut parts: Set<TypeId> = Set::new(core::ptr::null());
244                let part_list: alloc::vec::Vec<TypeId> = it.parts.clone();
245                for part in part_list {
246                    parts.insert(&unsafe { follow_type_id(part) });
247                }
248
249                let mut results: Set<TypeId> = Set::new(core::ptr::null());
250
251                let parts_iter: alloc::vec::Vec<TypeId> = parts.iter().copied().collect();
252                for part in parts_iter {
253                    let r = unsafe { (*self.arena).add_type(BlockedType::default()) };
254                    unsafe {
255                        (*(get_mutable_type_id::<BlockedType>(r))).set_owner(constraint);
256                    }
257
258                    let ok = self.constraint_solver_try_dispatch_has_indexer(
259                        _recursion_depth,
260                        constraint,
261                        part,
262                        index_type,
263                        r,
264                        _seen,
265                    );
266                    // If we've cut a recursive loop short, skip it.
267                    if !ok {
268                        continue;
269                    }
270
271                    let r = unsafe { follow_type_id(r) };
272                    if unsafe { get_type_id::<ErrorType>(r).is_null() } {
273                        results.insert(&r);
274                    }
275                }
276
277                if results.size() == 0 {
278                    self.bind_not_null_constraint_type_id_type_id(
279                        constraint,
280                        result_type,
281                        unsafe { (*self.builtin_types).errorType },
282                    );
283                } else if results.size() == 1 {
284                    let first = *results.iter().next().unwrap();
285                    self.bind_not_null_constraint_type_id_type_id(constraint, result_type, first);
286                } else {
287                    let parts_vec: alloc::vec::Vec<TypeId> = results.iter().copied().collect();
288                    let mutable_ty = unsafe { as_mutable_type_id(result_type) };
289                    unsafe {
290                        (*mutable_ty).ty =
291                            TypeVariant::Intersection(IntersectionType { parts: parts_vec });
292                    }
293                    let location = unsafe { (*constraint).location };
294                    self.unblock_type_id_location(result_type, location);
295                }
296            }
297
298            return true;
299        }
300
301        if let Some(ut) = unsafe { get_type_id::<UnionType>(subject_type).as_ref() } {
302            // Indexing into a union of types means constructing a union of
303            // results: we don't know _which_ type it could be.
304            if FFlag::LuauRemoveConstraintSolverEmplace.get() {
305                let mut ub = UnionBuilder::union_builder(self.arena, self.builtin_types);
306                let mut success = false;
307
308                let options: alloc::vec::Vec<TypeId> = ut.options.clone();
309                for option in options {
310                    let r = unsafe { (*self.arena).add_type(BlockedType::default()) };
311                    unsafe {
312                        (*(get_mutable_type_id::<BlockedType>(r))).set_owner(constraint);
313                    }
314
315                    let ok = self.constraint_solver_try_dispatch_has_indexer(
316                        _recursion_depth,
317                        constraint,
318                        option,
319                        index_type,
320                        r,
321                        _seen,
322                    );
323                    // If we've cut a recursive loop short, skip it.
324                    if !ok {
325                        continue;
326                    }
327
328                    let r = unsafe { follow_type_id(r) };
329                    success = true;
330                    ub.add(r);
331                }
332
333                // We need to distinguish between the empty case (there
334                // were no valid indexable types) and the top type (one of the
335                // indexable result types was unknown). UnionBuilder will opt to
336                // only record that its seen a top type as an optimization. we
337                // add a flag to distinguish these cases.
338                if success {
339                    let built = ub.build();
340                    self.bind_not_null_constraint_type_id_type_id(constraint, result_type, built);
341                } else {
342                    self.bind_not_null_constraint_type_id_type_id(
343                        constraint,
344                        result_type,
345                        unsafe { (*self.builtin_types).errorType },
346                    );
347                }
348            } else {
349                let mut parts: Set<TypeId> = Set::new(core::ptr::null());
350                let option_list: alloc::vec::Vec<TypeId> = ut.options.clone();
351                for part in option_list {
352                    parts.insert(&unsafe { follow_type_id(part) });
353                }
354
355                let mut results: Set<TypeId> = Set::new(core::ptr::null());
356
357                let parts_iter: alloc::vec::Vec<TypeId> = parts.iter().copied().collect();
358                for part in parts_iter {
359                    let r = unsafe { (*self.arena).add_type(BlockedType::default()) };
360                    unsafe {
361                        (*(get_mutable_type_id::<BlockedType>(r))).set_owner(constraint);
362                    }
363
364                    let ok = self.constraint_solver_try_dispatch_has_indexer(
365                        _recursion_depth,
366                        constraint,
367                        part,
368                        index_type,
369                        r,
370                        _seen,
371                    );
372                    // If we've cut a recursive loop short, skip it.
373                    if !ok {
374                        continue;
375                    }
376
377                    let r = unsafe { follow_type_id(r) };
378                    results.insert(&r);
379                }
380
381                if results.size() == 0 {
382                    self.bind_not_null_constraint_type_id_type_id(
383                        constraint,
384                        result_type,
385                        unsafe { (*self.builtin_types).errorType },
386                    );
387                } else if results.size() == 1 {
388                    let first_result = *results.iter().next().unwrap();
389                    if !FFlag::LuauConstraintGraph.get() {
390                        // bind will already shift references.
391                        self.deprecate_d_shift_references(result_type, first_result);
392                    }
393                    self.bind_not_null_constraint_type_id_type_id(
394                        constraint,
395                        result_type,
396                        first_result,
397                    );
398                } else {
399                    let options_vec: alloc::vec::Vec<TypeId> = results.iter().copied().collect();
400                    let mutable_ty = unsafe { as_mutable_type_id(result_type) };
401                    unsafe {
402                        (*mutable_ty).ty = TypeVariant::Union(UnionType {
403                            options: options_vec,
404                        });
405                    }
406                    let location = unsafe { (*constraint).location };
407                    self.unblock_type_id_location(result_type, location);
408                }
409            }
410
411            return true;
412        }
413
414        self.bind_not_null_constraint_type_id_type_id(constraint, result_type, unsafe {
415            (*self.builtin_types).errorType
416        });
417        true
418    }
419}