use crate::{QueryDatabase, TypeData, TypeDatabase, TypeId};
use tsz_common::Atom;
pub use crate::type_queries_classifiers::{
AssignabilityEvalKind, AugmentationTargetKind, BindingElementTypeKind, ConstructorAccessKind,
ExcessPropertiesKind, InterfaceMergeKind, SymbolResolutionTraversalKind,
classify_for_assignability_eval, classify_for_augmentation, classify_for_binding_element,
classify_for_constructor_access, classify_for_excess_properties, classify_for_interface_merge,
classify_for_symbol_resolution_traversal, get_conditional_type_id, get_def_id,
get_enum_components, get_keyof_inner, get_lazy_def_id, get_mapped_type_id, get_type_identity,
};
pub use crate::type_queries_extended::get_application_info;
pub use crate::type_queries_extended::{
AbstractClassCheckKind, AbstractConstructorKind, ArrayLikeKind, BaseInstanceMergeKind,
CallSignaturesKind, ClassDeclTypeKind, ConstructorCheckKind, ConstructorReturnMergeKind,
ContextualLiteralAllowKind, ElementIndexableKind, IndexKeyKind, InstanceTypeKind,
KeyOfTypeKind, LazyTypeKind, LiteralKeyKind, LiteralTypeKind, MappedConstraintKind,
NamespaceMemberKind, PrivateBrandKind, PromiseTypeKind, PropertyAccessResolutionKind,
StringLiteralKeyKind, TypeArgumentExtractionKind, TypeParameterKind, TypeQueryKind,
TypeResolutionKind, classify_array_like, classify_element_indexable,
classify_for_abstract_check, classify_for_base_instance_merge, classify_for_call_signatures,
classify_for_class_decl, classify_for_constructor_check, classify_for_constructor_return_merge,
classify_for_contextual_literal, classify_for_instance_type, classify_for_lazy_resolution,
classify_for_private_brand, classify_for_property_access_resolution,
classify_for_string_literal_keys, classify_for_type_argument_extraction,
classify_for_type_resolution, classify_index_key, classify_literal_key, classify_literal_type,
classify_mapped_constraint, classify_namespace_member, classify_promise_type,
classify_type_parameter, classify_type_query, create_boolean_literal_type,
create_number_literal_type, create_string_literal_type, get_application_base,
get_boolean_literal_value, get_callable_type_param_count, get_literal_property_name,
get_number_literal_value, get_string_literal_atom, get_string_literal_value, get_tuple_list_id,
get_type_param_default, get_widened_literal_type, is_boolean_literal, is_direct_type_parameter,
is_invalid_index_type, is_number_literal, is_object_with_index_type, is_string_literal,
unwrap_readonly_for_lookup, widen_literal_to_primitive,
};
pub use crate::type_queries_data::*;
pub use crate::type_queries_flow::*;
pub fn get_keyof_type(db: &dyn TypeDatabase, type_id: TypeId) -> Option<TypeId> {
get_keyof_inner(db, type_id)
}
pub fn get_allowed_keys(db: &dyn TypeDatabase, type_id: TypeId) -> rustc_hash::FxHashSet<String> {
let atoms = collect_property_name_atoms_for_diagnostics(db, type_id, 10);
atoms.into_iter().map(|a| db.resolve_atom(a)).collect()
}
pub fn is_callable_type(db: &dyn TypeDatabase, type_id: TypeId) -> bool {
matches!(
db.lookup(type_id),
Some(TypeData::Callable(_) | TypeData::Function(_))
)
}
pub fn is_invokable_type(db: &dyn TypeDatabase, type_id: TypeId) -> bool {
match db.lookup(type_id) {
Some(TypeData::Function(_)) => true,
Some(TypeData::Callable(shape_id)) => {
let shape = db.callable_shape(shape_id);
!shape.call_signatures.is_empty()
}
Some(TypeData::Intersection(list_id)) => {
let members = db.type_list(list_id);
members.iter().any(|&m| is_invokable_type(db, m))
}
_ => false,
}
}
pub fn is_tuple_type(db: &dyn TypeDatabase, type_id: TypeId) -> bool {
matches!(db.lookup(type_id), Some(TypeData::Tuple(_)))
}
pub fn is_union_type(db: &dyn TypeDatabase, type_id: TypeId) -> bool {
matches!(db.lookup(type_id), Some(TypeData::Union(_)))
}
pub fn is_intersection_type(db: &dyn TypeDatabase, type_id: TypeId) -> bool {
matches!(db.lookup(type_id), Some(TypeData::Intersection(_)))
}
pub fn is_object_type(db: &dyn TypeDatabase, type_id: TypeId) -> bool {
matches!(
db.lookup(type_id),
Some(TypeData::Object(_) | TypeData::ObjectWithIndex(_))
)
}
pub fn is_array_type(db: &dyn TypeDatabase, type_id: TypeId) -> bool {
matches!(db.lookup(type_id), Some(TypeData::Array(_)))
}
pub fn is_literal_type(db: &dyn TypeDatabase, type_id: TypeId) -> bool {
matches!(db.lookup(type_id), Some(TypeData::Literal(_)))
}
pub fn is_generic_type(db: &dyn TypeDatabase, type_id: TypeId) -> bool {
matches!(db.lookup(type_id), Some(TypeData::Application(_)))
}
pub fn is_type_reference(db: &dyn TypeDatabase, type_id: TypeId) -> bool {
matches!(
db.lookup(type_id),
Some(TypeData::Lazy(_) | TypeData::Recursive(_) | TypeData::BoundParameter(_))
)
}
pub fn is_conditional_type(db: &dyn TypeDatabase, type_id: TypeId) -> bool {
matches!(db.lookup(type_id), Some(TypeData::Conditional(_)))
}
pub fn is_mapped_type(db: &dyn TypeDatabase, type_id: TypeId) -> bool {
matches!(db.lookup(type_id), Some(TypeData::Mapped(_)))
}
pub fn is_template_literal_type(db: &dyn TypeDatabase, type_id: TypeId) -> bool {
matches!(db.lookup(type_id), Some(TypeData::TemplateLiteral(_)))
}
pub fn is_uninstantiated_type_parameter(db: &dyn TypeDatabase, type_id: TypeId) -> bool {
match db.lookup(type_id) {
Some(
TypeData::TypeParameter(_)
| TypeData::BoundParameter(_)
| TypeData::Infer(_)
| TypeData::Lazy(_),
) => true,
Some(TypeData::Intersection(list_id) | TypeData::Union(list_id)) => {
let elements = db.type_list(list_id);
elements
.iter()
.any(|&e| is_uninstantiated_type_parameter(db, e))
}
_ => false,
}
}
pub fn is_type_parameter(db: &dyn TypeDatabase, type_id: TypeId) -> bool {
matches!(
db.lookup(type_id),
Some(TypeData::TypeParameter(_) | TypeData::BoundParameter(_) | TypeData::Infer(_))
)
}
pub fn is_index_access_type(db: &dyn TypeDatabase, type_id: TypeId) -> bool {
matches!(db.lookup(type_id), Some(TypeData::IndexAccess(_, _)))
}
pub fn is_keyof_type(db: &dyn TypeDatabase, type_id: TypeId) -> bool {
matches!(db.lookup(type_id), Some(TypeData::KeyOf(_)))
}
pub fn is_type_query(db: &dyn TypeDatabase, type_id: TypeId) -> bool {
matches!(db.lookup(type_id), Some(TypeData::TypeQuery(_)))
}
pub fn is_readonly_type(db: &dyn TypeDatabase, type_id: TypeId) -> bool {
matches!(db.lookup(type_id), Some(TypeData::ReadonlyType(_)))
}
pub fn is_unique_symbol_type(db: &dyn TypeDatabase, type_id: TypeId) -> bool {
matches!(db.lookup(type_id), Some(TypeData::UniqueSymbol(_)))
}
pub fn is_type_usable_as_property_name(db: &dyn TypeDatabase, type_id: TypeId) -> bool {
if type_id == TypeId::ANY {
return true;
}
matches!(
db.lookup(type_id),
Some(
TypeData::Literal(crate::LiteralValue::String(_))
| TypeData::Literal(crate::LiteralValue::Number(_))
| TypeData::UniqueSymbol(_)
)
)
}
pub fn is_this_type(db: &dyn TypeDatabase, type_id: TypeId) -> bool {
matches!(db.lookup(type_id), Some(TypeData::ThisType))
}
pub fn is_error_type(db: &dyn TypeDatabase, type_id: TypeId) -> bool {
type_id == TypeId::ERROR || matches!(db.lookup(type_id), Some(TypeData::Error))
}
pub fn needs_evaluation_for_merge(db: &dyn TypeDatabase, type_id: TypeId) -> bool {
matches!(
db.lookup(type_id),
Some(TypeData::Application(_) | TypeData::Lazy(_))
)
}
pub fn get_function_return_type(db: &dyn TypeDatabase, type_id: TypeId) -> TypeId {
match db.lookup(type_id) {
Some(TypeData::Function(shape_id)) => db.function_shape(shape_id).return_type,
_ => TypeId::ERROR,
}
}
pub fn get_function_parameter_types(db: &dyn TypeDatabase, type_id: TypeId) -> Vec<TypeId> {
match db.lookup(type_id) {
Some(TypeData::Function(shape_id)) => db
.function_shape(shape_id)
.params
.iter()
.map(|p| p.type_id)
.collect(),
_ => Vec::new(),
}
}
pub fn is_intrinsic_type(db: &dyn TypeDatabase, type_id: TypeId) -> bool {
matches!(db.lookup(type_id), Some(TypeData::Intrinsic(_)))
}
pub fn is_primitive_type(db: &dyn TypeDatabase, type_id: TypeId) -> bool {
if type_id.is_intrinsic() {
return true;
}
matches!(
db.lookup(type_id),
Some(TypeData::Intrinsic(_) | TypeData::Literal(_))
)
}
use crate::types::IntrinsicKind;
macro_rules! define_intrinsic_check {
($fn_name:ident, $type_id:ident, $kind:ident) => {
pub fn $fn_name(db: &dyn TypeDatabase, type_id: TypeId) -> bool {
type_id == TypeId::$type_id
|| matches!(
db.lookup(type_id),
Some(TypeData::Intrinsic(IntrinsicKind::$kind))
)
}
};
}
define_intrinsic_check!(is_any_type, ANY, Any);
define_intrinsic_check!(is_unknown_type, UNKNOWN, Unknown);
define_intrinsic_check!(is_never_type, NEVER, Never);
define_intrinsic_check!(is_void_type, VOID, Void);
define_intrinsic_check!(is_undefined_type, UNDEFINED, Undefined);
define_intrinsic_check!(is_null_type, NULL, Null);
define_intrinsic_check!(is_string_type, STRING, String);
define_intrinsic_check!(is_number_type, NUMBER, Number);
define_intrinsic_check!(is_bigint_type, BIGINT, Bigint);
define_intrinsic_check!(is_boolean_type, BOOLEAN, Boolean);
define_intrinsic_check!(is_symbol_type, SYMBOL, Symbol);
pub fn is_object_like_type(db: &dyn TypeDatabase, type_id: TypeId) -> bool {
is_object_like_type_impl(db, type_id)
}
fn is_object_like_type_impl(db: &dyn TypeDatabase, type_id: TypeId) -> bool {
match db.lookup(type_id) {
Some(
TypeData::Object(_)
| TypeData::ObjectWithIndex(_)
| TypeData::Array(_)
| TypeData::Tuple(_)
| TypeData::Mapped(_)
| TypeData::Function(_)
| TypeData::Callable(_)
| TypeData::Intrinsic(IntrinsicKind::Object | IntrinsicKind::Function),
) => true,
Some(TypeData::ReadonlyType(inner)) => is_object_like_type_impl(db, inner),
Some(TypeData::Intersection(members)) => {
let members = db.type_list(members);
members
.iter()
.all(|&member| is_object_like_type_impl(db, member))
}
Some(TypeData::TypeParameter(info) | TypeData::Infer(info)) => info
.constraint
.is_some_and(|constraint| is_object_like_type_impl(db, constraint)),
_ => false,
}
}
pub fn is_function_type(db: &dyn TypeDatabase, type_id: TypeId) -> bool {
is_function_type_impl(db, type_id)
}
fn is_function_type_impl(db: &dyn TypeDatabase, type_id: TypeId) -> bool {
match db.lookup(type_id) {
Some(TypeData::Function(_) | TypeData::Callable(_)) => true,
Some(TypeData::Intersection(members)) => {
let members = db.type_list(members);
members
.iter()
.any(|&member| is_function_type_impl(db, member))
}
_ => false,
}
}
pub fn is_valid_spread_type(db: &dyn TypeDatabase, type_id: TypeId) -> bool {
is_valid_spread_type_impl(db, type_id, 0)
}
fn is_valid_spread_type_impl(db: &dyn TypeDatabase, type_id: TypeId, depth: u32) -> bool {
if depth > 20 {
return true;
}
match type_id {
TypeId::ANY | TypeId::NEVER | TypeId::ERROR => return true,
_ => {}
}
match db.lookup(type_id) {
Some(
TypeData::Intrinsic(
IntrinsicKind::String
| IntrinsicKind::Number
| IntrinsicKind::Boolean
| IntrinsicKind::Bigint
| IntrinsicKind::Symbol
| IntrinsicKind::Void
| IntrinsicKind::Null
| IntrinsicKind::Undefined
| IntrinsicKind::Unknown,
)
| TypeData::Literal(_),
) => false,
Some(TypeData::Union(members)) => {
let members = db.type_list(members);
members
.iter()
.all(|&m| is_valid_spread_type_impl(db, m, depth + 1))
}
Some(TypeData::Intersection(members)) => {
let members = db.type_list(members);
members
.iter()
.all(|&m| is_valid_spread_type_impl(db, m, depth + 1))
}
Some(TypeData::ReadonlyType(inner)) => is_valid_spread_type_impl(db, inner, depth + 1),
_ => true,
}
}
pub fn is_empty_object_type(db: &dyn TypeDatabase, type_id: TypeId) -> bool {
match db.lookup(type_id) {
Some(TypeData::Object(shape_id)) => {
let shape = db.object_shape(shape_id);
shape.properties.is_empty()
}
Some(TypeData::ObjectWithIndex(shape_id)) => {
let shape = db.object_shape(shape_id);
shape.properties.is_empty()
&& shape.string_index.is_none()
&& shape.number_index.is_none()
}
_ => false,
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum ConstructorTypeKind {
Callable,
Function(crate::types::FunctionShapeId),
Members(Vec<TypeId>),
Inner(TypeId),
Constraint(Option<TypeId>),
NeedsTypeEvaluation,
NeedsApplicationEvaluation,
TypeQuery(crate::types::SymbolRef),
NotConstructor,
}
pub fn classify_constructor_type(db: &dyn TypeDatabase, type_id: TypeId) -> ConstructorTypeKind {
let Some(key) = db.lookup(type_id) else {
return ConstructorTypeKind::NotConstructor;
};
match key {
TypeData::Callable(_) => ConstructorTypeKind::Callable,
TypeData::Function(shape_id) => ConstructorTypeKind::Function(shape_id),
TypeData::Intersection(members_id) | TypeData::Union(members_id) => {
let members = db.type_list(members_id);
ConstructorTypeKind::Members(members.to_vec())
}
TypeData::ReadonlyType(inner) | TypeData::NoInfer(inner) => {
ConstructorTypeKind::Inner(inner)
}
TypeData::TypeParameter(info) | TypeData::Infer(info) => {
ConstructorTypeKind::Constraint(info.constraint)
}
TypeData::Conditional(_)
| TypeData::Mapped(_)
| TypeData::IndexAccess(_, _)
| TypeData::KeyOf(_) => ConstructorTypeKind::NeedsTypeEvaluation,
TypeData::Application(_) => ConstructorTypeKind::NeedsApplicationEvaluation,
TypeData::TypeQuery(sym_ref) => ConstructorTypeKind::TypeQuery(sym_ref),
TypeData::Enum(_, _)
| TypeData::BoundParameter(_)
| TypeData::Intrinsic(_)
| TypeData::Literal(_)
| TypeData::Object(_)
| TypeData::ObjectWithIndex(_)
| TypeData::Array(_)
| TypeData::Tuple(_)
| TypeData::Lazy(_)
| TypeData::Recursive(_)
| TypeData::TemplateLiteral(_)
| TypeData::UniqueSymbol(_)
| TypeData::ThisType
| TypeData::StringIntrinsic { .. }
| TypeData::ModuleNamespace(_)
| TypeData::Error => ConstructorTypeKind::NotConstructor,
}
}
#[derive(Debug, Clone)]
pub enum StaticPropertySource {
Properties(Vec<crate::PropertyInfo>),
RecurseMembers(Vec<TypeId>),
RecurseSingle(TypeId),
NeedsEvaluation,
NeedsApplicationEvaluation,
None,
}
pub fn get_static_property_source(db: &dyn TypeDatabase, type_id: TypeId) -> StaticPropertySource {
let Some(key) = db.lookup(type_id) else {
return StaticPropertySource::None;
};
match key {
TypeData::Callable(shape_id) => {
let shape = db.callable_shape(shape_id);
StaticPropertySource::Properties(shape.properties.to_vec())
}
TypeData::Object(shape_id) | TypeData::ObjectWithIndex(shape_id) => {
let shape = db.object_shape(shape_id);
StaticPropertySource::Properties(shape.properties.to_vec())
}
TypeData::Intersection(members_id) | TypeData::Union(members_id) => {
let members = db.type_list(members_id);
StaticPropertySource::RecurseMembers(members.to_vec())
}
TypeData::TypeParameter(info) | TypeData::Infer(info) => {
if let Some(constraint) = info.constraint {
StaticPropertySource::RecurseSingle(constraint)
} else {
StaticPropertySource::None
}
}
TypeData::ReadonlyType(inner) => StaticPropertySource::RecurseSingle(inner),
TypeData::Conditional(_)
| TypeData::Mapped(_)
| TypeData::IndexAccess(_, _)
| TypeData::KeyOf(_) => StaticPropertySource::NeedsEvaluation,
TypeData::Application(_) => StaticPropertySource::NeedsApplicationEvaluation,
_ => StaticPropertySource::None,
}
}
pub fn has_construct_signatures(db: &dyn TypeDatabase, type_id: TypeId) -> bool {
match db.lookup(type_id) {
Some(TypeData::Callable(shape_id)) => {
let shape = db.callable_shape(shape_id);
!shape.construct_signatures.is_empty()
}
_ => false,
}
}
pub fn get_symbol_ref_from_type(
db: &dyn TypeDatabase,
type_id: TypeId,
) -> Option<crate::types::SymbolRef> {
match db.lookup(type_id) {
Some(TypeData::TypeQuery(sym_ref)) => Some(sym_ref),
_ => None,
}
}
#[derive(Debug, Clone)]
pub enum ConstructableTypeKind {
CallableWithConstruct,
CallableMaybePrototype,
Function,
SymbolRef(crate::types::SymbolRef),
TypeQueryRef(crate::types::SymbolRef),
TypeParameterWithConstraint(TypeId),
TypeParameterNoConstraint,
Intersection(Vec<TypeId>),
Application,
Object,
NotConstructable,
}
pub fn classify_for_constructability(
db: &dyn TypeDatabase,
type_id: TypeId,
) -> ConstructableTypeKind {
let Some(key) = db.lookup(type_id) else {
return ConstructableTypeKind::NotConstructable;
};
match key {
TypeData::Callable(shape_id) => {
let shape = db.callable_shape(shape_id);
if shape.construct_signatures.is_empty() {
ConstructableTypeKind::CallableMaybePrototype
} else {
ConstructableTypeKind::CallableWithConstruct
}
}
TypeData::Function(_) => ConstructableTypeKind::Function,
TypeData::TypeQuery(sym_ref) => ConstructableTypeKind::TypeQueryRef(sym_ref),
TypeData::TypeParameter(info) | TypeData::Infer(info) => {
if let Some(constraint) = info.constraint {
ConstructableTypeKind::TypeParameterWithConstraint(constraint)
} else {
ConstructableTypeKind::TypeParameterNoConstraint
}
}
TypeData::Intersection(members_id) => {
let members = db.type_list(members_id);
ConstructableTypeKind::Intersection(members.to_vec())
}
TypeData::Application(_) => ConstructableTypeKind::Application,
TypeData::Object(_) | TypeData::ObjectWithIndex(_) => ConstructableTypeKind::Object,
_ => ConstructableTypeKind::NotConstructable,
}
}
pub fn construct_to_call_callable(db: &dyn TypeDatabase, type_id: TypeId) -> Option<TypeId> {
match db.lookup(type_id) {
Some(TypeData::Callable(shape_id)) => {
let shape = db.callable_shape(shape_id);
if shape.construct_signatures.is_empty() {
None
} else {
Some(db.callable(crate::types::CallableShape {
call_signatures: shape.construct_signatures.clone(),
construct_signatures: Vec::new(),
properties: Vec::new(),
string_index: None,
number_index: None,
symbol: None,
}))
}
}
_ => None,
}
}
#[derive(Debug, Clone)]
pub enum ConstraintTypeKind {
TypeParameter {
constraint: Option<TypeId>,
default: Option<TypeId>,
},
Union(Vec<TypeId>),
Intersection(Vec<TypeId>),
SymbolRef(crate::types::SymbolRef),
Application { app_id: u32 },
Mapped { mapped_id: u32 },
KeyOf(TypeId),
Resolved(TypeId),
NoConstraint,
}
pub fn classify_for_constraint(db: &dyn TypeDatabase, type_id: TypeId) -> ConstraintTypeKind {
let Some(key) = db.lookup(type_id) else {
return ConstraintTypeKind::NoConstraint;
};
match key {
TypeData::TypeParameter(info) | TypeData::Infer(info) => {
ConstraintTypeKind::TypeParameter {
constraint: info.constraint,
default: info.default,
}
}
TypeData::Union(list_id) => {
let members = db.type_list(list_id);
ConstraintTypeKind::Union(members.to_vec())
}
TypeData::Intersection(list_id) => {
let members = db.type_list(list_id);
ConstraintTypeKind::Intersection(members.to_vec())
}
TypeData::Application(app_id) => ConstraintTypeKind::Application { app_id: app_id.0 },
TypeData::Mapped(mapped_id) => ConstraintTypeKind::Mapped {
mapped_id: mapped_id.0,
},
TypeData::KeyOf(operand) => ConstraintTypeKind::KeyOf(operand),
TypeData::Literal(_) => ConstraintTypeKind::Resolved(type_id),
TypeData::BoundParameter(_)
| TypeData::Intrinsic(_)
| TypeData::Object(_)
| TypeData::ObjectWithIndex(_)
| TypeData::Array(_)
| TypeData::Tuple(_)
| TypeData::Function(_)
| TypeData::Callable(_)
| TypeData::Conditional(_)
| TypeData::IndexAccess(_, _)
| TypeData::TemplateLiteral(_)
| TypeData::UniqueSymbol(_)
| TypeData::ThisType
| TypeData::ReadonlyType(_)
| TypeData::NoInfer(_)
| TypeData::TypeQuery(_)
| TypeData::StringIntrinsic { .. }
| TypeData::ModuleNamespace(_)
| TypeData::Enum(_, _)
| TypeData::Lazy(_)
| TypeData::Recursive(_)
| TypeData::Error => ConstraintTypeKind::NoConstraint,
}
}
#[derive(Debug, Clone)]
pub enum SignatureTypeKind {
Callable(crate::types::CallableShapeId),
Function(crate::types::FunctionShapeId),
Union(Vec<TypeId>),
Intersection(Vec<TypeId>),
ReadonlyType(TypeId),
TypeParameter { constraint: Option<TypeId> },
NeedsEvaluation(TypeId),
NoSignatures,
}
pub fn classify_for_signatures(db: &dyn TypeDatabase, type_id: TypeId) -> SignatureTypeKind {
if type_id == TypeId::ERROR || type_id == TypeId::NEVER {
return SignatureTypeKind::NoSignatures;
}
if type_id == TypeId::ANY {
return SignatureTypeKind::NoSignatures;
}
let Some(key) = db.lookup(type_id) else {
return SignatureTypeKind::NoSignatures;
};
match key {
TypeData::Callable(shape_id) => SignatureTypeKind::Callable(shape_id),
TypeData::Function(shape_id) => SignatureTypeKind::Function(shape_id),
TypeData::Union(members_id) => {
let members = db.type_list(members_id);
SignatureTypeKind::Union(members.to_vec())
}
TypeData::Intersection(members_id) => {
let members = db.type_list(members_id);
SignatureTypeKind::Intersection(members.to_vec())
}
TypeData::ReadonlyType(inner) | TypeData::NoInfer(inner) => {
SignatureTypeKind::ReadonlyType(inner)
}
TypeData::TypeParameter(info) | TypeData::Infer(info) => SignatureTypeKind::TypeParameter {
constraint: info.constraint,
},
TypeData::Conditional(_)
| TypeData::Mapped(_)
| TypeData::IndexAccess(_, _)
| TypeData::KeyOf(_) => SignatureTypeKind::NeedsEvaluation(type_id),
TypeData::BoundParameter(_)
| TypeData::Intrinsic(_)
| TypeData::Literal(_)
| TypeData::Object(_)
| TypeData::ObjectWithIndex(_)
| TypeData::Array(_)
| TypeData::Tuple(_)
| TypeData::Lazy(_)
| TypeData::Recursive(_)
| TypeData::Application(_)
| TypeData::TemplateLiteral(_)
| TypeData::UniqueSymbol(_)
| TypeData::ThisType
| TypeData::TypeQuery(_)
| TypeData::StringIntrinsic { .. }
| TypeData::ModuleNamespace(_)
| TypeData::Enum(_, _)
| TypeData::Error => SignatureTypeKind::NoSignatures,
}
}
#[derive(Debug, Clone)]
pub enum IterableTypeKind {
Tuple(Vec<crate::types::TupleElement>),
Array(TypeId),
Other,
}
pub fn classify_iterable_type(db: &dyn TypeDatabase, type_id: TypeId) -> IterableTypeKind {
let Some(key) = db.lookup(type_id) else {
return IterableTypeKind::Other;
};
match key {
TypeData::Tuple(tuple_id) => {
let elements = db.tuple_list(tuple_id);
IterableTypeKind::Tuple(elements.to_vec())
}
TypeData::Array(elem_type) => IterableTypeKind::Array(elem_type),
_ => IterableTypeKind::Other,
}
}
#[derive(Debug, Clone)]
pub enum FullIterableTypeKind {
Array(TypeId),
Tuple(Vec<crate::types::TupleElement>),
StringLiteral(tsz_common::interner::Atom),
Union(Vec<TypeId>),
Intersection(Vec<TypeId>),
Object(crate::types::ObjectShapeId),
Application { base: TypeId },
TypeParameter { constraint: Option<TypeId> },
Readonly(TypeId),
FunctionOrCallable,
ComplexType,
NotIterable,
}
pub fn classify_full_iterable_type(db: &dyn TypeDatabase, type_id: TypeId) -> FullIterableTypeKind {
let Some(key) = db.lookup(type_id) else {
return FullIterableTypeKind::NotIterable;
};
match key {
TypeData::Array(elem) => FullIterableTypeKind::Array(elem),
TypeData::Tuple(tuple_id) => {
let elements = db.tuple_list(tuple_id);
FullIterableTypeKind::Tuple(elements.to_vec())
}
TypeData::Literal(crate::LiteralValue::String(s)) => FullIterableTypeKind::StringLiteral(s),
TypeData::Union(members_id) => {
let members = db.type_list(members_id);
FullIterableTypeKind::Union(members.to_vec())
}
TypeData::Intersection(members_id) => {
let members = db.type_list(members_id);
FullIterableTypeKind::Intersection(members.to_vec())
}
TypeData::Object(shape_id) | TypeData::ObjectWithIndex(shape_id) => {
FullIterableTypeKind::Object(shape_id)
}
TypeData::Application(app_id) => {
let app = db.type_application(app_id);
FullIterableTypeKind::Application { base: app.base }
}
TypeData::TypeParameter(info) | TypeData::Infer(info) => {
FullIterableTypeKind::TypeParameter {
constraint: info.constraint,
}
}
TypeData::ReadonlyType(inner) | TypeData::NoInfer(inner) => {
FullIterableTypeKind::Readonly(inner)
}
TypeData::Function(_) | TypeData::Callable(_) => FullIterableTypeKind::FunctionOrCallable,
TypeData::IndexAccess(_, _) | TypeData::Conditional(_) | TypeData::Mapped(_) => {
FullIterableTypeKind::ComplexType
}
TypeData::BoundParameter(_)
| TypeData::Intrinsic(_)
| TypeData::Literal(_)
| TypeData::Lazy(_)
| TypeData::Recursive(_)
| TypeData::TemplateLiteral(_)
| TypeData::UniqueSymbol(_)
| TypeData::ThisType
| TypeData::TypeQuery(_)
| TypeData::KeyOf(_)
| TypeData::StringIntrinsic { .. }
| TypeData::ModuleNamespace(_)
| TypeData::Enum(_, _)
| TypeData::Error => FullIterableTypeKind::NotIterable,
}
}
#[derive(Debug, Clone)]
pub enum AsyncIterableTypeKind {
Union(Vec<TypeId>),
Object(crate::types::ObjectShapeId),
Readonly(TypeId),
NotAsyncIterable,
}
pub fn classify_async_iterable_type(
db: &dyn TypeDatabase,
type_id: TypeId,
) -> AsyncIterableTypeKind {
let Some(key) = db.lookup(type_id) else {
return AsyncIterableTypeKind::NotAsyncIterable;
};
match key {
TypeData::Union(members_id) => {
let members = db.type_list(members_id);
AsyncIterableTypeKind::Union(members.to_vec())
}
TypeData::Object(shape_id) | TypeData::ObjectWithIndex(shape_id) => {
AsyncIterableTypeKind::Object(shape_id)
}
TypeData::ReadonlyType(inner) => AsyncIterableTypeKind::Readonly(inner),
_ => AsyncIterableTypeKind::NotAsyncIterable,
}
}
#[derive(Debug, Clone)]
pub enum ForOfElementKind {
Array(TypeId),
Tuple(Vec<crate::types::TupleElement>),
Union(Vec<TypeId>),
Readonly(TypeId),
String,
Other,
}
pub fn classify_for_of_element_type(db: &dyn TypeDatabase, type_id: TypeId) -> ForOfElementKind {
let Some(key) = db.lookup(type_id) else {
return ForOfElementKind::Other;
};
match key {
TypeData::Array(elem) => ForOfElementKind::Array(elem),
TypeData::Tuple(tuple_id) => {
let elements = db.tuple_list(tuple_id);
ForOfElementKind::Tuple(elements.to_vec())
}
TypeData::Union(members_id) => {
let members = db.type_list(members_id);
ForOfElementKind::Union(members.to_vec())
}
TypeData::ReadonlyType(inner) | TypeData::NoInfer(inner) => {
ForOfElementKind::Readonly(inner)
}
TypeData::Literal(crate::LiteralValue::String(_)) => ForOfElementKind::String,
_ => ForOfElementKind::Other,
}
}
#[derive(Debug, Clone)]
pub enum PropertyLookupKind {
Object(crate::types::ObjectShapeId),
ObjectWithIndex(crate::types::ObjectShapeId),
Union(Vec<TypeId>),
Intersection(Vec<TypeId>),
Array(TypeId),
Tuple(Vec<crate::types::TupleElement>),
NoProperties,
}
pub fn classify_for_property_lookup(db: &dyn TypeDatabase, type_id: TypeId) -> PropertyLookupKind {
let Some(key) = db.lookup(type_id) else {
return PropertyLookupKind::NoProperties;
};
match key {
TypeData::Object(shape_id) => PropertyLookupKind::Object(shape_id),
TypeData::ObjectWithIndex(shape_id) => PropertyLookupKind::ObjectWithIndex(shape_id),
TypeData::Union(list_id) => {
let members = db.type_list(list_id);
PropertyLookupKind::Union(members.to_vec())
}
TypeData::Intersection(list_id) => {
let members = db.type_list(list_id);
PropertyLookupKind::Intersection(members.to_vec())
}
TypeData::Array(elem_type) => PropertyLookupKind::Array(elem_type),
TypeData::Tuple(tuple_id) => {
let elements = db.tuple_list(tuple_id);
PropertyLookupKind::Tuple(elements.to_vec())
}
TypeData::BoundParameter(_)
| TypeData::Intrinsic(_)
| TypeData::Literal(_)
| TypeData::Function(_)
| TypeData::Callable(_)
| TypeData::TypeParameter(_)
| TypeData::Infer(_)
| TypeData::Lazy(_)
| TypeData::Recursive(_)
| TypeData::Application(_)
| TypeData::Conditional(_)
| TypeData::Mapped(_)
| TypeData::IndexAccess(_, _)
| TypeData::KeyOf(_)
| TypeData::TemplateLiteral(_)
| TypeData::UniqueSymbol(_)
| TypeData::ThisType
| TypeData::TypeQuery(_)
| TypeData::ReadonlyType(_)
| TypeData::NoInfer(_)
| TypeData::StringIntrinsic { .. }
| TypeData::ModuleNamespace(_)
| TypeData::Enum(_, _)
| TypeData::Error => PropertyLookupKind::NoProperties,
}
}
#[derive(Debug, Clone)]
pub enum EvaluationNeeded {
Resolved(TypeId),
SymbolRef(crate::types::SymbolRef),
TypeQuery(crate::types::SymbolRef),
Application {
app_id: crate::types::TypeApplicationId,
},
IndexAccess { object: TypeId, index: TypeId },
KeyOf(TypeId),
Mapped {
mapped_id: crate::types::MappedTypeId,
},
Conditional {
cond_id: crate::types::ConditionalTypeId,
},
Callable(crate::types::CallableShapeId),
Function(crate::types::FunctionShapeId),
Union(Vec<TypeId>),
Intersection(Vec<TypeId>),
TypeParameter { constraint: Option<TypeId> },
Readonly(TypeId),
}
pub fn classify_for_evaluation(db: &dyn TypeDatabase, type_id: TypeId) -> EvaluationNeeded {
let Some(key) = db.lookup(type_id) else {
return EvaluationNeeded::Resolved(type_id);
};
match key {
TypeData::TypeQuery(sym_ref) => EvaluationNeeded::TypeQuery(sym_ref),
TypeData::Application(app_id) => EvaluationNeeded::Application { app_id },
TypeData::IndexAccess(object, index) => EvaluationNeeded::IndexAccess { object, index },
TypeData::KeyOf(inner) => EvaluationNeeded::KeyOf(inner),
TypeData::Mapped(mapped_id) => EvaluationNeeded::Mapped { mapped_id },
TypeData::Conditional(cond_id) => EvaluationNeeded::Conditional { cond_id },
TypeData::Callable(shape_id) => EvaluationNeeded::Callable(shape_id),
TypeData::Function(shape_id) => EvaluationNeeded::Function(shape_id),
TypeData::Union(list_id) => {
let members = db.type_list(list_id);
EvaluationNeeded::Union(members.to_vec())
}
TypeData::Intersection(list_id) => {
let members = db.type_list(list_id);
EvaluationNeeded::Intersection(members.to_vec())
}
TypeData::TypeParameter(info) | TypeData::Infer(info) => EvaluationNeeded::TypeParameter {
constraint: info.constraint,
},
TypeData::ReadonlyType(inner) | TypeData::NoInfer(inner) => {
EvaluationNeeded::Readonly(inner)
}
TypeData::BoundParameter(_)
| TypeData::Intrinsic(_)
| TypeData::Literal(_)
| TypeData::Object(_)
| TypeData::ObjectWithIndex(_)
| TypeData::Array(_)
| TypeData::Tuple(_)
| TypeData::Lazy(_)
| TypeData::Recursive(_)
| TypeData::TemplateLiteral(_)
| TypeData::UniqueSymbol(_)
| TypeData::ThisType
| TypeData::StringIntrinsic { .. }
| TypeData::ModuleNamespace(_)
| TypeData::Enum(_, _)
| TypeData::Error => EvaluationNeeded::Resolved(type_id),
}
}
pub fn evaluate_contextual_structure_with(
db: &dyn QueryDatabase,
type_id: TypeId,
evaluate_leaf: &mut dyn FnMut(TypeId) -> TypeId,
) -> TypeId {
fn visit(
db: &dyn QueryDatabase,
type_id: TypeId,
evaluate_leaf: &mut dyn FnMut(TypeId) -> TypeId,
) -> TypeId {
match classify_for_evaluation(db, type_id) {
EvaluationNeeded::Union(members) => {
let mut changed = false;
let evaluated: Vec<TypeId> = members
.iter()
.map(|&member| {
let ev = visit(db, member, evaluate_leaf);
if ev != member {
changed = true;
}
ev
})
.collect();
if changed {
db.factory().union(evaluated)
} else {
type_id
}
}
EvaluationNeeded::Intersection(members) => {
let mut changed = false;
let evaluated: Vec<TypeId> = members
.iter()
.map(|&member| {
let ev = visit(db, member, evaluate_leaf);
if ev != member {
changed = true;
}
ev
})
.collect();
if changed {
db.factory().intersection(evaluated)
} else {
type_id
}
}
EvaluationNeeded::Application { .. }
| EvaluationNeeded::Mapped { .. }
| EvaluationNeeded::Conditional { .. } => {
let evaluated = evaluate_leaf(type_id);
if evaluated != type_id {
evaluated
} else {
type_id
}
}
_ if get_lazy_def_id(db, type_id).is_some() => {
let evaluated = evaluate_leaf(type_id);
if evaluated != type_id {
evaluated
} else {
type_id
}
}
_ => type_id,
}
}
visit(db, type_id, evaluate_leaf)
}
#[derive(Debug, Clone)]
pub enum PropertyAccessClassification {
Direct(TypeId),
SymbolRef(crate::types::SymbolRef),
TypeQuery(crate::types::SymbolRef),
Application {
app_id: crate::types::TypeApplicationId,
},
Union(Vec<TypeId>),
Intersection(Vec<TypeId>),
IndexAccess { object: TypeId, index: TypeId },
Readonly(TypeId),
Callable(TypeId),
TypeParameter { constraint: Option<TypeId> },
NeedsEvaluation(TypeId),
Resolved(TypeId),
}
pub fn classify_for_property_access(
db: &dyn TypeDatabase,
type_id: TypeId,
) -> PropertyAccessClassification {
let Some(key) = db.lookup(type_id) else {
return PropertyAccessClassification::Resolved(type_id);
};
match key {
TypeData::Object(_) | TypeData::ObjectWithIndex(_) => {
PropertyAccessClassification::Direct(type_id)
}
TypeData::TypeQuery(sym_ref) => PropertyAccessClassification::TypeQuery(sym_ref),
TypeData::Application(app_id) => PropertyAccessClassification::Application { app_id },
TypeData::Union(list_id) => {
let members = db.type_list(list_id);
PropertyAccessClassification::Union(members.to_vec())
}
TypeData::Intersection(list_id) => {
let members = db.type_list(list_id);
PropertyAccessClassification::Intersection(members.to_vec())
}
TypeData::IndexAccess(object, index) => {
PropertyAccessClassification::IndexAccess { object, index }
}
TypeData::ReadonlyType(inner) | TypeData::NoInfer(inner) => PropertyAccessClassification::Readonly(inner),
TypeData::Function(_) | TypeData::Callable(_) => {
PropertyAccessClassification::Callable(type_id)
}
TypeData::TypeParameter(info) | TypeData::Infer(info) => {
PropertyAccessClassification::TypeParameter {
constraint: info.constraint,
}
}
TypeData::Conditional(_) | TypeData::Mapped(_) | TypeData::KeyOf(_) => {
PropertyAccessClassification::NeedsEvaluation(type_id)
}
TypeData::BoundParameter(_)
| TypeData::Intrinsic(_)
| TypeData::Literal(_)
| TypeData::Array(_)
| TypeData::Tuple(_)
| TypeData::Lazy(_)
| TypeData::Recursive(_)
| TypeData::TemplateLiteral(_)
| TypeData::UniqueSymbol(_)
| TypeData::ThisType
| TypeData::StringIntrinsic { .. }
| TypeData::ModuleNamespace(_)
| TypeData::Error
| TypeData::Enum(_, _) => PropertyAccessClassification::Resolved(type_id),
}
}
#[derive(Debug, Clone)]
pub enum TypeTraversalKind {
Application {
app_id: crate::types::TypeApplicationId,
base: TypeId,
args: Vec<TypeId>,
},
SymbolRef(crate::types::SymbolRef),
Lazy(crate::def::DefId),
TypeQuery(crate::types::SymbolRef),
TypeParameter {
constraint: Option<TypeId>,
default: Option<TypeId>,
},
Members(Vec<TypeId>),
Function(crate::types::FunctionShapeId),
Callable(crate::types::CallableShapeId),
Object(crate::types::ObjectShapeId),
Array(TypeId),
Tuple(crate::types::TupleListId),
Conditional(crate::types::ConditionalTypeId),
Mapped(crate::types::MappedTypeId),
Readonly(TypeId),
IndexAccess { object: TypeId, index: TypeId },
KeyOf(TypeId),
TemplateLiteral(Vec<TypeId>),
StringIntrinsic(TypeId),
Terminal,
}
pub fn classify_for_traversal(db: &dyn TypeDatabase, type_id: TypeId) -> TypeTraversalKind {
let Some(key) = db.lookup(type_id) else {
return TypeTraversalKind::Terminal;
};
match key {
TypeData::Application(app_id) => {
let app = db.type_application(app_id);
TypeTraversalKind::Application {
app_id,
base: app.base,
args: app.args.clone(),
}
}
TypeData::TypeParameter(info) | TypeData::Infer(info) => TypeTraversalKind::TypeParameter {
constraint: info.constraint,
default: info.default,
},
TypeData::Union(list_id) | TypeData::Intersection(list_id) => {
let members = db.type_list(list_id);
TypeTraversalKind::Members(members.to_vec())
}
TypeData::Function(shape_id) => TypeTraversalKind::Function(shape_id),
TypeData::Callable(shape_id) => TypeTraversalKind::Callable(shape_id),
TypeData::Object(shape_id) | TypeData::ObjectWithIndex(shape_id) => {
TypeTraversalKind::Object(shape_id)
}
TypeData::Array(elem) => TypeTraversalKind::Array(elem),
TypeData::Tuple(list_id) => TypeTraversalKind::Tuple(list_id),
TypeData::Conditional(cond_id) => TypeTraversalKind::Conditional(cond_id),
TypeData::Mapped(mapped_id) => TypeTraversalKind::Mapped(mapped_id),
TypeData::ReadonlyType(inner) | TypeData::NoInfer(inner) => {
TypeTraversalKind::Readonly(inner)
}
TypeData::IndexAccess(object, index) => TypeTraversalKind::IndexAccess { object, index },
TypeData::KeyOf(inner) => TypeTraversalKind::KeyOf(inner),
TypeData::TemplateLiteral(list_id) => {
let spans = db.template_list(list_id);
let types: Vec<TypeId> = spans
.iter()
.filter_map(|span| match span {
crate::types::TemplateSpan::Type(id) => Some(*id),
_ => None,
})
.collect();
if types.is_empty() {
TypeTraversalKind::Terminal
} else {
TypeTraversalKind::TemplateLiteral(types)
}
}
TypeData::StringIntrinsic { type_arg, .. } => TypeTraversalKind::StringIntrinsic(type_arg),
TypeData::Lazy(def_id) => TypeTraversalKind::Lazy(def_id),
TypeData::TypeQuery(symbol_ref) => TypeTraversalKind::TypeQuery(symbol_ref),
TypeData::BoundParameter(_)
| TypeData::Intrinsic(_)
| TypeData::Literal(_)
| TypeData::Recursive(_)
| TypeData::UniqueSymbol(_)
| TypeData::ThisType
| TypeData::ModuleNamespace(_)
| TypeData::Error
| TypeData::Enum(_, _) => TypeTraversalKind::Terminal,
}
}
pub fn get_lazy_if_def(db: &dyn TypeDatabase, type_id: TypeId) -> Option<crate::def::DefId> {
match db.lookup(type_id) {
Some(TypeData::Lazy(def_id)) => Some(def_id),
_ => None,
}
}
#[derive(Debug, Clone)]
pub enum PropertyTraversalKind {
Object(std::sync::Arc<crate::types::ObjectShape>),
Callable(std::sync::Arc<crate::types::CallableShape>),
Members(Vec<TypeId>),
Other,
}
pub fn classify_property_traversal(
db: &dyn TypeDatabase,
type_id: TypeId,
) -> PropertyTraversalKind {
match classify_for_traversal(db, type_id) {
TypeTraversalKind::Object(_) => get_object_shape(db, type_id)
.map_or(PropertyTraversalKind::Other, PropertyTraversalKind::Object),
TypeTraversalKind::Callable(_) => get_callable_shape(db, type_id).map_or(
PropertyTraversalKind::Other,
PropertyTraversalKind::Callable,
),
TypeTraversalKind::Members(members) => PropertyTraversalKind::Members(members),
_ => PropertyTraversalKind::Other,
}
}
pub fn collect_property_name_atoms_for_diagnostics(
db: &dyn TypeDatabase,
type_id: TypeId,
max_depth: usize,
) -> Vec<Atom> {
fn collect_inner(
db: &dyn TypeDatabase,
type_id: TypeId,
out: &mut Vec<Atom>,
depth: usize,
max_depth: usize,
) {
if depth > max_depth {
return;
}
match classify_property_traversal(db, type_id) {
PropertyTraversalKind::Object(shape) => {
for prop in &shape.properties {
out.push(prop.name);
}
}
PropertyTraversalKind::Callable(shape) => {
for prop in &shape.properties {
out.push(prop.name);
}
}
PropertyTraversalKind::Members(members) => {
for member in members {
collect_inner(db, member, out, depth + 1, max_depth);
}
}
PropertyTraversalKind::Other => {}
}
}
let mut atoms = Vec::new();
collect_inner(db, type_id, &mut atoms, 0, max_depth);
atoms.sort_unstable();
atoms.dedup();
atoms
}
pub fn collect_accessible_property_names_for_suggestion(
db: &dyn TypeDatabase,
type_id: TypeId,
max_depth: usize,
) -> Vec<Atom> {
if let Some(TypeData::Union(list_id)) = db.lookup(type_id) {
let members = db.type_list(list_id).to_vec();
if members.is_empty() {
return vec![];
}
let mut common = collect_property_name_atoms_for_diagnostics(db, members[0], max_depth);
common.sort_unstable();
common.dedup();
for &member in &members[1..] {
let mut member_props =
collect_property_name_atoms_for_diagnostics(db, member, max_depth);
member_props.sort_unstable();
member_props.dedup();
common.retain(|a| member_props.binary_search(a).is_ok());
if common.is_empty() {
return vec![];
}
}
return common;
}
collect_property_name_atoms_for_diagnostics(db, type_id, max_depth)
}
pub fn is_only_null_or_undefined(db: &dyn TypeDatabase, type_id: TypeId) -> bool {
if type_id == TypeId::NULL || type_id == TypeId::UNDEFINED {
return true;
}
match db.lookup(type_id) {
Some(TypeData::Intrinsic(IntrinsicKind::Null | IntrinsicKind::Undefined)) => true,
Some(TypeData::Union(list_id)) => {
let members = db.type_list(list_id);
members.iter().all(|&m| is_only_null_or_undefined(db, m))
}
_ => false,
}
}