1use crate::TypeDatabase;
8use crate::type_queries::{get_union_members, is_invokable_type};
9use crate::type_queries_extended::{
10 StringLiteralKeyKind, classify_for_string_literal_keys, get_string_literal_value,
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 match classify_for_constructor_instance(db, type_id) {
103 ConstructorInstanceKind::Callable(shape_id) => {
104 let shape = db.callable_shape(shape_id);
105 if shape.construct_signatures.is_empty() {
106 return None;
107 }
108 let returns: Vec<TypeId> = shape
109 .construct_signatures
110 .iter()
111 .map(|s| s.return_type)
112 .collect();
113 Some(if returns.len() == 1 {
114 returns[0]
115 } else {
116 db.union(returns)
117 })
118 }
119 ConstructorInstanceKind::Union(members) => {
120 let instance_types: Vec<TypeId> = members
121 .into_iter()
122 .filter_map(|m| instance_type_from_constructor(db, m))
123 .collect();
124 if instance_types.is_empty() {
125 None
126 } else if instance_types.len() == 1 {
127 Some(instance_types[0])
128 } else {
129 Some(db.union(instance_types))
130 }
131 }
132 ConstructorInstanceKind::Intersection(members) => {
133 members
135 .into_iter()
136 .find_map(|m| instance_type_from_constructor(db, m))
137 }
138 ConstructorInstanceKind::None => None,
139 }
140}
141
142#[derive(Debug, Clone)]
145pub enum TypeParameterConstraintKind {
146 TypeParameter { constraint: Option<TypeId> },
148 None,
150}
151
152pub fn classify_for_type_parameter_constraint(
154 db: &dyn TypeDatabase,
155 type_id: TypeId,
156) -> TypeParameterConstraintKind {
157 let Some(key) = db.lookup(type_id) else {
158 return TypeParameterConstraintKind::None;
159 };
160
161 match key {
162 TypeData::TypeParameter(info) | TypeData::Infer(info) => {
163 TypeParameterConstraintKind::TypeParameter {
164 constraint: info.constraint,
165 }
166 }
167 _ => TypeParameterConstraintKind::None,
168 }
169}
170
171#[derive(Debug, Clone)]
174pub enum UnionMembersKind {
175 Union(Vec<TypeId>),
177 NotUnion,
179}
180
181pub fn classify_for_union_members(db: &dyn TypeDatabase, type_id: TypeId) -> UnionMembersKind {
183 let Some(key) = db.lookup(type_id) else {
184 return UnionMembersKind::NotUnion;
185 };
186
187 match key {
188 TypeData::Union(members_id) => {
189 let members = db.type_list(members_id);
190 UnionMembersKind::Union(members.to_vec())
191 }
192 _ => UnionMembersKind::NotUnion,
193 }
194}
195
196#[derive(Debug, Clone)]
199pub enum NonObjectKind {
200 Literal,
202 IntrinsicPrimitive,
204 MaybeObject,
206}
207
208pub fn classify_for_non_object(db: &dyn TypeDatabase, type_id: TypeId) -> NonObjectKind {
210 let Some(key) = db.lookup(type_id) else {
211 return NonObjectKind::MaybeObject;
212 };
213
214 match key {
215 TypeData::Literal(_) => NonObjectKind::Literal,
216 TypeData::Intrinsic(kind) => {
217 use crate::IntrinsicKind;
218
219 match kind {
220 IntrinsicKind::Void
221 | IntrinsicKind::Undefined
222 | IntrinsicKind::Null
223 | IntrinsicKind::Boolean
224 | IntrinsicKind::Number
225 | IntrinsicKind::String
226 | IntrinsicKind::Bigint
227 | IntrinsicKind::Symbol
228 | IntrinsicKind::Never => NonObjectKind::IntrinsicPrimitive,
229 _ => NonObjectKind::MaybeObject,
230 }
231 }
232 _ => NonObjectKind::MaybeObject,
233 }
234}
235
236#[derive(Debug, Clone)]
239pub enum PropertyPresenceKind {
240 IntrinsicObject,
242 Object(crate::types::ObjectShapeId),
244 Callable(crate::types::CallableShapeId),
246 ArrayLike,
248 Unknown,
250}
251
252pub fn classify_for_property_presence(
254 db: &dyn TypeDatabase,
255 type_id: TypeId,
256) -> PropertyPresenceKind {
257 let Some(key) = db.lookup(type_id) else {
258 return PropertyPresenceKind::Unknown;
259 };
260
261 match key {
262 TypeData::Intrinsic(crate::IntrinsicKind::Object) => PropertyPresenceKind::IntrinsicObject,
263 TypeData::Object(shape_id) | TypeData::ObjectWithIndex(shape_id) => {
264 PropertyPresenceKind::Object(shape_id)
265 }
266 TypeData::Callable(shape_id) => PropertyPresenceKind::Callable(shape_id),
267 TypeData::Array(_) | TypeData::Tuple(_) => PropertyPresenceKind::ArrayLike,
268 _ => PropertyPresenceKind::Unknown,
269 }
270}
271
272#[derive(Debug, Clone)]
275pub enum FalsyComponentKind {
276 Literal(crate::LiteralValue),
278 Union(Vec<TypeId>),
280 TypeParameter,
282 None,
284}
285
286pub fn classify_for_falsy_component(db: &dyn TypeDatabase, type_id: TypeId) -> FalsyComponentKind {
288 let Some(key) = db.lookup(type_id) else {
289 return FalsyComponentKind::None;
290 };
291
292 match key {
293 TypeData::Literal(literal) => FalsyComponentKind::Literal(literal),
294 TypeData::Union(members_id) => {
295 let members = db.type_list(members_id);
296 FalsyComponentKind::Union(members.to_vec())
297 }
298 TypeData::TypeParameter(_) | TypeData::Infer(_) => FalsyComponentKind::TypeParameter,
299 _ => FalsyComponentKind::None,
300 }
301}
302
303#[derive(Debug, Clone)]
306pub enum LiteralValueKind {
307 String(tsz_common::interner::Atom),
309 Number(f64),
311 None,
313}
314
315pub fn classify_for_literal_value(db: &dyn TypeDatabase, type_id: TypeId) -> LiteralValueKind {
317 let Some(key) = db.lookup(type_id) else {
318 return LiteralValueKind::None;
319 };
320
321 match key {
322 TypeData::Literal(crate::LiteralValue::String(atom)) => LiteralValueKind::String(atom),
323 TypeData::Literal(crate::LiteralValue::Number(num)) => LiteralValueKind::Number(num.0),
324 _ => LiteralValueKind::None,
325 }
326}
327
328pub fn is_narrowing_literal(db: &dyn TypeDatabase, type_id: TypeId) -> Option<TypeId> {
337 if type_id == TypeId::NULL || type_id == TypeId::UNDEFINED {
339 return Some(type_id);
340 }
341 let key = db.lookup(type_id)?;
342 match key {
343 TypeData::Literal(_) | TypeData::Enum(_, _) => Some(type_id),
344 _ => None,
345 }
346}
347
348pub fn is_unit_type(db: &dyn TypeDatabase, type_id: TypeId) -> bool {
353 if type_id == TypeId::NULL
354 || type_id == TypeId::UNDEFINED
355 || type_id == TypeId::VOID
356 || type_id == TypeId::BOOLEAN_TRUE
357 || type_id == TypeId::BOOLEAN_FALSE
358 {
359 return true;
360 }
361
362 if crate::visitor::is_literal_type_db(db, type_id) {
363 return true;
364 }
365
366 if let Some(list_id) = crate::visitor::union_list_id(db, type_id) {
367 let members = db.type_list(list_id);
368 return members.iter().all(|&m| is_unit_type(db, m));
369 }
370
371 false
372}
373
374pub fn union_contains(db: &dyn TypeDatabase, type_id: TypeId, target: TypeId) -> bool {
376 if let Some(members) = get_union_members(db, type_id) {
377 members.contains(&target)
378 } else {
379 false
380 }
381}
382
383pub fn type_includes_undefined(db: &dyn TypeDatabase, type_id: TypeId) -> bool {
385 type_id == TypeId::UNDEFINED || union_contains(db, type_id, TypeId::UNDEFINED)
386}
387
388pub fn extract_string_literal_keys(
392 db: &dyn TypeDatabase,
393 type_id: TypeId,
394) -> Vec<tsz_common::interner::Atom> {
395 match classify_for_string_literal_keys(db, type_id) {
396 StringLiteralKeyKind::SingleString(name) => vec![name],
397 StringLiteralKeyKind::Union(members) => members
398 .iter()
399 .filter_map(|&member| get_string_literal_value(db, member))
400 .collect(),
401 StringLiteralKeyKind::NotStringLiteral => Vec::new(),
402 }
403}
404
405pub fn get_return_type(db: &dyn TypeDatabase, type_id: TypeId) -> Option<TypeId> {
426 match db.lookup(type_id) {
427 Some(TypeData::Function(shape_id)) => Some(db.function_shape(shape_id).return_type),
428 Some(TypeData::Callable(shape_id)) => {
429 let shape = db.callable_shape(shape_id);
430 shape.call_signatures.first().map(|sig| sig.return_type)
432 }
433 Some(TypeData::Intersection(list_id)) => {
434 let members = db.type_list(list_id);
436 members.iter().find_map(|&m| get_return_type(db, m))
437 }
438 _ => {
439 if type_id == TypeId::ANY {
441 Some(TypeId::ANY)
442 } else if type_id == TypeId::NEVER {
443 Some(TypeId::NEVER)
444 } else {
445 None
446 }
447 }
448 }
449}
450
451use crate::operations_property::PropertyAccessEvaluator;
456
457pub fn is_promise_like(db: &dyn crate::db::QueryDatabase, type_id: TypeId) -> bool {
486 if type_id == TypeId::ANY {
488 return true;
489 }
490
491 let evaluator = PropertyAccessEvaluator::new(db);
494 evaluator
495 .resolve_property_access(type_id, "then")
496 .success_type()
497 .is_some_and(|then_type| {
498 is_invokable_type(db, then_type)
501 })
502}
503
504pub fn is_valid_for_in_target(db: &dyn TypeDatabase, type_id: TypeId) -> bool {
535 if type_id == TypeId::ANY {
537 return true;
538 }
539
540 if type_id == TypeId::STRING || type_id == TypeId::NUMBER || type_id == TypeId::BOOLEAN {
542 return true;
543 }
544
545 use crate::types::IntrinsicKind;
546 match db.lookup(type_id) {
547 Some(TypeData::Object(_) | TypeData::ObjectWithIndex(_))
549 | Some(TypeData::Array(_))
550 | Some(TypeData::TypeParameter(_))
551 | Some(TypeData::Tuple(_))
552 | Some(TypeData::Literal(_)) => true,
553 Some(TypeData::Union(list_id)) => {
555 let members = db.type_list(list_id);
556 members.iter().all(|&m| is_valid_for_in_target(db, m))
557 }
558 Some(TypeData::Intersection(list_id)) => {
560 let members = db.type_list(list_id);
561 members.iter().any(|&m| is_valid_for_in_target(db, m))
562 }
563 Some(TypeData::Intrinsic(kind)) => matches!(
565 kind,
566 IntrinsicKind::String
567 | IntrinsicKind::Number
568 | IntrinsicKind::Boolean
569 | IntrinsicKind::Symbol
570 ),
571 _ => false,
573 }
574}
575
576pub fn types_are_comparable(db: &dyn TypeDatabase, source: TypeId, target: TypeId) -> bool {
586 types_are_comparable_inner(db, source, target, 0)
587}
588
589fn types_are_comparable_inner(
590 db: &dyn TypeDatabase,
591 source: TypeId,
592 target: TypeId,
593 depth: u32,
594) -> bool {
595 if depth > 5 {
597 return false;
598 }
599
600 if source == target {
602 return true;
603 }
604
605 if let Some(TypeData::Union(list_id)) = db.lookup(source) {
611 let members = db.type_list(list_id);
612 return members
613 .iter()
614 .any(|&m| types_are_comparable_inner(db, m, target, depth + 1));
615 }
616 if let Some(TypeData::Union(list_id)) = db.lookup(target) {
617 let members = db.type_list(list_id);
618 return members
619 .iter()
620 .any(|&m| types_are_comparable_inner(db, source, m, depth + 1));
621 }
622
623 if is_primitive_comparable(db, source, target) || is_primitive_comparable(db, target, source) {
628 return true;
629 }
630
631 types_have_common_properties(db, source, target, depth)
633}
634
635fn is_primitive_comparable(db: &dyn TypeDatabase, base: TypeId, other: TypeId) -> bool {
637 if base == TypeId::STRING {
639 if let Some(TypeData::Literal(lit)) = db.lookup(other) {
640 return matches!(lit, crate::types::LiteralValue::String(_));
641 }
642 return other == TypeId::STRING;
643 }
644 if base == TypeId::NUMBER {
646 if let Some(TypeData::Literal(lit)) = db.lookup(other) {
647 return matches!(lit, crate::types::LiteralValue::Number(_));
648 }
649 return other == TypeId::NUMBER;
650 }
651 if base == TypeId::BOOLEAN {
653 return other == TypeId::BOOLEAN_TRUE
654 || other == TypeId::BOOLEAN_FALSE
655 || other == TypeId::BOOLEAN;
656 }
657 if base == TypeId::BIGINT {
659 if let Some(TypeData::Literal(lit)) = db.lookup(other) {
660 return matches!(lit, crate::types::LiteralValue::BigInt(_));
661 }
662 return other == TypeId::BIGINT;
663 }
664 if let Some(TypeData::Literal(lit_a)) = db.lookup(base) {
667 if let Some(TypeData::Literal(lit_b)) = db.lookup(other) {
668 return std::mem::discriminant(&lit_a) == std::mem::discriminant(&lit_b);
669 }
670 return match lit_a {
672 crate::types::LiteralValue::String(_) => other == TypeId::STRING,
673 crate::types::LiteralValue::Number(_) => other == TypeId::NUMBER,
674 crate::types::LiteralValue::BigInt(_) => other == TypeId::BIGINT,
675 crate::types::LiteralValue::Boolean(_) => {
676 other == TypeId::BOOLEAN
677 || other == TypeId::BOOLEAN_TRUE
678 || other == TypeId::BOOLEAN_FALSE
679 }
680 };
681 }
682 if (base == TypeId::BOOLEAN_TRUE || base == TypeId::BOOLEAN_FALSE)
684 && (other == TypeId::BOOLEAN_TRUE || other == TypeId::BOOLEAN_FALSE)
685 {
686 return true;
687 }
688 false
689}
690
691fn types_have_common_properties(
693 db: &dyn TypeDatabase,
694 source: TypeId,
695 target: TypeId,
696 depth: u32,
697) -> bool {
698 fn get_properties(db: &dyn TypeDatabase, type_id: TypeId) -> Vec<(Atom, TypeId)> {
700 match db.lookup(type_id) {
701 Some(TypeData::Object(shape_id) | TypeData::ObjectWithIndex(shape_id)) => {
702 let shape = db.object_shape(shape_id);
703 shape
704 .properties
705 .iter()
706 .map(|p| (p.name, p.type_id))
707 .collect()
708 }
709 Some(TypeData::Callable(callable_id)) => {
710 let shape = db.callable_shape(callable_id);
711 shape
712 .properties
713 .iter()
714 .map(|p| (p.name, p.type_id))
715 .collect()
716 }
717 Some(TypeData::Intersection(list_id)) => {
718 let members = db.type_list(list_id);
719 let mut props = Vec::new();
720 for &member in members.iter() {
721 props.extend(get_properties(db, member));
722 }
723 props
724 }
725 _ => Vec::new(),
726 }
727 }
728
729 let source_props = get_properties(db, source);
730 let target_props = get_properties(db, target);
731
732 if source_props.is_empty() || target_props.is_empty() {
733 return false;
734 }
735
736 use rustc_hash::FxHashMap;
740 let mut target_by_name: FxHashMap<Atom, Vec<TypeId>> = FxHashMap::default();
741 for (name, ty) in target_props {
742 target_by_name.entry(name).or_default().push(ty);
743 }
744
745 source_props.iter().any(|(source_name, source_ty)| {
746 target_by_name.get(source_name).is_some_and(|target_tys| {
747 target_tys
748 .iter()
749 .any(|target_ty| types_are_comparable_inner(db, *source_ty, *target_ty, depth + 1))
750 })
751 })
752}
753
754#[allow(clippy::match_same_arms)]
760pub fn has_type_query_for_symbol(
761 db: &dyn TypeDatabase,
762 type_id: TypeId,
763 target_sym_id: u32,
764 mut resolve_lazy: impl FnMut(TypeId) -> TypeId,
765) -> bool {
766 use crate::TypeData;
767 use rustc_hash::FxHashSet;
768
769 let mut worklist = vec![type_id];
770 let mut visited = FxHashSet::default();
771
772 while let Some(ty) = worklist.pop() {
773 if !visited.insert(ty) {
774 continue;
775 }
776
777 let resolved = resolve_lazy(ty);
778 if resolved != ty {
779 worklist.push(resolved);
780 continue;
781 }
782
783 let Some(key) = db.lookup(ty) else { continue };
784 match key {
785 TypeData::TypeQuery(sym_ref) => {
786 if sym_ref.0 == target_sym_id {
787 return true;
788 }
789 }
790 TypeData::Array(elem) => worklist.push(elem),
791 TypeData::Union(list) | TypeData::Intersection(list) => {
792 let members = db.type_list(list);
793 worklist.extend(members.iter().copied());
794 }
795 TypeData::Tuple(list) => {
796 let elements = db.tuple_list(list);
797 for elem in elements.iter() {
798 worklist.push(elem.type_id);
799 }
800 }
801 TypeData::Conditional(id) => {
802 let cond = db.conditional_type(id);
803 worklist.push(cond.check_type);
804 worklist.push(cond.extends_type);
805 worklist.push(cond.true_type);
806 worklist.push(cond.false_type);
807 }
808 TypeData::Application(id) => {
809 let app = db.type_application(id);
810 worklist.push(app.base);
811 worklist.extend(&app.args);
812 }
813 TypeData::IndexAccess(obj, idx) => {
814 worklist.push(obj);
815 worklist.push(idx);
816 }
817 TypeData::KeyOf(inner) | TypeData::ReadonlyType(inner) => {
818 worklist.push(inner);
819 }
820 TypeData::Function(_)
821 | TypeData::Object(_)
822 | TypeData::ObjectWithIndex(_)
823 | TypeData::Mapped(_) => {
824 }
827 _ => {}
828 }
829 }
830 false
831}
832
833pub fn extract_contextual_type_params(
843 db: &dyn TypeDatabase,
844 type_id: TypeId,
845) -> Option<Vec<crate::types::TypeParamInfo>> {
846 extract_contextual_type_params_inner(db, type_id, 0)
847}
848
849fn extract_contextual_type_params_inner(
850 db: &dyn TypeDatabase,
851 type_id: TypeId,
852 depth: u32,
853) -> Option<Vec<crate::types::TypeParamInfo>> {
854 if depth > 20 {
855 return None;
856 }
857
858 match db.lookup(type_id) {
859 Some(TypeData::Function(shape_id)) => {
860 let shape = db.function_shape(shape_id);
861 if shape.type_params.is_empty() {
862 None
863 } else {
864 Some(shape.type_params.clone())
865 }
866 }
867 Some(TypeData::Callable(shape_id)) => {
868 let shape = db.callable_shape(shape_id);
869 if shape.call_signatures.len() != 1 {
870 return None;
871 }
872 let sig = &shape.call_signatures[0];
873 if sig.type_params.is_empty() {
874 None
875 } else {
876 Some(sig.type_params.clone())
877 }
878 }
879 Some(TypeData::Application(app_id)) => {
880 let app = db.type_application(app_id);
881 extract_contextual_type_params_inner(db, app.base, depth + 1)
882 }
883 Some(TypeData::Union(list_id)) => {
884 let members = db.type_list(list_id);
885 if members.is_empty() {
886 return None;
887 }
888 let mut candidate: Option<Vec<crate::types::TypeParamInfo>> = None;
889 for &member in members.iter() {
890 let params = extract_contextual_type_params_inner(db, member, depth + 1)?;
891 if let Some(existing) = &candidate {
892 if existing.len() != params.len()
893 || existing
894 .iter()
895 .zip(params.iter())
896 .any(|(left, right)| left != right)
897 {
898 return None;
899 }
900 } else {
901 candidate = Some(params);
902 }
903 }
904 candidate
905 }
906 _ => None,
907 }
908}