use crate::def::DefId;
use crate::{TypeData, TypeDatabase, TypeId};
use rustc_hash::FxHashSet;
#[derive(Debug, Clone)]
pub enum LiteralTypeKind {
String(tsz_common::interner::Atom),
Number(f64),
BigInt(tsz_common::interner::Atom),
Boolean(bool),
NotLiteral,
}
pub fn classify_literal_type(db: &dyn TypeDatabase, type_id: TypeId) -> LiteralTypeKind {
let Some(key) = db.lookup(type_id) else {
return LiteralTypeKind::NotLiteral;
};
match key {
TypeData::Literal(crate::LiteralValue::String(atom)) => LiteralTypeKind::String(atom),
TypeData::Literal(crate::LiteralValue::Number(ordered_float)) => {
LiteralTypeKind::Number(ordered_float.0)
}
TypeData::Literal(crate::LiteralValue::BigInt(atom)) => LiteralTypeKind::BigInt(atom),
TypeData::Literal(crate::LiteralValue::Boolean(value)) => LiteralTypeKind::Boolean(value),
_ => LiteralTypeKind::NotLiteral,
}
}
pub fn is_string_literal(db: &dyn TypeDatabase, type_id: TypeId) -> bool {
matches!(
classify_literal_type(db, type_id),
LiteralTypeKind::String(_)
)
}
pub fn is_number_literal(db: &dyn TypeDatabase, type_id: TypeId) -> bool {
matches!(
classify_literal_type(db, type_id),
LiteralTypeKind::Number(_)
)
}
pub fn is_boolean_literal(db: &dyn TypeDatabase, type_id: TypeId) -> bool {
matches!(
classify_literal_type(db, type_id),
LiteralTypeKind::Boolean(_)
)
}
pub fn get_string_literal_atom(
db: &dyn TypeDatabase,
type_id: TypeId,
) -> Option<tsz_common::interner::Atom> {
match classify_literal_type(db, type_id) {
LiteralTypeKind::String(atom) => Some(atom),
_ => None,
}
}
pub fn get_number_literal_value(db: &dyn TypeDatabase, type_id: TypeId) -> Option<f64> {
match classify_literal_type(db, type_id) {
LiteralTypeKind::Number(value) => Some(value),
_ => None,
}
}
pub fn get_boolean_literal_value(db: &dyn TypeDatabase, type_id: TypeId) -> Option<bool> {
match classify_literal_type(db, type_id) {
LiteralTypeKind::Boolean(value) => Some(value),
_ => None,
}
}
pub fn is_invalid_index_type(db: &dyn TypeDatabase, type_id: TypeId) -> bool {
let mut visited = FxHashSet::default();
is_invalid_index_type_inner(db, type_id, &mut visited)
}
fn is_invalid_index_type_inner(
db: &dyn TypeDatabase,
type_id: TypeId,
visited: &mut FxHashSet<TypeId>,
) -> bool {
if !visited.insert(type_id) {
return false;
}
if matches!(
type_id,
TypeId::ANY | TypeId::UNKNOWN | TypeId::ERROR | TypeId::NEVER
) {
return false;
}
match db.lookup(type_id) {
Some(TypeData::Intrinsic(kind)) => matches!(
kind,
crate::IntrinsicKind::Void
| crate::IntrinsicKind::Null
| crate::IntrinsicKind::Undefined
| crate::IntrinsicKind::Boolean
| crate::IntrinsicKind::Bigint
| crate::IntrinsicKind::Object
| crate::IntrinsicKind::Function
),
Some(TypeData::Literal(value)) => matches!(
value,
crate::LiteralValue::Boolean(_) | crate::LiteralValue::BigInt(_)
),
Some(
TypeData::Array(_)
| TypeData::Tuple(_)
| TypeData::Object(_)
| TypeData::ObjectWithIndex(_)
| TypeData::Function(_)
| TypeData::Callable(_)
| TypeData::Lazy(_),
) => true,
Some(TypeData::Union(list_id) | TypeData::Intersection(list_id)) => db
.type_list(list_id)
.iter()
.any(|&member| is_invalid_index_type_inner(db, member, visited)),
Some(TypeData::TypeParameter(info)) => info
.constraint
.is_some_and(|constraint| is_invalid_index_type_inner(db, constraint, visited)),
_ => false,
}
}
#[derive(Debug, Clone)]
pub enum SpreadTypeKind {
Array(TypeId),
Tuple(crate::types::TupleListId),
Object(crate::types::ObjectShapeId),
ObjectWithIndex(crate::types::ObjectShapeId),
StringLiteral(tsz_common::interner::Atom),
Lazy(DefId),
Other,
NotSpreadable,
}
pub fn classify_spread_type(db: &dyn TypeDatabase, type_id: TypeId) -> SpreadTypeKind {
if type_id.is_any() || type_id == TypeId::STRING {
return SpreadTypeKind::Other;
}
if type_id.is_unknown() {
return SpreadTypeKind::NotSpreadable;
}
let Some(key) = db.lookup(type_id) else {
return SpreadTypeKind::NotSpreadable;
};
match key {
TypeData::Array(element_type) => SpreadTypeKind::Array(element_type),
TypeData::Tuple(tuple_id) => SpreadTypeKind::Tuple(tuple_id),
TypeData::Object(shape_id) => SpreadTypeKind::Object(shape_id),
TypeData::ObjectWithIndex(shape_id) => SpreadTypeKind::ObjectWithIndex(shape_id),
TypeData::Literal(crate::LiteralValue::String(atom)) => SpreadTypeKind::StringLiteral(atom),
TypeData::Lazy(def_id) => SpreadTypeKind::Lazy(def_id),
_ => SpreadTypeKind::Other,
}
}
pub fn is_iterable_type_kind(db: &dyn TypeDatabase, type_id: TypeId) -> bool {
if type_id == TypeId::STRING {
return true;
}
let Some(key) = db.lookup(type_id) else {
return false;
};
match key {
TypeData::Array(_)
| TypeData::Tuple(_)
| TypeData::Literal(crate::LiteralValue::String(_)) => true,
TypeData::Object(shape_id) => {
let shape = db.object_shape(shape_id);
shape.properties.iter().any(|prop| {
let prop_name = db.resolve_atom_ref(prop.name);
(prop_name.as_ref() == "[Symbol.iterator]" || prop_name.as_ref() == "next")
&& prop.is_method
})
}
_ => false,
}
}
pub fn get_iterable_element_type_from_db(db: &dyn TypeDatabase, type_id: TypeId) -> Option<TypeId> {
if type_id == TypeId::STRING {
return Some(TypeId::STRING);
}
let key = db.lookup(type_id)?;
match key {
TypeData::Array(elem_type) => Some(elem_type),
TypeData::Tuple(tuple_list_id) => {
let elements = db.tuple_list(tuple_list_id);
if elements.is_empty() {
Some(TypeId::NEVER)
} else {
let types: Vec<TypeId> = elements.iter().map(|e| e.type_id).collect();
Some(db.union(types))
}
}
TypeData::Literal(crate::LiteralValue::String(_)) => Some(TypeId::STRING),
TypeData::Object(shape_id) => {
let shape = db.object_shape(shape_id);
let has_iterator = shape.properties.iter().any(|prop| {
let prop_name = db.resolve_atom_ref(prop.name);
(prop_name.as_ref() == "[Symbol.iterator]" || prop_name.as_ref() == "next")
&& prop.is_method
});
has_iterator.then_some(TypeId::ANY)
}
_ => None,
}
}
#[derive(Debug, Clone)]
pub enum TypeParameterKind {
TypeParameter(crate::types::TypeParamInfo),
Infer(crate::types::TypeParamInfo),
Application(crate::types::TypeApplicationId),
Union(Vec<TypeId>),
Intersection(Vec<TypeId>),
Callable(crate::types::CallableShapeId),
NotTypeParameter,
}
pub fn classify_type_parameter(db: &dyn TypeDatabase, type_id: TypeId) -> TypeParameterKind {
let Some(key) = db.lookup(type_id) else {
return TypeParameterKind::NotTypeParameter;
};
match key {
TypeData::TypeParameter(info) => TypeParameterKind::TypeParameter(info),
TypeData::Infer(info) => TypeParameterKind::Infer(info),
TypeData::Application(app_id) => TypeParameterKind::Application(app_id),
TypeData::Union(list_id) => {
let members = db.type_list(list_id);
TypeParameterKind::Union(members.to_vec())
}
TypeData::Intersection(list_id) => {
let members = db.type_list(list_id);
TypeParameterKind::Intersection(members.to_vec())
}
TypeData::Callable(shape_id) => TypeParameterKind::Callable(shape_id),
_ => TypeParameterKind::NotTypeParameter,
}
}
pub fn is_direct_type_parameter(db: &dyn TypeDatabase, type_id: TypeId) -> bool {
matches!(
classify_type_parameter(db, type_id),
TypeParameterKind::TypeParameter(_) | TypeParameterKind::Infer(_)
)
}
pub fn get_type_param_default(db: &dyn TypeDatabase, type_id: TypeId) -> Option<TypeId> {
match db.lookup(type_id) {
Some(TypeData::TypeParameter(info) | TypeData::Infer(info)) => info.default,
_ => None,
}
}
pub fn get_callable_type_param_count(db: &dyn TypeDatabase, type_id: TypeId) -> usize {
match db.lookup(type_id) {
Some(TypeData::Callable(shape_id)) => {
let shape = db.callable_shape(shape_id);
shape
.call_signatures
.iter()
.map(|sig| sig.type_params.len())
.max()
.unwrap_or(0)
}
_ => 0,
}
}
#[derive(Debug, Clone)]
pub enum PromiseTypeKind {
Application {
app_id: crate::types::TypeApplicationId,
base: TypeId,
args: Vec<TypeId>,
},
Lazy(crate::def::DefId),
Object(crate::types::ObjectShapeId),
Union(Vec<TypeId>),
NotPromise,
}
pub fn classify_promise_type(db: &dyn TypeDatabase, type_id: TypeId) -> PromiseTypeKind {
let Some(key) = db.lookup(type_id) else {
return PromiseTypeKind::NotPromise;
};
match key {
TypeData::Application(app_id) => {
let app = db.type_application(app_id);
PromiseTypeKind::Application {
app_id,
base: app.base,
args: app.args.clone(),
}
}
TypeData::Lazy(def_id) => PromiseTypeKind::Lazy(def_id),
TypeData::Object(shape_id) => PromiseTypeKind::Object(shape_id),
TypeData::Union(list_id) => {
let members = db.type_list(list_id);
PromiseTypeKind::Union(members.to_vec())
}
_ => PromiseTypeKind::NotPromise,
}
}
#[derive(Debug, Clone)]
pub enum NewExpressionTypeKind {
Callable(crate::types::CallableShapeId),
Function(crate::types::FunctionShapeId),
TypeQuery(crate::types::SymbolRef),
Intersection(Vec<TypeId>),
Union(Vec<TypeId>),
TypeParameter { constraint: Option<TypeId> },
NotConstructable,
}
pub fn classify_for_new_expression(
db: &dyn TypeDatabase,
type_id: TypeId,
) -> NewExpressionTypeKind {
let Some(key) = db.lookup(type_id) else {
return NewExpressionTypeKind::NotConstructable;
};
match key {
TypeData::Callable(shape_id) => NewExpressionTypeKind::Callable(shape_id),
TypeData::Function(shape_id) => NewExpressionTypeKind::Function(shape_id),
TypeData::TypeQuery(sym_ref) => NewExpressionTypeKind::TypeQuery(sym_ref),
TypeData::Intersection(list_id) => {
let members = db.type_list(list_id);
NewExpressionTypeKind::Intersection(members.to_vec())
}
TypeData::Union(list_id) => {
let members = db.type_list(list_id);
NewExpressionTypeKind::Union(members.to_vec())
}
TypeData::TypeParameter(info) | TypeData::Infer(info) => {
NewExpressionTypeKind::TypeParameter {
constraint: info.constraint,
}
}
TypeData::Object(shape_id) | TypeData::ObjectWithIndex(shape_id) => {
let shape = db.object_shape(shape_id);
for prop in &shape.properties {
if let Some(TypeData::Callable(callable_shape_id)) = db.lookup(prop.type_id) {
let callable_shape = db.callable_shape(callable_shape_id);
if !callable_shape.construct_signatures.is_empty() {
return NewExpressionTypeKind::Callable(callable_shape_id);
}
}
}
NewExpressionTypeKind::NotConstructable
}
_ => NewExpressionTypeKind::NotConstructable,
}
}
#[derive(Debug, Clone)]
pub enum AbstractClassCheckKind {
TypeQuery(crate::types::SymbolRef),
Union(Vec<TypeId>),
Intersection(Vec<TypeId>),
NotAbstract,
}
pub fn classify_for_abstract_check(
db: &dyn TypeDatabase,
type_id: TypeId,
) -> AbstractClassCheckKind {
let Some(key) = db.lookup(type_id) else {
return AbstractClassCheckKind::NotAbstract;
};
match key {
TypeData::TypeQuery(sym_ref) => AbstractClassCheckKind::TypeQuery(sym_ref),
TypeData::Union(list_id) => {
let members = db.type_list(list_id);
AbstractClassCheckKind::Union(members.to_vec())
}
TypeData::Intersection(list_id) => {
let members = db.type_list(list_id);
AbstractClassCheckKind::Intersection(members.to_vec())
}
_ => AbstractClassCheckKind::NotAbstract,
}
}
#[derive(Debug, Clone)]
pub enum ConstructSignatureKind {
Callable(crate::types::CallableShapeId),
Lazy(crate::def::DefId),
TypeQuery(crate::types::SymbolRef),
Application(crate::types::TypeApplicationId),
Union(Vec<TypeId>),
Intersection(Vec<TypeId>),
TypeParameter { constraint: Option<TypeId> },
Function(crate::types::FunctionShapeId),
NoConstruct,
}
pub fn classify_for_construct_signature(
db: &dyn TypeDatabase,
type_id: TypeId,
) -> ConstructSignatureKind {
let Some(key) = db.lookup(type_id) else {
return ConstructSignatureKind::NoConstruct;
};
match key {
TypeData::Callable(shape_id) => ConstructSignatureKind::Callable(shape_id),
TypeData::Lazy(def_id) => ConstructSignatureKind::Lazy(def_id),
TypeData::TypeQuery(sym_ref) => ConstructSignatureKind::TypeQuery(sym_ref),
TypeData::Application(app_id) => ConstructSignatureKind::Application(app_id),
TypeData::Union(list_id) => {
let members = db.type_list(list_id);
ConstructSignatureKind::Union(members.to_vec())
}
TypeData::Intersection(list_id) => {
let members = db.type_list(list_id);
ConstructSignatureKind::Intersection(members.to_vec())
}
TypeData::TypeParameter(info) | TypeData::Infer(info) => {
ConstructSignatureKind::TypeParameter {
constraint: info.constraint,
}
}
TypeData::Function(shape_id) => ConstructSignatureKind::Function(shape_id),
_ => ConstructSignatureKind::NoConstruct,
}
}
#[derive(Debug, Clone)]
pub enum KeyOfTypeKind {
Object(crate::types::ObjectShapeId),
NoKeys,
}
pub fn classify_for_keyof(db: &dyn TypeDatabase, type_id: TypeId) -> KeyOfTypeKind {
let Some(key) = db.lookup(type_id) else {
return KeyOfTypeKind::NoKeys;
};
match key {
TypeData::Object(shape_id) | TypeData::ObjectWithIndex(shape_id) => {
KeyOfTypeKind::Object(shape_id)
}
_ => KeyOfTypeKind::NoKeys,
}
}
#[derive(Debug, Clone)]
pub enum StringLiteralKeyKind {
SingleString(tsz_common::interner::Atom),
Union(Vec<TypeId>),
NotStringLiteral,
}
pub fn classify_for_string_literal_keys(
db: &dyn TypeDatabase,
type_id: TypeId,
) -> StringLiteralKeyKind {
let Some(key) = db.lookup(type_id) else {
return StringLiteralKeyKind::NotStringLiteral;
};
match key {
TypeData::Literal(crate::types::LiteralValue::String(name)) => {
StringLiteralKeyKind::SingleString(name)
}
TypeData::Union(list_id) => {
let members = db.type_list(list_id);
StringLiteralKeyKind::Union(members.to_vec())
}
_ => StringLiteralKeyKind::NotStringLiteral,
}
}
pub fn get_string_literal_value(
db: &dyn TypeDatabase,
type_id: TypeId,
) -> Option<tsz_common::interner::Atom> {
match db.lookup(type_id) {
Some(TypeData::Literal(crate::types::LiteralValue::String(name))) => Some(name),
_ => None,
}
}
pub fn get_literal_property_name(
db: &dyn TypeDatabase,
type_id: TypeId,
) -> Option<tsz_common::interner::Atom> {
match db.lookup(type_id) {
Some(TypeData::Literal(crate::types::LiteralValue::String(name))) => Some(name),
Some(TypeData::Literal(crate::types::LiteralValue::Number(num))) => {
let s = format!("{}", num.0);
Some(db.intern_string(&s))
}
Some(TypeData::UniqueSymbol(sym)) => {
let s = format!("__unique_{}", sym.0);
Some(db.intern_string(&s))
}
Some(TypeData::Enum(_, member_type)) => get_literal_property_name(db, member_type),
_ => None,
}
}
#[derive(Debug, Clone)]
pub enum ClassDeclTypeKind {
Object(crate::types::ObjectShapeId),
Members(Vec<TypeId>),
NotObject,
}
pub fn classify_for_class_decl(db: &dyn TypeDatabase, type_id: TypeId) -> ClassDeclTypeKind {
let Some(key) = db.lookup(type_id) else {
return ClassDeclTypeKind::NotObject;
};
match key {
TypeData::Object(shape_id) | TypeData::ObjectWithIndex(shape_id) => {
ClassDeclTypeKind::Object(shape_id)
}
TypeData::Union(list_id) | TypeData::Intersection(list_id) => {
let members = db.type_list(list_id);
ClassDeclTypeKind::Members(members.to_vec())
}
_ => ClassDeclTypeKind::NotObject,
}
}
#[derive(Debug, Clone)]
pub enum CallSignaturesKind {
Callable(crate::types::CallableShapeId),
MultipleSignatures(Vec<crate::CallSignature>),
NoSignatures,
}
pub fn classify_for_call_signatures(db: &dyn TypeDatabase, type_id: TypeId) -> CallSignaturesKind {
let Some(key) = db.lookup(type_id) else {
return CallSignaturesKind::NoSignatures;
};
match key {
TypeData::Callable(shape_id) => CallSignaturesKind::Callable(shape_id),
TypeData::Union(list_id) => {
let members = db.type_list(list_id);
let mut call_signatures = Vec::new();
for &member in members.iter() {
match db.lookup(member) {
Some(TypeData::Callable(shape_id)) => {
let shape = db.callable_shape(shape_id);
call_signatures.extend(shape.call_signatures.iter().cloned());
}
_ => continue,
}
}
if call_signatures.is_empty() {
CallSignaturesKind::NoSignatures
} else {
CallSignaturesKind::MultipleSignatures(call_signatures)
}
}
_ => CallSignaturesKind::NoSignatures,
}
}
pub fn get_application_info(
db: &dyn TypeDatabase,
type_id: TypeId,
) -> Option<(TypeId, Vec<TypeId>)> {
match db.lookup(type_id) {
Some(TypeData::Application(app_id)) => {
let app = db.type_application(app_id);
Some((app.base, app.args.clone()))
}
_ => None,
}
}
#[derive(Debug, Clone)]
pub enum TypeParameterContentKind {
IsTypeParameter,
Array(TypeId),
Tuple(crate::types::TupleListId),
Union(Vec<TypeId>),
Intersection(Vec<TypeId>),
Application { base: TypeId, args: Vec<TypeId> },
NotTypeParameter,
}
pub fn classify_for_type_parameter_content(
db: &dyn TypeDatabase,
type_id: TypeId,
) -> TypeParameterContentKind {
let Some(key) = db.lookup(type_id) else {
return TypeParameterContentKind::NotTypeParameter;
};
match key {
TypeData::TypeParameter(_) | TypeData::Infer(_) => {
TypeParameterContentKind::IsTypeParameter
}
TypeData::Array(elem) => TypeParameterContentKind::Array(elem),
TypeData::Tuple(list_id) => TypeParameterContentKind::Tuple(list_id),
TypeData::Union(list_id) => {
let members = db.type_list(list_id);
TypeParameterContentKind::Union(members.to_vec())
}
TypeData::Intersection(list_id) => {
let members = db.type_list(list_id);
TypeParameterContentKind::Intersection(members.to_vec())
}
TypeData::Application(app_id) => {
let app = db.type_application(app_id);
TypeParameterContentKind::Application {
base: app.base,
args: app.args.clone(),
}
}
_ => TypeParameterContentKind::NotTypeParameter,
}
}
#[derive(Debug, Clone)]
pub enum TypeDepthKind {
Array(TypeId),
Tuple(crate::types::TupleListId),
Members(Vec<TypeId>),
Application { base: TypeId, args: Vec<TypeId> },
Terminal,
}
pub fn classify_for_type_depth(db: &dyn TypeDatabase, type_id: TypeId) -> TypeDepthKind {
let Some(key) = db.lookup(type_id) else {
return TypeDepthKind::Terminal;
};
match key {
TypeData::Array(elem) => TypeDepthKind::Array(elem),
TypeData::Tuple(list_id) => TypeDepthKind::Tuple(list_id),
TypeData::Union(list_id) | TypeData::Intersection(list_id) => {
let members = db.type_list(list_id);
TypeDepthKind::Members(members.to_vec())
}
TypeData::Application(app_id) => {
let app = db.type_application(app_id);
TypeDepthKind::Application {
base: app.base,
args: app.args.clone(),
}
}
_ => TypeDepthKind::Terminal,
}
}
#[derive(Debug, Clone)]
pub enum SpreadPropertyKind {
Object(crate::types::ObjectShapeId),
Callable(crate::types::CallableShapeId),
Intersection(Vec<TypeId>),
NoProperties,
}
pub fn classify_for_spread_properties(
db: &dyn TypeDatabase,
type_id: TypeId,
) -> SpreadPropertyKind {
let Some(key) = db.lookup(type_id) else {
return SpreadPropertyKind::NoProperties;
};
match key {
TypeData::Object(shape_id) | TypeData::ObjectWithIndex(shape_id) => {
SpreadPropertyKind::Object(shape_id)
}
TypeData::Callable(shape_id) => SpreadPropertyKind::Callable(shape_id),
TypeData::Intersection(list_id) => {
let members = db.type_list(list_id);
SpreadPropertyKind::Intersection(members.to_vec())
}
_ => SpreadPropertyKind::NoProperties,
}
}
#[derive(Debug, Clone)]
pub enum LazyTypeKind {
Lazy(crate::def::DefId),
NotLazy,
}
pub fn classify_for_lazy_resolution(db: &dyn TypeDatabase, type_id: TypeId) -> LazyTypeKind {
let Some(key) = db.lookup(type_id) else {
return LazyTypeKind::NotLazy;
};
match key {
TypeData::Lazy(def_id) => LazyTypeKind::Lazy(def_id),
_ => LazyTypeKind::NotLazy,
}
}
#[derive(Debug, Clone)]
pub enum ConstructorCheckKind {
TypeParameter { constraint: Option<TypeId> },
Intersection(Vec<TypeId>),
Union(Vec<TypeId>),
Application { base: TypeId },
Lazy(crate::def::DefId),
TypeQuery(crate::types::SymbolRef),
Other,
}
pub fn classify_for_constructor_check(
db: &dyn TypeDatabase,
type_id: TypeId,
) -> ConstructorCheckKind {
let Some(key) = db.lookup(type_id) else {
return ConstructorCheckKind::Other;
};
match key {
TypeData::TypeParameter(info) | TypeData::Infer(info) => {
ConstructorCheckKind::TypeParameter {
constraint: info.constraint,
}
}
TypeData::Intersection(members_id) => {
let members = db.type_list(members_id);
ConstructorCheckKind::Intersection(members.to_vec())
}
TypeData::Union(members_id) => {
let members = db.type_list(members_id);
ConstructorCheckKind::Union(members.to_vec())
}
TypeData::Application(app_id) => {
let app = db.type_application(app_id);
ConstructorCheckKind::Application { base: app.base }
}
TypeData::Lazy(def_id) => ConstructorCheckKind::Lazy(def_id),
TypeData::TypeQuery(sym_ref) => ConstructorCheckKind::TypeQuery(sym_ref),
_ => ConstructorCheckKind::Other,
}
}
pub fn is_narrowable_type_key(db: &dyn TypeDatabase, type_id: TypeId) -> bool {
matches!(
db.lookup(type_id),
Some(
TypeData::Union(_)
| TypeData::TypeParameter(_)
| TypeData::Infer(_)
| TypeData::Lazy(_)
)
)
}
#[derive(Debug, Clone)]
pub enum PrivateBrandKind {
Object(crate::types::ObjectShapeId),
Callable(crate::types::CallableShapeId),
None,
}
pub fn classify_for_private_brand(db: &dyn TypeDatabase, type_id: TypeId) -> PrivateBrandKind {
match db.lookup(type_id) {
Some(TypeData::Object(shape_id) | TypeData::ObjectWithIndex(shape_id)) => {
PrivateBrandKind::Object(shape_id)
}
Some(TypeData::Callable(shape_id)) => PrivateBrandKind::Callable(shape_id),
_ => PrivateBrandKind::None,
}
}
pub fn get_widened_literal_type(db: &dyn TypeDatabase, type_id: TypeId) -> Option<TypeId> {
match db.lookup(type_id) {
Some(TypeData::Literal(crate::LiteralValue::String(_))) => Some(TypeId::STRING),
Some(TypeData::Literal(crate::LiteralValue::Number(_))) => Some(TypeId::NUMBER),
Some(TypeData::Literal(crate::LiteralValue::BigInt(_))) => Some(TypeId::BIGINT),
Some(TypeData::Literal(crate::LiteralValue::Boolean(_))) => Some(TypeId::BOOLEAN),
_ => None,
}
}
pub fn get_tuple_list_id(
db: &dyn TypeDatabase,
type_id: TypeId,
) -> Option<crate::types::TupleListId> {
match db.lookup(type_id) {
Some(TypeData::Tuple(list_id)) => Some(list_id),
_ => None,
}
}
pub fn get_application_base(db: &dyn TypeDatabase, type_id: TypeId) -> Option<TypeId> {
match db.lookup(type_id) {
Some(TypeData::Application(app_id)) => Some(db.type_application(app_id).base),
_ => None,
}
}
#[derive(Debug, Clone)]
pub enum LiteralKeyKind {
StringLiteral(tsz_common::interner::Atom),
NumberLiteral(f64),
Union(Vec<TypeId>),
Other,
}
pub fn classify_literal_key(db: &dyn TypeDatabase, type_id: TypeId) -> LiteralKeyKind {
match db.lookup(type_id) {
Some(TypeData::Literal(crate::LiteralValue::String(atom))) => {
LiteralKeyKind::StringLiteral(atom)
}
Some(TypeData::Literal(crate::LiteralValue::Number(num))) => {
LiteralKeyKind::NumberLiteral(num.0)
}
Some(TypeData::Union(members_id)) => {
LiteralKeyKind::Union(db.type_list(members_id).to_vec())
}
_ => LiteralKeyKind::Other,
}
}
pub fn get_literal_value(db: &dyn TypeDatabase, type_id: TypeId) -> Option<crate::LiteralValue> {
match db.lookup(type_id) {
Some(TypeData::Literal(value)) => Some(value),
_ => None,
}
}
pub fn widen_literal_to_primitive(db: &dyn TypeDatabase, type_id: TypeId) -> TypeId {
match db.lookup(type_id) {
Some(TypeData::Literal(ref lit)) => match lit {
crate::LiteralValue::String(_) => TypeId::STRING,
crate::LiteralValue::Number(_) => TypeId::NUMBER,
crate::LiteralValue::Boolean(_) => TypeId::BOOLEAN,
crate::LiteralValue::BigInt(_) => TypeId::BIGINT,
},
_ => type_id,
}
}
pub fn get_object_property_type(
db: &dyn TypeDatabase,
type_id: TypeId,
property_name: &str,
) -> Option<TypeId> {
match db.lookup(type_id) {
Some(TypeData::Object(shape_id) | TypeData::ObjectWithIndex(shape_id)) => {
let shape = db.object_shape(shape_id);
for prop in &shape.properties {
let prop_name = db.resolve_atom_ref(prop.name);
if prop_name.as_ref() == property_name {
return Some(prop.type_id);
}
}
None
}
_ => None,
}
}
pub fn get_function_return_type(db: &dyn TypeDatabase, type_id: TypeId) -> Option<TypeId> {
match db.lookup(type_id) {
Some(TypeData::Function(shape_id)) => {
let shape = db.function_shape(shape_id);
Some(shape.return_type)
}
_ => None,
}
}
pub fn is_object_with_index_type(db: &dyn TypeDatabase, type_id: TypeId) -> bool {
matches!(db.lookup(type_id), Some(TypeData::ObjectWithIndex(_)))
}
#[derive(Debug, Clone)]
pub enum ArrayLikeKind {
Array(TypeId),
Tuple,
Readonly(TypeId),
Union(Vec<TypeId>),
Intersection(Vec<TypeId>),
Other,
}
pub fn classify_array_like(db: &dyn TypeDatabase, type_id: TypeId) -> ArrayLikeKind {
match db.lookup(type_id) {
Some(TypeData::Array(elem)) => ArrayLikeKind::Array(elem),
Some(TypeData::Tuple(_)) => ArrayLikeKind::Tuple,
Some(TypeData::ReadonlyType(inner)) => ArrayLikeKind::Readonly(inner),
Some(TypeData::TypeParameter(info) | TypeData::Infer(info)) => {
info.constraint.map_or(ArrayLikeKind::Other, |constraint| {
classify_array_like(db, constraint)
})
}
Some(TypeData::Union(members_id)) => {
ArrayLikeKind::Union(db.type_list(members_id).to_vec())
}
Some(TypeData::Intersection(members_id)) => {
ArrayLikeKind::Intersection(db.type_list(members_id).to_vec())
}
_ => ArrayLikeKind::Other,
}
}
#[derive(Debug, Clone)]
pub enum IndexKeyKind {
String,
Number,
StringLiteral,
NumberLiteral,
Union(Vec<TypeId>),
Other,
}
pub fn classify_index_key(db: &dyn TypeDatabase, type_id: TypeId) -> IndexKeyKind {
match db.lookup(type_id) {
Some(TypeData::Intrinsic(crate::IntrinsicKind::String)) => IndexKeyKind::String,
Some(TypeData::Intrinsic(crate::IntrinsicKind::Number)) => IndexKeyKind::Number,
Some(TypeData::Literal(crate::LiteralValue::String(_))) => IndexKeyKind::StringLiteral,
Some(TypeData::Literal(crate::LiteralValue::Number(_))) => IndexKeyKind::NumberLiteral,
Some(TypeData::Union(members_id)) => IndexKeyKind::Union(db.type_list(members_id).to_vec()),
_ => IndexKeyKind::Other,
}
}
#[derive(Debug, Clone)]
pub enum ElementIndexableKind {
Array,
Tuple,
ObjectWithIndex { has_string: bool, has_number: bool },
Union(Vec<TypeId>),
Intersection(Vec<TypeId>),
StringLike,
Other,
}
pub fn classify_element_indexable(db: &dyn TypeDatabase, type_id: TypeId) -> ElementIndexableKind {
match db.lookup(type_id) {
Some(TypeData::Array(_)) => ElementIndexableKind::Array,
Some(TypeData::Tuple(_)) => ElementIndexableKind::Tuple,
Some(TypeData::ObjectWithIndex(shape_id)) => {
let shape = db.object_shape(shape_id);
ElementIndexableKind::ObjectWithIndex {
has_string: shape.string_index.is_some(),
has_number: shape.number_index.is_some(),
}
}
Some(TypeData::Union(members_id)) => {
ElementIndexableKind::Union(db.type_list(members_id).to_vec())
}
Some(TypeData::Intersection(members_id)) => {
ElementIndexableKind::Intersection(db.type_list(members_id).to_vec())
}
Some(TypeData::Literal(crate::LiteralValue::String(_)))
| Some(TypeData::Intrinsic(crate::IntrinsicKind::String)) => {
ElementIndexableKind::StringLike
}
Some(TypeData::Enum(_, _)) => ElementIndexableKind::ObjectWithIndex {
has_string: true,
has_number: true,
},
_ => ElementIndexableKind::Other,
}
}
#[derive(Debug, Clone)]
pub enum TypeQueryKind {
TypeQuery(crate::types::SymbolRef),
ApplicationWithTypeQuery {
base_sym_ref: crate::types::SymbolRef,
args: Vec<TypeId>,
},
Application {
app_id: crate::types::TypeApplicationId,
},
Other,
}
pub fn classify_type_query(db: &dyn TypeDatabase, type_id: TypeId) -> TypeQueryKind {
match db.lookup(type_id) {
Some(TypeData::TypeQuery(sym_ref)) => TypeQueryKind::TypeQuery(sym_ref),
Some(TypeData::Application(app_id)) => {
let app = db.type_application(app_id);
match db.lookup(app.base) {
Some(TypeData::TypeQuery(base_sym_ref)) => {
TypeQueryKind::ApplicationWithTypeQuery {
base_sym_ref,
args: app.args.clone(),
}
}
_ => TypeQueryKind::Application { app_id },
}
}
_ => TypeQueryKind::Other,
}
}
#[derive(Debug, Clone)]
pub enum SymbolRefKind {
Lazy(crate::def::DefId),
TypeQuery(crate::types::SymbolRef),
Other,
}
pub fn classify_symbol_ref(db: &dyn TypeDatabase, type_id: TypeId) -> SymbolRefKind {
match db.lookup(type_id) {
Some(TypeData::Lazy(def_id)) => SymbolRefKind::Lazy(def_id),
Some(TypeData::TypeQuery(sym_ref)) => SymbolRefKind::TypeQuery(sym_ref),
_ => SymbolRefKind::Other,
}
}
#[derive(Debug, Clone)]
pub enum TypeContainsKind {
Array(TypeId),
Tuple(crate::types::TupleListId),
Members(Vec<TypeId>),
Object(crate::types::ObjectShapeId),
Function(crate::types::FunctionShapeId),
Callable(crate::types::CallableShapeId),
Application(crate::types::TypeApplicationId),
Conditional(crate::types::ConditionalTypeId),
Mapped(crate::types::MappedTypeId),
IndexAccess {
base: TypeId,
index: TypeId,
},
TemplateLiteral(crate::types::TemplateLiteralId),
Inner(TypeId),
TypeParam {
constraint: Option<TypeId>,
default: Option<TypeId>,
},
Terminal,
}
pub fn classify_for_contains_traversal(db: &dyn TypeDatabase, type_id: TypeId) -> TypeContainsKind {
match db.lookup(type_id) {
Some(TypeData::Array(elem)) => TypeContainsKind::Array(elem),
Some(TypeData::Tuple(list_id)) => TypeContainsKind::Tuple(list_id),
Some(TypeData::Union(list_id) | TypeData::Intersection(list_id)) => {
TypeContainsKind::Members(db.type_list(list_id).to_vec())
}
Some(TypeData::Object(shape_id) | TypeData::ObjectWithIndex(shape_id)) => {
TypeContainsKind::Object(shape_id)
}
Some(TypeData::Function(shape_id)) => TypeContainsKind::Function(shape_id),
Some(TypeData::Callable(shape_id)) => TypeContainsKind::Callable(shape_id),
Some(TypeData::Application(app_id)) => TypeContainsKind::Application(app_id),
Some(TypeData::Conditional(cond_id)) => TypeContainsKind::Conditional(cond_id),
Some(TypeData::Mapped(mapped_id)) => TypeContainsKind::Mapped(mapped_id),
Some(TypeData::IndexAccess(base, index)) => TypeContainsKind::IndexAccess { base, index },
Some(TypeData::TemplateLiteral(template_id)) => {
TypeContainsKind::TemplateLiteral(template_id)
}
Some(TypeData::KeyOf(inner) | TypeData::ReadonlyType(inner)) => {
TypeContainsKind::Inner(inner)
}
Some(TypeData::TypeParameter(info) | TypeData::Infer(info)) => {
TypeContainsKind::TypeParam {
constraint: info.constraint,
default: info.default,
}
}
_ => TypeContainsKind::Terminal,
}
}
#[derive(Debug, Clone)]
pub enum NamespaceMemberKind {
Lazy(DefId),
ModuleNamespace(crate::types::SymbolRef),
Callable(crate::types::CallableShapeId),
Enum(DefId),
Other,
}
pub fn classify_namespace_member(db: &dyn TypeDatabase, type_id: TypeId) -> NamespaceMemberKind {
match db.lookup(type_id) {
Some(TypeData::Callable(shape_id)) => NamespaceMemberKind::Callable(shape_id),
Some(TypeData::Lazy(def_id)) => NamespaceMemberKind::Lazy(def_id),
Some(TypeData::ModuleNamespace(sym_ref)) => NamespaceMemberKind::ModuleNamespace(sym_ref),
Some(TypeData::Enum(def_id, _)) => NamespaceMemberKind::Enum(def_id),
_ => NamespaceMemberKind::Other,
}
}
pub fn unwrap_readonly_for_lookup(db: &dyn TypeDatabase, type_id: TypeId) -> TypeId {
match db.lookup(type_id) {
Some(TypeData::ReadonlyType(inner)) => inner,
_ => type_id,
}
}
pub fn create_string_literal_type(db: &dyn TypeDatabase, value: &str) -> TypeId {
let atom = db.intern_string(value);
db.literal_string_atom(atom)
}
pub fn create_number_literal_type(db: &dyn TypeDatabase, value: f64) -> TypeId {
db.literal_number(value)
}
pub fn create_boolean_literal_type(db: &dyn TypeDatabase, value: bool) -> TypeId {
db.literal_boolean(value)
}
#[derive(Debug, Clone)]
pub enum InstanceTypeKind {
Callable(crate::types::CallableShapeId),
Function(crate::types::FunctionShapeId),
Intersection(Vec<TypeId>),
Union(Vec<TypeId>),
Readonly(TypeId),
TypeParameter { constraint: Option<TypeId> },
SymbolRef(crate::types::SymbolRef),
NeedsEvaluation,
NotConstructor,
}
pub fn classify_for_instance_type(db: &dyn TypeDatabase, type_id: TypeId) -> InstanceTypeKind {
let Some(key) = db.lookup(type_id) else {
return InstanceTypeKind::NotConstructor;
};
match key {
TypeData::Callable(shape_id) => InstanceTypeKind::Callable(shape_id),
TypeData::Function(shape_id) => InstanceTypeKind::Function(shape_id),
TypeData::Intersection(list_id) => {
let members = db.type_list(list_id);
InstanceTypeKind::Intersection(members.to_vec())
}
TypeData::Union(list_id) => {
let members = db.type_list(list_id);
InstanceTypeKind::Union(members.to_vec())
}
TypeData::ReadonlyType(inner) => InstanceTypeKind::Readonly(inner),
TypeData::TypeParameter(info) | TypeData::Infer(info) => InstanceTypeKind::TypeParameter {
constraint: info.constraint,
},
TypeData::TypeQuery(sym_ref) => InstanceTypeKind::SymbolRef(sym_ref),
TypeData::Conditional(_)
| TypeData::Mapped(_)
| TypeData::IndexAccess(_, _)
| TypeData::KeyOf(_)
| TypeData::Application(_) => InstanceTypeKind::NeedsEvaluation,
_ => InstanceTypeKind::NotConstructor,
}
}
#[derive(Debug, Clone)]
pub enum ConstructorReturnMergeKind {
Callable(crate::types::CallableShapeId),
Function(crate::types::FunctionShapeId),
Intersection(Vec<TypeId>),
Other,
}
pub fn classify_for_constructor_return_merge(
db: &dyn TypeDatabase,
type_id: TypeId,
) -> ConstructorReturnMergeKind {
let Some(key) = db.lookup(type_id) else {
return ConstructorReturnMergeKind::Other;
};
match key {
TypeData::Callable(shape_id) => ConstructorReturnMergeKind::Callable(shape_id),
TypeData::Function(shape_id) => ConstructorReturnMergeKind::Function(shape_id),
TypeData::Intersection(list_id) => {
let members = db.type_list(list_id);
ConstructorReturnMergeKind::Intersection(members.to_vec())
}
_ => ConstructorReturnMergeKind::Other,
}
}
#[derive(Debug, Clone)]
pub enum AbstractConstructorKind {
TypeQuery(crate::types::SymbolRef),
Callable(crate::types::CallableShapeId),
Application(crate::types::TypeApplicationId),
NotAbstract,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum AbstractConstructorAnchor {
TypeQuery(crate::types::SymbolRef),
CallableType(TypeId),
NotAbstract,
}
pub fn classify_for_abstract_constructor(
db: &dyn TypeDatabase,
type_id: TypeId,
) -> AbstractConstructorKind {
let Some(key) = db.lookup(type_id) else {
return AbstractConstructorKind::NotAbstract;
};
match key {
TypeData::TypeQuery(sym_ref) => AbstractConstructorKind::TypeQuery(sym_ref),
TypeData::Callable(shape_id) => AbstractConstructorKind::Callable(shape_id),
TypeData::Application(app_id) => AbstractConstructorKind::Application(app_id),
_ => AbstractConstructorKind::NotAbstract,
}
}
pub fn resolve_abstract_constructor_anchor(
db: &dyn TypeDatabase,
type_id: TypeId,
) -> AbstractConstructorAnchor {
let mut current = type_id;
let mut visited = FxHashSet::default();
while visited.insert(current) {
match classify_for_abstract_constructor(db, current) {
AbstractConstructorKind::TypeQuery(sym_ref) => {
return AbstractConstructorAnchor::TypeQuery(sym_ref);
}
AbstractConstructorKind::Callable(_) => {
return AbstractConstructorAnchor::CallableType(current);
}
AbstractConstructorKind::Application(app_id) => {
let app = db.type_application(app_id);
if app.base == current {
break;
}
current = app.base;
}
AbstractConstructorKind::NotAbstract => break,
}
}
AbstractConstructorAnchor::NotAbstract
}
#[derive(Debug, Clone)]
pub enum PropertyAccessResolutionKind {
Lazy(DefId),
TypeQuery(crate::types::SymbolRef),
Application(crate::types::TypeApplicationId),
TypeParameter { constraint: Option<TypeId> },
NeedsEvaluation,
Union(Vec<TypeId>),
Intersection(Vec<TypeId>),
Readonly(TypeId),
FunctionLike,
Resolved,
}
pub fn classify_for_property_access_resolution(
db: &dyn TypeDatabase,
type_id: TypeId,
) -> PropertyAccessResolutionKind {
let Some(key) = db.lookup(type_id) else {
return PropertyAccessResolutionKind::Resolved;
};
match key {
TypeData::TypeQuery(sym_ref) => PropertyAccessResolutionKind::TypeQuery(sym_ref),
TypeData::Lazy(def_id) => PropertyAccessResolutionKind::Lazy(def_id),
TypeData::Application(app_id) => PropertyAccessResolutionKind::Application(app_id),
TypeData::TypeParameter(info) | TypeData::Infer(info) => {
PropertyAccessResolutionKind::TypeParameter {
constraint: info.constraint,
}
}
TypeData::Conditional(_)
| TypeData::Mapped(_)
| TypeData::IndexAccess(_, _)
| TypeData::KeyOf(_) => PropertyAccessResolutionKind::NeedsEvaluation,
TypeData::Union(list_id) => {
let members = db.type_list(list_id);
PropertyAccessResolutionKind::Union(members.to_vec())
}
TypeData::Intersection(list_id) => {
let members = db.type_list(list_id);
PropertyAccessResolutionKind::Intersection(members.to_vec())
}
TypeData::ReadonlyType(inner) => PropertyAccessResolutionKind::Readonly(inner),
TypeData::Function(_) | TypeData::Callable(_) => PropertyAccessResolutionKind::FunctionLike,
_ => PropertyAccessResolutionKind::Resolved,
}
}
#[derive(Debug, Clone)]
pub enum ContextualLiteralAllowKind {
Members(Vec<TypeId>),
TypeParameter { constraint: Option<TypeId> },
Application,
Mapped,
TemplateLiteral,
NotAllowed,
}
pub fn classify_for_contextual_literal(
db: &dyn TypeDatabase,
type_id: TypeId,
) -> ContextualLiteralAllowKind {
let Some(key) = db.lookup(type_id) else {
return ContextualLiteralAllowKind::NotAllowed;
};
match key {
TypeData::Union(list_id) | TypeData::Intersection(list_id) => {
let members = db.type_list(list_id);
ContextualLiteralAllowKind::Members(members.to_vec())
}
TypeData::TypeParameter(info) | TypeData::Infer(info) => {
ContextualLiteralAllowKind::TypeParameter {
constraint: info.constraint,
}
}
TypeData::Application(_) => ContextualLiteralAllowKind::Application,
TypeData::Mapped(_) => ContextualLiteralAllowKind::Mapped,
TypeData::TemplateLiteral(_) => ContextualLiteralAllowKind::TemplateLiteral,
_ => ContextualLiteralAllowKind::NotAllowed,
}
}
#[derive(Debug, Clone)]
pub enum MappedConstraintKind {
KeyOf(TypeId),
Resolved,
Other,
}
pub fn classify_mapped_constraint(db: &dyn TypeDatabase, type_id: TypeId) -> MappedConstraintKind {
let Some(key) = db.lookup(type_id) else {
return MappedConstraintKind::Other;
};
match key {
TypeData::KeyOf(operand) => MappedConstraintKind::KeyOf(operand),
TypeData::Union(_) | TypeData::Literal(_) => MappedConstraintKind::Resolved,
_ => MappedConstraintKind::Other,
}
}
#[derive(Debug, Clone)]
pub enum TypeResolutionKind {
Lazy(DefId),
Application,
Resolved,
}
pub fn classify_for_type_resolution(db: &dyn TypeDatabase, type_id: TypeId) -> TypeResolutionKind {
let Some(key) = db.lookup(type_id) else {
return TypeResolutionKind::Resolved;
};
match key {
TypeData::Lazy(def_id) => TypeResolutionKind::Lazy(def_id),
TypeData::Application(_) => TypeResolutionKind::Application,
_ => TypeResolutionKind::Resolved,
}
}
#[derive(Debug, Clone)]
pub enum TypeArgumentExtractionKind {
Function(crate::types::FunctionShapeId),
Callable(crate::types::CallableShapeId),
Other,
}
pub fn classify_for_type_argument_extraction(
db: &dyn TypeDatabase,
type_id: TypeId,
) -> TypeArgumentExtractionKind {
let Some(key) = db.lookup(type_id) else {
return TypeArgumentExtractionKind::Other;
};
match key {
TypeData::Function(shape_id) => TypeArgumentExtractionKind::Function(shape_id),
TypeData::Callable(shape_id) => TypeArgumentExtractionKind::Callable(shape_id),
_ => TypeArgumentExtractionKind::Other,
}
}
#[derive(Debug, Clone)]
pub enum BaseInstanceMergeKind {
Object(crate::types::ObjectShapeId),
Intersection(Vec<TypeId>),
Union(Vec<TypeId>),
Other,
}
pub fn classify_for_base_instance_merge(
db: &dyn TypeDatabase,
type_id: TypeId,
) -> BaseInstanceMergeKind {
let Some(key) = db.lookup(type_id) else {
return BaseInstanceMergeKind::Other;
};
match key {
TypeData::Object(shape_id) | TypeData::ObjectWithIndex(shape_id) => {
BaseInstanceMergeKind::Object(shape_id)
}
TypeData::Intersection(list_id) => {
let members = db.type_list(list_id);
BaseInstanceMergeKind::Intersection(members.to_vec())
}
TypeData::Union(list_id) => {
let members = db.type_list(list_id);
BaseInstanceMergeKind::Union(members.to_vec())
}
_ => BaseInstanceMergeKind::Other,
}
}