Skip to main content

luaur_analysis/methods/
unifier_try_unify_tables.rs

1//! Source: `Analysis/src/Unifier.cpp` (Unifier::tryUnifyTables, L1829-2149)
2use 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    /// `void Unifier::tryUnifyTables(TypeId subTy, TypeId superTy, bool isIntersection, const LiteralProperties* literalProperties)`
23    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                // The Instantiation machinery is translated elsewhere in this crate;
55                // keep this branch structurally present but avoid inventing
56                // construction APIs (mirrors Unifier::tryUnifyFunctions). The C++
57                // failure path here reports UnificationTooComplex.
58                self.report_error_location_type_error_data(
59                    self.location,
60                    TypeErrorData::UnificationTooComplex(UnificationTooComplex::default()),
61                );
62            }
63        }
64
65        // Optimization: First test that the property sets are compatible without doing any recursive unification
66        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        // And vice versa if we're invariant
93        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        // Width subtyping: any property in the supertype must be in the subtype,
119        // and the types must agree.
120        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                // TODO: read-only properties don't need invariance
130                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                // TODO: read-only indexers don't need invariance
166                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                // This is sound because unsealed table types are precise.
200            } 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            // Recursive unification can change the txn log, and invalidate the old
212            // table. If we detect that this has happened, we start over.
213            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                // already unified above
255            } 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                    // Incredibly, the old solver depends on this bug somehow.
281                    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                // nothing
316            } 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        // Unify indexers
364        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            // Symmetric if we are invariant
430            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        // Changing the indexer can invalidate the table pointers.
439        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        // Types are commonly cyclic; unifying a property may change the table itself.
477        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}