1use crate::TypeDatabase;
8use crate::type_queries::{
9 StringLiteralKeyKind, classify_for_string_literal_keys, get_string_literal_value,
10 get_union_members, is_invokable_type,
11};
12use crate::types::{TypeData, TypeId};
13use tsz_common::Atom;
14
15#[derive(Debug, Clone)]
22pub enum PredicateSignatureKind {
23 Function(crate::types::FunctionShapeId),
25 Callable(crate::types::CallableShapeId),
27 Union(Vec<TypeId>),
29 Intersection(Vec<TypeId>),
31 None,
33}
34
35pub fn classify_for_predicate_signature(
37 db: &dyn TypeDatabase,
38 type_id: TypeId,
39) -> PredicateSignatureKind {
40 let Some(key) = db.lookup(type_id) else {
41 return PredicateSignatureKind::None;
42 };
43
44 match key {
45 TypeData::Function(shape_id) => PredicateSignatureKind::Function(shape_id),
46 TypeData::Callable(shape_id) => PredicateSignatureKind::Callable(shape_id),
47 TypeData::Union(members_id) => {
48 let members = db.type_list(members_id);
49 PredicateSignatureKind::Union(members.to_vec())
50 }
51 TypeData::Intersection(members_id) => {
52 let members = db.type_list(members_id);
53 PredicateSignatureKind::Intersection(members.to_vec())
54 }
55 _ => PredicateSignatureKind::None,
56 }
57}
58
59#[derive(Debug, Clone)]
62pub enum ConstructorInstanceKind {
63 Callable(crate::types::CallableShapeId),
65 Union(Vec<TypeId>),
67 Intersection(Vec<TypeId>),
69 None,
71}
72
73pub fn classify_for_constructor_instance(
75 db: &dyn TypeDatabase,
76 type_id: TypeId,
77) -> ConstructorInstanceKind {
78 let Some(key) = db.lookup(type_id) else {
79 return ConstructorInstanceKind::None;
80 };
81
82 match key {
83 TypeData::Callable(shape_id) => ConstructorInstanceKind::Callable(shape_id),
84 TypeData::Union(members_id) => {
85 let members = db.type_list(members_id);
86 ConstructorInstanceKind::Union(members.to_vec())
87 }
88 TypeData::Intersection(members_id) => {
89 let members = db.type_list(members_id);
90 ConstructorInstanceKind::Intersection(members.to_vec())
91 }
92 _ => ConstructorInstanceKind::None,
93 }
94}
95
96pub fn instance_type_from_constructor(db: &dyn TypeDatabase, type_id: TypeId) -> Option<TypeId> {
102 if type_id == TypeId::ANY || type_id == TypeId::UNKNOWN {
103 return Some(type_id);
104 }
105
106 match classify_for_constructor_instance(db, type_id) {
107 ConstructorInstanceKind::Callable(shape_id) => {
108 let shape = db.callable_shape(shape_id);
109 if shape.construct_signatures.is_empty() {
110 return None;
111 }
112 let returns: Vec<TypeId> = shape
113 .construct_signatures
114 .iter()
115 .map(|s| s.return_type)
116 .collect();
117 Some(if returns.len() == 1 {
118 returns[0]
119 } else {
120 db.union(returns)
121 })
122 }
123 ConstructorInstanceKind::Union(members) => {
124 let instance_types: Vec<TypeId> = members
125 .into_iter()
126 .filter_map(|m| instance_type_from_constructor(db, m))
127 .collect();
128 if instance_types.is_empty() {
129 None
130 } else if instance_types.len() == 1 {
131 Some(instance_types[0])
132 } else {
133 Some(db.union(instance_types))
134 }
135 }
136 ConstructorInstanceKind::Intersection(members) => {
137 members
139 .into_iter()
140 .find_map(|m| instance_type_from_constructor(db, m))
141 }
142 ConstructorInstanceKind::None => None,
143 }
144}
145
146#[derive(Debug, Clone)]
149pub enum TypeParameterConstraintKind {
150 TypeParameter { constraint: Option<TypeId> },
152 None,
154}
155
156pub fn classify_for_type_parameter_constraint(
158 db: &dyn TypeDatabase,
159 type_id: TypeId,
160) -> TypeParameterConstraintKind {
161 let Some(key) = db.lookup(type_id) else {
162 return TypeParameterConstraintKind::None;
163 };
164
165 match key {
166 TypeData::TypeParameter(info) | TypeData::Infer(info) => {
167 TypeParameterConstraintKind::TypeParameter {
168 constraint: info.constraint,
169 }
170 }
171 _ => TypeParameterConstraintKind::None,
172 }
173}
174
175#[derive(Debug, Clone)]
178pub enum UnionMembersKind {
179 Union(Vec<TypeId>),
181 NotUnion,
183}
184
185pub fn classify_for_union_members(db: &dyn TypeDatabase, type_id: TypeId) -> UnionMembersKind {
187 let Some(key) = db.lookup(type_id) else {
188 return UnionMembersKind::NotUnion;
189 };
190
191 match key {
192 TypeData::Union(members_id) => {
193 let members = db.type_list(members_id);
194 UnionMembersKind::Union(members.to_vec())
195 }
196 _ => UnionMembersKind::NotUnion,
197 }
198}
199
200#[derive(Debug, Clone)]
203pub enum NonObjectKind {
204 Literal,
206 IntrinsicPrimitive,
208 MaybeObject,
210}
211
212pub fn classify_for_non_object(db: &dyn TypeDatabase, type_id: TypeId) -> NonObjectKind {
214 let Some(key) = db.lookup(type_id) else {
215 return NonObjectKind::MaybeObject;
216 };
217
218 match key {
219 TypeData::Literal(_) => NonObjectKind::Literal,
220 TypeData::Intrinsic(kind) => {
221 use crate::IntrinsicKind;
222
223 match kind {
224 IntrinsicKind::Void
225 | IntrinsicKind::Undefined
226 | IntrinsicKind::Null
227 | IntrinsicKind::Boolean
228 | IntrinsicKind::Number
229 | IntrinsicKind::String
230 | IntrinsicKind::Bigint
231 | IntrinsicKind::Symbol
232 | IntrinsicKind::Never => NonObjectKind::IntrinsicPrimitive,
233 _ => NonObjectKind::MaybeObject,
234 }
235 }
236 _ => NonObjectKind::MaybeObject,
237 }
238}
239
240#[derive(Debug, Clone)]
243pub enum PropertyPresenceKind {
244 IntrinsicObject,
246 Object(crate::types::ObjectShapeId),
248 Callable(crate::types::CallableShapeId),
250 ArrayLike,
252 Unknown,
254}
255
256pub fn classify_for_property_presence(
258 db: &dyn TypeDatabase,
259 type_id: TypeId,
260) -> PropertyPresenceKind {
261 let Some(key) = db.lookup(type_id) else {
262 return PropertyPresenceKind::Unknown;
263 };
264
265 match key {
266 TypeData::Intrinsic(crate::IntrinsicKind::Object) => PropertyPresenceKind::IntrinsicObject,
267 TypeData::Object(shape_id) | TypeData::ObjectWithIndex(shape_id) => {
268 PropertyPresenceKind::Object(shape_id)
269 }
270 TypeData::Callable(shape_id) => PropertyPresenceKind::Callable(shape_id),
271 TypeData::Array(_) | TypeData::Tuple(_) => PropertyPresenceKind::ArrayLike,
272 _ => PropertyPresenceKind::Unknown,
273 }
274}
275
276#[derive(Debug, Clone)]
279pub enum FalsyComponentKind {
280 Literal(crate::LiteralValue),
282 Union(Vec<TypeId>),
284 TypeParameter,
286 None,
288}
289
290pub fn classify_for_falsy_component(db: &dyn TypeDatabase, type_id: TypeId) -> FalsyComponentKind {
292 let Some(key) = db.lookup(type_id) else {
293 return FalsyComponentKind::None;
294 };
295
296 match key {
297 TypeData::Literal(literal) => FalsyComponentKind::Literal(literal),
298 TypeData::Union(members_id) => {
299 let members = db.type_list(members_id);
300 FalsyComponentKind::Union(members.to_vec())
301 }
302 TypeData::TypeParameter(_) | TypeData::Infer(_) => FalsyComponentKind::TypeParameter,
303 _ => FalsyComponentKind::None,
304 }
305}
306
307#[derive(Debug, Clone)]
310pub enum LiteralValueKind {
311 String(tsz_common::interner::Atom),
313 Number(f64),
315 None,
317}
318
319pub fn classify_for_literal_value(db: &dyn TypeDatabase, type_id: TypeId) -> LiteralValueKind {
321 let Some(key) = db.lookup(type_id) else {
322 return LiteralValueKind::None;
323 };
324
325 match key {
326 TypeData::Literal(crate::LiteralValue::String(atom)) => LiteralValueKind::String(atom),
327 TypeData::Literal(crate::LiteralValue::Number(num)) => LiteralValueKind::Number(num.0),
328 _ => LiteralValueKind::None,
329 }
330}
331
332pub fn is_narrowing_literal(db: &dyn TypeDatabase, type_id: TypeId) -> Option<TypeId> {
341 if type_id == TypeId::NULL || type_id == TypeId::UNDEFINED {
343 return Some(type_id);
344 }
345 let key = db.lookup(type_id)?;
346 match key {
347 TypeData::Literal(_) | TypeData::Enum(_, _) => Some(type_id),
348 _ => None,
349 }
350}
351
352pub fn is_unit_type(db: &dyn TypeDatabase, type_id: TypeId) -> bool {
362 if type_id == TypeId::NULL
363 || type_id == TypeId::UNDEFINED
364 || type_id == TypeId::BOOLEAN_TRUE
365 || type_id == TypeId::BOOLEAN_FALSE
366 {
367 return true;
368 }
369
370 match db.lookup(type_id) {
371 Some(TypeData::Literal(_))
372 | Some(TypeData::Enum(_, _))
373 | Some(TypeData::UniqueSymbol(_)) => true,
374 Some(TypeData::Union(list_id)) => {
375 let members = db.type_list(list_id);
376 members.iter().all(|&m| is_unit_type(db, m))
377 }
378 _ => false,
379 }
380}
381
382pub fn union_contains(db: &dyn TypeDatabase, type_id: TypeId, target: TypeId) -> bool {
384 if let Some(members) = get_union_members(db, type_id) {
385 members.contains(&target)
386 } else {
387 false
388 }
389}
390
391pub fn type_includes_undefined(db: &dyn TypeDatabase, type_id: TypeId) -> bool {
393 type_id == TypeId::UNDEFINED || union_contains(db, type_id, TypeId::UNDEFINED)
394}
395
396pub fn extract_string_literal_keys(
400 db: &dyn TypeDatabase,
401 type_id: TypeId,
402) -> Vec<tsz_common::interner::Atom> {
403 match classify_for_string_literal_keys(db, type_id) {
404 StringLiteralKeyKind::SingleString(name) => vec![name],
405 StringLiteralKeyKind::Union(members) => members
406 .iter()
407 .filter_map(|&member| get_string_literal_value(db, member))
408 .collect(),
409 StringLiteralKeyKind::NotStringLiteral => Vec::new(),
410 }
411}
412
413pub fn get_return_type(db: &dyn TypeDatabase, type_id: TypeId) -> Option<TypeId> {
434 match db.lookup(type_id) {
435 Some(TypeData::Function(shape_id)) => Some(db.function_shape(shape_id).return_type),
436 Some(TypeData::Callable(shape_id)) => {
437 let shape = db.callable_shape(shape_id);
438 shape.call_signatures.first().map(|sig| sig.return_type)
440 }
441 Some(TypeData::Intersection(list_id)) => {
442 let members = db.type_list(list_id);
444 members.iter().find_map(|&m| get_return_type(db, m))
445 }
446 _ => {
447 if type_id == TypeId::ANY {
449 Some(TypeId::ANY)
450 } else if type_id == TypeId::NEVER {
451 Some(TypeId::NEVER)
452 } else {
453 None
454 }
455 }
456 }
457}
458
459use crate::operations::property::PropertyAccessEvaluator;
464
465pub fn is_promise_like(db: &dyn crate::caches::db::QueryDatabase, type_id: TypeId) -> bool {
494 if type_id == TypeId::ANY {
496 return true;
497 }
498
499 let evaluator = PropertyAccessEvaluator::new(db);
502 evaluator
503 .resolve_property_access(type_id, "then")
504 .success_type()
505 .is_some_and(|then_type| {
506 is_invokable_type(db, then_type)
509 })
510}
511
512pub fn is_valid_for_in_target(db: &dyn TypeDatabase, type_id: TypeId) -> bool {
543 if type_id == TypeId::ANY {
545 return true;
546 }
547
548 if type_id == TypeId::STRING || type_id == TypeId::NUMBER || type_id == TypeId::BOOLEAN {
550 return true;
551 }
552
553 use crate::types::IntrinsicKind;
554 match db.lookup(type_id) {
555 Some(TypeData::Object(_) | TypeData::ObjectWithIndex(_))
557 | Some(TypeData::Array(_))
558 | Some(TypeData::TypeParameter(_))
559 | Some(TypeData::Tuple(_))
560 | Some(TypeData::Literal(_)) => true,
561 Some(TypeData::Union(list_id)) => {
563 let members = db.type_list(list_id);
564 members.iter().all(|&m| is_valid_for_in_target(db, m))
565 }
566 Some(TypeData::Intersection(list_id)) => {
568 let members = db.type_list(list_id);
569 members.iter().any(|&m| is_valid_for_in_target(db, m))
570 }
571 Some(TypeData::Intrinsic(kind)) => matches!(
573 kind,
574 IntrinsicKind::String
575 | IntrinsicKind::Number
576 | IntrinsicKind::Boolean
577 | IntrinsicKind::Symbol
578 ),
579 _ => false,
581 }
582}
583
584pub fn types_are_comparable(db: &dyn TypeDatabase, source: TypeId, target: TypeId) -> bool {
594 types_are_comparable_inner(db, source, target, 0)
595}
596
597fn types_are_comparable_inner(
598 db: &dyn TypeDatabase,
599 source: TypeId,
600 target: TypeId,
601 depth: u32,
602) -> bool {
603 if depth > 5 {
605 return false;
606 }
607
608 if source == target {
610 return true;
611 }
612
613 if let Some(TypeData::Union(list_id)) = db.lookup(source) {
619 let members = db.type_list(list_id);
620 return members
621 .iter()
622 .any(|&m| types_are_comparable_inner(db, m, target, depth + 1));
623 }
624 if let Some(TypeData::Union(list_id)) = db.lookup(target) {
625 let members = db.type_list(list_id);
626 return members
627 .iter()
628 .any(|&m| types_are_comparable_inner(db, source, m, depth + 1));
629 }
630
631 if is_primitive_comparable(db, source, target) || is_primitive_comparable(db, target, source) {
636 return true;
637 }
638
639 types_have_common_properties(db, source, target, depth)
641}
642
643fn is_primitive_comparable(db: &dyn TypeDatabase, base: TypeId, other: TypeId) -> bool {
645 if base == TypeId::STRING {
647 if let Some(TypeData::Literal(lit)) = db.lookup(other) {
648 return matches!(lit, crate::types::LiteralValue::String(_));
649 }
650 return other == TypeId::STRING;
651 }
652 if base == TypeId::NUMBER {
654 if let Some(TypeData::Literal(lit)) = db.lookup(other) {
655 return matches!(lit, crate::types::LiteralValue::Number(_));
656 }
657 return other == TypeId::NUMBER;
658 }
659 if base == TypeId::BOOLEAN {
661 return other == TypeId::BOOLEAN_TRUE
662 || other == TypeId::BOOLEAN_FALSE
663 || other == TypeId::BOOLEAN;
664 }
665 if base == TypeId::BIGINT {
667 if let Some(TypeData::Literal(lit)) = db.lookup(other) {
668 return matches!(lit, crate::types::LiteralValue::BigInt(_));
669 }
670 return other == TypeId::BIGINT;
671 }
672 if let Some(TypeData::Literal(lit_a)) = db.lookup(base) {
675 if let Some(TypeData::Literal(lit_b)) = db.lookup(other) {
676 return std::mem::discriminant(&lit_a) == std::mem::discriminant(&lit_b);
677 }
678 return match lit_a {
680 crate::types::LiteralValue::String(_) => other == TypeId::STRING,
681 crate::types::LiteralValue::Number(_) => other == TypeId::NUMBER,
682 crate::types::LiteralValue::BigInt(_) => other == TypeId::BIGINT,
683 crate::types::LiteralValue::Boolean(_) => {
684 other == TypeId::BOOLEAN
685 || other == TypeId::BOOLEAN_TRUE
686 || other == TypeId::BOOLEAN_FALSE
687 }
688 };
689 }
690 if (base == TypeId::BOOLEAN_TRUE || base == TypeId::BOOLEAN_FALSE)
692 && (other == TypeId::BOOLEAN_TRUE || other == TypeId::BOOLEAN_FALSE)
693 {
694 return true;
695 }
696 false
697}
698
699fn types_have_common_properties(
701 db: &dyn TypeDatabase,
702 source: TypeId,
703 target: TypeId,
704 depth: u32,
705) -> bool {
706 fn get_properties(db: &dyn TypeDatabase, type_id: TypeId) -> Vec<(Atom, TypeId)> {
708 match db.lookup(type_id) {
709 Some(TypeData::Object(shape_id) | TypeData::ObjectWithIndex(shape_id)) => {
710 let shape = db.object_shape(shape_id);
711 shape
712 .properties
713 .iter()
714 .map(|p| (p.name, p.type_id))
715 .collect()
716 }
717 Some(TypeData::Callable(callable_id)) => {
718 let shape = db.callable_shape(callable_id);
719 shape
720 .properties
721 .iter()
722 .map(|p| (p.name, p.type_id))
723 .collect()
724 }
725 Some(TypeData::Intersection(list_id)) => {
726 let members = db.type_list(list_id);
727 let mut props = Vec::new();
728 for &member in members.iter() {
729 props.extend(get_properties(db, member));
730 }
731 props
732 }
733 _ => Vec::new(),
734 }
735 }
736
737 let source_props = get_properties(db, source);
738 let target_props = get_properties(db, target);
739
740 if source_props.is_empty() || target_props.is_empty() {
741 return false;
742 }
743
744 use rustc_hash::FxHashMap;
748 let mut target_by_name: FxHashMap<Atom, Vec<TypeId>> = FxHashMap::default();
749 for (name, ty) in target_props {
750 target_by_name.entry(name).or_default().push(ty);
751 }
752
753 source_props.iter().any(|(source_name, source_ty)| {
754 target_by_name.get(source_name).is_some_and(|target_tys| {
755 target_tys
756 .iter()
757 .any(|target_ty| types_are_comparable_inner(db, *source_ty, *target_ty, depth + 1))
758 })
759 })
760}
761
762#[allow(clippy::match_same_arms)]
768pub fn has_type_query_for_symbol(
769 db: &dyn TypeDatabase,
770 type_id: TypeId,
771 target_sym_id: u32,
772 mut resolve_lazy: impl FnMut(TypeId) -> TypeId,
773) -> bool {
774 use crate::TypeData;
775 use rustc_hash::FxHashSet;
776
777 let mut worklist = vec![type_id];
778 let mut visited = FxHashSet::default();
779
780 while let Some(ty) = worklist.pop() {
781 if !visited.insert(ty) {
782 continue;
783 }
784
785 let resolved = resolve_lazy(ty);
786 if resolved != ty {
787 worklist.push(resolved);
788 continue;
789 }
790
791 let Some(key) = db.lookup(ty) else { continue };
792 match key {
793 TypeData::TypeQuery(sym_ref) => {
794 if sym_ref.0 == target_sym_id {
795 return true;
796 }
797 }
798 TypeData::Array(elem) => worklist.push(elem),
799 TypeData::Union(list) | TypeData::Intersection(list) => {
800 let members = db.type_list(list);
801 worklist.extend(members.iter().copied());
802 }
803 TypeData::Tuple(list) => {
804 let elements = db.tuple_list(list);
805 for elem in elements.iter() {
806 worklist.push(elem.type_id);
807 }
808 }
809 TypeData::Conditional(id) => {
810 let cond = db.conditional_type(id);
811 worklist.push(cond.check_type);
812 worklist.push(cond.extends_type);
813 worklist.push(cond.true_type);
814 worklist.push(cond.false_type);
815 }
816 TypeData::Application(id) => {
817 let app = db.type_application(id);
818 worklist.push(app.base);
819 worklist.extend(&app.args);
820 }
821 TypeData::IndexAccess(obj, idx) => {
822 worklist.push(obj);
823 worklist.push(idx);
824 }
825 TypeData::KeyOf(inner) | TypeData::ReadonlyType(inner) => {
826 worklist.push(inner);
827 }
828 TypeData::Function(_)
829 | TypeData::Object(_)
830 | TypeData::ObjectWithIndex(_)
831 | TypeData::Mapped(_) => {
832 }
835 _ => {}
836 }
837 }
838 false
839}
840
841pub fn extract_contextual_type_params(
851 db: &dyn TypeDatabase,
852 type_id: TypeId,
853) -> Option<Vec<crate::types::TypeParamInfo>> {
854 extract_contextual_type_params_inner(db, type_id, 0)
855}
856
857fn extract_contextual_type_params_inner(
858 db: &dyn TypeDatabase,
859 type_id: TypeId,
860 depth: u32,
861) -> Option<Vec<crate::types::TypeParamInfo>> {
862 if depth > 20 {
863 return None;
864 }
865
866 match db.lookup(type_id) {
867 Some(TypeData::Function(shape_id)) => {
868 let shape = db.function_shape(shape_id);
869 if shape.type_params.is_empty() {
870 None
871 } else {
872 Some(shape.type_params.clone())
873 }
874 }
875 Some(TypeData::Callable(shape_id)) => {
876 let shape = db.callable_shape(shape_id);
877 if shape.call_signatures.len() != 1 {
878 return None;
879 }
880 let sig = &shape.call_signatures[0];
881 if sig.type_params.is_empty() {
882 None
883 } else {
884 Some(sig.type_params.clone())
885 }
886 }
887 Some(TypeData::Application(app_id)) => {
888 let app = db.type_application(app_id);
889 extract_contextual_type_params_inner(db, app.base, depth + 1)
890 }
891 Some(TypeData::Union(list_id)) => {
892 let members = db.type_list(list_id);
893 if members.is_empty() {
894 return None;
895 }
896 let mut candidate: Option<Vec<crate::types::TypeParamInfo>> = None;
897 for &member in members.iter() {
898 let params = extract_contextual_type_params_inner(db, member, depth + 1)?;
899 if let Some(existing) = &candidate {
900 if existing.len() != params.len()
901 || existing
902 .iter()
903 .zip(params.iter())
904 .any(|(left, right)| left != right)
905 {
906 return None;
907 }
908 } else {
909 candidate = Some(params);
910 }
911 }
912 candidate
913 }
914 _ => None,
915 }
916}