use crate::types::TypeListId;
use crate::visitor::TypeVisitor;
use crate::{IntrinsicKind, LiteralValue, QueryDatabase, TypeData, TypeDatabase, TypeId};
#[derive(Clone, Debug, PartialEq)]
pub enum BinaryOpResult {
Success(TypeId),
TypeError {
left: TypeId,
right: TypeId,
op: &'static str,
},
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum PrimitiveClass {
String,
Number,
Boolean,
Bigint,
Symbol,
Null,
Undefined,
}
macro_rules! primitive_visitor {
($name:ident, $ik:expr, $lit_pat:pat => $lit_result:expr $(, $feat:ident)*) => {
struct $name<'a> { _db: &'a dyn TypeDatabase }
impl<'a> TypeVisitor for $name<'a> {
type Output = bool;
fn visit_intrinsic(&mut self, kind: IntrinsicKind) -> bool { kind == $ik }
fn visit_literal(&mut self, value: &LiteralValue) -> bool {
match value { $lit_pat => $lit_result, _ => false }
}
$(primitive_visitor!(@method $feat);)*
fn default_output() -> bool { false }
}
};
(@method check_union_all) => {
fn visit_union(&mut self, list_id: u32) -> bool {
let members = self._db.type_list(TypeListId(list_id));
!members.is_empty() && members.iter().all(|&m| self.visit_type(self._db, m))
}
};
(@method check_constraint) => {
fn visit_type_parameter(&mut self, info: &crate::types::TypeParamInfo) -> bool {
info.constraint.map(|c| self.visit_type(self._db, c)).unwrap_or(false)
}
fn visit_infer(&mut self, info: &crate::types::TypeParamInfo) -> bool {
info.constraint.map(|c| self.visit_type(self._db, c)).unwrap_or(false)
}
};
(@method recurse_enum) => {
fn visit_enum(&mut self, _def_id: u32, member_type: TypeId) -> bool {
self.visit_type(self._db, member_type)
}
};
(@method ref_conservative) => {
fn visit_ref(&mut self, _symbol_ref: u32) -> bool { true }
};
(@method match_template_literal) => {
fn visit_template_literal(&mut self, _template_id: u32) -> bool { true }
};
(@method match_unique_symbol) => {
fn visit_unique_symbol(&mut self, _symbol_ref: u32) -> bool { true }
};
(@method check_intersection_any) => {
fn visit_intersection(&mut self, list_id: u32) -> bool {
let members = self._db.type_list(TypeListId(list_id));
members.iter().any(|&m| self.visit_type(self._db, m))
}
};
}
primitive_visitor!(NumberLikeVisitor, IntrinsicKind::Number,
LiteralValue::Number(_) => true,
check_union_all, check_constraint, recurse_enum, check_intersection_any);
primitive_visitor!(StringLikeVisitor, IntrinsicKind::String,
LiteralValue::String(_) => true,
check_union_all, check_constraint, recurse_enum, match_template_literal, check_intersection_any);
primitive_visitor!(BigIntLikeVisitor, IntrinsicKind::Bigint,
LiteralValue::BigInt(_) => true,
check_union_all, check_constraint, recurse_enum, check_intersection_any);
primitive_visitor!(BooleanLikeVisitor, IntrinsicKind::Boolean,
LiteralValue::Boolean(_) => true);
struct InstanceofLeftOperandVisitor<'a> {
_db: &'a dyn TypeDatabase,
}
impl<'a> TypeVisitor for InstanceofLeftOperandVisitor<'a> {
type Output = bool;
fn visit_intrinsic(&mut self, kind: IntrinsicKind) -> bool {
matches!(
kind,
IntrinsicKind::Any | IntrinsicKind::Unknown | IntrinsicKind::Object
)
}
fn visit_literal(&mut self, _value: &LiteralValue) -> bool {
false
}
fn visit_type_parameter(&mut self, _info: &crate::types::TypeParamInfo) -> bool {
true
}
fn visit_object(&mut self, _shape_id: u32) -> bool {
true
}
fn visit_object_with_index(&mut self, _shape_id: u32) -> bool {
true
}
fn visit_array(&mut self, _element_type: TypeId) -> bool {
true
}
fn visit_tuple(&mut self, _list_id: u32) -> bool {
true
}
fn visit_function(&mut self, _shape_id: u32) -> bool {
true
}
fn visit_callable(&mut self, _shape_id: u32) -> bool {
true
}
fn visit_application(&mut self, _app_id: u32) -> bool {
true
}
fn visit_readonly_type(&mut self, _type_id: TypeId) -> bool {
true
}
fn visit_union(&mut self, list_id: u32) -> bool {
let members = self._db.type_list(crate::types::TypeListId(list_id));
let mut any_valid = false;
for &m in members.iter() {
if self.visit_type(self._db, m) {
any_valid = true;
break;
}
}
any_valid
}
fn visit_intersection(&mut self, list_id: u32) -> bool {
let members = self._db.type_list(crate::types::TypeListId(list_id));
let mut any_valid = false;
for &m in members.iter() {
if self.visit_type(self._db, m) {
any_valid = true;
break;
}
}
any_valid
}
fn default_output() -> bool {
false
}
}
struct SymbolLikeVisitor<'a> {
_db: &'a dyn TypeDatabase,
}
impl<'a> TypeVisitor for SymbolLikeVisitor<'a> {
type Output = bool;
fn visit_intrinsic(&mut self, kind: IntrinsicKind) -> bool {
kind == IntrinsicKind::Symbol
}
fn visit_literal(&mut self, value: &LiteralValue) -> bool {
let _ = value;
false
}
fn visit_ref(&mut self, _symbol_ref: u32) -> bool {
true
}
fn visit_unique_symbol(&mut self, _symbol_ref: u32) -> bool {
true
}
fn default_output() -> bool {
false
}
}
struct OrderableVisitor<'a> {
_db: &'a dyn TypeDatabase,
}
impl<'a> TypeVisitor for OrderableVisitor<'a> {
type Output = bool;
fn visit_intrinsic(&mut self, kind: IntrinsicKind) -> bool {
matches!(
kind,
IntrinsicKind::String | IntrinsicKind::Number | IntrinsicKind::Bigint
)
}
fn visit_literal(&mut self, value: &LiteralValue) -> bool {
matches!(
value,
LiteralValue::String(_) | LiteralValue::Number(_) | LiteralValue::BigInt(_)
)
}
fn visit_union(&mut self, list_id: u32) -> bool {
let members = self._db.type_list(TypeListId(list_id));
!members.is_empty() && members.iter().all(|&m| self.visit_type(self._db, m))
}
fn visit_enum(&mut self, _def_id: u32, member_type: TypeId) -> bool {
self.visit_type(self._db, member_type)
}
fn visit_template_literal(&mut self, _template_id: u32) -> bool {
true }
fn visit_intersection(&mut self, list_id: u32) -> bool {
let members = self._db.type_list(TypeListId(list_id));
members.iter().any(|&m| self.visit_type(self._db, m))
}
fn visit_type_parameter(&mut self, info: &crate::types::TypeParamInfo) -> bool {
info.constraint
.map(|c| self.visit_type(self._db, c))
.unwrap_or(false)
}
fn default_output() -> bool {
false
}
}
struct PrimitiveClassVisitor;
impl TypeVisitor for PrimitiveClassVisitor {
type Output = Option<PrimitiveClass>;
fn visit_intrinsic(&mut self, kind: IntrinsicKind) -> Self::Output {
match kind {
IntrinsicKind::String => Some(PrimitiveClass::String),
IntrinsicKind::Number => Some(PrimitiveClass::Number),
IntrinsicKind::Boolean => Some(PrimitiveClass::Boolean),
IntrinsicKind::Bigint => Some(PrimitiveClass::Bigint),
IntrinsicKind::Symbol => Some(PrimitiveClass::Symbol),
IntrinsicKind::Null => Some(PrimitiveClass::Null),
IntrinsicKind::Undefined | IntrinsicKind::Void => Some(PrimitiveClass::Undefined),
_ => None,
}
}
fn visit_literal(&mut self, value: &LiteralValue) -> Self::Output {
match value {
LiteralValue::String(_) => Some(PrimitiveClass::String),
LiteralValue::Number(_) => Some(PrimitiveClass::Number),
LiteralValue::Boolean(_) => Some(PrimitiveClass::Boolean),
LiteralValue::BigInt(_) => Some(PrimitiveClass::Bigint),
}
}
fn visit_template_literal(&mut self, _template_id: u32) -> Self::Output {
Some(PrimitiveClass::String)
}
fn visit_unique_symbol(&mut self, _symbol_ref: u32) -> Self::Output {
Some(PrimitiveClass::Symbol)
}
fn default_output() -> Self::Output {
None
}
}
const fn intrinsic_overlaps_literal(kind: IntrinsicKind, value: &LiteralValue) -> bool {
matches!(
(kind, value),
(IntrinsicKind::String, LiteralValue::String(_))
| (IntrinsicKind::Number, LiteralValue::Number(_))
| (IntrinsicKind::Boolean, LiteralValue::Boolean(_))
| (IntrinsicKind::Bigint, LiteralValue::BigInt(_))
)
}
struct OverlapChecker<'a> {
db: &'a dyn TypeDatabase,
left: TypeId,
}
impl<'a> OverlapChecker<'a> {
fn new(db: &'a dyn TypeDatabase, left: TypeId) -> Self {
Self { db, left }
}
fn check(&mut self, right: TypeId) -> bool {
if self.left == right {
return true;
}
if matches!(
(self.left, right),
(TypeId::ANY | TypeId::UNKNOWN | TypeId::ERROR, _)
| (_, TypeId::ANY | TypeId::UNKNOWN | TypeId::ERROR)
) {
return true;
}
if self.left == TypeId::NEVER || right == TypeId::NEVER {
return false;
}
if self.db.intersection2(self.left, right) == TypeId::NEVER {
return false;
}
self.visit_type(self.db, right)
}
}
impl<'a> TypeVisitor for OverlapChecker<'a> {
type Output = bool;
fn visit_intrinsic(&mut self, _kind: IntrinsicKind) -> Self::Output {
true
}
fn visit_union(&mut self, list_id: u32) -> Self::Output {
let members = self.db.type_list(TypeListId(list_id));
members.iter().any(|&member| self.check(member))
}
fn visit_type_parameter(&mut self, info: &crate::types::TypeParamInfo) -> Self::Output {
match info.constraint {
Some(constraint) => self.check(constraint),
None => panic!("TypeParameter without constraint should not reach visitor"),
}
}
fn visit_infer(&mut self, info: &crate::types::TypeParamInfo) -> Self::Output {
match info.constraint {
Some(constraint) => self.check(constraint),
None => panic!("Infer without constraint should not reach visitor"),
}
}
fn visit_literal(&mut self, value: &LiteralValue) -> Self::Output {
match self.db.lookup(self.left) {
Some(TypeData::Literal(left_lit)) => left_lit == *value,
Some(TypeData::Union(members)) => {
let members = self.db.type_list(members);
members.iter().any(|&m| match self.db.lookup(m) {
Some(TypeData::Literal(lit)) => lit == *value,
Some(TypeData::Intrinsic(kind)) => intrinsic_overlaps_literal(kind, value),
_ => false,
})
}
Some(TypeData::Intrinsic(kind)) => intrinsic_overlaps_literal(kind, value),
Some(TypeData::Intersection(members)) => {
let members = self.db.type_list(members);
members.iter().any(|&m| match self.db.lookup(m) {
Some(TypeData::Literal(lit)) => lit == *value,
Some(TypeData::Intrinsic(kind)) => intrinsic_overlaps_literal(kind, value),
_ => false,
})
}
_ => false,
}
}
fn default_output() -> Self::Output {
true
}
}
pub struct BinaryOpEvaluator<'a> {
interner: &'a dyn QueryDatabase,
}
impl<'a> BinaryOpEvaluator<'a> {
pub fn new(interner: &'a dyn QueryDatabase) -> Self {
Self { interner }
}
pub fn is_valid_instanceof_left_operand(&self, type_id: TypeId) -> bool {
if type_id == TypeId::ERROR || type_id == TypeId::ANY || type_id == TypeId::UNKNOWN {
return true;
}
let mut visitor = InstanceofLeftOperandVisitor { _db: self.interner };
visitor.visit_type(self.interner, type_id)
}
pub fn is_valid_instanceof_right_operand<F>(
&self,
type_id: TypeId,
func_ty: TypeId,
assignable_check: &mut F,
) -> bool
where
F: FnMut(TypeId, TypeId) -> bool,
{
if type_id == TypeId::ANY
|| type_id == TypeId::UNKNOWN
|| type_id == TypeId::ERROR
|| type_id == TypeId::FUNCTION
{
return true;
}
if let Some(crate::TypeData::Union(list_id)) = self.interner.lookup(type_id) {
let members = self.interner.type_list(list_id);
let mut all_valid = true;
for &m in members.iter() {
if !self.is_valid_instanceof_right_operand(m, func_ty, assignable_check) {
all_valid = false;
break;
}
}
return all_valid && !members.is_empty();
}
if let Some(crate::TypeData::Intersection(list_id)) = self.interner.lookup(type_id) {
let members = self.interner.type_list(list_id);
let mut any_valid = false;
for &m in members.iter() {
if self.is_valid_instanceof_right_operand(m, func_ty, assignable_check) {
any_valid = true;
break;
}
}
return any_valid;
}
assignable_check(type_id, func_ty) || assignable_check(type_id, TypeId::FUNCTION)
}
pub fn is_arithmetic_operand(&self, type_id: TypeId) -> bool {
if type_id == TypeId::ANY
|| type_id == TypeId::ERROR
|| type_id == TypeId::UNKNOWN
|| type_id == TypeId::NEVER
{
return true;
}
self.is_number_like(type_id) || self.is_bigint_like(type_id)
}
pub fn evaluate(&self, left: TypeId, right: TypeId, op: &'static str) -> BinaryOpResult {
match op {
"+" => self.evaluate_plus(left, right),
"-" | "*" | "/" | "%" | "**" | "&" | "|" | "^" | "<<" | ">>" | ">>>" => {
self.evaluate_arithmetic(left, right, op)
}
"==" | "!=" | "===" | "!==" => {
if self.has_overlap(left, right) {
BinaryOpResult::Success(TypeId::BOOLEAN)
} else {
BinaryOpResult::TypeError { left, right, op }
}
}
"<" | ">" | "<=" | ">=" => self.evaluate_comparison(left, right),
"&&" | "||" | "??" => self.evaluate_logical(left, right, op),
_ => BinaryOpResult::TypeError { left, right, op },
}
}
fn evaluate_plus(&self, left: TypeId, right: TypeId) -> BinaryOpResult {
if left == TypeId::UNKNOWN || right == TypeId::UNKNOWN {
return BinaryOpResult::Success(TypeId::UNKNOWN);
}
let left = if left == TypeId::ERROR {
TypeId::ANY
} else {
left
};
let right = if right == TypeId::ERROR {
TypeId::ANY
} else {
right
};
if self.is_symbol_like(left) || self.is_symbol_like(right) {
return BinaryOpResult::TypeError {
left,
right,
op: "+",
};
}
if left == TypeId::ANY || right == TypeId::ANY {
return BinaryOpResult::Success(TypeId::ANY);
}
if self.is_string_like(left) || self.is_string_like(right) {
let valid_left = self.is_string_like(left) || self.is_valid_string_concat_operand(left);
let valid_right =
self.is_string_like(right) || self.is_valid_string_concat_operand(right);
if valid_left && valid_right {
return BinaryOpResult::Success(TypeId::STRING);
}
return BinaryOpResult::TypeError {
left,
right,
op: "+",
};
}
if self.is_number_like(left) && self.is_number_like(right) {
return BinaryOpResult::Success(TypeId::NUMBER);
}
if self.is_bigint_like(left) && self.is_bigint_like(right) {
return BinaryOpResult::Success(TypeId::BIGINT);
}
BinaryOpResult::TypeError {
left,
right,
op: "+",
}
}
fn evaluate_arithmetic(&self, left: TypeId, right: TypeId, op: &'static str) -> BinaryOpResult {
if left == TypeId::UNKNOWN || right == TypeId::UNKNOWN {
return BinaryOpResult::Success(TypeId::UNKNOWN);
}
let left = if left == TypeId::ERROR {
TypeId::ANY
} else {
left
};
let right = if right == TypeId::ERROR {
TypeId::ANY
} else {
right
};
if self.is_symbol_like(left) || self.is_symbol_like(right) {
return BinaryOpResult::TypeError { left, right, op };
}
if left == TypeId::ANY || right == TypeId::ANY {
return BinaryOpResult::Success(TypeId::NUMBER);
}
if self.is_number_like(left) && self.is_number_like(right) {
return BinaryOpResult::Success(TypeId::NUMBER);
}
if self.is_bigint_like(left) && self.is_bigint_like(right) {
return BinaryOpResult::Success(TypeId::BIGINT);
}
BinaryOpResult::TypeError { left, right, op }
}
fn evaluate_comparison(&self, left: TypeId, right: TypeId) -> BinaryOpResult {
if left == TypeId::UNKNOWN || right == TypeId::UNKNOWN {
return BinaryOpResult::Success(TypeId::BOOLEAN);
}
let left = if left == TypeId::ERROR {
TypeId::ANY
} else {
left
};
let right = if right == TypeId::ERROR {
TypeId::ANY
} else {
right
};
if self.is_symbol_like(left) || self.is_symbol_like(right) {
return BinaryOpResult::TypeError {
left,
right,
op: "<",
};
}
if left == TypeId::ANY || right == TypeId::ANY {
return BinaryOpResult::Success(TypeId::BOOLEAN);
}
if self.is_number_like(left) && self.is_number_like(right) {
return BinaryOpResult::Success(TypeId::BOOLEAN);
}
if self.is_string_like(left) && self.is_string_like(right) {
return BinaryOpResult::Success(TypeId::BOOLEAN);
}
if self.is_bigint_like(left) && self.is_bigint_like(right) {
return BinaryOpResult::Success(TypeId::BOOLEAN);
}
if self.is_boolean_like(left) && self.is_boolean_like(right) {
return BinaryOpResult::Success(TypeId::BOOLEAN);
}
if self.is_orderable(left) && self.is_orderable(right) {
return BinaryOpResult::Success(TypeId::BOOLEAN);
}
BinaryOpResult::TypeError {
left,
right,
op: "<",
}
}
fn evaluate_logical(&self, left: TypeId, right: TypeId, op: &'static str) -> BinaryOpResult {
let ctx = crate::narrowing::NarrowingContext::new(self.interner);
let result = if op == "&&" {
let falsy_left = ctx.narrow_to_falsy(left);
let truthy_left = ctx.narrow_by_truthiness(left);
if truthy_left == TypeId::NEVER {
left
} else if falsy_left == TypeId::NEVER {
right
} else {
self.interner.union2(falsy_left, right)
}
} else if op == "||" {
let truthy_left = ctx.narrow_by_truthiness(left);
let falsy_left = ctx.narrow_to_falsy(left);
if falsy_left == TypeId::NEVER {
left
} else if truthy_left == TypeId::NEVER {
right
} else {
self.interner.union2(truthy_left, right)
}
} else {
let non_nullish_left = ctx.narrow_by_nullishness(left, false);
let nullish_left = ctx.narrow_by_nullishness(left, true);
if nullish_left == TypeId::NEVER {
left
} else if non_nullish_left == TypeId::NEVER {
right
} else {
self.interner.union2(non_nullish_left, right)
}
};
BinaryOpResult::Success(result)
}
fn is_number_like(&self, type_id: TypeId) -> bool {
if type_id == TypeId::NUMBER || type_id == TypeId::ANY {
return true;
}
let mut visitor = NumberLikeVisitor { _db: self.interner };
visitor.visit_type(self.interner, type_id)
}
fn is_string_like(&self, type_id: TypeId) -> bool {
if type_id == TypeId::STRING || type_id == TypeId::ANY {
return true;
}
let mut visitor = StringLikeVisitor { _db: self.interner };
visitor.visit_type(self.interner, type_id)
}
fn is_bigint_like(&self, type_id: TypeId) -> bool {
if type_id == TypeId::BIGINT || type_id == TypeId::ANY {
return true;
}
let mut visitor = BigIntLikeVisitor { _db: self.interner };
visitor.visit_type(self.interner, type_id)
}
fn is_orderable(&self, type_id: TypeId) -> bool {
if type_id == TypeId::ANY
|| type_id == TypeId::NUMBER
|| type_id == TypeId::STRING
|| type_id == TypeId::BIGINT
{
return true;
}
let mut visitor = OrderableVisitor { _db: self.interner };
visitor.visit_type(self.interner, type_id)
}
pub fn has_overlap(&self, left: TypeId, right: TypeId) -> bool {
if left == right {
return true;
}
if left == TypeId::ANY
|| right == TypeId::ANY
|| left == TypeId::UNKNOWN
|| right == TypeId::UNKNOWN
|| left == TypeId::ERROR
|| right == TypeId::ERROR
{
return true;
}
if left == TypeId::NEVER || right == TypeId::NEVER {
return false;
}
if let Some(TypeData::TypeParameter(info) | TypeData::Infer(info)) =
self.interner.lookup(left)
{
if let Some(constraint) = info.constraint {
return self.has_overlap(constraint, right);
}
return true;
}
if let Some(TypeData::TypeParameter(info) | TypeData::Infer(info)) =
self.interner.lookup(right)
{
if let Some(constraint) = info.constraint {
return self.has_overlap(left, constraint);
}
return true;
}
if let Some(TypeData::Union(members)) = self.interner.lookup(left) {
let members = self.interner.type_list(members);
return members
.iter()
.any(|member| self.has_overlap(*member, right));
}
if let Some(TypeData::Union(members)) = self.interner.lookup(right) {
let members = self.interner.type_list(members);
return members.iter().any(|member| self.has_overlap(left, *member));
}
if self.primitive_classes_disjoint(left, right) {
return false;
}
if self.interner.intersection2(left, right) == TypeId::NEVER {
return false;
}
let mut checker = OverlapChecker::new(self.interner, left);
checker.check(right)
}
fn primitive_classes_disjoint(&self, left: TypeId, right: TypeId) -> bool {
match (self.primitive_class(left), self.primitive_class(right)) {
(Some(left_class), Some(right_class)) => left_class != right_class,
_ => false,
}
}
fn primitive_class(&self, type_id: TypeId) -> Option<PrimitiveClass> {
let mut visitor = PrimitiveClassVisitor;
visitor.visit_type(self.interner, type_id)
}
pub fn is_symbol_like(&self, type_id: TypeId) -> bool {
if type_id == TypeId::SYMBOL {
return true;
}
let mut visitor = SymbolLikeVisitor { _db: self.interner };
visitor.visit_type(self.interner, type_id)
}
pub fn is_valid_computed_property_name_type(&self, type_id: TypeId) -> bool {
if type_id == TypeId::ANY || type_id == TypeId::NEVER || type_id == TypeId::ERROR {
return true;
}
if let Some(TypeData::Union(list_id)) = self.interner.lookup(type_id) {
let members = self.interner.type_list(list_id);
return !members.is_empty()
&& members
.iter()
.all(|&m| self.is_valid_computed_property_name_type(m));
}
self.is_string_like(type_id) || self.is_number_like(type_id) || self.is_symbol_like(type_id)
}
pub fn is_boolean_like(&self, type_id: TypeId) -> bool {
if type_id == TypeId::BOOLEAN || type_id == TypeId::ANY {
return true;
}
let mut visitor = BooleanLikeVisitor { _db: self.interner };
visitor.visit_type(self.interner, type_id)
}
fn is_valid_string_concat_operand(&self, type_id: TypeId) -> bool {
if type_id == TypeId::ANY || type_id == TypeId::ERROR || type_id == TypeId::NEVER {
return true;
}
if type_id == TypeId::UNKNOWN {
return false;
}
if let Some(TypeData::Union(list_id)) = self.interner.lookup(type_id) {
let members = self.interner.type_list(list_id);
return !members.is_empty()
&& members
.iter()
.all(|&member| self.is_valid_string_concat_operand(member));
}
if self.is_symbol_like(type_id) {
return false;
}
if self.is_number_like(type_id)
|| self.is_boolean_like(type_id)
|| self.is_bigint_like(type_id)
|| type_id == TypeId::NULL
|| type_id == TypeId::UNDEFINED
|| type_id == TypeId::VOID
{
return true;
}
true
}
}