1use crate::types::{IndexInfo, IndexSignature, ObjectShapeId};
31use crate::utils;
32use crate::visitor::TypeVisitor;
33use crate::{TypeDatabase, TypeId};
34
35#[derive(Debug, Clone, Copy, PartialEq, Eq)]
37pub enum IndexKind {
38 String,
40 Number,
42}
43
44struct StringIndexResolver<'a> {
50 db: &'a dyn TypeDatabase,
51}
52
53impl<'a> TypeVisitor for StringIndexResolver<'a> {
54 type Output = Option<TypeId>;
55
56 fn visit_intrinsic(&mut self, _kind: crate::types::IntrinsicKind) -> Self::Output {
57 None
58 }
59
60 fn visit_literal(&mut self, _value: &crate::LiteralValue) -> Self::Output {
61 None
62 }
63
64 fn visit_object_with_index(&mut self, shape_id: u32) -> Self::Output {
65 let shape = self.db.object_shape(ObjectShapeId(shape_id));
66 shape.string_index.as_ref().map(|idx| idx.value_type)
67 }
68
69 fn visit_array(&mut self, element_type: TypeId) -> Self::Output {
70 Some(element_type)
72 }
73
74 fn visit_tuple(&mut self, _list_id: u32) -> Self::Output {
75 Some(TypeId::UNKNOWN)
77 }
78
79 fn visit_union(&mut self, list_id: u32) -> Self::Output {
80 let types = self.db.type_list(crate::types::TypeListId(list_id));
81 types.iter().find_map(|&t| self.visit_type(self.db, t))
82 }
83
84 fn visit_intersection(&mut self, list_id: u32) -> Self::Output {
85 let types = self.db.type_list(crate::types::TypeListId(list_id));
86 types.first().and_then(|&t| self.visit_type(self.db, t))
88 }
89
90 fn visit_readonly_type(&mut self, inner_type: TypeId) -> Self::Output {
91 self.visit_type(self.db, inner_type)
92 }
93
94 fn default_output() -> Self::Output {
95 None
96 }
97}
98
99struct NumberIndexResolver<'a> {
101 db: &'a dyn TypeDatabase,
102}
103
104impl<'a> TypeVisitor for NumberIndexResolver<'a> {
105 type Output = Option<TypeId>;
106
107 fn visit_intrinsic(&mut self, _kind: crate::types::IntrinsicKind) -> Self::Output {
108 None
109 }
110
111 fn visit_literal(&mut self, _value: &crate::LiteralValue) -> Self::Output {
112 None
113 }
114
115 fn visit_object_with_index(&mut self, shape_id: u32) -> Self::Output {
116 let shape = self.db.object_shape(ObjectShapeId(shape_id));
117 shape.number_index.as_ref().map(|idx| idx.value_type)
118 }
119
120 fn visit_array(&mut self, element_type: TypeId) -> Self::Output {
121 Some(element_type)
122 }
123
124 fn visit_tuple(&mut self, _list_id: u32) -> Self::Output {
125 Some(TypeId::UNKNOWN)
126 }
127
128 fn visit_union(&mut self, list_id: u32) -> Self::Output {
129 let types = self.db.type_list(crate::types::TypeListId(list_id));
130 types.iter().find_map(|&t| self.visit_type(self.db, t))
131 }
132
133 fn visit_intersection(&mut self, list_id: u32) -> Self::Output {
134 let types = self.db.type_list(crate::types::TypeListId(list_id));
135 types.first().and_then(|&t| self.visit_type(self.db, t))
136 }
137
138 fn visit_readonly_type(&mut self, inner_type: TypeId) -> Self::Output {
139 self.visit_type(self.db, inner_type)
140 }
141
142 fn default_output() -> Self::Output {
143 None
144 }
145}
146
147struct ReadonlyChecker<'a> {
149 db: &'a dyn TypeDatabase,
150 kind: IndexKind,
151}
152
153impl<'a> TypeVisitor for ReadonlyChecker<'a> {
154 type Output = bool;
155
156 fn visit_intrinsic(&mut self, _kind: crate::types::IntrinsicKind) -> Self::Output {
157 false
158 }
159
160 fn visit_literal(&mut self, _value: &crate::LiteralValue) -> Self::Output {
161 false
162 }
163
164 fn visit_array(&mut self, _element_type: TypeId) -> Self::Output {
165 false }
167
168 fn visit_tuple(&mut self, _list_id: u32) -> Self::Output {
169 false }
171
172 fn visit_object_with_index(&mut self, shape_id: u32) -> Self::Output {
173 let shape = self.db.object_shape(ObjectShapeId(shape_id));
174 match self.kind {
175 IndexKind::String => shape.string_index.as_ref().is_some_and(|idx| idx.readonly),
176 IndexKind::Number => shape.number_index.as_ref().is_some_and(|idx| idx.readonly),
177 }
178 }
179
180 fn visit_union(&mut self, list_id: u32) -> Self::Output {
181 let types = self.db.type_list(crate::types::TypeListId(list_id));
182 types.iter().any(|&t| self.visit_type(self.db, t))
184 }
185
186 fn visit_intersection(&mut self, list_id: u32) -> Self::Output {
187 let types = self.db.type_list(crate::types::TypeListId(list_id));
188 types.iter().all(|&t| self.visit_type(self.db, t))
190 }
191
192 fn visit_readonly_type(&mut self, inner_type: TypeId) -> Self::Output {
193 self.visit_type(self.db, inner_type)
194 }
195
196 fn visit_lazy(&mut self, def_id: u32) -> Self::Output {
197 let resolved = crate::evaluation::evaluate::evaluate_type(self.db, TypeId(def_id));
199 self.visit_type(self.db, resolved)
200 }
201
202 fn default_output() -> Self::Output {
203 false
204 }
205}
206
207struct IndexInfoCollector<'a> {
209 db: &'a dyn TypeDatabase,
210}
211
212impl<'a> TypeVisitor for IndexInfoCollector<'a> {
213 type Output = IndexInfo;
214
215 fn visit_intrinsic(&mut self, _kind: crate::types::IntrinsicKind) -> Self::Output {
216 IndexInfo {
217 string_index: None,
218 number_index: None,
219 }
220 }
221
222 fn visit_literal(&mut self, _value: &crate::LiteralValue) -> Self::Output {
223 IndexInfo {
224 string_index: None,
225 number_index: None,
226 }
227 }
228
229 fn visit_object_with_index(&mut self, shape_id: u32) -> Self::Output {
230 let shape = self.db.object_shape(ObjectShapeId(shape_id));
231 IndexInfo {
232 string_index: shape.string_index.clone(),
233 number_index: shape.number_index.clone(),
234 }
235 }
236
237 fn visit_array(&mut self, elem: TypeId) -> Self::Output {
238 IndexInfo {
239 string_index: None,
240 number_index: Some(IndexSignature {
241 key_type: TypeId::NUMBER,
242 value_type: elem,
243 readonly: false,
244 }),
245 }
246 }
247
248 fn visit_tuple(&mut self, _list_id: u32) -> Self::Output {
249 IndexInfo {
250 string_index: None,
251 number_index: Some(IndexSignature {
252 key_type: TypeId::NUMBER,
253 value_type: TypeId::UNKNOWN,
254 readonly: false,
255 }),
256 }
257 }
258
259 fn visit_readonly_type(&mut self, inner_type: TypeId) -> Self::Output {
260 let mut info = self.visit_type(self.db, inner_type);
261 if let Some(idx) = &mut info.string_index {
263 idx.readonly = true;
264 }
265 if let Some(idx) = &mut info.number_index {
266 idx.readonly = true;
267 }
268 info
269 }
270
271 fn visit_union(&mut self, _list_id: u32) -> Self::Output {
272 IndexInfo {
274 string_index: None,
275 number_index: None,
276 }
277 }
278
279 fn visit_intersection(&mut self, _list_id: u32) -> Self::Output {
280 IndexInfo {
281 string_index: None,
282 number_index: None,
283 }
284 }
285
286 fn visit_lazy(&mut self, def_id: u32) -> Self::Output {
287 let resolved = crate::evaluation::evaluate::evaluate_type(self.db, TypeId(def_id));
289 self.visit_type(self.db, resolved)
290 }
291
292 fn default_output() -> Self::Output {
293 IndexInfo {
294 string_index: None,
295 number_index: None,
296 }
297 }
298}
299
300pub struct IndexSignatureResolver<'a> {
305 db: &'a dyn TypeDatabase,
306}
307
308impl<'a> IndexSignatureResolver<'a> {
309 pub fn new(db: &'a dyn TypeDatabase) -> Self {
311 Self { db }
312 }
313
314 pub fn resolve_string_index(&self, obj: TypeId) -> Option<TypeId> {
325 let mut visitor = StringIndexResolver { db: self.db };
326 visitor.visit_type(self.db, obj)
327 }
328
329 pub fn resolve_number_index(&self, obj: TypeId) -> Option<TypeId> {
342 let mut visitor = NumberIndexResolver { db: self.db };
343 visitor.visit_type(self.db, obj)
344 }
345
346 pub fn is_readonly(&self, obj: TypeId, kind: IndexKind) -> bool {
362 let mut visitor = ReadonlyChecker { db: self.db, kind };
363 visitor.visit_type(self.db, obj)
364 }
365
366 pub fn get_index_info(&self, obj: TypeId) -> IndexInfo {
371 let mut collector = IndexInfoCollector { db: self.db };
372 collector.visit_type(self.db, obj)
373 }
374
375 pub fn has_index_signature(&self, obj: TypeId, kind: IndexKind) -> bool {
386 match kind {
387 IndexKind::String => self.resolve_string_index(obj).is_some(),
388 IndexKind::Number => self.resolve_number_index(obj).is_some(),
389 }
390 }
391
392 pub fn is_numeric_index_name(&self, name: &str) -> bool {
403 if name.starts_with('-') && name != "-Infinity" {
404 return false;
405 }
406 utils::is_numeric_literal_name(name)
407 }
408}
409
410#[cfg(test)]
411#[path = "../../tests/index_signatures_tests.rs"]
412mod tests;