luaur_analysis/functions/
index_function_impl.rs1use crate::enums::reduction::Reduction;
11use crate::functions::follow_type::follow_type_id;
12use crate::functions::get_singleton_type::get_singleton_type;
13use crate::functions::get_type_alt_j::get_type_id;
14use crate::functions::is_pending::is_pending;
15use crate::functions::search_props_and_indexer::search_props_and_indexer;
16use crate::functions::tbl_index_into_builtin_type_functions::tbl_index_into;
17use crate::records::boolean_singleton::BooleanSingleton;
18use crate::records::extern_type::ExternType;
19use crate::records::singleton_type::SingletonType;
20use crate::records::table_type::TableType;
21use crate::records::type_function_context::TypeFunctionContext;
22use crate::records::type_function_reduction_result::TypeFunctionReductionResult;
23use crate::records::union_type::UnionType;
24use crate::type_aliases::error_vec::ErrorVec;
25use crate::type_aliases::type_id::TypeId;
26use crate::type_aliases::type_pack_id::TypePackId;
27use alloc::vec;
28use alloc::vec::Vec;
29use luaur_ast::records::location::Location;
30use luaur_ast::records::position::Position;
31use luaur_common::macros::luau_assert::LUAU_ASSERT;
32use luaur_common::records::dense_hash_set::DenseHashSet;
33
34fn empty_location() -> Location {
35 Location::new(
36 Position { line: 0, column: 0 },
37 Position { line: 0, column: 0 },
38 )
39}
40
41fn tbl_index_into_2(
46 indexer: TypeId,
47 indexee: TypeId,
48 result: &mut DenseHashSet<TypeId>,
49 ctx: *mut TypeFunctionContext,
50 is_raw: bool,
51) -> bool {
52 let mut seen_set: DenseHashSet<TypeId> = DenseHashSet::new(core::ptr::null());
53 tbl_index_into(indexer, indexee, result, &mut seen_set, ctx, is_raw)
54}
55
56fn erroneous() -> TypeFunctionReductionResult {
57 TypeFunctionReductionResult {
58 result: None,
59 reduction_status: Reduction::Erroneous,
60 blocked_types: vec![],
61 blocked_packs: vec![],
62 error: None,
63 messages: vec![],
64 }
65}
66
67fn maybe_ok_blocked(blocked: Vec<TypeId>) -> TypeFunctionReductionResult {
68 TypeFunctionReductionResult {
69 result: None,
70 reduction_status: Reduction::MaybeOk,
71 blocked_types: blocked,
72 blocked_packs: vec![],
73 error: None,
74 messages: vec![],
75 }
76}
77
78fn is_boolean_singleton(ty: TypeId) -> bool {
79 let singleton = unsafe { get_type_id::<SingletonType>(follow_type_id(ty)) };
80 !singleton.is_null() && !get_singleton_type::<BooleanSingleton>(singleton).is_null()
81}
82
83fn is_nonempty_table(ty: TypeId) -> bool {
84 let table = unsafe { get_type_id::<TableType>(follow_type_id(ty)) };
85 unsafe { table.as_ref() }
86 .map(|table| !table.props.is_empty())
87 .unwrap_or(false)
88}
89
90pub fn index_function_impl(
91 type_params: Vec<TypeId>,
92 _pack_params: Vec<TypePackId>,
93 ctx: *mut TypeFunctionContext,
94 is_raw: bool,
95) -> TypeFunctionReductionResult {
96 let ctx_ref = unsafe { &*ctx };
97
98 let indexee_ty = unsafe { follow_type_id(type_params[0]) };
99
100 if is_pending(indexee_ty, ctx_ref.solver) {
101 return maybe_ok_blocked(vec![indexee_ty]);
102 }
103
104 let Some(indexee_norm_ty) =
105 (unsafe { (*ctx_ref.normalizer.as_ptr()).try_normalize(indexee_ty) })
106 else {
107 return maybe_ok_blocked(vec![]);
108 };
109
110 if indexee_norm_ty.should_suppress_errors() {
112 return TypeFunctionReductionResult {
113 result: Some(unsafe { ctx_ref.builtins.as_ref().anyType }),
114 reduction_status: Reduction::MaybeOk,
115 blocked_types: vec![],
116 blocked_packs: vec![],
117 error: None,
118 messages: vec![],
119 };
120 }
121
122 if indexee_norm_ty.has_tables() == indexee_norm_ty.has_extern_types() {
124 return erroneous();
125 }
126
127 if indexee_norm_ty.has_tops()
130 || indexee_norm_ty.has_booleans()
131 || indexee_norm_ty.has_errors()
132 || indexee_norm_ty.has_nils()
133 || indexee_norm_ty.has_numbers()
134 || indexee_norm_ty.has_strings()
135 || indexee_norm_ty.has_threads()
136 || indexee_norm_ty.has_buffers()
137 || indexee_norm_ty.has_functions()
138 || indexee_norm_ty.has_tyvars()
139 {
140 return erroneous();
141 }
142
143 let indexer_ty = unsafe { follow_type_id(type_params[1]) };
144
145 if is_pending(indexer_ty, ctx_ref.solver) {
146 return maybe_ok_blocked(vec![indexer_ty]);
147 }
148
149 let Some(indexer_norm_ty) =
150 (unsafe { (*ctx_ref.normalizer.as_ptr()).try_normalize(indexer_ty) })
151 else {
152 return maybe_ok_blocked(vec![]);
153 };
154
155 if indexer_norm_ty.has_tops() || indexer_norm_ty.has_errors() {
157 return erroneous();
158 }
159
160 let single_type: Vec<TypeId> = vec![indexer_ty];
162 let types_to_find: &Vec<TypeId> =
163 if let Some(union_ty) = unsafe { get_type_id::<UnionType>(indexer_ty).as_ref() } {
164 &union_ty.options
165 } else {
166 &single_type
167 };
168
169 let mut properties: DenseHashSet<TypeId> = DenseHashSet::new(core::ptr::null()); if indexee_norm_ty.has_extern_types() {
172 LUAU_ASSERT!(!indexee_norm_ty.has_tables());
173
174 if is_raw {
175 return erroneous();
178 }
179
180 for &extern_type_iter in &indexee_norm_ty.extern_types.ordering {
182 let extern_ty = unsafe { get_type_id::<ExternType>(extern_type_iter) };
183 if extern_ty.is_null() {
184 LUAU_ASSERT!(false); return erroneous();
186 }
187
188 for &ty in types_to_find {
189 let extern_ref = unsafe { &*extern_ty };
191 if search_props_and_indexer(
192 ty,
193 extern_ref.props.clone(),
194 extern_ref.indexer.clone(),
195 &mut properties,
196 ctx,
197 ) {
198 continue; }
200
201 let mut parent = extern_ref.parent;
202 let mut found_in_parent = false;
203 while let Some(parent_ty) = parent {
204 if found_in_parent {
205 break;
206 }
207 let parent_extern_type =
208 unsafe { get_type_id::<ExternType>(follow_type_id(parent_ty)) };
209 let parent_ref = unsafe { &*parent_extern_type };
210 found_in_parent = search_props_and_indexer(
211 ty,
212 parent_ref.props.clone(),
213 parent_ref.indexer.clone(),
214 &mut properties,
215 ctx,
216 );
217 parent = parent_ref.parent;
218 }
219
220 if found_in_parent {
222 continue;
223 }
224
225 let mut dummy: ErrorVec = vec![];
229 let mm_type = unsafe {
230 crate::functions::find_metatable_entry::find_metatable_entry(
231 ctx_ref.builtins.as_ptr(),
232 &mut dummy,
233 extern_type_iter,
234 "__index",
235 empty_location(),
236 )
237 };
238 let mm_type = match mm_type {
239 Some(mm) => mm,
240 None => return erroneous(), };
242
243 if !tbl_index_into_2(ty, mm_type, &mut properties, ctx, is_raw) {
244 return erroneous();
246 }
247 }
248 }
249 }
250
251 if indexee_norm_ty.has_tables() {
252 LUAU_ASSERT!(!indexee_norm_ty.has_extern_types());
253
254 for &tables_iter in &indexee_norm_ty.tables.order {
256 for &ty in types_to_find {
257 if !tbl_index_into_2(ty, tables_iter, &mut properties, ctx, is_raw) {
258 if is_raw {
259 properties.insert(unsafe { ctx_ref.builtins.as_ref().nilType });
260 } else if is_boolean_singleton(ty) && is_nonempty_table(tables_iter) {
261 properties.insert(unsafe { ctx_ref.builtins.as_ref().unknownType });
262 } else {
263 return erroneous();
264 }
265 }
266 }
267 }
268 }
269
270 if properties.size() == 1 {
272 let only = *properties
273 .iter()
274 .next()
275 .expect("properties has exactly one element");
276 return TypeFunctionReductionResult {
277 result: Some(only),
278 reduction_status: Reduction::MaybeOk,
279 blocked_types: vec![],
280 blocked_packs: vec![],
281 error: None,
282 messages: vec![],
283 };
284 }
285
286 let options: Vec<TypeId> = properties.iter().copied().collect();
287 let union_ty = unsafe { (*ctx_ref.arena.as_ptr()).add_type(UnionType { options }) };
288 TypeFunctionReductionResult {
289 result: Some(union_ty),
290 reduction_status: Reduction::MaybeOk,
291 blocked_types: vec![],
292 blocked_packs: vec![],
293 error: None,
294 messages: vec![],
295 }
296}