use crate::TypeDatabase;
use crate::type_queries::{get_union_members, is_invokable_type};
use crate::type_queries_extended::{
StringLiteralKeyKind, classify_for_string_literal_keys, get_string_literal_value,
};
use crate::types::{TypeData, TypeId};
use tsz_common::Atom;
#[derive(Debug, Clone)]
pub enum PredicateSignatureKind {
Function(crate::types::FunctionShapeId),
Callable(crate::types::CallableShapeId),
Union(Vec<TypeId>),
Intersection(Vec<TypeId>),
None,
}
pub fn classify_for_predicate_signature(
db: &dyn TypeDatabase,
type_id: TypeId,
) -> PredicateSignatureKind {
let Some(key) = db.lookup(type_id) else {
return PredicateSignatureKind::None;
};
match key {
TypeData::Function(shape_id) => PredicateSignatureKind::Function(shape_id),
TypeData::Callable(shape_id) => PredicateSignatureKind::Callable(shape_id),
TypeData::Union(members_id) => {
let members = db.type_list(members_id);
PredicateSignatureKind::Union(members.to_vec())
}
TypeData::Intersection(members_id) => {
let members = db.type_list(members_id);
PredicateSignatureKind::Intersection(members.to_vec())
}
_ => PredicateSignatureKind::None,
}
}
#[derive(Debug, Clone)]
pub enum ConstructorInstanceKind {
Callable(crate::types::CallableShapeId),
Union(Vec<TypeId>),
Intersection(Vec<TypeId>),
None,
}
pub fn classify_for_constructor_instance(
db: &dyn TypeDatabase,
type_id: TypeId,
) -> ConstructorInstanceKind {
let Some(key) = db.lookup(type_id) else {
return ConstructorInstanceKind::None;
};
match key {
TypeData::Callable(shape_id) => ConstructorInstanceKind::Callable(shape_id),
TypeData::Union(members_id) => {
let members = db.type_list(members_id);
ConstructorInstanceKind::Union(members.to_vec())
}
TypeData::Intersection(members_id) => {
let members = db.type_list(members_id);
ConstructorInstanceKind::Intersection(members.to_vec())
}
_ => ConstructorInstanceKind::None,
}
}
pub fn instance_type_from_constructor(db: &dyn TypeDatabase, type_id: TypeId) -> Option<TypeId> {
if type_id == TypeId::ANY || type_id == TypeId::UNKNOWN {
return Some(type_id);
}
match classify_for_constructor_instance(db, type_id) {
ConstructorInstanceKind::Callable(shape_id) => {
let shape = db.callable_shape(shape_id);
if shape.construct_signatures.is_empty() {
return None;
}
let returns: Vec<TypeId> = shape
.construct_signatures
.iter()
.map(|s| s.return_type)
.collect();
Some(if returns.len() == 1 {
returns[0]
} else {
db.union(returns)
})
}
ConstructorInstanceKind::Union(members) => {
let instance_types: Vec<TypeId> = members
.into_iter()
.filter_map(|m| instance_type_from_constructor(db, m))
.collect();
if instance_types.is_empty() {
None
} else if instance_types.len() == 1 {
Some(instance_types[0])
} else {
Some(db.union(instance_types))
}
}
ConstructorInstanceKind::Intersection(members) => {
members
.into_iter()
.find_map(|m| instance_type_from_constructor(db, m))
}
ConstructorInstanceKind::None => None,
}
}
#[derive(Debug, Clone)]
pub enum TypeParameterConstraintKind {
TypeParameter { constraint: Option<TypeId> },
None,
}
pub fn classify_for_type_parameter_constraint(
db: &dyn TypeDatabase,
type_id: TypeId,
) -> TypeParameterConstraintKind {
let Some(key) = db.lookup(type_id) else {
return TypeParameterConstraintKind::None;
};
match key {
TypeData::TypeParameter(info) | TypeData::Infer(info) => {
TypeParameterConstraintKind::TypeParameter {
constraint: info.constraint,
}
}
_ => TypeParameterConstraintKind::None,
}
}
#[derive(Debug, Clone)]
pub enum UnionMembersKind {
Union(Vec<TypeId>),
NotUnion,
}
pub fn classify_for_union_members(db: &dyn TypeDatabase, type_id: TypeId) -> UnionMembersKind {
let Some(key) = db.lookup(type_id) else {
return UnionMembersKind::NotUnion;
};
match key {
TypeData::Union(members_id) => {
let members = db.type_list(members_id);
UnionMembersKind::Union(members.to_vec())
}
_ => UnionMembersKind::NotUnion,
}
}
#[derive(Debug, Clone)]
pub enum NonObjectKind {
Literal,
IntrinsicPrimitive,
MaybeObject,
}
pub fn classify_for_non_object(db: &dyn TypeDatabase, type_id: TypeId) -> NonObjectKind {
let Some(key) = db.lookup(type_id) else {
return NonObjectKind::MaybeObject;
};
match key {
TypeData::Literal(_) => NonObjectKind::Literal,
TypeData::Intrinsic(kind) => {
use crate::IntrinsicKind;
match kind {
IntrinsicKind::Void
| IntrinsicKind::Undefined
| IntrinsicKind::Null
| IntrinsicKind::Boolean
| IntrinsicKind::Number
| IntrinsicKind::String
| IntrinsicKind::Bigint
| IntrinsicKind::Symbol
| IntrinsicKind::Never => NonObjectKind::IntrinsicPrimitive,
_ => NonObjectKind::MaybeObject,
}
}
_ => NonObjectKind::MaybeObject,
}
}
#[derive(Debug, Clone)]
pub enum PropertyPresenceKind {
IntrinsicObject,
Object(crate::types::ObjectShapeId),
Callable(crate::types::CallableShapeId),
ArrayLike,
Unknown,
}
pub fn classify_for_property_presence(
db: &dyn TypeDatabase,
type_id: TypeId,
) -> PropertyPresenceKind {
let Some(key) = db.lookup(type_id) else {
return PropertyPresenceKind::Unknown;
};
match key {
TypeData::Intrinsic(crate::IntrinsicKind::Object) => PropertyPresenceKind::IntrinsicObject,
TypeData::Object(shape_id) | TypeData::ObjectWithIndex(shape_id) => {
PropertyPresenceKind::Object(shape_id)
}
TypeData::Callable(shape_id) => PropertyPresenceKind::Callable(shape_id),
TypeData::Array(_) | TypeData::Tuple(_) => PropertyPresenceKind::ArrayLike,
_ => PropertyPresenceKind::Unknown,
}
}
#[derive(Debug, Clone)]
pub enum FalsyComponentKind {
Literal(crate::LiteralValue),
Union(Vec<TypeId>),
TypeParameter,
None,
}
pub fn classify_for_falsy_component(db: &dyn TypeDatabase, type_id: TypeId) -> FalsyComponentKind {
let Some(key) = db.lookup(type_id) else {
return FalsyComponentKind::None;
};
match key {
TypeData::Literal(literal) => FalsyComponentKind::Literal(literal),
TypeData::Union(members_id) => {
let members = db.type_list(members_id);
FalsyComponentKind::Union(members.to_vec())
}
TypeData::TypeParameter(_) | TypeData::Infer(_) => FalsyComponentKind::TypeParameter,
_ => FalsyComponentKind::None,
}
}
#[derive(Debug, Clone)]
pub enum LiteralValueKind {
String(tsz_common::interner::Atom),
Number(f64),
None,
}
pub fn classify_for_literal_value(db: &dyn TypeDatabase, type_id: TypeId) -> LiteralValueKind {
let Some(key) = db.lookup(type_id) else {
return LiteralValueKind::None;
};
match key {
TypeData::Literal(crate::LiteralValue::String(atom)) => LiteralValueKind::String(atom),
TypeData::Literal(crate::LiteralValue::Number(num)) => LiteralValueKind::Number(num.0),
_ => LiteralValueKind::None,
}
}
pub fn is_narrowing_literal(db: &dyn TypeDatabase, type_id: TypeId) -> Option<TypeId> {
if type_id == TypeId::NULL || type_id == TypeId::UNDEFINED {
return Some(type_id);
}
let key = db.lookup(type_id)?;
match key {
TypeData::Literal(_) | TypeData::Enum(_, _) => Some(type_id),
_ => None,
}
}
pub fn is_unit_type(db: &dyn TypeDatabase, type_id: TypeId) -> bool {
if type_id == TypeId::NULL
|| type_id == TypeId::UNDEFINED
|| type_id == TypeId::VOID
|| type_id == TypeId::BOOLEAN_TRUE
|| type_id == TypeId::BOOLEAN_FALSE
{
return true;
}
if crate::visitor::is_literal_type_db(db, type_id) {
return true;
}
if let Some(list_id) = crate::visitor::union_list_id(db, type_id) {
let members = db.type_list(list_id);
return members.iter().all(|&m| is_unit_type(db, m));
}
false
}
pub fn union_contains(db: &dyn TypeDatabase, type_id: TypeId, target: TypeId) -> bool {
if let Some(members) = get_union_members(db, type_id) {
members.contains(&target)
} else {
false
}
}
pub fn type_includes_undefined(db: &dyn TypeDatabase, type_id: TypeId) -> bool {
type_id == TypeId::UNDEFINED || union_contains(db, type_id, TypeId::UNDEFINED)
}
pub fn extract_string_literal_keys(
db: &dyn TypeDatabase,
type_id: TypeId,
) -> Vec<tsz_common::interner::Atom> {
match classify_for_string_literal_keys(db, type_id) {
StringLiteralKeyKind::SingleString(name) => vec![name],
StringLiteralKeyKind::Union(members) => members
.iter()
.filter_map(|&member| get_string_literal_value(db, member))
.collect(),
StringLiteralKeyKind::NotStringLiteral => Vec::new(),
}
}
pub fn get_return_type(db: &dyn TypeDatabase, type_id: TypeId) -> Option<TypeId> {
match db.lookup(type_id) {
Some(TypeData::Function(shape_id)) => Some(db.function_shape(shape_id).return_type),
Some(TypeData::Callable(shape_id)) => {
let shape = db.callable_shape(shape_id);
shape.call_signatures.first().map(|sig| sig.return_type)
}
Some(TypeData::Intersection(list_id)) => {
let members = db.type_list(list_id);
members.iter().find_map(|&m| get_return_type(db, m))
}
_ => {
if type_id == TypeId::ANY {
Some(TypeId::ANY)
} else if type_id == TypeId::NEVER {
Some(TypeId::NEVER)
} else {
None
}
}
}
}
use crate::operations_property::PropertyAccessEvaluator;
pub fn is_promise_like(db: &dyn crate::db::QueryDatabase, type_id: TypeId) -> bool {
if type_id == TypeId::ANY {
return true;
}
let evaluator = PropertyAccessEvaluator::new(db);
evaluator
.resolve_property_access(type_id, "then")
.success_type()
.is_some_and(|then_type| {
is_invokable_type(db, then_type)
})
}
pub fn is_valid_for_in_target(db: &dyn TypeDatabase, type_id: TypeId) -> bool {
if type_id == TypeId::ANY {
return true;
}
if type_id == TypeId::STRING || type_id == TypeId::NUMBER || type_id == TypeId::BOOLEAN {
return true;
}
use crate::types::IntrinsicKind;
match db.lookup(type_id) {
Some(TypeData::Object(_) | TypeData::ObjectWithIndex(_))
| Some(TypeData::Array(_))
| Some(TypeData::TypeParameter(_))
| Some(TypeData::Tuple(_))
| Some(TypeData::Literal(_)) => true,
Some(TypeData::Union(list_id)) => {
let members = db.type_list(list_id);
members.iter().all(|&m| is_valid_for_in_target(db, m))
}
Some(TypeData::Intersection(list_id)) => {
let members = db.type_list(list_id);
members.iter().any(|&m| is_valid_for_in_target(db, m))
}
Some(TypeData::Intrinsic(kind)) => matches!(
kind,
IntrinsicKind::String
| IntrinsicKind::Number
| IntrinsicKind::Boolean
| IntrinsicKind::Symbol
),
_ => false,
}
}
pub fn types_are_comparable(db: &dyn TypeDatabase, source: TypeId, target: TypeId) -> bool {
types_are_comparable_inner(db, source, target, 0)
}
fn types_are_comparable_inner(
db: &dyn TypeDatabase,
source: TypeId,
target: TypeId,
depth: u32,
) -> bool {
if depth > 5 {
return false;
}
if source == target {
return true;
}
if let Some(TypeData::Union(list_id)) = db.lookup(source) {
let members = db.type_list(list_id);
return members
.iter()
.any(|&m| types_are_comparable_inner(db, m, target, depth + 1));
}
if let Some(TypeData::Union(list_id)) = db.lookup(target) {
let members = db.type_list(list_id);
return members
.iter()
.any(|&m| types_are_comparable_inner(db, source, m, depth + 1));
}
if is_primitive_comparable(db, source, target) || is_primitive_comparable(db, target, source) {
return true;
}
types_have_common_properties(db, source, target, depth)
}
fn is_primitive_comparable(db: &dyn TypeDatabase, base: TypeId, other: TypeId) -> bool {
if base == TypeId::STRING {
if let Some(TypeData::Literal(lit)) = db.lookup(other) {
return matches!(lit, crate::types::LiteralValue::String(_));
}
return other == TypeId::STRING;
}
if base == TypeId::NUMBER {
if let Some(TypeData::Literal(lit)) = db.lookup(other) {
return matches!(lit, crate::types::LiteralValue::Number(_));
}
return other == TypeId::NUMBER;
}
if base == TypeId::BOOLEAN {
return other == TypeId::BOOLEAN_TRUE
|| other == TypeId::BOOLEAN_FALSE
|| other == TypeId::BOOLEAN;
}
if base == TypeId::BIGINT {
if let Some(TypeData::Literal(lit)) = db.lookup(other) {
return matches!(lit, crate::types::LiteralValue::BigInt(_));
}
return other == TypeId::BIGINT;
}
if let Some(TypeData::Literal(lit_a)) = db.lookup(base) {
if let Some(TypeData::Literal(lit_b)) = db.lookup(other) {
return std::mem::discriminant(&lit_a) == std::mem::discriminant(&lit_b);
}
return match lit_a {
crate::types::LiteralValue::String(_) => other == TypeId::STRING,
crate::types::LiteralValue::Number(_) => other == TypeId::NUMBER,
crate::types::LiteralValue::BigInt(_) => other == TypeId::BIGINT,
crate::types::LiteralValue::Boolean(_) => {
other == TypeId::BOOLEAN
|| other == TypeId::BOOLEAN_TRUE
|| other == TypeId::BOOLEAN_FALSE
}
};
}
if (base == TypeId::BOOLEAN_TRUE || base == TypeId::BOOLEAN_FALSE)
&& (other == TypeId::BOOLEAN_TRUE || other == TypeId::BOOLEAN_FALSE)
{
return true;
}
false
}
fn types_have_common_properties(
db: &dyn TypeDatabase,
source: TypeId,
target: TypeId,
depth: u32,
) -> bool {
fn get_properties(db: &dyn TypeDatabase, type_id: TypeId) -> Vec<(Atom, TypeId)> {
match db.lookup(type_id) {
Some(TypeData::Object(shape_id) | TypeData::ObjectWithIndex(shape_id)) => {
let shape = db.object_shape(shape_id);
shape
.properties
.iter()
.map(|p| (p.name, p.type_id))
.collect()
}
Some(TypeData::Callable(callable_id)) => {
let shape = db.callable_shape(callable_id);
shape
.properties
.iter()
.map(|p| (p.name, p.type_id))
.collect()
}
Some(TypeData::Intersection(list_id)) => {
let members = db.type_list(list_id);
let mut props = Vec::new();
for &member in members.iter() {
props.extend(get_properties(db, member));
}
props
}
_ => Vec::new(),
}
}
let source_props = get_properties(db, source);
let target_props = get_properties(db, target);
if source_props.is_empty() || target_props.is_empty() {
return false;
}
use rustc_hash::FxHashMap;
let mut target_by_name: FxHashMap<Atom, Vec<TypeId>> = FxHashMap::default();
for (name, ty) in target_props {
target_by_name.entry(name).or_default().push(ty);
}
source_props.iter().any(|(source_name, source_ty)| {
target_by_name.get(source_name).is_some_and(|target_tys| {
target_tys
.iter()
.any(|target_ty| types_are_comparable_inner(db, *source_ty, *target_ty, depth + 1))
})
})
}
#[allow(clippy::match_same_arms)]
pub fn has_type_query_for_symbol(
db: &dyn TypeDatabase,
type_id: TypeId,
target_sym_id: u32,
mut resolve_lazy: impl FnMut(TypeId) -> TypeId,
) -> bool {
use crate::TypeData;
use rustc_hash::FxHashSet;
let mut worklist = vec![type_id];
let mut visited = FxHashSet::default();
while let Some(ty) = worklist.pop() {
if !visited.insert(ty) {
continue;
}
let resolved = resolve_lazy(ty);
if resolved != ty {
worklist.push(resolved);
continue;
}
let Some(key) = db.lookup(ty) else { continue };
match key {
TypeData::TypeQuery(sym_ref) => {
if sym_ref.0 == target_sym_id {
return true;
}
}
TypeData::Array(elem) => worklist.push(elem),
TypeData::Union(list) | TypeData::Intersection(list) => {
let members = db.type_list(list);
worklist.extend(members.iter().copied());
}
TypeData::Tuple(list) => {
let elements = db.tuple_list(list);
for elem in elements.iter() {
worklist.push(elem.type_id);
}
}
TypeData::Conditional(id) => {
let cond = db.conditional_type(id);
worklist.push(cond.check_type);
worklist.push(cond.extends_type);
worklist.push(cond.true_type);
worklist.push(cond.false_type);
}
TypeData::Application(id) => {
let app = db.type_application(id);
worklist.push(app.base);
worklist.extend(&app.args);
}
TypeData::IndexAccess(obj, idx) => {
worklist.push(obj);
worklist.push(idx);
}
TypeData::KeyOf(inner) | TypeData::ReadonlyType(inner) => {
worklist.push(inner);
}
TypeData::Function(_)
| TypeData::Object(_)
| TypeData::ObjectWithIndex(_)
| TypeData::Mapped(_) => {
}
_ => {}
}
}
false
}
pub fn extract_contextual_type_params(
db: &dyn TypeDatabase,
type_id: TypeId,
) -> Option<Vec<crate::types::TypeParamInfo>> {
extract_contextual_type_params_inner(db, type_id, 0)
}
fn extract_contextual_type_params_inner(
db: &dyn TypeDatabase,
type_id: TypeId,
depth: u32,
) -> Option<Vec<crate::types::TypeParamInfo>> {
if depth > 20 {
return None;
}
match db.lookup(type_id) {
Some(TypeData::Function(shape_id)) => {
let shape = db.function_shape(shape_id);
if shape.type_params.is_empty() {
None
} else {
Some(shape.type_params.clone())
}
}
Some(TypeData::Callable(shape_id)) => {
let shape = db.callable_shape(shape_id);
if shape.call_signatures.len() != 1 {
return None;
}
let sig = &shape.call_signatures[0];
if sig.type_params.is_empty() {
None
} else {
Some(sig.type_params.clone())
}
}
Some(TypeData::Application(app_id)) => {
let app = db.type_application(app_id);
extract_contextual_type_params_inner(db, app.base, depth + 1)
}
Some(TypeData::Union(list_id)) => {
let members = db.type_list(list_id);
if members.is_empty() {
return None;
}
let mut candidate: Option<Vec<crate::types::TypeParamInfo>> = None;
for &member in members.iter() {
let params = extract_contextual_type_params_inner(db, member, depth + 1)?;
if let Some(existing) = &candidate {
if existing.len() != params.len()
|| existing
.iter()
.zip(params.iter())
.any(|(left, right)| left != right)
{
return None;
}
} else {
candidate = Some(params);
}
}
candidate
}
_ => None,
}
}