use crate::evaluate::{evaluate_index_access_with_options, evaluate_type};
use crate::{LiteralValue, TypeData, TypeDatabase, TypeId};
#[derive(Debug, Clone)]
pub enum ElementAccessResult {
Success(TypeId),
NotIndexable {
type_id: TypeId,
},
IndexOutOfBounds {
type_id: TypeId,
index: usize,
length: usize,
},
NoIndexSignature {
type_id: TypeId,
},
}
pub struct ElementAccessEvaluator<'a> {
interner: &'a dyn TypeDatabase,
no_unchecked_indexed_access: bool,
}
impl<'a> ElementAccessEvaluator<'a> {
pub fn new(interner: &'a dyn TypeDatabase) -> Self {
Self {
interner,
no_unchecked_indexed_access: false,
}
}
pub const fn set_no_unchecked_indexed_access(&mut self, enabled: bool) {
self.no_unchecked_indexed_access = enabled;
}
pub fn resolve_element_access(
&self,
object_type: TypeId,
index_type: TypeId,
literal_index: Option<usize>,
) -> ElementAccessResult {
let evaluated_object = evaluate_type(self.interner, object_type);
if evaluated_object == TypeId::ERROR || index_type == TypeId::ERROR {
return ElementAccessResult::Success(TypeId::ERROR);
}
if evaluated_object == TypeId::ANY {
return ElementAccessResult::Success(TypeId::ANY);
}
let result_type = evaluate_index_access_with_options(
self.interner,
object_type,
index_type,
self.no_unchecked_indexed_access,
);
if !self.is_indexable(evaluated_object) {
return ElementAccessResult::NotIndexable {
type_id: evaluated_object,
};
}
if let Some(TypeData::Tuple(elements)) = self.interner.lookup(evaluated_object)
&& let Some(index) = literal_index
{
let tuple_elements = self.interner.tuple_list(elements);
let has_rest = tuple_elements.iter().any(|e| e.rest);
if !has_rest && index >= tuple_elements.len() {
return ElementAccessResult::IndexOutOfBounds {
type_id: evaluated_object,
index,
length: tuple_elements.len(),
};
}
}
if result_type == TypeId::UNDEFINED
&& self.should_report_no_index_signature(evaluated_object, index_type)
{
return ElementAccessResult::NoIndexSignature {
type_id: evaluated_object,
};
}
ElementAccessResult::Success(result_type)
}
fn is_indexable(&self, type_id: TypeId) -> bool {
match self.interner.lookup(type_id) {
Some(
TypeData::Array(_)
| TypeData::Tuple(_)
| TypeData::Object(_)
| TypeData::ObjectWithIndex(_)
| TypeData::StringIntrinsic { .. }
| TypeData::Literal(LiteralValue::String(_))
| TypeData::Intersection(_),
) => true,
Some(TypeData::Union(members)) => {
let members = self.interner.type_list(members);
members.iter().all(|&m| self.is_indexable(m))
}
_ => {
if type_id == TypeId::STRING || type_id == TypeId::ANY {
return true;
}
false
}
}
}
fn should_report_no_index_signature(&self, object_type: TypeId, index_type: TypeId) -> bool {
let index_type = evaluate_type(self.interner, index_type);
let mut checker = crate::subtype::SubtypeChecker::new(self.interner);
match self.interner.lookup(object_type) {
Some(TypeData::Object(_)) => {
checker.reset();
if checker.is_subtype_of(index_type, TypeId::STRING) {
return true;
}
checker.reset();
if checker.is_subtype_of(index_type, TypeId::NUMBER) {
return true;
}
false
}
Some(TypeData::ObjectWithIndex(shape_id)) => {
let shape = self.interner.object_shape(shape_id);
checker.reset();
if checker.is_subtype_of(index_type, TypeId::STRING) && shape.string_index.is_none()
{
return true;
}
checker.reset();
if checker.is_subtype_of(index_type, TypeId::NUMBER)
&& shape.number_index.is_none()
&& shape.string_index.is_none()
{
return true;
}
false
}
_ => false,
}
}
}