Skip to main content

luaur_analysis/methods/
constraint_solver_try_dispatch_constraint_solver_alt_n.rs

1use crate::enums::table_state::TableState;
2use crate::records::assign_index_constraint::AssignIndexConstraint;
3use crate::records::constraint::Constraint;
4use crate::records::constraint_solver::ConstraintSolver;
5use crate::records::extern_type::ExternType;
6use crate::records::free_type::FreeType;
7use crate::records::intersection_type::IntersectionType;
8use crate::records::table_indexer::TableIndexer;
9use crate::records::table_type as TableTypeRec;
10use crate::records::table_type::TableType;
11use crate::records::type_ids::TypeIds;
12use crate::records::type_level::TypeLevel;
13use crate::records::union_type::UnionType;
14use crate::type_aliases::type_id::TypeId;
15
16use crate::functions::add_union::add_union;
17use crate::functions::follow_type::follow_type_id;
18use crate::functions::follow_type_utils::follow_optional_ty;
19use crate::functions::simplify_intersection_simplify::simplify_intersection;
20
21use crate::records::builtin_types::BuiltinTypes;
22
23use luaur_common::macros::luau_assert::LUAU_ASSERT;
24
25impl ConstraintSolver {
26    pub fn try_dispatch_assign_index_constraint_not_null_constraint(
27        &mut self,
28        c: &AssignIndexConstraint,
29        constraint: *const Constraint,
30    ) -> bool {
31        let lhs_type: TypeId = unsafe { follow_type_id(c.lhs_type) };
32        let index_type: TypeId = unsafe { follow_type_id(c.index_type) };
33        let rhs_type: TypeId = unsafe { follow_type_id(c.rhs_type) };
34
35        if self.is_blocked_type_id(lhs_type) {
36            return self.block_type_id_not_null_constraint(lhs_type, constraint);
37        }
38
39        // Important: In every codepath through this function, the type `c.propType`
40        // must be bound to something.
41        let mut table_stuff = |lhs_table: &mut TableType| -> Option<bool> {
42            if lhs_table.indexer.is_some() {
43                let indexer: &TableIndexer = lhs_table.indexer.as_ref().unwrap();
44
45                self.constraint_solver_unify(constraint, index_type, indexer.index_type);
46                self.constraint_solver_unify(constraint, rhs_type, indexer.index_result_type);
47
48                let prop_bound = self.bind_not_null_constraint_type_id_type_id(
49                    constraint,
50                    c.prop_type,
51                    add_union(
52                        self.arena,
53                        self.builtin_types,
54                        &[indexer.index_result_type, unsafe {
55                            (*self.builtin_types).nilType
56                        }],
57                    ),
58                );
59
60                // Note: bind_not_null_constraint_* return type is () in Rust bindings,
61                // but we still preserve the original logic shape.
62                let _ = prop_bound;
63
64                return Some(true);
65            }
66
67            if lhs_table.state == TableState::Unsealed || lhs_table.state == TableState::Free {
68                lhs_table.indexer = Some(TableIndexer {
69                    index_type,
70                    index_result_type: rhs_type,
71                    is_read_only: false,
72                });
73
74                self.bind_not_null_constraint_type_id_type_id(constraint, c.prop_type, rhs_type);
75                return Some(true);
76            }
77
78            None
79        };
80
81        let lhs_free: *mut FreeType = unsafe {
82            crate::functions::get_mutable_type::get_mutable_type_id::<FreeType>(lhs_type)
83        };
84        if !lhs_free.is_null() {
85            let lhs_upper = unsafe { follow_type_id((*lhs_free).upper_bound) };
86            let lhs_table: *mut TableType = unsafe {
87                crate::functions::get_mutable_type::get_mutable_type_id::<TableType>(lhs_upper)
88            };
89            if !lhs_table.is_null() {
90                let res = unsafe { table_stuff(&mut *lhs_table) };
91                if let Some(v) = res {
92                    return v;
93                }
94            }
95
96            let new_upper_bound = unsafe {
97                (*self.arena).add_type(
98                    TableType::table_type_props_optional_table_indexer_type_level_scope_table_state(
99                        &Default::default(),
100                        Some(TableIndexer {
101                            index_type,
102                            index_result_type: rhs_type,
103                            is_read_only: false,
104                        }),
105                        TypeLevel::default(),
106                        (*constraint).scope,
107                        TableState::Free,
108                    ),
109                )
110            };
111
112            let new_table: *const TableType = unsafe {
113                crate::functions::get_type_alt_j::get_type_id::<TableType>(new_upper_bound)
114            };
115            LUAU_ASSERT!(!new_table.is_null());
116
117            self.constraint_solver_unify(constraint, lhs_type, new_upper_bound);
118
119            let new_table_mut: *mut TableType = unsafe {
120                crate::functions::get_mutable_type::get_mutable_type_id::<TableType>(
121                    new_upper_bound,
122                )
123            };
124            LUAU_ASSERT!(!new_table_mut.is_null());
125            LUAU_ASSERT!(unsafe { (*new_table_mut).indexer.is_some() });
126
127            let idx_res = unsafe { (*new_table_mut).indexer.as_ref().unwrap().index_result_type };
128            self.bind_not_null_constraint_type_id_type_id(constraint, c.prop_type, idx_res);
129            return true;
130        }
131
132        let lhs_table: *mut TableType = unsafe {
133            crate::functions::get_mutable_type::get_mutable_type_id::<TableType>(lhs_type)
134        };
135        if !lhs_table.is_null() {
136            let res = unsafe { table_stuff(&mut *lhs_table) };
137            if let Some(v) = res {
138                return v;
139            }
140        }
141
142        let mut lhs_extern_type: *mut ExternType = unsafe {
143            crate::functions::get_type_alt_j::get_type_id::<ExternType>(lhs_type) as *mut ExternType
144        };
145        if !lhs_extern_type.is_null() {
146            loop {
147                if unsafe { (*lhs_extern_type).indexer.is_some() } {
148                    let indexer = unsafe { (*lhs_extern_type).indexer.as_ref().unwrap() };
149                    self.constraint_solver_unify(constraint, index_type, indexer.index_type);
150                    self.constraint_solver_unify(constraint, rhs_type, indexer.index_result_type);
151
152                    let res_ty = add_union(
153                        self.arena,
154                        self.builtin_types,
155                        &[indexer.index_result_type, unsafe {
156                            (*self.builtin_types).nilType
157                        }],
158                    );
159                    self.bind_not_null_constraint_type_id_type_id(constraint, c.prop_type, res_ty);
160                    return true;
161                }
162
163                if let Some(parent) = unsafe { (*lhs_extern_type).parent } {
164                    lhs_extern_type = unsafe {
165                        crate::functions::get_type_alt_j::get_type_id::<ExternType>(parent)
166                            as *mut ExternType
167                    };
168                    continue;
169                }
170
171                break;
172            }
173            return true;
174        }
175
176        let lhs_intersection: *mut IntersectionType = unsafe {
177            crate::functions::get_mutable_type::get_mutable_type_id::<IntersectionType>(lhs_type)
178        };
179        if !lhs_intersection.is_null() {
180            let mut parts = TypeIds::type_ids();
181
182            // The port should iterate `lhsIntersection` similarly to C++ range-for.
183            // Use `begin_mut/end` style only if available; otherwise, fall back to `parts`.
184            // Here we call into the existing iterator API on IntersectionType if present.
185            // If IntersectionType has a public `parts` field, we can use it directly.
186            let intersection_parts: &alloc::vec::Vec<TypeId> =
187                unsafe { &(*lhs_intersection).parts };
188            for &t in intersection_parts.iter() {
189                let followed = unsafe { follow_type_id(t) };
190
191                let tbl_ptr: *mut TableType = unsafe {
192                    crate::functions::get_mutable_type::get_mutable_type_id::<TableType>(followed)
193                };
194                if !tbl_ptr.is_null() {
195                    if let Some(indexer) = unsafe { &(*tbl_ptr).indexer } {
196                        self.constraint_solver_unify(constraint, index_type, indexer.index_type);
197                        parts.insert_type_id(indexer.index_result_type);
198                    }
199
200                    if unsafe {
201                        (*tbl_ptr).state == TableState::Unsealed
202                            || (*tbl_ptr).state == TableState::Free
203                    } {
204                        unsafe {
205                            (*tbl_ptr).indexer = Some(TableIndexer {
206                                index_type,
207                                index_result_type: rhs_type,
208                                is_read_only: false,
209                            });
210                        }
211                        parts.insert_type_id(rhs_type);
212                    }
213
214                    continue;
215                }
216
217                let cls_ptr: *const ExternType = unsafe {
218                    crate::functions::get_type_alt_j::get_type_id::<ExternType>(followed)
219                };
220                if !cls_ptr.is_null() {
221                    let mut cls_mut = cls_ptr as *mut ExternType;
222                    loop {
223                        if unsafe { (*cls_mut).indexer.is_some() } {
224                            let indexer = unsafe { (*cls_mut).indexer.as_ref().unwrap() };
225                            self.constraint_solver_unify(
226                                constraint,
227                                index_type,
228                                indexer.index_type,
229                            );
230                            parts.insert_type_id(indexer.index_result_type);
231                            break;
232                        }
233
234                        if let Some(parent) = unsafe { (*cls_mut).parent } {
235                            cls_mut = unsafe {
236                                crate::functions::get_type_alt_j::get_type_id::<ExternType>(parent)
237                                    as *mut ExternType
238                            };
239                            continue;
240                        }
241
242                        break;
243                    }
244                }
245            }
246
247            let scope = unsafe { (*constraint).scope };
248            let location = unsafe { (*constraint).location };
249
250            let res =
251                self.simplify_intersection_not_null_scope_location_type_ids(scope, location, parts);
252            self.constraint_solver_unify(constraint, rhs_type, res);
253        }
254
255        // Other types do not support index assignment.
256        self.bind_not_null_constraint_type_id_type_id(constraint, c.prop_type, unsafe {
257            (*self.builtin_types).errorType
258        });
259
260        true
261    }
262}