1use crate::def::DefId;
11use crate::{TypeData, TypeDatabase, TypeId};
12use rustc_hash::FxHashSet;
13
14#[derive(Debug, Clone)]
21pub enum LiteralTypeKind {
22 String(tsz_common::interner::Atom),
24 Number(f64),
26 BigInt(tsz_common::interner::Atom),
28 Boolean(bool),
30 NotLiteral,
32}
33
34pub fn classify_literal_type(db: &dyn TypeDatabase, type_id: TypeId) -> LiteralTypeKind {
42 let Some(key) = db.lookup(type_id) else {
43 return LiteralTypeKind::NotLiteral;
44 };
45
46 match key {
47 TypeData::Literal(crate::LiteralValue::String(atom)) => LiteralTypeKind::String(atom),
48 TypeData::Literal(crate::LiteralValue::Number(ordered_float)) => {
49 LiteralTypeKind::Number(ordered_float.0)
50 }
51 TypeData::Literal(crate::LiteralValue::BigInt(atom)) => LiteralTypeKind::BigInt(atom),
52 TypeData::Literal(crate::LiteralValue::Boolean(value)) => LiteralTypeKind::Boolean(value),
53 _ => LiteralTypeKind::NotLiteral,
54 }
55}
56
57pub fn is_string_literal(db: &dyn TypeDatabase, type_id: TypeId) -> bool {
59 matches!(
60 classify_literal_type(db, type_id),
61 LiteralTypeKind::String(_)
62 )
63}
64
65pub fn is_number_literal(db: &dyn TypeDatabase, type_id: TypeId) -> bool {
67 matches!(
68 classify_literal_type(db, type_id),
69 LiteralTypeKind::Number(_)
70 )
71}
72
73pub fn is_boolean_literal(db: &dyn TypeDatabase, type_id: TypeId) -> bool {
75 matches!(
76 classify_literal_type(db, type_id),
77 LiteralTypeKind::Boolean(_)
78 )
79}
80
81pub fn get_string_literal_atom(
83 db: &dyn TypeDatabase,
84 type_id: TypeId,
85) -> Option<tsz_common::interner::Atom> {
86 match classify_literal_type(db, type_id) {
87 LiteralTypeKind::String(atom) => Some(atom),
88 _ => None,
89 }
90}
91
92pub fn get_number_literal_value(db: &dyn TypeDatabase, type_id: TypeId) -> Option<f64> {
94 match classify_literal_type(db, type_id) {
95 LiteralTypeKind::Number(value) => Some(value),
96 _ => None,
97 }
98}
99
100pub fn get_boolean_literal_value(db: &dyn TypeDatabase, type_id: TypeId) -> Option<bool> {
102 match classify_literal_type(db, type_id) {
103 LiteralTypeKind::Boolean(value) => Some(value),
104 _ => None,
105 }
106}
107
108pub fn is_invalid_index_type(db: &dyn TypeDatabase, type_id: TypeId) -> bool {
116 let mut visited = FxHashSet::default();
117 is_invalid_index_type_inner(db, type_id, &mut visited).is_some()
118}
119
120pub fn get_invalid_index_type_member(db: &dyn TypeDatabase, type_id: TypeId) -> Option<TypeId> {
123 let mut visited = FxHashSet::default();
124 is_invalid_index_type_inner(db, type_id, &mut visited)
125}
126
127fn is_invalid_index_type_inner(
128 db: &dyn TypeDatabase,
129 type_id: TypeId,
130 visited: &mut FxHashSet<TypeId>,
131) -> Option<TypeId> {
132 if !visited.insert(type_id) {
133 return None;
134 }
135
136 if matches!(
137 type_id,
138 TypeId::ANY | TypeId::UNKNOWN | TypeId::ERROR | TypeId::NEVER
139 ) {
140 return None;
141 }
142
143 let is_invalid = match db.lookup(type_id) {
144 Some(TypeData::Intrinsic(kind)) => matches!(
145 kind,
146 crate::IntrinsicKind::Void
147 | crate::IntrinsicKind::Null
148 | crate::IntrinsicKind::Undefined
149 | crate::IntrinsicKind::Boolean
150 | crate::IntrinsicKind::Bigint
151 | crate::IntrinsicKind::Object
152 | crate::IntrinsicKind::Function
153 ),
154 Some(TypeData::Literal(value)) => matches!(
155 value,
156 crate::LiteralValue::Boolean(_) | crate::LiteralValue::BigInt(_)
157 ),
158 Some(
159 TypeData::Array(_)
160 | TypeData::Tuple(_)
161 | TypeData::Object(_)
162 | TypeData::ObjectWithIndex(_)
163 | TypeData::Function(_)
164 | TypeData::Callable(_)
165 | TypeData::Lazy(_),
166 ) => true,
167 Some(TypeData::Union(list_id) | TypeData::Intersection(list_id)) => {
168 for &member in db.type_list(list_id).iter() {
169 if let Some(invalid_member) = is_invalid_index_type_inner(db, member, visited) {
170 return Some(invalid_member);
171 }
172 }
173 false
174 }
175 Some(TypeData::TypeParameter(info)) => {
176 if let Some(constraint) = info.constraint
177 && let Some(invalid_member) = is_invalid_index_type_inner(db, constraint, visited)
178 {
179 return Some(invalid_member);
180 }
181 false
182 }
183 _ => false,
184 };
185
186 if is_invalid { Some(type_id) } else { None }
187}
188
189#[derive(Debug, Clone)]
198pub enum SpreadTypeKind {
199 Array(TypeId),
201 Tuple(crate::types::TupleListId),
203 Object(crate::types::ObjectShapeId),
205 ObjectWithIndex(crate::types::ObjectShapeId),
207 StringLiteral(tsz_common::interner::Atom),
209 Lazy(DefId),
211 Other,
213 NotSpreadable,
215}
216
217pub fn classify_spread_type(db: &dyn TypeDatabase, type_id: TypeId) -> SpreadTypeKind {
222 if type_id.is_any() || type_id == TypeId::STRING {
224 return SpreadTypeKind::Other;
225 }
226 if type_id.is_unknown() {
227 return SpreadTypeKind::NotSpreadable;
228 }
229
230 let Some(key) = db.lookup(type_id) else {
231 return SpreadTypeKind::NotSpreadable;
232 };
233
234 match key {
235 TypeData::Array(element_type) => SpreadTypeKind::Array(element_type),
236 TypeData::Tuple(tuple_id) => SpreadTypeKind::Tuple(tuple_id),
237 TypeData::Object(shape_id) => SpreadTypeKind::Object(shape_id),
238 TypeData::ObjectWithIndex(shape_id) => SpreadTypeKind::ObjectWithIndex(shape_id),
239 TypeData::Literal(crate::LiteralValue::String(atom)) => SpreadTypeKind::StringLiteral(atom),
240 TypeData::Lazy(def_id) => SpreadTypeKind::Lazy(def_id),
241 _ => SpreadTypeKind::Other,
242 }
243}
244
245pub fn is_iterable_type_kind(db: &dyn TypeDatabase, type_id: TypeId) -> bool {
249 if type_id == TypeId::STRING {
251 return true;
252 }
253
254 let Some(key) = db.lookup(type_id) else {
255 return false;
256 };
257
258 match key {
259 TypeData::Array(_)
260 | TypeData::Tuple(_)
261 | TypeData::Literal(crate::LiteralValue::String(_)) => true,
262 TypeData::Object(shape_id) => {
263 let shape = db.object_shape(shape_id);
265 shape.properties.iter().any(|prop| {
266 let prop_name = db.resolve_atom_ref(prop.name);
267 (prop_name.as_ref() == "[Symbol.iterator]" || prop_name.as_ref() == "next")
268 && prop.is_method
269 })
270 }
271 _ => false,
272 }
273}
274
275pub fn get_iterable_element_type_from_db(db: &dyn TypeDatabase, type_id: TypeId) -> Option<TypeId> {
277 if type_id == TypeId::STRING {
279 return Some(TypeId::STRING);
280 }
281
282 let key = db.lookup(type_id)?;
283
284 match key {
285 TypeData::Array(elem_type) => Some(elem_type),
286 TypeData::Tuple(tuple_list_id) => {
287 let elements = db.tuple_list(tuple_list_id);
288 if elements.is_empty() {
289 Some(TypeId::NEVER)
290 } else {
291 let types: Vec<TypeId> = elements.iter().map(|e| e.type_id).collect();
292 Some(db.union(types))
293 }
294 }
295 TypeData::Literal(crate::LiteralValue::String(_)) => Some(TypeId::STRING),
296 TypeData::Object(shape_id) => {
297 let shape = db.object_shape(shape_id);
300 let has_iterator = shape.properties.iter().any(|prop| {
301 let prop_name = db.resolve_atom_ref(prop.name);
302 (prop_name.as_ref() == "[Symbol.iterator]" || prop_name.as_ref() == "next")
303 && prop.is_method
304 });
305 has_iterator.then_some(TypeId::ANY)
306 }
307 _ => None,
308 }
309}
310
311#[derive(Debug, Clone)]
320pub enum TypeParameterKind {
321 TypeParameter(crate::types::TypeParamInfo),
323 Infer(crate::types::TypeParamInfo),
325 Application(crate::types::TypeApplicationId),
327 Union(Vec<TypeId>),
329 Intersection(Vec<TypeId>),
331 Callable(crate::types::CallableShapeId),
333 NotTypeParameter,
335}
336
337pub fn classify_type_parameter(db: &dyn TypeDatabase, type_id: TypeId) -> TypeParameterKind {
341 let Some(key) = db.lookup(type_id) else {
342 return TypeParameterKind::NotTypeParameter;
343 };
344
345 match key {
346 TypeData::TypeParameter(info) => TypeParameterKind::TypeParameter(info),
347 TypeData::Infer(info) => TypeParameterKind::Infer(info),
348 TypeData::Application(app_id) => TypeParameterKind::Application(app_id),
349 TypeData::Union(list_id) => {
350 let members = db.type_list(list_id);
351 TypeParameterKind::Union(members.to_vec())
352 }
353 TypeData::Intersection(list_id) => {
354 let members = db.type_list(list_id);
355 TypeParameterKind::Intersection(members.to_vec())
356 }
357 TypeData::Callable(shape_id) => TypeParameterKind::Callable(shape_id),
358 _ => TypeParameterKind::NotTypeParameter,
359 }
360}
361
362pub fn is_direct_type_parameter(db: &dyn TypeDatabase, type_id: TypeId) -> bool {
364 matches!(
365 classify_type_parameter(db, type_id),
366 TypeParameterKind::TypeParameter(_) | TypeParameterKind::Infer(_)
367 )
368}
369
370pub fn get_type_param_default(db: &dyn TypeDatabase, type_id: TypeId) -> Option<TypeId> {
372 match db.lookup(type_id) {
373 Some(TypeData::TypeParameter(info) | TypeData::Infer(info)) => info.default,
374 _ => None,
375 }
376}
377
378pub fn get_callable_type_param_count(db: &dyn TypeDatabase, type_id: TypeId) -> usize {
380 match db.lookup(type_id) {
381 Some(TypeData::Callable(shape_id)) => {
382 let shape = db.callable_shape(shape_id);
383 shape
384 .call_signatures
385 .iter()
386 .map(|sig| sig.type_params.len())
387 .max()
388 .unwrap_or(0)
389 }
390 _ => 0,
391 }
392}
393
394#[derive(Debug, Clone)]
403pub enum PromiseTypeKind {
404 Application {
406 app_id: crate::types::TypeApplicationId,
407 base: TypeId,
408 args: Vec<TypeId>,
409 },
410 Lazy(crate::def::DefId),
412 Object(crate::types::ObjectShapeId),
414 Union(Vec<TypeId>),
416 NotPromise,
418}
419
420pub fn classify_promise_type(db: &dyn TypeDatabase, type_id: TypeId) -> PromiseTypeKind {
425 let Some(key) = db.lookup(type_id) else {
426 return PromiseTypeKind::NotPromise;
427 };
428
429 match key {
430 TypeData::Application(app_id) => {
431 let app = db.type_application(app_id);
432 PromiseTypeKind::Application {
433 app_id,
434 base: app.base,
435 args: app.args.clone(),
436 }
437 }
438 TypeData::Lazy(def_id) => PromiseTypeKind::Lazy(def_id),
439 TypeData::Object(shape_id) => PromiseTypeKind::Object(shape_id),
440 TypeData::Union(list_id) => {
441 let members = db.type_list(list_id);
442 PromiseTypeKind::Union(members.to_vec())
443 }
444 _ => PromiseTypeKind::NotPromise,
445 }
446}
447
448#[derive(Debug, Clone)]
454pub enum KeyOfTypeKind {
455 Object(crate::types::ObjectShapeId),
457 NoKeys,
459}
460
461pub fn classify_for_keyof(db: &dyn TypeDatabase, type_id: TypeId) -> KeyOfTypeKind {
463 let Some(key) = db.lookup(type_id) else {
464 return KeyOfTypeKind::NoKeys;
465 };
466
467 match key {
468 TypeData::Object(shape_id) | TypeData::ObjectWithIndex(shape_id) => {
469 KeyOfTypeKind::Object(shape_id)
470 }
471 _ => KeyOfTypeKind::NoKeys,
472 }
473}
474
475#[derive(Debug, Clone)]
481pub enum StringLiteralKeyKind {
482 SingleString(tsz_common::interner::Atom),
484 Union(Vec<TypeId>),
486 NotStringLiteral,
488}
489
490pub fn classify_for_string_literal_keys(
492 db: &dyn TypeDatabase,
493 type_id: TypeId,
494) -> StringLiteralKeyKind {
495 let Some(key) = db.lookup(type_id) else {
496 return StringLiteralKeyKind::NotStringLiteral;
497 };
498
499 match key {
500 TypeData::Literal(crate::types::LiteralValue::String(name)) => {
501 StringLiteralKeyKind::SingleString(name)
502 }
503 TypeData::Union(list_id) => {
504 let members = db.type_list(list_id);
505 StringLiteralKeyKind::Union(members.to_vec())
506 }
507 _ => StringLiteralKeyKind::NotStringLiteral,
508 }
509}
510
511pub fn get_string_literal_value(
514 db: &dyn TypeDatabase,
515 type_id: TypeId,
516) -> Option<tsz_common::interner::Atom> {
517 match db.lookup(type_id) {
518 Some(TypeData::Literal(crate::types::LiteralValue::String(name))) => Some(name),
519 _ => None,
520 }
521}
522
523pub fn get_literal_property_name(
525 db: &dyn TypeDatabase,
526 type_id: TypeId,
527) -> Option<tsz_common::interner::Atom> {
528 match db.lookup(type_id) {
529 Some(TypeData::Literal(crate::types::LiteralValue::String(name))) => Some(name),
530 Some(TypeData::Literal(crate::types::LiteralValue::Number(num))) => {
531 let s = format!("{}", num.0);
533 Some(db.intern_string(&s))
534 }
535 Some(TypeData::UniqueSymbol(sym)) => {
536 let s = format!("__unique_{}", sym.0);
537 Some(db.intern_string(&s))
538 }
539 Some(TypeData::Enum(_, member_type)) => get_literal_property_name(db, member_type),
540 _ => None,
541 }
542}
543
544#[derive(Debug, Clone)]
550pub enum CallSignaturesKind {
551 Callable(crate::types::CallableShapeId),
553 MultipleSignatures(Vec<crate::CallSignature>),
555 NoSignatures,
557}
558
559pub fn classify_for_call_signatures(db: &dyn TypeDatabase, type_id: TypeId) -> CallSignaturesKind {
561 let Some(key) = db.lookup(type_id) else {
562 return CallSignaturesKind::NoSignatures;
563 };
564
565 match key {
566 TypeData::Callable(shape_id) => CallSignaturesKind::Callable(shape_id),
567 TypeData::Union(list_id) => {
568 let members = db.type_list(list_id);
570 let mut call_signatures = Vec::new();
571
572 for &member in members.iter() {
573 match db.lookup(member) {
574 Some(TypeData::Callable(shape_id)) => {
575 let shape = db.callable_shape(shape_id);
576 call_signatures.extend(shape.call_signatures.iter().cloned());
578 }
579 _ => continue,
580 }
581 }
582
583 if call_signatures.is_empty() {
584 CallSignaturesKind::NoSignatures
585 } else {
586 CallSignaturesKind::MultipleSignatures(call_signatures)
587 }
588 }
589 _ => CallSignaturesKind::NoSignatures,
590 }
591}
592
593pub fn get_application_info(
600 db: &dyn TypeDatabase,
601 type_id: TypeId,
602) -> Option<(TypeId, Vec<TypeId>)> {
603 match db.lookup(type_id) {
604 Some(TypeData::Application(app_id)) => {
605 let app = db.type_application(app_id);
606 Some((app.base, app.args.clone()))
607 }
608 _ => None,
609 }
610}
611
612#[derive(Debug, Clone)]
618pub enum TypeParameterContentKind {
619 IsTypeParameter,
621 Array(TypeId),
623 Tuple(crate::types::TupleListId),
625 Union(Vec<TypeId>),
627 Intersection(Vec<TypeId>),
629 Application { base: TypeId, args: Vec<TypeId> },
631 NotTypeParameter,
633}
634
635pub fn classify_for_type_parameter_content(
637 db: &dyn TypeDatabase,
638 type_id: TypeId,
639) -> TypeParameterContentKind {
640 let Some(key) = db.lookup(type_id) else {
641 return TypeParameterContentKind::NotTypeParameter;
642 };
643
644 match key {
645 TypeData::TypeParameter(_) | TypeData::Infer(_) => {
646 TypeParameterContentKind::IsTypeParameter
647 }
648 TypeData::Array(elem) => TypeParameterContentKind::Array(elem),
649 TypeData::Tuple(list_id) => TypeParameterContentKind::Tuple(list_id),
650 TypeData::Union(list_id) => {
651 let members = db.type_list(list_id);
652 TypeParameterContentKind::Union(members.to_vec())
653 }
654 TypeData::Intersection(list_id) => {
655 let members = db.type_list(list_id);
656 TypeParameterContentKind::Intersection(members.to_vec())
657 }
658 TypeData::Application(app_id) => {
659 let app = db.type_application(app_id);
660 TypeParameterContentKind::Application {
661 base: app.base,
662 args: app.args.clone(),
663 }
664 }
665 _ => TypeParameterContentKind::NotTypeParameter,
666 }
667}
668
669#[derive(Debug, Clone)]
675pub enum TypeDepthKind {
676 Array(TypeId),
678 Tuple(crate::types::TupleListId),
680 Members(Vec<TypeId>),
682 Application { base: TypeId, args: Vec<TypeId> },
684 Terminal,
686}
687
688pub fn classify_for_type_depth(db: &dyn TypeDatabase, type_id: TypeId) -> TypeDepthKind {
690 let Some(key) = db.lookup(type_id) else {
691 return TypeDepthKind::Terminal;
692 };
693
694 match key {
695 TypeData::Array(elem) => TypeDepthKind::Array(elem),
696 TypeData::Tuple(list_id) => TypeDepthKind::Tuple(list_id),
697 TypeData::Union(list_id) | TypeData::Intersection(list_id) => {
698 let members = db.type_list(list_id);
699 TypeDepthKind::Members(members.to_vec())
700 }
701 TypeData::Application(app_id) => {
702 let app = db.type_application(app_id);
703 TypeDepthKind::Application {
704 base: app.base,
705 args: app.args.clone(),
706 }
707 }
708 _ => TypeDepthKind::Terminal,
709 }
710}
711
712#[derive(Debug, Clone)]
718pub enum SpreadPropertyKind {
719 Object(crate::types::ObjectShapeId),
721 Callable(crate::types::CallableShapeId),
723 Intersection(Vec<TypeId>),
725 NoProperties,
727}
728
729pub fn classify_for_spread_properties(
731 db: &dyn TypeDatabase,
732 type_id: TypeId,
733) -> SpreadPropertyKind {
734 let Some(key) = db.lookup(type_id) else {
735 return SpreadPropertyKind::NoProperties;
736 };
737
738 match key {
739 TypeData::Object(shape_id) | TypeData::ObjectWithIndex(shape_id) => {
740 SpreadPropertyKind::Object(shape_id)
741 }
742 TypeData::Callable(shape_id) => SpreadPropertyKind::Callable(shape_id),
743 TypeData::Intersection(list_id) => {
744 let members = db.type_list(list_id);
745 SpreadPropertyKind::Intersection(members.to_vec())
746 }
747 _ => SpreadPropertyKind::NoProperties,
748 }
749}
750
751#[derive(Debug, Clone)]
757pub enum LazyTypeKind {
758 Lazy(crate::def::DefId),
760 NotLazy,
762}
763
764pub fn classify_for_lazy_resolution(db: &dyn TypeDatabase, type_id: TypeId) -> LazyTypeKind {
766 let Some(key) = db.lookup(type_id) else {
767 return LazyTypeKind::NotLazy;
768 };
769
770 match key {
771 TypeData::Lazy(def_id) => LazyTypeKind::Lazy(def_id),
772 _ => LazyTypeKind::NotLazy,
773 }
774}
775
776pub fn is_narrowable_type_key(db: &dyn TypeDatabase, type_id: TypeId) -> bool {
778 matches!(
783 db.lookup(type_id),
784 Some(
785 TypeData::Union(_)
786 | TypeData::TypeParameter(_)
787 | TypeData::Infer(_)
788 | TypeData::Lazy(_)
789 )
790 )
791}
792
793#[derive(Debug, Clone)]
799pub enum PrivateBrandKind {
800 Object(crate::types::ObjectShapeId),
802 Callable(crate::types::CallableShapeId),
804 None,
806}
807
808pub fn classify_for_private_brand(db: &dyn TypeDatabase, type_id: TypeId) -> PrivateBrandKind {
810 match db.lookup(type_id) {
811 Some(TypeData::Object(shape_id) | TypeData::ObjectWithIndex(shape_id)) => {
812 PrivateBrandKind::Object(shape_id)
813 }
814 Some(TypeData::Callable(shape_id)) => PrivateBrandKind::Callable(shape_id),
815 _ => PrivateBrandKind::None,
816 }
817}
818
819pub fn get_widened_literal_type(db: &dyn TypeDatabase, type_id: TypeId) -> Option<TypeId> {
821 match db.lookup(type_id) {
822 Some(TypeData::Literal(ref lit)) => Some(lit.primitive_type_id()),
823 _ => None,
824 }
825}
826
827pub fn get_tuple_list_id(
829 db: &dyn TypeDatabase,
830 type_id: TypeId,
831) -> Option<crate::types::TupleListId> {
832 match db.lookup(type_id) {
833 Some(TypeData::Tuple(list_id)) => Some(list_id),
834 _ => None,
835 }
836}
837
838pub fn get_application_base(db: &dyn TypeDatabase, type_id: TypeId) -> Option<TypeId> {
840 match db.lookup(type_id) {
841 Some(TypeData::Application(app_id)) => Some(db.type_application(app_id).base),
842 _ => None,
843 }
844}
845
846#[derive(Debug, Clone)]
852pub enum LiteralKeyKind {
853 StringLiteral(tsz_common::interner::Atom),
854 NumberLiteral(f64),
855 Union(Vec<TypeId>),
856 Other,
857}
858
859pub fn classify_literal_key(db: &dyn TypeDatabase, type_id: TypeId) -> LiteralKeyKind {
861 match db.lookup(type_id) {
862 Some(TypeData::Literal(crate::LiteralValue::String(atom))) => {
863 LiteralKeyKind::StringLiteral(atom)
864 }
865 Some(TypeData::Literal(crate::LiteralValue::Number(num))) => {
866 LiteralKeyKind::NumberLiteral(num.0)
867 }
868 Some(TypeData::Union(members_id)) => {
869 LiteralKeyKind::Union(db.type_list(members_id).to_vec())
870 }
871 _ => LiteralKeyKind::Other,
872 }
873}
874
875pub fn get_literal_value(db: &dyn TypeDatabase, type_id: TypeId) -> Option<crate::LiteralValue> {
877 match db.lookup(type_id) {
878 Some(TypeData::Literal(value)) => Some(value),
879 _ => None,
880 }
881}
882
883pub fn widen_literal_to_primitive(db: &dyn TypeDatabase, type_id: TypeId) -> TypeId {
892 match db.lookup(type_id) {
893 Some(TypeData::Literal(ref lit)) => lit.primitive_type_id(),
894 _ => type_id,
895 }
896}
897
898pub fn get_object_property_type(
902 db: &dyn TypeDatabase,
903 type_id: TypeId,
904 property_name: &str,
905) -> Option<TypeId> {
906 match db.lookup(type_id) {
907 Some(TypeData::Object(shape_id) | TypeData::ObjectWithIndex(shape_id)) => {
908 let shape = db.object_shape(shape_id);
909 for prop in &shape.properties {
910 let prop_name = db.resolve_atom_ref(prop.name);
911 if prop_name.as_ref() == property_name {
912 return Some(prop.type_id);
913 }
914 }
915 None
916 }
917 _ => None,
918 }
919}
920
921pub fn get_function_return_type(db: &dyn TypeDatabase, type_id: TypeId) -> Option<TypeId> {
925 match db.lookup(type_id) {
926 Some(TypeData::Function(shape_id)) => {
927 let shape = db.function_shape(shape_id);
928 Some(shape.return_type)
929 }
930 _ => None,
931 }
932}
933
934pub fn is_object_with_index_type(db: &dyn TypeDatabase, type_id: TypeId) -> bool {
938 matches!(db.lookup(type_id), Some(TypeData::ObjectWithIndex(_)))
939}
940
941#[derive(Debug, Clone)]
947pub enum ArrayLikeKind {
948 Array(TypeId),
949 Tuple,
950 Readonly(TypeId),
951 Union(Vec<TypeId>),
952 Intersection(Vec<TypeId>),
953 Other,
954}
955
956pub fn classify_array_like(db: &dyn TypeDatabase, type_id: TypeId) -> ArrayLikeKind {
958 match db.lookup(type_id) {
959 Some(TypeData::Array(elem)) => ArrayLikeKind::Array(elem),
960 Some(TypeData::Tuple(_)) => ArrayLikeKind::Tuple,
961 Some(TypeData::ReadonlyType(inner)) => ArrayLikeKind::Readonly(inner),
962 Some(TypeData::TypeParameter(info) | TypeData::Infer(info)) => {
963 info.constraint.map_or(ArrayLikeKind::Other, |constraint| {
964 classify_array_like(db, constraint)
965 })
966 }
967 Some(TypeData::Union(members_id)) => {
968 ArrayLikeKind::Union(db.type_list(members_id).to_vec())
969 }
970 Some(TypeData::Intersection(members_id)) => {
971 ArrayLikeKind::Intersection(db.type_list(members_id).to_vec())
972 }
973 _ => ArrayLikeKind::Other,
974 }
975}
976
977#[derive(Debug, Clone)]
983pub enum IndexKeyKind {
984 String,
985 Number,
986 StringLiteral,
987 NumberLiteral,
988 Union(Vec<TypeId>),
989 Other,
990}
991
992pub fn classify_index_key(db: &dyn TypeDatabase, type_id: TypeId) -> IndexKeyKind {
994 match db.lookup(type_id) {
995 Some(TypeData::Intrinsic(crate::IntrinsicKind::String)) => IndexKeyKind::String,
996 Some(TypeData::Intrinsic(crate::IntrinsicKind::Number)) => IndexKeyKind::Number,
997 Some(TypeData::Literal(crate::LiteralValue::String(_))) => IndexKeyKind::StringLiteral,
998 Some(TypeData::Literal(crate::LiteralValue::Number(_))) => IndexKeyKind::NumberLiteral,
999 Some(TypeData::Union(members_id)) => IndexKeyKind::Union(db.type_list(members_id).to_vec()),
1000 _ => IndexKeyKind::Other,
1001 }
1002}
1003
1004#[derive(Debug, Clone)]
1010pub enum ElementIndexableKind {
1011 Array,
1012 Tuple,
1013 ObjectWithIndex { has_string: bool, has_number: bool },
1014 Union(Vec<TypeId>),
1015 Intersection(Vec<TypeId>),
1016 StringLike,
1017 Other,
1018}
1019
1020pub fn classify_element_indexable(db: &dyn TypeDatabase, type_id: TypeId) -> ElementIndexableKind {
1022 match db.lookup(type_id) {
1023 Some(TypeData::Array(_)) => ElementIndexableKind::Array,
1024 Some(TypeData::Tuple(_)) => ElementIndexableKind::Tuple,
1025 Some(TypeData::ObjectWithIndex(shape_id)) => {
1026 let shape = db.object_shape(shape_id);
1027 ElementIndexableKind::ObjectWithIndex {
1028 has_string: shape.string_index.is_some(),
1029 has_number: shape.number_index.is_some(),
1030 }
1031 }
1032 Some(TypeData::Union(members_id)) => {
1033 ElementIndexableKind::Union(db.type_list(members_id).to_vec())
1034 }
1035 Some(TypeData::Intersection(members_id)) => {
1036 ElementIndexableKind::Intersection(db.type_list(members_id).to_vec())
1037 }
1038 Some(TypeData::Literal(crate::LiteralValue::String(_)))
1039 | Some(TypeData::Intrinsic(crate::IntrinsicKind::String)) => {
1040 ElementIndexableKind::StringLike
1041 }
1042 Some(TypeData::Enum(_, _)) => ElementIndexableKind::ObjectWithIndex {
1045 has_string: true,
1046 has_number: true,
1047 },
1048 _ => ElementIndexableKind::Other,
1049 }
1050}
1051
1052#[derive(Debug, Clone)]
1058pub enum TypeQueryKind {
1059 TypeQuery(crate::types::SymbolRef),
1060 ApplicationWithTypeQuery {
1061 base_sym_ref: crate::types::SymbolRef,
1062 args: Vec<TypeId>,
1063 },
1064 Application {
1065 app_id: crate::types::TypeApplicationId,
1066 },
1067 Other,
1068}
1069
1070pub fn classify_type_query(db: &dyn TypeDatabase, type_id: TypeId) -> TypeQueryKind {
1072 match db.lookup(type_id) {
1073 Some(TypeData::TypeQuery(sym_ref)) => TypeQueryKind::TypeQuery(sym_ref),
1074 Some(TypeData::Application(app_id)) => {
1075 let app = db.type_application(app_id);
1076 match db.lookup(app.base) {
1077 Some(TypeData::TypeQuery(base_sym_ref)) => {
1078 TypeQueryKind::ApplicationWithTypeQuery {
1079 base_sym_ref,
1080 args: app.args.clone(),
1081 }
1082 }
1083 _ => TypeQueryKind::Application { app_id },
1084 }
1085 }
1086 _ => TypeQueryKind::Other,
1087 }
1088}
1089
1090#[derive(Debug, Clone)]
1096pub enum SymbolRefKind {
1097 Lazy(crate::def::DefId),
1099 TypeQuery(crate::types::SymbolRef),
1100 Other,
1101}
1102
1103pub fn classify_symbol_ref(db: &dyn TypeDatabase, type_id: TypeId) -> SymbolRefKind {
1105 match db.lookup(type_id) {
1106 Some(TypeData::Lazy(def_id)) => SymbolRefKind::Lazy(def_id),
1107 Some(TypeData::TypeQuery(sym_ref)) => SymbolRefKind::TypeQuery(sym_ref),
1108 _ => SymbolRefKind::Other,
1109 }
1110}
1111
1112#[derive(Debug, Clone)]
1118pub enum TypeContainsKind {
1119 Array(TypeId),
1120 Tuple(crate::types::TupleListId),
1121 Members(Vec<TypeId>),
1122 Object(crate::types::ObjectShapeId),
1123 Function(crate::types::FunctionShapeId),
1124 Callable(crate::types::CallableShapeId),
1125 Application(crate::types::TypeApplicationId),
1126 Conditional(crate::types::ConditionalTypeId),
1127 Mapped(crate::types::MappedTypeId),
1128 IndexAccess {
1129 base: TypeId,
1130 index: TypeId,
1131 },
1132 TemplateLiteral(crate::types::TemplateLiteralId),
1133 Inner(TypeId),
1134 TypeParam {
1135 constraint: Option<TypeId>,
1136 default: Option<TypeId>,
1137 },
1138 Terminal,
1139}
1140
1141pub fn classify_for_contains_traversal(db: &dyn TypeDatabase, type_id: TypeId) -> TypeContainsKind {
1143 match db.lookup(type_id) {
1144 Some(TypeData::Array(elem)) => TypeContainsKind::Array(elem),
1145 Some(TypeData::Tuple(list_id)) => TypeContainsKind::Tuple(list_id),
1146 Some(TypeData::Union(list_id) | TypeData::Intersection(list_id)) => {
1147 TypeContainsKind::Members(db.type_list(list_id).to_vec())
1148 }
1149 Some(TypeData::Object(shape_id) | TypeData::ObjectWithIndex(shape_id)) => {
1150 TypeContainsKind::Object(shape_id)
1151 }
1152 Some(TypeData::Function(shape_id)) => TypeContainsKind::Function(shape_id),
1153 Some(TypeData::Callable(shape_id)) => TypeContainsKind::Callable(shape_id),
1154 Some(TypeData::Application(app_id)) => TypeContainsKind::Application(app_id),
1155 Some(TypeData::Conditional(cond_id)) => TypeContainsKind::Conditional(cond_id),
1156 Some(TypeData::Mapped(mapped_id)) => TypeContainsKind::Mapped(mapped_id),
1157 Some(TypeData::IndexAccess(base, index)) => TypeContainsKind::IndexAccess { base, index },
1158 Some(TypeData::TemplateLiteral(template_id)) => {
1159 TypeContainsKind::TemplateLiteral(template_id)
1160 }
1161 Some(TypeData::KeyOf(inner) | TypeData::ReadonlyType(inner)) => {
1162 TypeContainsKind::Inner(inner)
1163 }
1164 Some(TypeData::TypeParameter(info) | TypeData::Infer(info)) => {
1165 TypeContainsKind::TypeParam {
1166 constraint: info.constraint,
1167 default: info.default,
1168 }
1169 }
1170 _ => TypeContainsKind::Terminal,
1171 }
1172}
1173
1174#[derive(Debug, Clone)]
1180pub enum NamespaceMemberKind {
1181 Lazy(DefId),
1182 ModuleNamespace(crate::types::SymbolRef),
1183 Callable(crate::types::CallableShapeId),
1184 Enum(DefId),
1186 Other,
1187}
1188
1189pub fn classify_namespace_member(db: &dyn TypeDatabase, type_id: TypeId) -> NamespaceMemberKind {
1191 match db.lookup(type_id) {
1192 Some(TypeData::Callable(shape_id)) => NamespaceMemberKind::Callable(shape_id),
1193 Some(TypeData::Lazy(def_id)) => NamespaceMemberKind::Lazy(def_id),
1194 Some(TypeData::ModuleNamespace(sym_ref)) => NamespaceMemberKind::ModuleNamespace(sym_ref),
1195 Some(TypeData::Enum(def_id, _)) => NamespaceMemberKind::Enum(def_id),
1197 _ => NamespaceMemberKind::Other,
1198 }
1199}
1200
1201pub fn unwrap_readonly_for_lookup(db: &dyn TypeDatabase, type_id: TypeId) -> TypeId {
1203 match db.lookup(type_id) {
1204 Some(TypeData::ReadonlyType(inner)) => inner,
1205 _ => type_id,
1206 }
1207}
1208
1209pub fn create_string_literal_type(db: &dyn TypeDatabase, value: &str) -> TypeId {
1217 let atom = db.intern_string(value);
1218 db.literal_string_atom(atom)
1219}
1220
1221pub fn create_number_literal_type(db: &dyn TypeDatabase, value: f64) -> TypeId {
1225 db.literal_number(value)
1226}
1227
1228pub fn create_boolean_literal_type(db: &dyn TypeDatabase, value: bool) -> TypeId {
1232 db.literal_boolean(value)
1233}
1234
1235#[derive(Debug, Clone)]
1241pub enum PropertyAccessResolutionKind {
1242 Lazy(DefId),
1244 TypeQuery(crate::types::SymbolRef),
1246 Application(crate::types::TypeApplicationId),
1248 TypeParameter { constraint: Option<TypeId> },
1250 NeedsEvaluation,
1252 Union(Vec<TypeId>),
1254 Intersection(Vec<TypeId>),
1256 Readonly(TypeId),
1258 FunctionLike,
1260 Resolved,
1262}
1263
1264pub fn classify_for_property_access_resolution(
1266 db: &dyn TypeDatabase,
1267 type_id: TypeId,
1268) -> PropertyAccessResolutionKind {
1269 let Some(key) = db.lookup(type_id) else {
1270 return PropertyAccessResolutionKind::Resolved;
1271 };
1272
1273 match key {
1274 TypeData::TypeQuery(sym_ref) => PropertyAccessResolutionKind::TypeQuery(sym_ref),
1275 TypeData::Lazy(def_id) => PropertyAccessResolutionKind::Lazy(def_id),
1276 TypeData::Application(app_id) => PropertyAccessResolutionKind::Application(app_id),
1277 TypeData::TypeParameter(info) | TypeData::Infer(info) => {
1278 PropertyAccessResolutionKind::TypeParameter {
1279 constraint: info.constraint,
1280 }
1281 }
1282 TypeData::Conditional(_)
1283 | TypeData::Mapped(_)
1284 | TypeData::IndexAccess(_, _)
1285 | TypeData::KeyOf(_) => PropertyAccessResolutionKind::NeedsEvaluation,
1286 TypeData::Union(list_id) => {
1287 let members = db.type_list(list_id);
1288 PropertyAccessResolutionKind::Union(members.to_vec())
1289 }
1290 TypeData::Intersection(list_id) => {
1291 let members = db.type_list(list_id);
1292 PropertyAccessResolutionKind::Intersection(members.to_vec())
1293 }
1294 TypeData::ReadonlyType(inner) => PropertyAccessResolutionKind::Readonly(inner),
1295 TypeData::Function(_) | TypeData::Callable(_) => PropertyAccessResolutionKind::FunctionLike,
1296 _ => PropertyAccessResolutionKind::Resolved,
1297 }
1298}
1299
1300#[derive(Debug, Clone)]
1306pub enum ContextualLiteralAllowKind {
1307 Members(Vec<TypeId>),
1309 TypeParameter { constraint: Option<TypeId> },
1311 Application,
1313 Mapped,
1315 TemplateLiteral,
1319 NotAllowed,
1321}
1322
1323pub fn classify_for_contextual_literal(
1325 db: &dyn TypeDatabase,
1326 type_id: TypeId,
1327) -> ContextualLiteralAllowKind {
1328 let Some(key) = db.lookup(type_id) else {
1329 return ContextualLiteralAllowKind::NotAllowed;
1330 };
1331
1332 match key {
1333 TypeData::Union(list_id) | TypeData::Intersection(list_id) => {
1334 let members = db.type_list(list_id);
1335 ContextualLiteralAllowKind::Members(members.to_vec())
1336 }
1337 TypeData::TypeParameter(info) | TypeData::Infer(info) => {
1338 ContextualLiteralAllowKind::TypeParameter {
1339 constraint: info.constraint,
1340 }
1341 }
1342 TypeData::Application(_) => ContextualLiteralAllowKind::Application,
1343 TypeData::Mapped(_) => ContextualLiteralAllowKind::Mapped,
1344 TypeData::TemplateLiteral(_) => ContextualLiteralAllowKind::TemplateLiteral,
1345 _ => ContextualLiteralAllowKind::NotAllowed,
1346 }
1347}
1348
1349#[derive(Debug, Clone)]
1355pub enum MappedConstraintKind {
1356 KeyOf(TypeId),
1358 Resolved,
1360 Other,
1362}
1363
1364pub fn classify_mapped_constraint(db: &dyn TypeDatabase, type_id: TypeId) -> MappedConstraintKind {
1366 let Some(key) = db.lookup(type_id) else {
1367 return MappedConstraintKind::Other;
1368 };
1369
1370 match key {
1371 TypeData::KeyOf(operand) => MappedConstraintKind::KeyOf(operand),
1372 TypeData::Union(_) | TypeData::Literal(_) => MappedConstraintKind::Resolved,
1373 _ => MappedConstraintKind::Other,
1374 }
1375}
1376
1377#[derive(Debug, Clone)]
1383pub enum TypeResolutionKind {
1384 Lazy(DefId),
1386 Application,
1388 Resolved,
1390}
1391
1392pub fn classify_for_type_resolution(db: &dyn TypeDatabase, type_id: TypeId) -> TypeResolutionKind {
1394 let Some(key) = db.lookup(type_id) else {
1395 return TypeResolutionKind::Resolved;
1396 };
1397
1398 match key {
1399 TypeData::Lazy(def_id) => TypeResolutionKind::Lazy(def_id),
1400 TypeData::Application(_) => TypeResolutionKind::Application,
1401 _ => TypeResolutionKind::Resolved,
1402 }
1403}
1404
1405#[derive(Debug, Clone)]
1411pub enum TypeArgumentExtractionKind {
1412 Function(crate::types::FunctionShapeId),
1414 Callable(crate::types::CallableShapeId),
1416 Other,
1418}
1419
1420pub fn classify_for_type_argument_extraction(
1422 db: &dyn TypeDatabase,
1423 type_id: TypeId,
1424) -> TypeArgumentExtractionKind {
1425 let Some(key) = db.lookup(type_id) else {
1426 return TypeArgumentExtractionKind::Other;
1427 };
1428
1429 match key {
1430 TypeData::Function(shape_id) => TypeArgumentExtractionKind::Function(shape_id),
1431 TypeData::Callable(shape_id) => TypeArgumentExtractionKind::Callable(shape_id),
1432 _ => TypeArgumentExtractionKind::Other,
1433 }
1434}