use crate::TypeDatabase;
use crate::types::{PropertyInfo, TypeData, TypeId};
use tsz_common::Atom;
pub fn contains_type_parameters_db(db: &dyn TypeDatabase, type_id: TypeId) -> bool {
contains_type_matching_impl(db, type_id, |key| {
matches!(key, TypeData::TypeParameter(_) | TypeData::Infer(_))
})
}
pub fn contains_infer_types_db(db: &dyn TypeDatabase, type_id: TypeId) -> bool {
contains_type_matching_impl(db, type_id, |key| matches!(key, TypeData::Infer(_)))
}
pub fn contains_error_type_db(db: &dyn TypeDatabase, type_id: TypeId) -> bool {
if type_id == TypeId::ERROR {
return true;
}
contains_type_matching_impl(db, type_id, |key| matches!(key, TypeData::Error))
}
fn contains_type_matching_impl<F>(db: &dyn TypeDatabase, type_id: TypeId, predicate: F) -> bool
where
F: Fn(&TypeData) -> bool + Copy,
{
let mut checker = ContainsTypeChecker {
db,
predicate,
guard: crate::recursion::RecursionGuard::with_profile(
crate::recursion::RecursionProfile::ShallowTraversal,
),
};
checker.check(type_id)
}
struct ContainsTypeChecker<'a, F>
where
F: Fn(&TypeData) -> bool,
{
db: &'a dyn TypeDatabase,
predicate: F,
guard: crate::recursion::RecursionGuard<TypeId>,
}
impl<'a, F> ContainsTypeChecker<'a, F>
where
F: Fn(&TypeData) -> bool,
{
fn check(&mut self, type_id: TypeId) -> bool {
let Some(key) = self.db.lookup(type_id) else {
return false;
};
if (self.predicate)(&key) {
return true;
}
match self.guard.enter(type_id) {
crate::recursion::RecursionResult::Entered => {}
_ => return false,
}
let result = self.check_key(&key);
self.guard.leave(type_id);
result
}
fn check_key(&mut self, key: &TypeData) -> bool {
match key {
TypeData::Intrinsic(_)
| TypeData::Literal(_)
| TypeData::Error
| TypeData::Lazy(_)
| TypeData::Recursive(_)
| TypeData::TypeQuery(_)
| TypeData::UniqueSymbol(_)
| TypeData::ModuleNamespace(_) => false,
TypeData::ThisType | TypeData::BoundParameter(_) => true,
TypeData::Object(shape_id) | TypeData::ObjectWithIndex(shape_id) => {
let shape = self.db.object_shape(*shape_id);
shape.properties.iter().any(|p| self.check(p.type_id))
|| shape
.string_index
.as_ref()
.is_some_and(|i| self.check(i.value_type))
|| shape
.number_index
.as_ref()
.is_some_and(|i| self.check(i.value_type))
}
TypeData::Union(list_id) | TypeData::Intersection(list_id) => {
let members = self.db.type_list(*list_id);
members.iter().any(|&m| self.check(m))
}
TypeData::Array(elem) => self.check(*elem),
TypeData::Tuple(list_id) => {
let elements = self.db.tuple_list(*list_id);
elements.iter().any(|e| self.check(e.type_id))
}
TypeData::Function(shape_id) => {
let shape = self.db.function_shape(*shape_id);
shape.params.iter().any(|p| self.check(p.type_id))
|| self.check(shape.return_type)
|| shape.this_type.is_some_and(|t| self.check(t))
}
TypeData::Callable(shape_id) => {
let shape = self.db.callable_shape(*shape_id);
shape.call_signatures.iter().any(|s| {
s.params.iter().any(|p| self.check(p.type_id)) || self.check(s.return_type)
}) || shape.construct_signatures.iter().any(|s| {
s.params.iter().any(|p| self.check(p.type_id)) || self.check(s.return_type)
}) || shape.properties.iter().any(|p| self.check(p.type_id))
}
TypeData::TypeParameter(info) | TypeData::Infer(info) => {
info.constraint.is_some_and(|c| self.check(c))
|| info.default.is_some_and(|d| self.check(d))
}
TypeData::Application(app_id) => {
let app = self.db.type_application(*app_id);
self.check(app.base) || app.args.iter().any(|&a| self.check(a))
}
TypeData::Conditional(cond_id) => {
let cond = self.db.conditional_type(*cond_id);
self.check(cond.check_type)
|| self.check(cond.extends_type)
|| self.check(cond.true_type)
|| self.check(cond.false_type)
}
TypeData::Mapped(mapped_id) => {
let mapped = self.db.mapped_type(*mapped_id);
self.check(mapped.constraint)
|| self.check(mapped.template)
|| mapped.name_type.is_some_and(|n| self.check(n))
}
TypeData::IndexAccess(obj, idx) => self.check(*obj) || self.check(*idx),
TypeData::TemplateLiteral(list_id) => {
let spans = self.db.template_list(*list_id);
spans.iter().any(|span| {
if let crate::types::TemplateSpan::Type(type_id) = span {
self.check(*type_id)
} else {
false
}
})
}
TypeData::KeyOf(inner) | TypeData::ReadonlyType(inner) | TypeData::NoInfer(inner) => {
self.check(*inner)
}
TypeData::StringIntrinsic { type_arg, .. } => self.check(*type_arg),
TypeData::Enum(_def_id, member_type) => self.check(*member_type),
}
}
}
pub fn get_union_members(db: &dyn TypeDatabase, type_id: TypeId) -> Option<Vec<TypeId>> {
match db.lookup(type_id) {
Some(TypeData::Union(list_id)) => {
let members = db.type_list(list_id);
Some(members.to_vec())
}
_ => None,
}
}
pub fn get_intersection_members(db: &dyn TypeDatabase, type_id: TypeId) -> Option<Vec<TypeId>> {
match db.lookup(type_id) {
Some(TypeData::Intersection(list_id)) => {
let members = db.type_list(list_id);
Some(members.to_vec())
}
_ => None,
}
}
pub fn map_compound_members(
db: &dyn TypeDatabase,
type_id: TypeId,
mut f: impl FnMut(TypeId) -> TypeId,
) -> Option<TypeId> {
match db.lookup(type_id) {
Some(TypeData::Union(list_id)) => {
let members = db.type_list(list_id);
let mapped: Vec<TypeId> = members.iter().map(|&m| f(m)).collect();
Some(db.union(mapped))
}
Some(TypeData::Intersection(list_id)) => {
let members = db.type_list(list_id);
let mapped: Vec<TypeId> = members.iter().map(|&m| f(m)).collect();
Some(db.intersection(mapped))
}
_ => None,
}
}
pub fn map_compound_members_if_changed(
db: &dyn TypeDatabase,
type_id: TypeId,
mut f: impl FnMut(TypeId) -> TypeId,
) -> Option<TypeId> {
match db.lookup(type_id) {
Some(TypeData::Union(list_id)) => {
let members = db.type_list(list_id);
let mapped: Vec<TypeId> = members.iter().map(|&m| f(m)).collect();
if mapped.iter().eq(members.iter()) {
Some(type_id)
} else {
Some(db.union(mapped))
}
}
Some(TypeData::Intersection(list_id)) => {
let members = db.type_list(list_id);
let mapped: Vec<TypeId> = members.iter().map(|&m| f(m)).collect();
if mapped.iter().eq(members.iter()) {
Some(type_id)
} else {
Some(db.intersection(mapped))
}
}
_ => None,
}
}
pub fn get_array_element_type(db: &dyn TypeDatabase, type_id: TypeId) -> Option<TypeId> {
match db.lookup(type_id) {
Some(TypeData::Array(element_type)) => Some(element_type),
_ => None,
}
}
pub fn get_tuple_elements(
db: &dyn TypeDatabase,
type_id: TypeId,
) -> Option<Vec<crate::types::TupleElement>> {
match db.lookup(type_id) {
Some(TypeData::Tuple(list_id)) => {
let elements = db.tuple_list(list_id);
Some(elements.to_vec())
}
_ => None,
}
}
pub fn get_tuple_element_type_union(db: &dyn TypeDatabase, type_id: TypeId) -> Option<TypeId> {
let elems = get_tuple_elements(db, type_id)?;
let mut members = Vec::with_capacity(elems.len());
for elem in elems {
let mut ty = if elem.rest {
get_array_element_type(db, elem.type_id).unwrap_or(elem.type_id)
} else {
elem.type_id
};
if elem.optional {
ty = db.union(vec![ty, TypeId::UNDEFINED]);
}
members.push(ty);
}
Some(db.union(members))
}
pub fn keyof_object_properties(db: &dyn TypeDatabase, type_id: TypeId) -> Option<TypeId> {
let shape = get_object_shape(db, type_id)?;
if shape.properties.is_empty() {
return Some(TypeId::NEVER);
}
let key_types: Vec<TypeId> = shape
.properties
.iter()
.filter(|p| p.visibility == crate::Visibility::Public)
.map(|p| db.literal_string_atom(p.name))
.collect();
Some(crate::utils::union_or_single(db, key_types))
}
pub fn get_array_applicable_type(db: &dyn TypeDatabase, type_id: TypeId) -> Option<TypeId> {
match db.lookup(type_id) {
Some(TypeData::Tuple(_) | TypeData::Array(_)) => Some(type_id),
Some(TypeData::Union(list_id)) => {
let members = db.type_list(list_id);
let applicable: Vec<TypeId> = members
.iter()
.filter(|&&m| matches!(db.lookup(m), Some(TypeData::Tuple(_) | TypeData::Array(_))))
.copied()
.collect();
match applicable.len() {
0 => None,
1 => Some(applicable[0]),
_ => Some(db.union(applicable)),
}
}
_ => None,
}
}
pub fn unpack_tuple_rest_parameter(
db: &dyn TypeDatabase,
param: &crate::types::ParamInfo,
) -> Vec<crate::types::ParamInfo> {
if !param.rest {
return vec![param.clone()];
}
if let Some(tuple_elements) = get_tuple_elements(db, param.type_id) {
tuple_elements
.into_iter()
.map(|elem| crate::types::ParamInfo {
name: elem.name, type_id: elem.type_id,
optional: elem.optional,
rest: elem.rest, })
.collect()
} else {
vec![param.clone()]
}
}
pub fn get_object_shape_id(
db: &dyn TypeDatabase,
type_id: TypeId,
) -> Option<crate::types::ObjectShapeId> {
match db.lookup(type_id) {
Some(TypeData::Object(shape_id) | TypeData::ObjectWithIndex(shape_id)) => Some(shape_id),
_ => None,
}
}
pub fn get_object_shape(
db: &dyn TypeDatabase,
type_id: TypeId,
) -> Option<std::sync::Arc<crate::types::ObjectShape>> {
match db.lookup(type_id) {
Some(TypeData::Object(shape_id) | TypeData::ObjectWithIndex(shape_id)) => {
Some(db.object_shape(shape_id))
}
_ => None,
}
}
pub fn find_property_in_object(
db: &dyn TypeDatabase,
type_id: TypeId,
name: Atom,
) -> Option<crate::types::PropertyInfo> {
let shape = get_object_shape(db, type_id)?;
PropertyInfo::find_in_slice(&shape.properties, name).cloned()
}
pub fn find_property_in_object_by_str(
db: &dyn TypeDatabase,
type_id: TypeId,
name: &str,
) -> Option<crate::types::PropertyInfo> {
let shape = get_object_shape(db, type_id)?;
shape
.properties
.iter()
.find(|p| db.resolve_atom_ref(p.name).as_ref() == name)
.cloned()
}
pub fn find_property_in_type_by_str(
db: &dyn TypeDatabase,
type_id: TypeId,
name: &str,
) -> Option<crate::types::PropertyInfo> {
match db.lookup(type_id)? {
TypeData::Object(shape_id) | TypeData::ObjectWithIndex(shape_id) => {
let shape = db.object_shape(shape_id);
shape
.properties
.iter()
.find(|p| db.resolve_atom_ref(p.name).as_ref() == name)
.cloned()
}
TypeData::Callable(shape_id) => {
let shape = db.callable_shape(shape_id);
shape
.properties
.iter()
.find(|p| db.resolve_atom_ref(p.name).as_ref() == name)
.cloned()
}
_ => None,
}
}
pub fn unwrap_readonly(db: &dyn TypeDatabase, type_id: TypeId) -> TypeId {
match db.lookup(type_id) {
Some(TypeData::ReadonlyType(inner)) => inner,
_ => type_id,
}
}
pub fn unwrap_readonly_deep(db: &dyn TypeDatabase, type_id: TypeId) -> TypeId {
let mut current = type_id;
let mut depth = 0;
const MAX_DEPTH: usize = 100;
while let Some(TypeData::ReadonlyType(inner)) = db.lookup(current) {
depth += 1;
if depth > MAX_DEPTH {
break;
}
current = inner;
}
current
}
pub fn is_object_type_with_shape(db: &dyn TypeDatabase, type_id: TypeId) -> bool {
matches!(
db.lookup(type_id),
Some(TypeData::Object(_) | TypeData::ObjectWithIndex(_))
)
}
pub fn get_type_parameter_info(
db: &dyn TypeDatabase,
type_id: TypeId,
) -> Option<crate::types::TypeParamInfo> {
match db.lookup(type_id) {
Some(TypeData::TypeParameter(info) | TypeData::Infer(info)) => Some(info),
_ => None,
}
}
pub fn get_type_parameter_constraint(db: &dyn TypeDatabase, type_id: TypeId) -> Option<TypeId> {
match db.lookup(type_id) {
Some(TypeData::TypeParameter(info) | TypeData::Infer(info)) => info.constraint,
_ => None,
}
}
pub fn get_callable_shape_id(
db: &dyn TypeDatabase,
type_id: TypeId,
) -> Option<crate::types::CallableShapeId> {
match db.lookup(type_id) {
Some(TypeData::Callable(shape_id)) => Some(shape_id),
_ => None,
}
}
pub fn get_callable_shape(
db: &dyn TypeDatabase,
type_id: TypeId,
) -> Option<std::sync::Arc<crate::types::CallableShape>> {
match db.lookup(type_id) {
Some(TypeData::Callable(shape_id)) => Some(db.callable_shape(shape_id)),
_ => None,
}
}
pub fn has_call_signatures(db: &dyn TypeDatabase, type_id: TypeId) -> bool {
get_callable_shape(db, type_id).is_some_and(|shape| !shape.call_signatures.is_empty())
}
pub fn get_call_signatures(
db: &dyn TypeDatabase,
type_id: TypeId,
) -> Option<Vec<crate::CallSignature>> {
get_callable_shape(db, type_id).map(|shape| shape.call_signatures.clone())
}
pub fn get_construct_signatures(
db: &dyn TypeDatabase,
type_id: TypeId,
) -> Option<Vec<crate::CallSignature>> {
get_callable_shape(db, type_id).map(|shape| shape.construct_signatures.clone())
}
pub fn get_construct_return_type_union(
db: &dyn TypeDatabase,
shape_id: crate::types::CallableShapeId,
) -> Option<TypeId> {
let shape = db.callable_shape(shape_id);
if shape.construct_signatures.is_empty() {
return None;
}
let returns: Vec<TypeId> = shape
.construct_signatures
.iter()
.map(|sig| sig.return_type)
.collect();
Some(crate::utils::union_or_single(db, returns))
}
pub fn get_function_shape_id(
db: &dyn TypeDatabase,
type_id: TypeId,
) -> Option<crate::types::FunctionShapeId> {
match db.lookup(type_id) {
Some(TypeData::Function(shape_id)) => Some(shape_id),
_ => None,
}
}
pub fn get_function_shape(
db: &dyn TypeDatabase,
type_id: TypeId,
) -> Option<std::sync::Arc<crate::types::FunctionShape>> {
match db.lookup(type_id) {
Some(TypeData::Function(shape_id)) => Some(db.function_shape(shape_id)),
_ => None,
}
}
pub fn rewrite_function_error_slots_to_any(db: &dyn TypeDatabase, type_id: TypeId) -> TypeId {
let Some(shape) = get_function_shape(db, type_id) else {
return type_id;
};
let has_error = shape.params.iter().any(|p| p.type_id == TypeId::ERROR)
|| shape.return_type == TypeId::ERROR;
if !has_error {
return type_id;
}
let params = shape
.params
.iter()
.map(|p| crate::types::ParamInfo {
type_id: if p.type_id == TypeId::ERROR {
TypeId::ANY
} else {
p.type_id
},
..p.clone()
})
.collect();
let return_type = if shape.return_type == TypeId::ERROR {
TypeId::ANY
} else {
shape.return_type
};
db.function(crate::types::FunctionShape {
type_params: shape.type_params.clone(),
params,
this_type: shape.this_type,
return_type,
type_predicate: shape.type_predicate.clone(),
is_constructor: shape.is_constructor,
is_method: shape.is_method,
})
}
pub fn replace_function_return_type(
db: &dyn TypeDatabase,
type_id: TypeId,
new_return: TypeId,
) -> TypeId {
let Some(shape) = get_function_shape(db, type_id) else {
return type_id;
};
if shape.return_type == new_return {
return type_id;
}
db.function(crate::types::FunctionShape {
type_params: shape.type_params.clone(),
params: shape.params.clone(),
this_type: shape.this_type,
return_type: new_return,
type_predicate: shape.type_predicate.clone(),
is_constructor: shape.is_constructor,
is_method: shape.is_method,
})
}
pub fn get_conditional_type(
db: &dyn TypeDatabase,
type_id: TypeId,
) -> Option<std::sync::Arc<crate::types::ConditionalType>> {
match db.lookup(type_id) {
Some(TypeData::Conditional(cond_id)) => Some(db.conditional_type(cond_id)),
_ => None,
}
}
pub fn get_mapped_type(
db: &dyn TypeDatabase,
type_id: TypeId,
) -> Option<std::sync::Arc<crate::types::MappedType>> {
match db.lookup(type_id) {
Some(TypeData::Mapped(mapped_id)) => Some(db.mapped_type(mapped_id)),
_ => None,
}
}
pub fn get_type_application(
db: &dyn TypeDatabase,
type_id: TypeId,
) -> Option<std::sync::Arc<crate::types::TypeApplication>> {
match db.lookup(type_id) {
Some(TypeData::Application(app_id)) => Some(db.type_application(app_id)),
_ => None,
}
}
pub fn get_index_access_types(db: &dyn TypeDatabase, type_id: TypeId) -> Option<(TypeId, TypeId)> {
match db.lookup(type_id) {
Some(TypeData::IndexAccess(obj, idx)) => Some((obj, idx)),
_ => None,
}
}
pub fn get_keyof_type(db: &dyn TypeDatabase, type_id: TypeId) -> Option<TypeId> {
match db.lookup(type_id) {
Some(TypeData::KeyOf(inner)) => Some(inner),
_ => None,
}
}
pub fn get_private_brand_name(db: &dyn TypeDatabase, type_id: TypeId) -> Option<String> {
match db.lookup(type_id)? {
TypeData::Object(shape_id) | TypeData::ObjectWithIndex(shape_id) => {
let shape = db.object_shape(shape_id);
for prop in &shape.properties {
let name = db.resolve_atom(prop.name);
if name.starts_with("__private_brand_") {
return Some(name);
}
}
None
}
TypeData::Callable(shape_id) => {
let shape = db.callable_shape(shape_id);
for prop in &shape.properties {
let name = db.resolve_atom(prop.name);
if name.starts_with("__private_brand_") {
return Some(name);
}
}
None
}
_ => None,
}
}
pub fn get_private_field_name(db: &dyn TypeDatabase, type_id: TypeId) -> Option<String> {
match db.lookup(type_id)? {
TypeData::Object(shape_id) | TypeData::ObjectWithIndex(shape_id) => {
let shape = db.object_shape(shape_id);
for prop in &shape.properties {
let name = db.resolve_atom(prop.name);
if name.starts_with('#') && !name.starts_with("__private_brand_") {
return Some(name);
}
}
None
}
TypeData::Callable(shape_id) => {
let shape = db.callable_shape(shape_id);
for prop in &shape.properties {
let name = db.resolve_atom(prop.name);
if name.starts_with('#') && !name.starts_with("__private_brand_") {
return Some(name);
}
}
None
}
_ => None,
}
}
pub fn get_type_shape_symbol(
db: &dyn TypeDatabase,
type_id: TypeId,
) -> Option<tsz_binder::SymbolId> {
match db.lookup(type_id)? {
TypeData::Object(shape_id) | TypeData::ObjectWithIndex(shape_id) => {
db.object_shape(shape_id).symbol
}
TypeData::Callable(shape_id) => db.callable_shape(shape_id).symbol,
_ => None,
}
}
pub fn get_lazy_def_id(db: &dyn TypeDatabase, type_id: TypeId) -> Option<crate::def::DefId> {
match db.lookup(type_id) {
Some(TypeData::Lazy(def_id)) => Some(def_id),
_ => None,
}
}
pub fn get_enum_def_id(db: &dyn TypeDatabase, type_id: TypeId) -> Option<crate::def::DefId> {
match db.lookup(type_id) {
Some(TypeData::Enum(def_id, _)) => Some(def_id),
_ => None,
}
}