use crate::TypeDatabase;
use crate::TypeResolver;
use crate::is_subtype_of;
use crate::subtype::SubtypeChecker;
use crate::types::{IntrinsicKind, LiteralValue, TypeData, TypeId};
pub fn compute_conditional_expression_type(
interner: &dyn TypeDatabase,
condition: TypeId,
true_type: TypeId,
false_type: TypeId,
) -> TypeId {
if condition == TypeId::ERROR {
return TypeId::ERROR;
}
if true_type == TypeId::ERROR {
return TypeId::ERROR;
}
if false_type == TypeId::ERROR {
return TypeId::ERROR;
}
if condition == TypeId::ANY {
return interner.union2(true_type, false_type);
}
if condition == TypeId::NEVER {
return TypeId::NEVER;
}
if let Some(TypeData::Literal(LiteralValue::Boolean(true))) = interner.lookup(condition) {
return true_type;
}
if let Some(TypeData::Literal(LiteralValue::Boolean(false))) = interner.lookup(condition) {
return false_type;
}
if matches!(
interner.lookup(condition),
Some(TypeData::Intrinsic(IntrinsicKind::Null))
| Some(TypeData::Intrinsic(IntrinsicKind::Undefined))
) {
return false_type;
}
if true_type == false_type {
return true_type;
}
interner.union2(true_type, false_type)
}
pub fn compute_template_expression_type(_interner: &dyn TypeDatabase, parts: &[TypeId]) -> TypeId {
for &part in parts {
if part == TypeId::ERROR {
return TypeId::ERROR;
}
if part == TypeId::NEVER {
return TypeId::NEVER;
}
}
TypeId::STRING
}
pub fn compute_best_common_type<R: TypeResolver>(
interner: &dyn TypeDatabase,
types: &[TypeId],
resolver: Option<&R>,
) -> TypeId {
if types.is_empty() {
return TypeId::NEVER;
}
for &ty in types {
if ty == TypeId::ERROR {
return TypeId::ERROR;
}
}
if types.len() == 1 {
return types[0];
}
let first = types[0];
if types.iter().all(|&ty| ty == first) {
return first;
}
let widened = widen_literals(interner, types);
if let Some(res) = resolver
&& let Some(common_enum_type) = common_parent_enum_type(interner, &widened, res)
{
return common_enum_type;
}
if widened.len() > 2 {
let all_unit = widened.iter().all(|&ty| interner.is_unit_type(ty));
if all_unit {
return interner.union(widened.to_vec());
}
}
if let Some(res) = resolver {
let mut checker = SubtypeChecker::with_resolver(interner, res);
for &candidate in &widened {
let is_supertype = widened.iter().all(|&ty| {
checker.guard.reset();
checker.is_subtype_of(ty, candidate)
});
if is_supertype {
return candidate;
}
}
} else {
let mut checker = SubtypeChecker::new(interner);
for &candidate in &widened {
let is_supertype = widened.iter().all(|&ty| {
checker.guard.reset();
checker.is_subtype_of(ty, candidate)
});
if is_supertype {
return candidate;
}
}
}
if let Some(base) = find_common_base_type(interner, &widened) {
if all_types_are_narrower_than_base(interner, &widened, base) {
return base;
}
}
interner.union(widened.to_vec())
}
fn widen_literals(interner: &dyn TypeDatabase, types: &[TypeId]) -> Vec<TypeId> {
types
.iter()
.map(|&ty| {
if let Some(key) = interner.lookup(ty)
&& let crate::types::TypeData::Literal(ref lit) = key
{
return match lit {
crate::types::LiteralValue::String(_) => TypeId::STRING,
crate::types::LiteralValue::Number(_) => TypeId::NUMBER,
crate::types::LiteralValue::Boolean(_) => TypeId::BOOLEAN,
crate::types::LiteralValue::BigInt(_) => TypeId::BIGINT,
};
}
ty })
.collect()
}
fn find_common_base_type(interner: &dyn TypeDatabase, types: &[TypeId]) -> Option<TypeId> {
if types.is_empty() {
return None;
}
let first_base = get_base_type(interner, types[0])?;
for &ty in types.iter().skip(1) {
let base = get_base_type(interner, ty)?;
if base != first_base {
return None;
}
}
Some(first_base)
}
fn get_base_type(interner: &dyn TypeDatabase, ty: TypeId) -> Option<TypeId> {
match interner.lookup(ty) {
Some(crate::types::TypeData::Literal(ref lit)) => {
let base = match lit {
crate::types::LiteralValue::String(_) => TypeId::STRING,
crate::types::LiteralValue::Number(_) => TypeId::NUMBER,
crate::types::LiteralValue::Boolean(_) => TypeId::BOOLEAN,
crate::types::LiteralValue::BigInt(_) => TypeId::BIGINT,
};
Some(base)
}
_ => Some(ty),
}
}
fn all_types_are_narrower_than_base(
interner: &dyn TypeDatabase,
types: &[TypeId],
base: TypeId,
) -> bool {
types.iter().all(|&ty| is_subtype_of(interner, ty, base))
}
fn common_parent_enum_type<R: TypeResolver>(
interner: &dyn TypeDatabase,
types: &[TypeId],
resolver: &R,
) -> Option<TypeId> {
let mut parent_def = None;
for &ty in types {
let TypeData::Enum(def_id, _) = interner.lookup(ty)? else {
return None;
};
let current_parent = resolver.get_enum_parent_def_id(def_id).unwrap_or(def_id);
if let Some(existing) = parent_def {
if existing != current_parent {
return None;
}
} else {
parent_def = Some(current_parent);
}
}
let parent_def = parent_def?;
resolver
.resolve_lazy(parent_def, interner)
.or_else(|| Some(interner.lazy(parent_def)))
}
#[cfg(test)]
#[path = "../tests/expression_ops_tests.rs"]
mod tests;