1use crate::enums::table_state::TableState;
3use crate::enums::variance::Variance;
4use crate::functions::get_mutable_txn_log::get_mutable_pending_type;
5use crate::functions::is_optional::is_optional;
6use crate::functions::is_prim::is_prim;
7use crate::functions::maybe_string::maybe_string;
8use crate::records::missing_properties::{Context as MissingPropertiesContext, MissingProperties};
9use crate::records::primitive_type::Type as PrimType;
10use crate::records::table_type::TableType;
11use crate::records::unification_too_complex::UnificationTooComplex;
12use crate::records::unifier::Unifier;
13use crate::type_aliases::literal_properties::LiteralProperties;
14use crate::type_aliases::type_error_data::TypeErrorData;
15use crate::type_aliases::type_id::TypeId;
16use alloc::string::String;
17use alloc::vec::Vec;
18use luaur_common::macros::luau_assert::LUAU_ASSERT;
19use std::collections::HashMap;
20
21impl Unifier {
22 pub fn unifier_try_unify_tables(
24 &mut self,
25 mut sub_ty: TypeId,
26 mut super_ty: TypeId,
27 is_intersection: bool,
28 literal_properties: *const LiteralProperties,
29 ) {
30 if is_prim(self.log.follow_type_id(sub_ty), PrimType::Table) {
31 sub_ty = unsafe { (*self.builtin_types).emptyTableType };
32 }
33
34 if is_prim(self.log.follow_type_id(super_ty), PrimType::Table) {
35 super_ty = unsafe { (*self.builtin_types).emptyTableType };
36 }
37
38 let active_sub_ty = sub_ty;
39 let mut super_table = self.log.txn_log_get_mutable::<TableType, TypeId>(super_ty);
40 let mut sub_table = self.log.txn_log_get_mutable::<TableType, TypeId>(sub_ty);
41
42 if super_table.is_null() || sub_table.is_null() {
43 self.ice_string("passed non-table types to unifyTables");
44 }
45
46 let mut missing_properties: Vec<String> = Vec::new();
47 let mut extra_properties: Vec<String> = Vec::new();
48
49 if luaur_common::FFlag::LuauInstantiateInSubtyping.get() {
50 if self.variance == Variance::Covariant
51 && unsafe { (*sub_table).state } == TableState::Generic
52 && unsafe { (*super_table).state } != TableState::Generic
53 {
54 self.report_error_location_type_error_data(
59 self.location,
60 TypeErrorData::UnificationTooComplex(UnificationTooComplex::default()),
61 );
62 }
63 }
64
65 if unsafe { (*sub_table).indexer.is_none() && (*sub_table).state != TableState::Free } {
67 for (prop_name, super_prop) in unsafe { (*super_table).props.iter() } {
68 let sub_has = unsafe { (*sub_table).props.contains_key(prop_name) };
69
70 if !sub_has
71 && unsafe { (*sub_table).state } == TableState::Unsealed
72 && !is_optional(super_prop.type_deprecated())
73 {
74 missing_properties.push(prop_name.clone());
75 }
76 }
77
78 if !missing_properties.is_empty() {
79 self.report_error_location_type_error_data(
80 self.location,
81 TypeErrorData::MissingProperties(MissingProperties {
82 super_type: super_ty,
83 sub_type: sub_ty,
84 properties: core::mem::take(&mut missing_properties),
85 context: MissingPropertiesContext::Missing,
86 }),
87 );
88 return;
89 }
90 }
91
92 if self.variance == Variance::Invariant
94 && unsafe { (*super_table).indexer.is_none() }
95 && unsafe { (*super_table).state } != TableState::Unsealed
96 && unsafe { (*super_table).state } != TableState::Free
97 {
98 for (prop_name, _sub_prop) in unsafe { (*sub_table).props.iter() } {
99 if unsafe { !(*super_table).props.contains_key(prop_name) } {
100 extra_properties.push(prop_name.clone());
101 }
102 }
103
104 if !extra_properties.is_empty() {
105 self.report_error_location_type_error_data(
106 self.location,
107 TypeErrorData::MissingProperties(MissingProperties {
108 super_type: super_ty,
109 sub_type: sub_ty,
110 properties: core::mem::take(&mut extra_properties),
111 context: MissingPropertiesContext::Extra,
112 }),
113 );
114 return;
115 }
116 }
117
118 let super_props: Vec<String> = unsafe { (*super_table).props.keys().cloned().collect() };
121 for name in super_props {
122 let prop_ty = match unsafe { (*super_table).props.get(&name) } {
123 Some(p) => p.type_deprecated(),
124 None => continue,
125 };
126 let sub_prop = unsafe { (*sub_table).props.get(&name).map(|p| p.clone()) };
127
128 if let Some(sub_prop) = sub_prop {
129 let old_variance = self.variance;
131 if literal_properties.is_null()
132 || unsafe { (*literal_properties).find(&name).is_none() }
133 {
134 self.variance = Variance::Invariant;
135 }
136
137 let mut inner_state = self.unifier_make_child_unifier();
138 inner_state.try_unify_type_id_type_id_bool_bool_literal_properties(
139 sub_prop.type_deprecated(),
140 prop_ty,
141 false,
142 false,
143 None,
144 );
145
146 let inner_errors = inner_state.errors.clone();
147 self.check_child_unifier_type_mismatch_error_vec_string_type_id_type_id(
148 &inner_errors,
149 &name,
150 super_ty,
151 sub_ty,
152 );
153
154 if inner_state.errors.is_empty() {
155 self.log.concat(inner_state.log);
156 }
157 self.failure |= inner_state.failure;
158 self.variance = old_variance;
159 } else if unsafe {
160 (*sub_table)
161 .indexer
162 .as_ref()
163 .map_or(false, |ix| maybe_string(ix.index_type))
164 } {
165 let old_variance = self.variance;
167 if literal_properties.is_null()
168 || unsafe { (*literal_properties).find(&name).is_none() }
169 {
170 self.variance = Variance::Invariant;
171 }
172
173 let index_result =
174 unsafe { (*sub_table).indexer.as_ref().unwrap().index_result_type };
175 let mut inner_state = self.unifier_make_child_unifier();
176 inner_state.try_unify_type_id_type_id_bool_bool_literal_properties(
177 index_result,
178 prop_ty,
179 false,
180 false,
181 None,
182 );
183
184 let inner_errors = inner_state.errors.clone();
185 self.check_child_unifier_type_mismatch_error_vec_string_type_id_type_id(
186 &inner_errors,
187 &name,
188 super_ty,
189 sub_ty,
190 );
191
192 if inner_state.errors.is_empty() {
193 self.log.concat(inner_state.log);
194 }
195 self.failure |= inner_state.failure;
196 self.variance = old_variance;
197 } else if unsafe { (*sub_table).state } == TableState::Unsealed && is_optional(prop_ty)
198 {
199 } else if unsafe { (*sub_table).state } == TableState::Free {
201 let prop_clone = unsafe { (*super_table).props.get(&name).unwrap().clone() };
202 let pending_sub = self.log.queue_type_id(active_sub_ty);
203 let ttv = unsafe { get_mutable_pending_type::<TableType>(pending_sub) };
204 LUAU_ASSERT!(!ttv.is_null());
205 unsafe { (*ttv).props.insert(name.clone(), prop_clone) };
206 sub_table = ttv;
207 } else {
208 missing_properties.push(name.clone());
209 }
210
211 let super_ty_new = self.log.follow_type_id(super_ty);
214 let sub_ty_new = self.log.follow_type_id(active_sub_ty);
215
216 if (super_ty != super_ty_new || active_sub_ty != sub_ty_new) && self.errors.is_empty() {
217 return self.try_unify_type_id_type_id_bool_bool_literal_properties(
218 sub_ty,
219 super_ty,
220 false,
221 is_intersection,
222 None,
223 );
224 }
225
226 let new_super_table = self
227 .log
228 .txn_log_get_mutable::<TableType, TypeId>(super_ty_new);
229 let new_sub_table = self
230 .log
231 .txn_log_get_mutable::<TableType, TypeId>(sub_ty_new);
232
233 if super_table != new_super_table || sub_table != new_sub_table {
234 if self.errors.is_empty() {
235 self.unifier_try_unify_tables(
236 sub_ty,
237 super_ty,
238 is_intersection,
239 core::ptr::null(),
240 );
241 }
242 return;
243 }
244 }
245
246 let sub_props: Vec<String> = unsafe { (*sub_table).props.keys().cloned().collect() };
247 for name in sub_props {
248 let prop = match unsafe { (*sub_table).props.get(&name) } {
249 Some(p) => p.clone(),
250 None => continue,
251 };
252
253 if unsafe { (*super_table).props.contains_key(&name) } {
254 } else if unsafe {
256 (*super_table)
257 .indexer
258 .as_ref()
259 .map_or(false, |ix| maybe_string(ix.index_type))
260 } {
261 let old_variance = self.variance;
262 if literal_properties.is_null()
263 || unsafe { (*literal_properties).find(&name).is_none() }
264 {
265 self.variance = Variance::Invariant;
266 }
267
268 let super_index_result =
269 unsafe { (*super_table).indexer.as_ref().unwrap().index_result_type };
270 let mut inner_state = self.unifier_make_child_unifier();
271 if luaur_common::FFlag::LuauFixIndexerSubtypingOrdering.get() {
272 inner_state.try_unify_type_id_type_id_bool_bool_literal_properties(
273 prop.type_deprecated(),
274 super_index_result,
275 false,
276 false,
277 None,
278 );
279 } else {
280 inner_state.try_unify_type_id_type_id_bool_bool_literal_properties(
282 super_index_result,
283 prop.type_deprecated(),
284 false,
285 false,
286 None,
287 );
288 }
289
290 let inner_errors = inner_state.errors.clone();
291 self.check_child_unifier_type_mismatch_error_vec_string_type_id_type_id(
292 &inner_errors,
293 &name,
294 super_ty,
295 sub_ty,
296 );
297
298 if inner_state.errors.is_empty() {
299 self.log.concat(inner_state.log);
300 }
301 self.failure |= inner_state.failure;
302 self.variance = old_variance;
303 } else if unsafe { (*super_table).state } == TableState::Unsealed {
304 let mut clone = prop.clone();
305 let deep =
306 self.unifier_deeply_optional(clone.type_deprecated(), &mut HashMap::new());
307 clone.set_type(deep);
308
309 let pending_super = self.log.queue_type_id(super_ty);
310 let pending_super_ttv =
311 unsafe { get_mutable_pending_type::<TableType>(pending_super) };
312 unsafe { (*pending_super_ttv).props.insert(name.clone(), clone) };
313 super_table = pending_super_ttv;
314 } else if self.variance == Variance::Covariant {
315 } else if unsafe { (*super_table).state } == TableState::Free {
317 let pending_super = self.log.queue_type_id(super_ty);
318 let pending_super_ttv =
319 unsafe { get_mutable_pending_type::<TableType>(pending_super) };
320 unsafe {
321 (*pending_super_ttv)
322 .props
323 .insert(name.clone(), prop.clone())
324 };
325 super_table = pending_super_ttv;
326 } else {
327 extra_properties.push(name.clone());
328 }
329
330 let super_ty_new = self.log.follow_type_id(super_ty);
331 let sub_ty_new = self.log.follow_type_id(active_sub_ty);
332
333 if (super_ty != super_ty_new || active_sub_ty != sub_ty_new) && self.errors.is_empty() {
334 return self.try_unify_type_id_type_id_bool_bool_literal_properties(
335 sub_ty,
336 super_ty,
337 false,
338 is_intersection,
339 None,
340 );
341 }
342
343 let new_super_table = self
344 .log
345 .txn_log_get_mutable::<TableType, TypeId>(super_ty_new);
346 let new_sub_table = self
347 .log
348 .txn_log_get_mutable::<TableType, TypeId>(sub_ty_new);
349
350 if super_table != new_super_table || sub_table != new_sub_table {
351 if self.errors.is_empty() {
352 self.unifier_try_unify_tables(
353 sub_ty,
354 super_ty,
355 is_intersection,
356 core::ptr::null(),
357 );
358 }
359 return;
360 }
361 }
362
363 let super_has_indexer = unsafe { (*super_table).indexer.is_some() };
365 let sub_has_indexer = unsafe { (*sub_table).indexer.is_some() };
366
367 if super_has_indexer && sub_has_indexer {
368 let old_variance = self.variance;
369 self.variance = Variance::Invariant;
370
371 let sub_index_type = unsafe { (*sub_table).indexer.as_ref().unwrap().index_type };
372 let super_index_type = unsafe { (*super_table).indexer.as_ref().unwrap().index_type };
373 let sub_index_result =
374 unsafe { (*sub_table).indexer.as_ref().unwrap().index_result_type };
375 let super_index_result =
376 unsafe { (*super_table).indexer.as_ref().unwrap().index_result_type };
377
378 let mut inner_state = self.unifier_make_child_unifier();
379
380 inner_state.try_unify_type_id_type_id_bool_bool_literal_properties(
381 sub_index_type,
382 super_index_type,
383 false,
384 false,
385 None,
386 );
387
388 let reported = !inner_state.errors.is_empty();
389
390 let inner_errors = inner_state.errors.clone();
391 self.check_child_unifier_type_mismatch_error_vec_string_type_id_type_id(
392 &inner_errors,
393 "[indexer key]",
394 super_ty,
395 sub_ty,
396 );
397
398 inner_state.try_unify_type_id_type_id_bool_bool_literal_properties(
399 sub_index_result,
400 super_index_result,
401 false,
402 false,
403 None,
404 );
405
406 if !reported {
407 let inner_errors = inner_state.errors.clone();
408 self.check_child_unifier_type_mismatch_error_vec_string_type_id_type_id(
409 &inner_errors,
410 "[indexer value]",
411 super_ty,
412 sub_ty,
413 );
414 }
415
416 if inner_state.errors.is_empty() {
417 self.log.concat(inner_state.log);
418 }
419 self.failure |= inner_state.failure;
420 self.variance = old_variance;
421 } else if super_has_indexer {
422 if unsafe { (*sub_table).state } == TableState::Unsealed
423 || unsafe { (*sub_table).state } == TableState::Free
424 {
425 let indexer = unsafe { (*super_table).indexer.clone() };
426 self.log.change_indexer(sub_ty, indexer);
427 }
428 } else if sub_has_indexer && self.variance == Variance::Invariant {
429 if unsafe { (*super_table).state } == TableState::Unsealed
431 || unsafe { (*super_table).state } == TableState::Free
432 {
433 let indexer = unsafe { (*sub_table).indexer.clone() };
434 self.log.change_indexer(super_ty, indexer);
435 }
436 }
437
438 let super_ty_f = self.log.follow_type_id(super_ty);
440 let sub_ty_f = self.log.follow_type_id(active_sub_ty);
441 super_table = self
442 .log
443 .txn_log_get_mutable::<TableType, TypeId>(super_ty_f);
444 sub_table = self.log.txn_log_get_mutable::<TableType, TypeId>(sub_ty_f);
445
446 if super_table.is_null() || sub_table.is_null() {
447 return;
448 }
449
450 if !missing_properties.is_empty() {
451 self.report_error_location_type_error_data(
452 self.location,
453 TypeErrorData::MissingProperties(MissingProperties {
454 super_type: super_ty,
455 sub_type: sub_ty,
456 properties: core::mem::take(&mut missing_properties),
457 context: MissingPropertiesContext::Missing,
458 }),
459 );
460 return;
461 }
462
463 if !extra_properties.is_empty() {
464 self.report_error_location_type_error_data(
465 self.location,
466 TypeErrorData::MissingProperties(MissingProperties {
467 super_type: super_ty,
468 sub_type: sub_ty,
469 properties: core::mem::take(&mut extra_properties),
470 context: MissingPropertiesContext::Extra,
471 }),
472 );
473 return;
474 }
475
476 if unsafe { (*super_table).bound_to.is_some() || (*sub_table).bound_to.is_some() } {
478 return self.try_unify_type_id_type_id_bool_bool_literal_properties(
479 sub_ty, super_ty, false, false, None,
480 );
481 }
482
483 if unsafe { (*super_table).state } == TableState::Free {
484 self.log.bind_table(super_ty, Some(sub_ty));
485 } else if unsafe { (*sub_table).state } == TableState::Free {
486 self.log.bind_table(sub_ty, Some(super_ty));
487 }
488 }
489}