tsz_solver/objects/
element_access.rs1use crate::evaluation::evaluate::{evaluate_index_access_with_options, evaluate_type};
2use crate::{LiteralValue, TypeData, TypeDatabase, TypeId};
3
4#[derive(Debug, Clone)]
5pub enum ElementAccessResult {
6 Success(TypeId),
7 NotIndexable {
8 type_id: TypeId,
9 },
10 IndexOutOfBounds {
11 type_id: TypeId,
12 index: usize,
13 length: usize,
14 },
15 NoIndexSignature {
16 type_id: TypeId,
17 },
18}
19
20pub struct ElementAccessEvaluator<'a> {
21 interner: &'a dyn TypeDatabase,
22 no_unchecked_indexed_access: bool,
23}
24
25impl<'a> ElementAccessEvaluator<'a> {
26 pub fn new(interner: &'a dyn TypeDatabase) -> Self {
27 Self {
28 interner,
29 no_unchecked_indexed_access: false,
30 }
31 }
32
33 pub const fn set_no_unchecked_indexed_access(&mut self, enabled: bool) {
34 self.no_unchecked_indexed_access = enabled;
35 }
36
37 pub fn resolve_element_access(
38 &self,
39 object_type: TypeId,
40 index_type: TypeId,
41 literal_index: Option<usize>,
42 ) -> ElementAccessResult {
43 let evaluated_object = evaluate_type(self.interner, object_type);
45
46 if evaluated_object == TypeId::ERROR || index_type == TypeId::ERROR {
48 return ElementAccessResult::Success(TypeId::ERROR);
49 }
50 if evaluated_object == TypeId::ANY {
51 return ElementAccessResult::Success(TypeId::ANY);
52 }
53
54 let result_type = evaluate_index_access_with_options(
56 self.interner,
57 object_type,
58 index_type,
59 self.no_unchecked_indexed_access,
60 );
61
62 if !self.is_indexable(evaluated_object) {
64 return ElementAccessResult::NotIndexable {
65 type_id: evaluated_object,
66 };
67 }
68
69 if let Some(TypeData::Tuple(elements)) = self.interner.lookup(evaluated_object)
71 && let Some(index) = literal_index
72 {
73 let tuple_elements = self.interner.tuple_list(elements);
74
75 let has_rest = tuple_elements.iter().any(|e| e.rest);
77 if !has_rest && index >= tuple_elements.len() {
78 return ElementAccessResult::IndexOutOfBounds {
79 type_id: evaluated_object,
80 index,
81 length: tuple_elements.len(),
82 };
83 }
84 }
85
86 if result_type == TypeId::UNDEFINED
88 && self.should_report_no_index_signature(evaluated_object, index_type)
89 {
90 return ElementAccessResult::NoIndexSignature {
91 type_id: evaluated_object,
92 };
93 }
94
95 ElementAccessResult::Success(result_type)
96 }
97
98 fn is_indexable(&self, type_id: TypeId) -> bool {
99 match self.interner.lookup(type_id) {
100 Some(
101 TypeData::Array(_)
102 | TypeData::Tuple(_)
103 | TypeData::Object(_)
104 | TypeData::ObjectWithIndex(_)
105 | TypeData::StringIntrinsic { .. }
106 | TypeData::Literal(LiteralValue::String(_))
107 | TypeData::Intersection(_),
108 ) => true,
109 Some(TypeData::Union(members)) => {
110 let members = self.interner.type_list(members);
111 members.iter().all(|&m| self.is_indexable(m))
112 }
113 _ => {
114 if type_id == TypeId::STRING || type_id == TypeId::ANY {
115 return true;
116 }
117 false
118 }
119 }
120 }
121
122 fn should_report_no_index_signature(&self, object_type: TypeId, index_type: TypeId) -> bool {
123 let index_type = evaluate_type(self.interner, index_type);
124 let mut checker = crate::relations::subtype::SubtypeChecker::new(self.interner);
126 match self.interner.lookup(object_type) {
128 Some(TypeData::Object(_)) => {
129 checker.reset();
133 if checker.is_subtype_of(index_type, TypeId::STRING) {
134 return true;
135 }
136 checker.reset();
137 if checker.is_subtype_of(index_type, TypeId::NUMBER) {
138 return true;
139 }
140 false
141 }
142 Some(TypeData::ObjectWithIndex(shape_id)) => {
143 let shape = self.interner.object_shape(shape_id);
144 checker.reset();
145 if checker.is_subtype_of(index_type, TypeId::STRING) && shape.string_index.is_none()
146 {
147 return true;
148 }
149 checker.reset();
151 if checker.is_subtype_of(index_type, TypeId::NUMBER)
152 && shape.number_index.is_none()
153 && shape.string_index.is_none()
154 {
155 return true;
156 }
157 false
158 }
159 _ => false,
160 }
161 }
162}