1use super::property::{PropertyAccessEvaluator, PropertyAccessResult};
7use crate::types::{IntrinsicKind, LiteralValue, ObjectShapeId, TupleListId, TypeId, TypeListId};
8use crate::visitor::TypeVisitor;
9use tsz_common::interner::Atom;
10
11impl<'a> TypeVisitor for &PropertyAccessEvaluator<'a> {
19 type Output = Option<PropertyAccessResult>;
20
21 fn visit_intrinsic(&mut self, kind: IntrinsicKind) -> Self::Output {
22 match kind {
23 IntrinsicKind::Any => Some(PropertyAccessResult::Success {
24 type_id: TypeId::ANY,
25 write_type: None,
26 from_index_signature: false,
27 }),
28 IntrinsicKind::Never => {
29 Some(PropertyAccessResult::Success {
31 type_id: TypeId::NEVER,
32 write_type: None,
33 from_index_signature: false,
34 })
35 }
36 IntrinsicKind::Unknown => Some(PropertyAccessResult::IsUnknown),
37 IntrinsicKind::Void | IntrinsicKind::Null | IntrinsicKind::Undefined => {
38 let cause = if kind == IntrinsicKind::Void || kind == IntrinsicKind::Undefined {
39 TypeId::UNDEFINED
40 } else {
41 TypeId::NULL
42 };
43 Some(PropertyAccessResult::PossiblyNullOrUndefined {
44 property_type: None,
45 cause,
46 })
47 }
48 IntrinsicKind::String => {
50 let prop_name = self.current_prop_name.borrow();
51 let prop_atom = self.current_prop_atom.borrow();
52 match (prop_name.as_deref(), prop_atom.as_ref()) {
53 (Some(name), Some(&atom)) => Some(self.resolve_string_property(name, atom)),
54 _ => None,
55 }
56 }
57 IntrinsicKind::Number => {
58 let prop_name = self.current_prop_name.borrow();
59 let prop_atom = self.current_prop_atom.borrow();
60 match (prop_name.as_deref(), prop_atom.as_ref()) {
61 (Some(name), Some(&atom)) => Some(self.resolve_number_property(name, atom)),
62 _ => None,
63 }
64 }
65 IntrinsicKind::Boolean => {
66 let prop_name = self.current_prop_name.borrow();
67 let prop_atom = self.current_prop_atom.borrow();
68 match (prop_name.as_deref(), prop_atom.as_ref()) {
69 (Some(name), Some(&atom)) => Some(self.resolve_boolean_property(name, atom)),
70 _ => None,
71 }
72 }
73 IntrinsicKind::Bigint => {
74 let prop_name = self.current_prop_name.borrow();
75 let prop_atom = self.current_prop_atom.borrow();
76 match (prop_name.as_deref(), prop_atom.as_ref()) {
77 (Some(name), Some(&atom)) => Some(self.resolve_bigint_property(name, atom)),
78 _ => None,
79 }
80 }
81 IntrinsicKind::Symbol => {
83 let prop_name = self.current_prop_name.borrow();
85 let prop_atom = self.current_prop_atom.borrow();
86 match (prop_name.as_deref(), prop_atom.as_ref()) {
87 (Some(name), Some(&atom)) => {
88 Some(self.resolve_symbol_primitive_property(name, atom))
89 }
90 _ => None,
91 }
92 }
93 _ => None,
95 }
96 }
97
98 fn visit_literal(&mut self, value: &LiteralValue) -> Self::Output {
99 use crate::types::LiteralValue;
100
101 let prop_name = self.current_prop_name.borrow();
102 let prop_atom_opt = self.current_prop_atom.borrow();
103
104 let prop_name = prop_name.as_deref()?;
105 let prop_atom = match prop_atom_opt.as_ref() {
106 Some(&atom) => atom,
107 None => self.interner().intern_string(prop_name),
108 };
109
110 match value {
112 LiteralValue::String(_) => Some(self.resolve_string_property(prop_name, prop_atom)),
113 LiteralValue::Number(_) => Some(self.resolve_number_property(prop_name, prop_atom)),
114 LiteralValue::Boolean(_) => Some(self.resolve_boolean_property(prop_name, prop_atom)),
115 LiteralValue::BigInt(_) => Some(self.resolve_bigint_property(prop_name, prop_atom)),
116 }
117 }
118
119 fn visit_object(&mut self, shape_id: u32) -> Self::Output {
120 use crate::objects::index_signatures::{IndexKind, IndexSignatureResolver};
121
122 let prop_name = self.current_prop_name.borrow();
123 let prop_atom_opt = self.current_prop_atom.borrow();
124
125 let prop_name = prop_name.as_deref()?;
126 let prop_atom = match prop_atom_opt.as_ref() {
127 Some(&atom) => atom,
128 None => self.interner().intern_string(prop_name),
129 };
130
131 let shape = self.interner().object_shape(ObjectShapeId(shape_id));
132
133 if let Some(prop) =
135 self.lookup_object_property(ObjectShapeId(shape_id), &shape.properties, prop_atom)
136 {
137 let write = (prop.write_type != prop.type_id).then_some(prop.write_type);
138 return Some(PropertyAccessResult::Success {
139 type_id: self.optional_property_type(prop),
140 write_type: write,
141 from_index_signature: false,
142 });
143 }
144
145 if let Some(result) = self.resolve_object_member(prop_name, prop_atom) {
147 return Some(result);
148 }
149
150 let resolver = IndexSignatureResolver::new(self.interner());
152
153 let obj_type = self.interner().object_with_flags_and_symbol(
155 self.interner()
156 .object_shape(ObjectShapeId(shape_id))
157 .properties
158 .clone(),
159 self.interner().object_shape(ObjectShapeId(shape_id)).flags,
160 self.interner().object_shape(ObjectShapeId(shape_id)).symbol,
161 );
162
163 if resolver.has_index_signature(obj_type, IndexKind::String)
165 && let Some(value_type) = resolver.resolve_string_index(obj_type)
166 {
167 return Some(PropertyAccessResult::Success {
168 type_id: self.add_undefined_if_unchecked(value_type),
169 write_type: None,
170 from_index_signature: true,
171 });
172 }
173
174 if resolver.is_numeric_index_name(prop_name)
176 && let Some(value_type) = resolver.resolve_number_index(obj_type)
177 {
178 return Some(PropertyAccessResult::Success {
179 type_id: self.add_undefined_if_unchecked(value_type),
180 write_type: None,
181 from_index_signature: true,
182 });
183 }
184
185 Some(PropertyAccessResult::PropertyNotFound {
186 type_id: obj_type,
187 property_name: prop_atom,
188 })
189 }
190
191 fn visit_object_with_index(&mut self, shape_id: u32) -> Self::Output {
192 use crate::objects::index_signatures::IndexSignatureResolver;
193
194 let prop_name = self.current_prop_name.borrow();
195 let prop_atom_opt = self.current_prop_atom.borrow();
196
197 let prop_name = prop_name.as_deref()?;
198 let prop_atom = match prop_atom_opt.as_ref() {
199 Some(&atom) => atom,
200 None => self.interner().intern_string(prop_name),
201 };
202
203 let shape = self.interner().object_shape(ObjectShapeId(shape_id));
204
205 if let Some(prop) =
207 self.lookup_object_property(ObjectShapeId(shape_id), &shape.properties, prop_atom)
208 {
209 let write = (prop.write_type != prop.type_id).then_some(prop.write_type);
210 return Some(PropertyAccessResult::Success {
211 type_id: self.optional_property_type(prop),
212 write_type: write,
213 from_index_signature: false,
214 });
215 }
216
217 if let Some(result) = self.resolve_object_member(prop_name, prop_atom) {
219 return Some(result);
220 }
221
222 if let Some(ref idx) = shape.string_index {
224 return Some(PropertyAccessResult::Success {
225 type_id: self.add_undefined_if_unchecked(idx.value_type),
226 write_type: None,
227 from_index_signature: true,
228 });
229 }
230
231 let resolver = IndexSignatureResolver::new(self.interner());
233 if resolver.is_numeric_index_name(prop_name)
234 && let Some(ref idx) = shape.number_index
235 {
236 return Some(PropertyAccessResult::Success {
237 type_id: self.add_undefined_if_unchecked(idx.value_type),
238 write_type: None,
239 from_index_signature: true,
240 });
241 }
242
243 let obj_type = self.interner().object_with_index(
245 self.interner()
246 .object_shape(ObjectShapeId(shape_id))
247 .as_ref()
248 .clone(),
249 );
250
251 Some(PropertyAccessResult::PropertyNotFound {
252 type_id: obj_type,
253 property_name: prop_atom,
254 })
255 }
256
257 fn visit_array(&mut self, element_type: TypeId) -> Self::Output {
258 let prop_name = self.current_prop_name.borrow();
259 let prop_atom_opt = self.current_prop_atom.borrow();
260
261 let prop_name = prop_name.as_deref()?;
262 let prop_atom = match prop_atom_opt.as_ref() {
263 Some(&atom) => atom,
264 None => self.interner().intern_string(prop_name),
265 };
266
267 let obj_type = self.interner().array(element_type);
269 Some(self.resolve_array_property(obj_type, prop_name, prop_atom))
270 }
271
272 fn visit_tuple(&mut self, list_id: u32) -> Self::Output {
273 let prop_name = self.current_prop_name.borrow();
274 let prop_atom_opt = self.current_prop_atom.borrow();
275
276 let prop_name = prop_name.as_deref()?;
277 let prop_atom = match prop_atom_opt.as_ref() {
278 Some(&atom) => atom,
279 None => self.interner().intern_string(prop_name),
280 };
281
282 let obj_type = self
284 .interner()
285 .tuple(self.interner().tuple_list(TupleListId(list_id)).to_vec());
286 Some(self.resolve_array_property(obj_type, prop_name, prop_atom))
287 }
288
289 fn visit_template_literal(&mut self, _template_id: u32) -> Self::Output {
290 let prop_name = self.current_prop_name.borrow();
293 let prop_atom_opt = self.current_prop_atom.borrow();
294
295 let prop_name = prop_name.as_deref()?;
296 let prop_atom = match prop_atom_opt.as_ref() {
297 Some(&atom) => atom,
298 None => self.interner().intern_string(prop_name),
299 };
300
301 Some(self.resolve_string_property(prop_name, prop_atom))
302 }
303
304 fn visit_string_intrinsic(
305 &mut self,
306 _kind: crate::types::StringIntrinsicKind,
307 _type_arg: TypeId,
308 ) -> Self::Output {
309 let prop_name = self.current_prop_name.borrow();
312 let prop_atom_opt = self.current_prop_atom.borrow();
313
314 let prop_name = prop_name.as_deref()?;
315 let prop_atom = match prop_atom_opt.as_ref() {
316 Some(&atom) => atom,
317 None => self.interner().intern_string(prop_name),
318 };
319
320 Some(self.resolve_string_property(prop_name, prop_atom))
321 }
322
323 fn visit_union(&mut self, list_id: u32) -> Self::Output {
324 let prop_name = self.current_prop_name.borrow();
325 let prop_atom_opt = self.current_prop_atom.borrow();
326
327 let prop_name = prop_name.as_deref()?;
328
329 self.visit_union_impl(list_id, prop_name, prop_atom_opt.as_ref().copied())
330 }
331
332 fn default_output() -> Self::Output {
333 None
334 }
335}
336
337impl<'a> PropertyAccessEvaluator<'a> {
338 pub(crate) fn visit_object_impl(
342 &self,
343 shape_id: u32,
344 prop_name: &str,
345 prop_atom: Option<Atom>,
346 ) -> Option<PropertyAccessResult> {
347 use crate::objects::index_signatures::{IndexKind, IndexSignatureResolver};
348
349 let prop_atom = match prop_atom {
350 Some(atom) => atom,
351 None => self.interner().intern_string(prop_name),
352 };
353
354 let shape = self.interner().object_shape(ObjectShapeId(shape_id));
355
356 if let Some(prop) =
358 self.lookup_object_property(ObjectShapeId(shape_id), &shape.properties, prop_atom)
359 {
360 let write = (prop.write_type != prop.type_id).then_some(prop.write_type);
361 return Some(PropertyAccessResult::Success {
362 type_id: self.optional_property_type(prop),
363 write_type: write,
364 from_index_signature: false,
365 });
366 }
367
368 if let Some(result) = self.resolve_object_member(prop_name, prop_atom) {
370 return Some(result);
371 }
372
373 let resolver = IndexSignatureResolver::new(self.interner());
375
376 let obj_type = self.interner().object_with_flags_and_symbol(
378 self.interner()
379 .object_shape(ObjectShapeId(shape_id))
380 .properties
381 .clone(),
382 self.interner().object_shape(ObjectShapeId(shape_id)).flags,
383 self.interner().object_shape(ObjectShapeId(shape_id)).symbol,
384 );
385
386 if resolver.has_index_signature(obj_type, IndexKind::String)
388 && let Some(value_type) = resolver.resolve_string_index(obj_type)
389 {
390 return Some(PropertyAccessResult::Success {
391 type_id: self.add_undefined_if_unchecked(value_type),
392 write_type: None,
393 from_index_signature: true,
394 });
395 }
396
397 if resolver.is_numeric_index_name(prop_name)
399 && let Some(value_type) = resolver.resolve_number_index(obj_type)
400 {
401 return Some(PropertyAccessResult::Success {
402 type_id: self.add_undefined_if_unchecked(value_type),
403 write_type: None,
404 from_index_signature: true,
405 });
406 }
407
408 Some(PropertyAccessResult::PropertyNotFound {
409 type_id: obj_type,
410 property_name: prop_atom,
411 })
412 }
413
414 pub(crate) fn visit_object_with_index_impl(
415 &self,
416 shape_id: u32,
417 prop_name: &str,
418 prop_atom: Option<Atom>,
419 ) -> Option<PropertyAccessResult> {
420 use crate::objects::index_signatures::IndexSignatureResolver;
421
422 let prop_atom = match prop_atom {
423 Some(atom) => atom,
424 None => self.interner().intern_string(prop_name),
425 };
426
427 let shape = self.interner().object_shape(ObjectShapeId(shape_id));
428
429 if let Some(prop) =
431 self.lookup_object_property(ObjectShapeId(shape_id), &shape.properties, prop_atom)
432 {
433 let write = (prop.write_type != prop.type_id).then_some(prop.write_type);
434 return Some(PropertyAccessResult::Success {
435 type_id: self.optional_property_type(prop),
436 write_type: write,
437 from_index_signature: false,
438 });
439 }
440
441 if let Some(result) = self.resolve_object_member(prop_name, prop_atom) {
443 return Some(result);
444 }
445
446 if let Some(ref idx) = shape.string_index {
448 return Some(PropertyAccessResult::Success {
449 type_id: self.add_undefined_if_unchecked(idx.value_type),
450 write_type: None,
451 from_index_signature: true,
452 });
453 }
454
455 let resolver = IndexSignatureResolver::new(self.interner());
457 if resolver.is_numeric_index_name(prop_name)
458 && let Some(ref idx) = shape.number_index
459 {
460 return Some(PropertyAccessResult::Success {
461 type_id: self.add_undefined_if_unchecked(idx.value_type),
462 write_type: None,
463 from_index_signature: true,
464 });
465 }
466
467 let obj_type = self.interner().object_with_index((*shape).clone());
469
470 Some(PropertyAccessResult::PropertyNotFound {
471 type_id: obj_type,
472 property_name: prop_atom,
473 })
474 }
475
476 pub(crate) fn visit_array_impl(
477 &self,
478 obj_type: TypeId,
479 prop_name: &str,
480 prop_atom: Option<Atom>,
481 ) -> Option<PropertyAccessResult> {
482 let prop_atom = match prop_atom {
483 Some(atom) => atom,
484 None => self.interner().intern_string(prop_name),
485 };
486 Some(self.resolve_array_property(obj_type, prop_name, prop_atom))
487 }
488
489 pub(crate) fn visit_union_impl(
490 &self,
491 list_id: u32,
492 prop_name: &str,
493 prop_atom: Option<Atom>,
494 ) -> Option<PropertyAccessResult> {
495 use crate::objects::index_signatures::{IndexKind, IndexSignatureResolver};
496
497 let members = self.interner().type_list(crate::types::TypeListId(list_id));
498
499 if members.contains(&TypeId::ANY) {
501 return Some(PropertyAccessResult::Success {
502 type_id: TypeId::ANY,
503 write_type: None,
504 from_index_signature: false,
505 });
506 }
507
508 if members.contains(&TypeId::ERROR) {
510 return Some(PropertyAccessResult::Success {
511 type_id: TypeId::ERROR,
512 write_type: None,
513 from_index_signature: false,
514 });
515 }
516
517 let non_unknown_members: Vec<_> = members
520 .iter()
521 .filter(|&&t| t != TypeId::UNKNOWN)
522 .copied()
523 .collect();
524
525 if non_unknown_members.is_empty() {
526 return Some(PropertyAccessResult::IsUnknown);
528 }
529
530 let mut obj_type_cache: Option<TypeId> = None;
533 let mut obj_type_for_error = || {
534 *obj_type_cache.get_or_insert_with(|| {
535 self.interner()
536 .union(self.interner().type_list(TypeListId(list_id)).to_vec())
537 })
538 };
539
540 let prop_atom = match prop_atom {
541 Some(atom) => atom,
542 None => self.interner().intern_string(prop_name),
543 };
544
545 let mut valid_results = Vec::new();
547 let mut valid_write_results = Vec::new();
548 let mut any_has_divergent_write_type = false;
549 let mut nullable_causes = Vec::new();
550 let mut any_from_index = false; for &member in &non_unknown_members {
553 if member.is_nullable() {
555 let cause = if member == TypeId::VOID {
556 TypeId::UNDEFINED
557 } else {
558 member
559 };
560 nullable_causes.push(cause);
561 continue;
562 }
563
564 match self.resolve_property_access_inner(member, prop_name, Some(prop_atom)) {
565 PropertyAccessResult::Success {
566 type_id,
567 write_type,
568 from_index_signature,
569 } => {
570 valid_results.push(type_id);
571 if let Some(wt) = write_type {
572 valid_write_results.push(wt);
573 any_has_divergent_write_type = true;
574 } else {
575 valid_write_results.push(type_id);
576 }
577 if from_index_signature {
578 any_from_index = true; }
580 }
581 PropertyAccessResult::PossiblyNullOrUndefined {
582 property_type,
583 cause,
584 } => {
585 if let Some(t) = property_type {
586 valid_results.push(t);
587 valid_write_results.push(t);
588 }
589 nullable_causes.push(cause);
590 }
591 PropertyAccessResult::PropertyNotFound { .. } => {
593 return Some(PropertyAccessResult::PropertyNotFound {
594 type_id: obj_type_for_error(),
595 property_name: prop_atom,
596 });
597 }
598 PropertyAccessResult::IsUnknown => {
600 return Some(PropertyAccessResult::IsUnknown);
601 }
602 }
603 }
604
605 if valid_results.is_empty() && nullable_causes.is_empty() {
607 let resolver = IndexSignatureResolver::new(self.interner());
609 let obj_type = obj_type_for_error();
610
611 if resolver.has_index_signature(obj_type, IndexKind::String)
612 && let Some(value_type) = resolver.resolve_string_index(obj_type)
613 {
614 return Some(PropertyAccessResult::Success {
615 type_id: self.add_undefined_if_unchecked(value_type),
616 write_type: None,
617 from_index_signature: true,
618 });
619 }
620
621 if resolver.is_numeric_index_name(prop_name)
622 && let Some(value_type) = resolver.resolve_number_index(obj_type)
623 {
624 return Some(PropertyAccessResult::Success {
625 type_id: self.add_undefined_if_unchecked(value_type),
626 write_type: None,
627 from_index_signature: true,
628 });
629 }
630
631 return Some(PropertyAccessResult::PropertyNotFound {
632 type_id: obj_type,
633 property_name: prop_atom,
634 });
635 }
636
637 if !nullable_causes.is_empty() {
639 let cause = if nullable_causes.len() == 1 {
640 nullable_causes[0]
641 } else {
642 self.interner().union(nullable_causes)
643 };
644
645 let mut property_type = if valid_results.is_empty() {
646 None
647 } else if valid_results.len() == 1 {
648 Some(valid_results[0])
649 } else {
650 Some(self.interner().union(valid_results))
651 };
652
653 if any_from_index
654 && self.no_unchecked_indexed_access
655 && let Some(t) = property_type
656 {
657 property_type = Some(self.add_undefined_if_unchecked(t));
658 }
659
660 return Some(PropertyAccessResult::PossiblyNullOrUndefined {
661 property_type,
662 cause,
663 });
664 }
665
666 let mut type_id = self.interner().union(valid_results);
667 if any_from_index && self.no_unchecked_indexed_access {
668 type_id = self.add_undefined_if_unchecked(type_id);
669 }
670
671 let write_type = if any_has_divergent_write_type {
672 let mut wt = self.interner().union(valid_write_results);
673 if any_from_index && self.no_unchecked_indexed_access {
674 wt = self.add_undefined_if_unchecked(wt);
675 }
676 if wt != type_id { Some(wt) } else { None }
677 } else {
678 None
679 };
680
681 Some(PropertyAccessResult::Success {
683 type_id,
684 write_type,
685 from_index_signature: any_from_index, })
687 }
688}