use crate::instantiate::{TypeSubstitution, instantiate_type};
use crate::subtype::TypeResolver;
use crate::types::{
IntrinsicKind, LiteralValue, MappedModifier, MappedTypeId, ObjectShape, ObjectShapeId,
PropertyInfo, SymbolRef, TupleElement, TupleListId, TypeData, TypeId, TypeListId,
TypeParamInfo,
};
use crate::utils;
use crate::visitor::{
TypeVisitor, array_element_type, literal_number, literal_string, tuple_list_id, union_list_id,
};
use crate::{ApparentMemberKind, TypeDatabase};
use super::super::evaluate::{
ARRAY_METHODS_RETURN_ANY, ARRAY_METHODS_RETURN_BOOLEAN, ARRAY_METHODS_RETURN_NUMBER,
ARRAY_METHODS_RETURN_STRING, ARRAY_METHODS_RETURN_VOID, TypeEvaluator,
};
use super::apparent::make_apparent_method_type;
fn is_member(name: &str, list: &[&str]) -> bool {
list.contains(&name)
}
pub(crate) fn get_array_member_kind(name: &str) -> Option<ApparentMemberKind> {
if name == "length" {
return Some(ApparentMemberKind::Value(TypeId::NUMBER));
}
if is_member(name, ARRAY_METHODS_RETURN_ANY) {
return Some(ApparentMemberKind::Method(TypeId::ANY));
}
if is_member(name, ARRAY_METHODS_RETURN_BOOLEAN) {
return Some(ApparentMemberKind::Method(TypeId::BOOLEAN));
}
if is_member(name, ARRAY_METHODS_RETURN_NUMBER) {
return Some(ApparentMemberKind::Method(TypeId::NUMBER));
}
if is_member(name, ARRAY_METHODS_RETURN_VOID) {
return Some(ApparentMemberKind::Method(TypeId::VOID));
}
if is_member(name, ARRAY_METHODS_RETURN_STRING) {
return Some(ApparentMemberKind::Method(TypeId::STRING));
}
None
}
struct IndexAccessVisitor<'a, 'b, R: TypeResolver> {
evaluator: &'b mut TypeEvaluator<'a, R>,
object_type: TypeId,
index_type: TypeId,
}
impl<'a, 'b, R: TypeResolver> IndexAccessVisitor<'a, 'b, R> {
fn evaluate_apparent_primitive(&mut self, kind: IntrinsicKind) -> Option<TypeId> {
match kind {
IntrinsicKind::String
| IntrinsicKind::Number
| IntrinsicKind::Boolean
| IntrinsicKind::Bigint
| IntrinsicKind::Symbol => {
let shape = self.evaluator.apparent_primitive_shape(kind);
Some(
self.evaluator
.evaluate_object_with_index(&shape, self.index_type),
)
}
_ => None,
}
}
fn is_generic_index(&self) -> bool {
let key = match self.evaluator.interner().lookup(self.index_type) {
Some(k) => k,
None => return false,
};
matches!(
key,
TypeData::TypeParameter(_)
| TypeData::Infer(_)
| TypeData::KeyOf(_)
| TypeData::IndexAccess(_, _)
| TypeData::Conditional(_)
| TypeData::TemplateLiteral(_) | TypeData::Intersection(_)
)
}
fn evaluate_type_param(&mut self, param: &TypeParamInfo) -> Option<TypeId> {
if let Some(constraint) = param.constraint {
if constraint == self.object_type {
Some(
self.evaluator
.interner()
.index_access(self.object_type, self.index_type),
)
} else {
Some(
self.evaluator
.recurse_index_access(constraint, self.index_type),
)
}
} else {
Some(
self.evaluator
.interner()
.index_access(self.object_type, self.index_type),
)
}
}
}
impl<'a, 'b, R: TypeResolver> TypeVisitor for IndexAccessVisitor<'a, 'b, R> {
type Output = Option<TypeId>;
fn visit_intrinsic(&mut self, kind: IntrinsicKind) -> Self::Output {
self.evaluate_apparent_primitive(kind)
}
fn visit_literal(&mut self, value: &LiteralValue) -> Self::Output {
self.evaluator
.apparent_literal_kind(value)
.and_then(|kind| self.evaluate_apparent_primitive(kind))
}
fn visit_object(&mut self, shape_id: u32) -> Self::Output {
let shape = self
.evaluator
.interner()
.object_shape(ObjectShapeId(shape_id));
let result = self
.evaluator
.evaluate_object_index(&shape.properties, self.index_type);
if result == TypeId::UNDEFINED && self.is_generic_index() {
return None;
}
Some(result)
}
fn visit_object_with_index(&mut self, shape_id: u32) -> Self::Output {
let shape = self
.evaluator
.interner()
.object_shape(ObjectShapeId(shape_id));
let result = self
.evaluator
.evaluate_object_with_index(&shape, self.index_type);
if result == TypeId::UNDEFINED && self.is_generic_index() {
return None;
}
Some(result)
}
fn visit_union(&mut self, list_id: u32) -> Self::Output {
let members = self.evaluator.interner().type_list(TypeListId(list_id));
const MAX_UNION_INDEX_SIZE: usize = 100;
if members.len() > MAX_UNION_INDEX_SIZE {
self.evaluator.mark_depth_exceeded();
return Some(TypeId::ERROR);
}
let mut results = Vec::new();
for &member in members.iter() {
if self.evaluator.is_depth_exceeded() {
return Some(TypeId::ERROR);
}
let result = self.evaluator.recurse_index_access(member, self.index_type);
if result == TypeId::ERROR && self.evaluator.is_depth_exceeded() {
return Some(TypeId::ERROR);
}
if result != TypeId::UNDEFINED || self.evaluator.no_unchecked_indexed_access() {
results.push(result);
}
}
if results.is_empty() {
return Some(TypeId::UNDEFINED);
}
Some(self.evaluator.interner().union(results))
}
fn visit_intersection(&mut self, list_id: u32) -> Self::Output {
let members = self.evaluator.interner().type_list(TypeListId(list_id));
let mut results = Vec::new();
for &member in members.iter() {
let result = self.evaluator.recurse_index_access(member, self.index_type);
if result == TypeId::ERROR {
return Some(TypeId::ERROR);
}
if result != TypeId::UNDEFINED {
results.push(result);
}
}
if results.is_empty() {
Some(TypeId::UNDEFINED)
} else {
Some(self.evaluator.interner().union(results))
}
}
fn visit_lazy(&mut self, def_id: u32) -> Self::Output {
let def_id = crate::def::DefId(def_id);
if let Some(resolved) = self
.evaluator
.resolver()
.resolve_lazy(def_id, self.evaluator.interner())
{
return Some(
self.evaluator
.evaluate_index_access(resolved, self.index_type),
);
}
None
}
fn visit_array(&mut self, element_type: TypeId) -> Self::Output {
Some(
self.evaluator
.evaluate_array_index(element_type, self.index_type),
)
}
fn visit_tuple(&mut self, list_id: u32) -> Self::Output {
let elements = self.evaluator.interner().tuple_list(TupleListId(list_id));
Some(
self.evaluator
.evaluate_tuple_index(&elements, self.index_type),
)
}
fn visit_ref(&mut self, symbol_ref: u32) -> Self::Output {
let symbol_ref = SymbolRef(symbol_ref);
let resolved = if let Some(def_id) = self.evaluator.resolver().symbol_to_def_id(symbol_ref)
{
self.evaluator
.resolver()
.resolve_lazy(def_id, self.evaluator.interner())?
} else {
self.evaluator
.resolver()
.resolve_symbol_ref(symbol_ref, self.evaluator.interner())?
};
if resolved == self.object_type {
Some(
self.evaluator
.interner()
.index_access(self.object_type, self.index_type),
)
} else {
Some(
self.evaluator
.recurse_index_access(resolved, self.index_type),
)
}
}
fn visit_type_parameter(&mut self, param_info: &TypeParamInfo) -> Self::Output {
self.evaluate_type_param(param_info)
}
fn visit_infer(&mut self, param_info: &TypeParamInfo) -> Self::Output {
self.evaluate_type_param(param_info)
}
fn visit_readonly_type(&mut self, inner_type: TypeId) -> Self::Output {
Some(
self.evaluator
.recurse_index_access(inner_type, self.index_type),
)
}
fn visit_mapped(&mut self, mapped_id: u32) -> Self::Output {
let mapped = self
.evaluator
.interner()
.mapped_type(MappedTypeId(mapped_id));
if mapped.name_type.is_none() && mapped.constraint == self.index_type {
let mut subst = TypeSubstitution::new();
subst.insert(mapped.type_param.name, self.index_type);
let mut value_type = self.evaluator.evaluate(instantiate_type(
self.evaluator.interner(),
mapped.template,
&subst,
));
if matches!(mapped.optional_modifier, Some(MappedModifier::Add)) {
value_type = self
.evaluator
.interner()
.union2(value_type, TypeId::UNDEFINED);
}
return Some(value_type);
}
None
}
fn visit_template_literal(&mut self, _template_id: u32) -> Self::Output {
self.evaluate_apparent_primitive(IntrinsicKind::String)
}
fn default_output() -> Self::Output {
None
}
}
struct ArrayKeyVisitor<'a> {
db: &'a dyn TypeDatabase,
element_type: TypeId,
array_member_types_cache: Option<Vec<TypeId>>,
}
impl<'a> ArrayKeyVisitor<'a> {
fn new(db: &'a dyn TypeDatabase, element_type: TypeId) -> Self {
Self {
db,
element_type,
array_member_types_cache: None,
}
}
fn evaluate(&mut self, index_type: TypeId) -> TypeId {
let result = self.visit_type(self.db, index_type);
result.unwrap_or(self.element_type)
}
fn get_array_member_types(&mut self) -> Vec<TypeId> {
self.array_member_types_cache
.get_or_insert_with(|| {
vec![
TypeId::NUMBER,
make_apparent_method_type(self.db, TypeId::ANY),
make_apparent_method_type(self.db, TypeId::BOOLEAN),
make_apparent_method_type(self.db, TypeId::NUMBER),
make_apparent_method_type(self.db, TypeId::VOID),
make_apparent_method_type(self.db, TypeId::STRING),
]
})
.clone()
}
}
impl<'a> TypeVisitor for ArrayKeyVisitor<'a> {
type Output = Option<TypeId>;
fn visit_union(&mut self, list_id: u32) -> Self::Output {
let members = self.db.type_list(TypeListId(list_id));
let mut results = Vec::new();
for &member in members.iter() {
let result = self.evaluate(member);
if result != TypeId::UNDEFINED {
results.push(result);
}
}
if results.is_empty() {
Some(TypeId::UNDEFINED)
} else {
Some(self.db.union(results))
}
}
fn visit_intrinsic(&mut self, kind: IntrinsicKind) -> Self::Output {
match kind {
IntrinsicKind::Number => Some(self.element_type),
IntrinsicKind::String => Some(self.db.union(self.get_array_member_types())),
_ => Some(TypeId::UNDEFINED),
}
}
fn visit_literal(&mut self, value: &LiteralValue) -> Self::Output {
match value {
LiteralValue::Number(_) => Some(self.element_type),
LiteralValue::String(atom) => {
let name = self.db.resolve_atom_ref(*atom);
if utils::is_numeric_property_name(self.db, *atom) {
return Some(self.element_type);
}
if let Some(member) = get_array_member_kind(name.as_ref()) {
return match member {
ApparentMemberKind::Value(type_id) => Some(type_id),
ApparentMemberKind::Method(return_type) => {
Some(make_apparent_method_type(self.db, return_type))
}
};
}
Some(TypeId::UNDEFINED)
}
LiteralValue::Boolean(_) | LiteralValue::BigInt(_) => Some(TypeId::UNDEFINED),
}
}
fn default_output() -> Self::Output {
None
}
}
struct TupleKeyVisitor<'a> {
db: &'a dyn TypeDatabase,
elements: &'a [TupleElement],
array_member_types_cache: Option<Vec<TypeId>>,
}
impl<'a> TupleKeyVisitor<'a> {
fn new(db: &'a dyn TypeDatabase, elements: &'a [TupleElement]) -> Self {
Self {
db,
elements,
array_member_types_cache: None,
}
}
fn evaluate(&mut self, index_type: TypeId) -> TypeId {
let result = self.visit_type(self.db, index_type);
result.unwrap_or(TypeId::UNDEFINED)
}
fn tuple_element_type(&self, element: &TupleElement) -> TypeId {
let mut type_id = if element.rest {
self.rest_element_type(element.type_id)
} else {
element.type_id
};
if element.optional {
type_id = self.db.union2(type_id, TypeId::UNDEFINED);
}
type_id
}
fn rest_element_type(&self, type_id: TypeId) -> TypeId {
if let Some(elem) = array_element_type(self.db, type_id) {
return elem;
}
if let Some(elements) = tuple_list_id(self.db, type_id) {
let elements = self.db.tuple_list(elements);
let types: Vec<TypeId> = elements
.iter()
.map(|e| self.tuple_element_type(e))
.collect();
if types.is_empty() {
TypeId::NEVER
} else {
self.db.union(types)
}
} else {
type_id
}
}
fn tuple_index_literal(&self, idx: usize) -> Option<TypeId> {
for (logical_idx, element) in self.elements.iter().enumerate() {
if element.rest {
if let Some(rest_elements) = tuple_list_id(self.db, element.type_id) {
let rest_elements = self.db.tuple_list(rest_elements);
let inner_idx = idx.saturating_sub(logical_idx);
let inner_visitor = TupleKeyVisitor::new(self.db, &rest_elements);
return inner_visitor.tuple_index_literal(inner_idx);
}
return Some(self.tuple_element_type(element));
}
if logical_idx == idx {
return Some(self.tuple_element_type(element));
}
}
None
}
fn get_all_element_types(&self) -> Vec<TypeId> {
self.elements
.iter()
.map(|e| self.tuple_element_type(e))
.collect()
}
fn get_array_member_types(&mut self) -> Vec<TypeId> {
self.array_member_types_cache
.get_or_insert_with(|| {
vec![
TypeId::NUMBER,
make_apparent_method_type(self.db, TypeId::ANY),
make_apparent_method_type(self.db, TypeId::BOOLEAN),
make_apparent_method_type(self.db, TypeId::NUMBER),
make_apparent_method_type(self.db, TypeId::VOID),
make_apparent_method_type(self.db, TypeId::STRING),
]
})
.clone()
}
fn get_array_member_kind(&self, name: &str) -> Option<ApparentMemberKind> {
if name == "length" {
return Some(ApparentMemberKind::Value(TypeId::NUMBER));
}
if is_member(name, ARRAY_METHODS_RETURN_ANY) {
return Some(ApparentMemberKind::Method(TypeId::ANY));
}
if is_member(name, ARRAY_METHODS_RETURN_BOOLEAN) {
return Some(ApparentMemberKind::Method(TypeId::BOOLEAN));
}
if is_member(name, ARRAY_METHODS_RETURN_NUMBER) {
return Some(ApparentMemberKind::Method(TypeId::NUMBER));
}
if is_member(name, ARRAY_METHODS_RETURN_VOID) {
return Some(ApparentMemberKind::Method(TypeId::VOID));
}
if is_member(name, ARRAY_METHODS_RETURN_STRING) {
return Some(ApparentMemberKind::Method(TypeId::STRING));
}
None
}
}
impl<'a> TypeVisitor for TupleKeyVisitor<'a> {
type Output = Option<TypeId>;
fn visit_union(&mut self, list_id: u32) -> Self::Output {
let members = self.db.type_list(TypeListId(list_id));
let mut results = Vec::new();
for &member in members.iter() {
let result = self.evaluate(member);
if result != TypeId::UNDEFINED {
results.push(result);
}
}
if results.is_empty() {
Some(TypeId::UNDEFINED)
} else {
Some(self.db.union(results))
}
}
fn visit_intrinsic(&mut self, kind: IntrinsicKind) -> Self::Output {
match kind {
IntrinsicKind::String => {
let mut types = self.get_all_element_types();
types.extend(self.get_array_member_types());
if types.is_empty() {
Some(TypeId::NEVER)
} else {
Some(self.db.union(types))
}
}
IntrinsicKind::Number => {
let all_types = self.get_all_element_types();
if all_types.is_empty() {
Some(TypeId::NEVER)
} else {
Some(self.db.union(all_types))
}
}
_ => Some(TypeId::UNDEFINED),
}
}
fn visit_literal(&mut self, value: &LiteralValue) -> Self::Output {
match value {
LiteralValue::Number(n) => {
let value = n.0;
if !value.is_finite() || value.fract() != 0.0 || value < 0.0 {
return Some(TypeId::UNDEFINED);
}
let idx = value as usize;
self.tuple_index_literal(idx).or(Some(TypeId::UNDEFINED))
}
LiteralValue::String(atom) => {
if utils::is_numeric_property_name(self.db, *atom) {
let name = self.db.resolve_atom_ref(*atom);
if let Ok(idx) = name.as_ref().parse::<i64>()
&& let Ok(idx) = usize::try_from(idx)
{
return self.tuple_index_literal(idx).or(Some(TypeId::UNDEFINED));
}
return Some(TypeId::UNDEFINED);
}
let name = self.db.resolve_atom_ref(*atom);
if let Some(member) = self.get_array_member_kind(name.as_ref()) {
return match member {
ApparentMemberKind::Value(type_id) => Some(type_id),
ApparentMemberKind::Method(return_type) => {
Some(make_apparent_method_type(self.db, return_type))
}
};
}
Some(TypeId::UNDEFINED)
}
LiteralValue::Boolean(_) | LiteralValue::BigInt(_) => Some(TypeId::UNDEFINED),
}
}
fn default_output() -> Self::Output {
None
}
}
impl<'a, R: TypeResolver> TypeEvaluator<'a, R> {
pub(crate) fn recurse_index_access(
&mut self,
object_type: TypeId,
index_type: TypeId,
) -> TypeId {
let index_access = self.interner().index_access(object_type, index_type);
self.evaluate(index_access)
}
pub fn evaluate_index_access(&mut self, object_type: TypeId, index_type: TypeId) -> TypeId {
let evaluated_object = self.evaluate(object_type);
let evaluated_index = self.evaluate(index_type);
if evaluated_object != object_type || evaluated_index != index_type {
return self.recurse_index_access(evaluated_object, evaluated_index);
}
if evaluated_object == TypeId::ANY || evaluated_index == TypeId::ANY {
return TypeId::ANY;
}
if let Some(members_id) = union_list_id(self.interner(), index_type) {
let members = self.interner().type_list(members_id);
const MAX_UNION_INDEX_SIZE: usize = 100;
if members.len() > MAX_UNION_INDEX_SIZE {
self.mark_depth_exceeded();
return TypeId::ERROR;
}
let mut results = Vec::new();
for &member in members.iter() {
if self.is_depth_exceeded() {
return TypeId::ERROR;
}
let result = self.recurse_index_access(object_type, member);
if result == TypeId::ERROR && self.is_depth_exceeded() {
return TypeId::ERROR;
}
if result != TypeId::UNDEFINED || self.no_unchecked_indexed_access() {
results.push(result);
}
}
if results.is_empty() {
return TypeId::UNDEFINED;
}
return self.interner().union(results);
}
let interner = self.interner();
let mut visitor = IndexAccessVisitor {
evaluator: self,
object_type,
index_type,
};
if let Some(result) = visitor.visit_type(interner, object_type) {
return result;
}
self.interner().index_access(object_type, index_type)
}
pub(crate) fn evaluate_object_index(
&self,
props: &[PropertyInfo],
index_type: TypeId,
) -> TypeId {
if let Some(name) = literal_string(self.interner(), index_type) {
for prop in props {
if prop.name == name {
return self.optional_property_type(prop);
}
}
return TypeId::UNDEFINED;
}
if let Some(members) = union_list_id(self.interner(), index_type) {
let members = self.interner().type_list(members);
let mut results = Vec::new();
for &member in members.iter() {
let result = self.evaluate_object_index(props, member);
if result != TypeId::UNDEFINED || self.no_unchecked_indexed_access() {
results.push(result);
}
}
if results.is_empty() {
return TypeId::UNDEFINED;
}
return self.interner().union(results);
}
if index_type == TypeId::STRING {
let union = self.union_property_types(props);
return self.add_undefined_if_unchecked(union);
}
TypeId::UNDEFINED
}
pub(crate) fn evaluate_object_with_index(
&self,
shape: &ObjectShape,
index_type: TypeId,
) -> TypeId {
if let Some(members) = union_list_id(self.interner(), index_type) {
let members = self.interner().type_list(members);
let mut results = Vec::new();
for &member in members.iter() {
let result = self.evaluate_object_with_index(shape, member);
if result != TypeId::UNDEFINED || self.no_unchecked_indexed_access() {
results.push(result);
}
}
if results.is_empty() {
return TypeId::UNDEFINED;
}
return self.interner().union(results);
}
if let Some(name) = literal_string(self.interner(), index_type) {
for prop in &shape.properties {
if prop.name == name {
return self.optional_property_type(prop);
}
}
if utils::is_numeric_property_name(self.interner(), name)
&& let Some(number_index) = shape.number_index.as_ref()
{
return self.add_undefined_if_unchecked(number_index.value_type);
}
if let Some(string_index) = shape.string_index.as_ref() {
return self.add_undefined_if_unchecked(string_index.value_type);
}
return TypeId::UNDEFINED;
}
if literal_number(self.interner(), index_type).is_some() {
if let Some(number_index) = shape.number_index.as_ref() {
return self.add_undefined_if_unchecked(number_index.value_type);
}
if let Some(string_index) = shape.string_index.as_ref() {
return self.add_undefined_if_unchecked(string_index.value_type);
}
return TypeId::UNDEFINED;
}
if index_type == TypeId::STRING {
let result = if let Some(string_index) = shape.string_index.as_ref() {
string_index.value_type
} else {
self.union_property_types(&shape.properties)
};
return self.add_undefined_if_unchecked(result);
}
if index_type == TypeId::NUMBER {
let result = if let Some(number_index) = shape.number_index.as_ref() {
number_index.value_type
} else if let Some(string_index) = shape.string_index.as_ref() {
string_index.value_type
} else {
self.union_property_types(&shape.properties)
};
return self.add_undefined_if_unchecked(result);
}
TypeId::UNDEFINED
}
pub(crate) fn union_property_types(&self, props: &[PropertyInfo]) -> TypeId {
let all_types: Vec<TypeId> = props
.iter()
.map(|prop| self.optional_property_type(prop))
.collect();
if all_types.is_empty() {
TypeId::UNDEFINED
} else {
self.interner().union(all_types)
}
}
pub(crate) fn optional_property_type(&self, prop: &PropertyInfo) -> TypeId {
if prop.optional {
self.interner().union2(prop.type_id, TypeId::UNDEFINED)
} else {
prop.type_id
}
}
pub(crate) fn add_undefined_if_unchecked(&self, type_id: TypeId) -> TypeId {
if !self.no_unchecked_indexed_access() || type_id == TypeId::UNDEFINED {
return type_id;
}
self.interner().union2(type_id, TypeId::UNDEFINED)
}
pub(crate) fn rest_element_type(&self, type_id: TypeId) -> TypeId {
if let Some(elem) = array_element_type(self.interner(), type_id) {
return elem;
}
if let Some(elements) = tuple_list_id(self.interner(), type_id) {
let elements = self.interner().tuple_list(elements);
let types: Vec<TypeId> = elements
.iter()
.map(|e| self.tuple_element_type(e))
.collect();
if types.is_empty() {
TypeId::NEVER
} else {
self.interner().union(types)
}
} else {
type_id
}
}
pub(crate) fn tuple_element_type(&self, element: &TupleElement) -> TypeId {
let mut type_id = if element.rest {
self.rest_element_type(element.type_id)
} else {
element.type_id
};
if element.optional {
type_id = self.interner().union2(type_id, TypeId::UNDEFINED);
}
type_id
}
pub(crate) fn evaluate_tuple_index(
&self,
elements: &[TupleElement],
index_type: TypeId,
) -> TypeId {
let mut visitor = TupleKeyVisitor::new(self.interner(), elements);
let result = visitor.evaluate(index_type);
self.add_undefined_if_unchecked(result)
}
pub(crate) fn evaluate_array_index(&self, elem: TypeId, index_type: TypeId) -> TypeId {
let mut visitor = ArrayKeyVisitor::new(self.interner(), elem);
let result = visitor.evaluate(index_type);
self.add_undefined_if_unchecked(result)
}
}