use crate::enums::table_state::TableState;
use crate::functions::follow_type::follow_type_id;
use crate::functions::get_type_alt_j::get_type_id;
use crate::functions::is_prim::is_prim;
use crate::functions::simplify_intersection_simplify::simplify_intersection;
use crate::records::any_type::AnyType;
use crate::records::metatable_type::MetatableType;
use crate::records::never_type::NeverType;
use crate::records::normalizer::Normalizer;
use crate::records::primitive_type::Type as PrimType;
use crate::records::property_type::Property;
use crate::records::table_indexer::TableIndexer;
use crate::records::table_type::TableType;
use crate::type_aliases::seen_table_prop_pairs::SeenTablePropPairs;
use crate::type_aliases::type_id::TypeId;
use luaur_common::records::dense_hash_set::DenseHashSet;
use luaur_common::FFlag;
struct RcGuard {
count: *mut i32,
}
impl RcGuard {
fn new(count: *mut i32) -> Self {
unsafe {
*count += 1;
}
RcGuard { count }
}
}
impl Drop for RcGuard {
fn drop(&mut self) {
unsafe {
*self.count -= 1;
}
}
}
impl Normalizer {
pub fn intersection_of_tables(
&mut self,
here: TypeId,
there: TypeId,
seen_table_prop_pairs: &mut SeenTablePropPairs,
seen_set: &mut DenseHashSet<TypeId>,
) -> Option<TypeId> {
self.consume_fuel();
if here == there {
return Some(here);
}
let _rc = RcGuard::new(unsafe { &mut (*self.shared_state).counters.recursion_count });
let recursion_limit = unsafe { (*self.shared_state).counters.recursion_limit };
let recursion_count = unsafe { (*self.shared_state).counters.recursion_count };
if recursion_limit > 0 && recursion_limit < recursion_count {
return None;
}
if is_prim(here, PrimType::Table) {
return Some(there);
} else if is_prim(there, PrimType::Table) {
return Some(here);
}
if !unsafe { get_type_id::<NeverType>(here).is_null() } {
return Some(there);
} else if !unsafe { get_type_id::<NeverType>(there).is_null() } {
return Some(here);
} else if !unsafe { get_type_id::<AnyType>(here).is_null() } {
return Some(there);
} else if !unsafe { get_type_id::<AnyType>(there).is_null() } {
return Some(here);
}
let mut htable = here;
let mut hmtable: TypeId = core::ptr::null();
if let Some(hmtv) = unsafe { get_type_id::<MetatableType>(here).as_ref() } {
htable = unsafe { follow_type_id(hmtv.table()) };
hmtable = unsafe { follow_type_id(hmtv.metatable()) };
}
let mut ttable = there;
let mut tmtable: TypeId = core::ptr::null();
if let Some(tmtv) = unsafe { get_type_id::<MetatableType>(there).as_ref() } {
ttable = unsafe { follow_type_id(tmtv.table()) };
tmtable = unsafe { follow_type_id(tmtv.metatable()) };
}
let httv = unsafe { get_type_id::<TableType>(htable) };
if httv.is_null() {
return None;
}
let tttv = unsafe { get_type_id::<TableType>(ttable) };
if tttv.is_null() {
return None;
}
let httv = unsafe { &*httv };
let tttv = unsafe { &*tttv };
if httv.state == TableState::Free || tttv.state == TableState::Free {
return None;
}
if httv.state == TableState::Generic || tttv.state == TableState::Generic {
return None;
}
let mut state = httv.state;
if tttv.state == TableState::Unsealed {
state = tttv.state;
}
let level = if httv.level.subsumes(&tttv.level) {
tttv.level
} else {
httv.level
};
let scope = crate::functions::max_scope::max(httv.scope, tttv.scope);
let mut result: Option<TableType> = None;
let mut here_sub_there = true;
let mut there_sub_here = true;
for (name, hprop) in httv.props.iter() {
let mut prop: Property = hprop.clone();
let tfound = tttv.props.get(name);
match tfound {
None => {
there_sub_here = false;
}
Some(tprop) => {
if self.use_new_luau_solver() {
if let Some(hread) = hprop.read_ty {
if let Some(tread) = tprop.read_ty {
let ty = simplify_intersection(
self.builtin_types,
self.arena,
hread,
tread,
)
.result;
if !unsafe { get_type_id::<NeverType>(ty).is_null() } {
return Some(unsafe { (*self.builtin_types).neverType });
}
prop.read_ty = Some(ty);
here_sub_there &= ty == hread;
there_sub_here &= ty == tread;
} else {
prop.read_ty = Some(hread);
there_sub_here = false;
}
} else if let Some(tread) = tprop.read_ty {
prop.read_ty = Some(tread);
here_sub_there = false;
}
if let Some(hwrite) = hprop.write_ty {
if let Some(twrite) = tprop.write_ty {
let w = simplify_intersection(
self.builtin_types,
self.arena,
hwrite,
twrite,
)
.result;
prop.write_ty = Some(w);
here_sub_there &= w == hwrite;
there_sub_here &= w == twrite;
} else {
prop.write_ty = Some(hwrite);
there_sub_here = false;
}
} else if let Some(twrite) = tprop.write_ty {
prop.write_ty = Some(twrite);
here_sub_there = false;
}
} else {
let h_dep = hprop.type_deprecated();
let t_dep = tprop.type_deprecated();
let inter = self.intersection_type(h_dep, t_dep);
prop.set_type(inter);
here_sub_there &= prop.type_deprecated() == h_dep;
there_sub_here &= prop.type_deprecated() == t_dep;
}
}
}
if prop.read_ty.is_some() || prop.write_ty.is_some() {
if result.is_none() {
result = Some(TableType::table_type_table_state_type_level_scope(
state, level, scope,
));
}
result.as_mut().unwrap().props.insert(name.clone(), prop);
}
}
for (name, tprop) in tttv.props.iter() {
if !httv.props.contains_key(name) {
if result.is_none() {
result = Some(TableType::table_type_table_state_type_level_scope(
state, level, scope,
));
}
result
.as_mut()
.unwrap()
.props
.insert(name.clone(), tprop.clone());
here_sub_there = false;
}
}
if httv.indexer.is_some() && tttv.indexer.is_some() {
let hindexer = httv.indexer.as_ref().unwrap();
let tindexer = tttv.indexer.as_ref().unwrap();
if FFlag::LuauReadOnlyIndexers.get() {
let index = self.union_type(hindexer.index_type, tindexer.index_type);
let mut idx = TableIndexer {
index_type: index,
index_result_type: core::ptr::null(),
is_read_only: false,
};
if hindexer.is_read_only && tindexer.is_read_only {
idx.index_result_type = self
.intersection_type(hindexer.index_result_type, tindexer.index_result_type);
idx.is_read_only = true;
} else {
idx.index_result_type = self
.intersection_type(hindexer.index_result_type, tindexer.index_result_type);
}
let here_mode_match = hindexer.is_read_only == idx.is_read_only;
let there_mode_match = tindexer.is_read_only == idx.is_read_only;
here_sub_there &= here_mode_match
&& (hindexer.index_type == index)
&& (hindexer.index_result_type == idx.index_result_type);
there_sub_here &= there_mode_match
&& (tindexer.index_type == index)
&& (tindexer.index_result_type == idx.index_result_type);
if result.is_none() {
result = Some(TableType::table_type_table_state_type_level_scope(
state, level, scope,
));
}
result.as_mut().unwrap().indexer = Some(idx);
} else {
let index = self.union_type(hindexer.index_type, tindexer.index_type);
let index_result =
self.intersection_type(hindexer.index_result_type, tindexer.index_result_type);
if result.is_none() {
result = Some(TableType::table_type_table_state_type_level_scope(
state, level, scope,
));
}
result.as_mut().unwrap().indexer = Some(TableIndexer {
index_type: index,
index_result_type: index_result,
is_read_only: false,
});
here_sub_there &=
(hindexer.index_type == index) && (hindexer.index_result_type == index_result);
there_sub_here &=
(tindexer.index_type == index) && (tindexer.index_result_type == index_result);
}
} else if httv.indexer.is_some() {
if result.is_none() {
result = Some(TableType::table_type_table_state_type_level_scope(
state, level, scope,
));
}
result.as_mut().unwrap().indexer = httv.indexer;
there_sub_here = false;
} else if tttv.indexer.is_some() {
if result.is_none() {
result = Some(TableType::table_type_table_state_type_level_scope(
state, level, scope,
));
}
result.as_mut().unwrap().indexer = tttv.indexer;
here_sub_there = false;
}
let table: TypeId;
if here_sub_there {
table = htable;
} else if there_sub_here {
table = ttable;
} else if let Some(tt) = result {
table = unsafe { (*self.arena).add_type(tt) };
} else {
table = unsafe {
(*self.arena).add_type(TableType::table_type_table_state_type_level_scope(
state, level, scope,
))
};
}
if !tmtable.is_null() && !hmtable.is_null() {
match self.intersection_of_tables(hmtable, tmtable, seen_table_prop_pairs, seen_set) {
Some(mtable) => {
if table == htable && mtable == hmtable {
Some(here)
} else if table == ttable && mtable == tmtable {
Some(there)
} else {
Some(unsafe {
(*self.arena).add_type(MetatableType {
table,
metatable: mtable,
syntheticName: None,
})
})
}
}
None => None,
}
} else if !hmtable.is_null() {
if table == htable {
Some(here)
} else {
Some(unsafe {
(*self.arena).add_type(MetatableType {
table,
metatable: hmtable,
syntheticName: None,
})
})
}
} else if !tmtable.is_null() {
if table == ttable {
Some(there)
} else {
Some(unsafe {
(*self.arena).add_type(MetatableType {
table,
metatable: tmtable,
syntheticName: None,
})
})
}
} else {
Some(table)
}
}
}