use crate::TypeDatabase;
use crate::types::{ObjectShapeId, PropertyInfo, TypeId, TypeParamInfo};
use crate::visitor::{
is_literal_type, is_unit_type, object_shape_id, object_with_index_shape_id, type_param_info,
union_list_id,
};
use tsz_common::interner::Atom;
use super::super::{SubtypeChecker, SubtypeFailureReason, SubtypeResult, TypeResolver};
const MAX_DISCRIMINANT_COMBINATIONS: usize = 25;
impl<'a, R: TypeResolver> SubtypeChecker<'a, R> {
pub(crate) fn types_equivalent(&mut self, left: TypeId, right: TypeId) -> bool {
self.check_subtype(left, right).is_true() && self.check_subtype(right, left).is_true()
}
pub(crate) fn check_type_parameter_subtype(
&mut self,
s_info: &TypeParamInfo,
target: TypeId,
) -> SubtypeResult {
if let Some(t_info) = type_param_info(self.interner, target) {
if s_info.name == t_info.name {
return SubtypeResult::True;
}
if let Some(s_constraint) = s_info.constraint {
if s_constraint == target {
return SubtypeResult::True;
}
if self.check_subtype(s_constraint, target).is_true() {
return SubtypeResult::True;
}
}
return SubtypeResult::False;
}
if let Some(constraint) = s_info.constraint {
return self.check_subtype(constraint, target);
}
self.check_subtype(TypeId::UNKNOWN, target)
}
pub(crate) fn check_subtype_with_method_variance(
&mut self,
source: TypeId,
target: TypeId,
allow_bivariant: bool,
) -> SubtypeResult {
if allow_bivariant {
let prev = self.strict_function_types;
self.strict_function_types = false;
let result = self.check_subtype(source, target);
self.strict_function_types = prev;
return result;
}
self.check_subtype(source, target)
}
pub(crate) fn explain_failure_with_method_variance(
&mut self,
source: TypeId,
target: TypeId,
allow_bivariant: bool,
) -> Option<SubtypeFailureReason> {
if allow_bivariant {
let prev = self.strict_function_types;
self.strict_function_types = false;
let result = self.explain_failure(source, target);
self.strict_function_types = prev;
return result;
}
self.explain_failure(source, target)
}
pub(crate) fn type_related_to_discriminated_type(
&mut self,
source: TypeId,
target_members: &[TypeId],
) -> SubtypeResult {
let source_shape_id = match get_object_shape(self.interner, source) {
Some(id) => id,
None => return SubtypeResult::False,
};
let source_shape = self.interner.object_shape(source_shape_id);
let disc_props =
find_discriminant_properties(self.interner, &source_shape.properties, target_members);
if disc_props.is_empty() {
return SubtypeResult::False;
}
let mut candidate_targets: Option<Vec<bool>> = None;
for &(prop_name, source_prop_type) in &disc_props {
let source_values = get_discriminant_values(self.interner, source_prop_type);
if source_values.len() > MAX_DISCRIMINANT_COMBINATIONS {
return SubtypeResult::False;
}
let mut reachable = vec![false; target_members.len()];
for &value in &source_values {
let mut value_has_match = false;
for (i, &target_member) in target_members.iter().enumerate() {
let t_prop =
get_property_type_of_object(self.interner, target_member, prop_name);
if let Some(t_prop_type) = t_prop
&& self.check_subtype(value, t_prop_type).is_true()
{
reachable[i] = true;
value_has_match = true;
}
}
if !value_has_match {
return SubtypeResult::False;
}
}
match &mut candidate_targets {
Some(prev) => {
for (p, r) in prev.iter_mut().zip(reachable.iter()) {
*p = *p && *r;
}
}
None => candidate_targets = Some(reachable),
}
}
let candidates = match candidate_targets {
Some(c) => c,
None => return SubtypeResult::False,
};
let (disc_name, disc_source_type) = disc_props[0];
let source_values = get_discriminant_values(self.interner, disc_source_type);
for &value in &source_values {
let narrowed = narrow_object_property(self.interner, source_shape_id, disc_name, value);
let mut found = false;
for (i, &target_member) in target_members.iter().enumerate() {
if !candidates[i] {
continue;
}
if self.check_subtype(narrowed, target_member).is_true() {
found = true;
break;
}
}
if !found {
return SubtypeResult::False;
}
}
SubtypeResult::True
}
}
fn get_object_shape(db: &dyn TypeDatabase, type_id: TypeId) -> Option<ObjectShapeId> {
object_shape_id(db, type_id).or_else(|| object_with_index_shape_id(db, type_id))
}
fn get_type_constituents(db: &dyn TypeDatabase, type_id: TypeId) -> Vec<TypeId> {
if let Some(list_id) = union_list_id(db, type_id) {
db.type_list(list_id).to_vec()
} else {
vec![type_id]
}
}
fn get_discriminant_values(db: &dyn TypeDatabase, type_id: TypeId) -> Vec<TypeId> {
if type_id == TypeId::BOOLEAN {
return vec![TypeId::BOOLEAN_TRUE, TypeId::BOOLEAN_FALSE];
}
get_type_constituents(db, type_id)
}
fn get_property_type_of_object(
db: &dyn TypeDatabase,
type_id: TypeId,
prop_name: Atom,
) -> Option<TypeId> {
let shape_id = get_object_shape(db, type_id)?;
let shape = db.object_shape(shape_id);
let prop = shape
.properties
.binary_search_by(|p| p.name.cmp(&prop_name))
.ok()
.map(|idx| &shape.properties[idx])?;
if prop.optional {
Some(db.union2(prop.type_id, TypeId::UNDEFINED))
} else {
Some(prop.type_id)
}
}
fn find_discriminant_properties(
db: &dyn TypeDatabase,
source_props: &[PropertyInfo],
target_members: &[TypeId],
) -> Vec<(Atom, TypeId)> {
let mut result = Vec::new();
for prop in source_props {
if is_discriminant_for_union(db, prop.name, target_members) {
result.push((prop.name, prop.type_id));
}
}
result
}
fn is_discriminant_for_union(
db: &dyn TypeDatabase,
prop_name: Atom,
target_members: &[TypeId],
) -> bool {
let mut has_unit = false;
let mut seen_types: Vec<TypeId> = Vec::new();
for &member in target_members {
let shape_id = match get_object_shape(db, member) {
Some(id) => id,
None => return false, };
let shape = db.object_shape(shape_id);
let prop = match shape
.properties
.binary_search_by(|p| p.name.cmp(&prop_name))
.ok()
{
Some(idx) => &shape.properties[idx],
None => {
continue;
}
};
let prop_type = prop.type_id;
for &constituent in &get_type_constituents(db, prop_type) {
if is_unit_type(db, constituent) || is_literal_type(db, constituent) {
has_unit = true;
}
}
if !seen_types.contains(&prop_type) {
seen_types.push(prop_type);
}
}
has_unit && seen_types.len() > 1
}
fn narrow_object_property(
db: &dyn TypeDatabase,
shape_id: ObjectShapeId,
prop_name: Atom,
narrowed_type: TypeId,
) -> TypeId {
let shape = db.object_shape(shape_id);
let mut new_props: Vec<PropertyInfo> = shape.properties.to_vec();
if let Ok(idx) = new_props.binary_search_by(|p| p.name.cmp(&prop_name)) {
new_props[idx] = PropertyInfo {
type_id: narrowed_type,
write_type: narrowed_type,
..new_props[idx].clone()
};
}
db.object(new_props)
}