1use crate::types::{IntrinsicKind, ObjectShapeId};
15use crate::{TypeData, TypeDatabase, TypeId};
16use rustc_hash::FxHashMap;
17
18pub fn is_literal_type(types: &dyn TypeDatabase, type_id: TypeId) -> bool {
26 matches!(types.lookup(type_id), Some(TypeData::Literal(_)))
27}
28
29pub fn is_module_namespace_type(types: &dyn TypeDatabase, type_id: TypeId) -> bool {
33 matches!(types.lookup(type_id), Some(TypeData::ModuleNamespace(_)))
34}
35
36pub fn is_function_type(types: &dyn TypeDatabase, type_id: TypeId) -> bool {
40 is_function_type_impl(types, type_id)
41}
42
43fn is_function_type_impl(types: &dyn TypeDatabase, type_id: TypeId) -> bool {
44 match types.lookup(type_id) {
45 Some(TypeData::Function(_) | TypeData::Callable(_)) => true,
46 Some(TypeData::Intersection(members)) => {
47 let members = types.type_list(members);
48 members
49 .iter()
50 .any(|&member| is_function_type_impl(types, member))
51 }
52 _ => false,
53 }
54}
55
56pub fn is_object_like_type(types: &dyn TypeDatabase, type_id: TypeId) -> bool {
60 is_object_like_type_impl(types, type_id)
61}
62
63fn is_object_like_type_impl(types: &dyn TypeDatabase, type_id: TypeId) -> bool {
64 match types.lookup(type_id) {
65 Some(
66 TypeData::Object(_)
67 | TypeData::ObjectWithIndex(_)
68 | TypeData::Array(_)
69 | TypeData::Tuple(_)
70 | TypeData::Mapped(_)
71 | TypeData::Function(_)
72 | TypeData::Callable(_)
73 | TypeData::Intrinsic(IntrinsicKind::Object | IntrinsicKind::Function),
74 ) => true,
75 Some(TypeData::ReadonlyType(inner)) => is_object_like_type_impl(types, inner),
76 Some(TypeData::Intersection(members)) => {
77 let members = types.type_list(members);
78 members
79 .iter()
80 .all(|&member| is_object_like_type_impl(types, member))
81 }
82 Some(TypeData::TypeParameter(info) | TypeData::Infer(info)) => info
83 .constraint
84 .is_some_and(|constraint| is_object_like_type_impl(types, constraint)),
85 _ => false,
86 }
87}
88
89pub fn is_empty_object_type(types: &dyn TypeDatabase, type_id: TypeId) -> bool {
91 match types.lookup(type_id) {
92 Some(TypeData::Object(shape_id)) => {
93 let shape = types.object_shape(shape_id);
94 shape.properties.is_empty()
95 }
96 Some(TypeData::ObjectWithIndex(shape_id)) => {
97 let shape = types.object_shape(shape_id);
98 shape.properties.is_empty()
99 && shape.string_index.is_none()
100 && shape.number_index.is_none()
101 }
102 _ => false,
103 }
104}
105
106pub fn is_primitive_type(types: &dyn TypeDatabase, type_id: TypeId) -> bool {
108 if type_id.is_intrinsic() {
110 return true;
111 }
112 matches!(
113 types.lookup(type_id),
114 Some(TypeData::Intrinsic(_) | TypeData::Literal(_))
115 )
116}
117
118pub fn is_union_type(types: &dyn TypeDatabase, type_id: TypeId) -> bool {
120 matches!(types.lookup(type_id), Some(TypeData::Union(_)))
121}
122
123pub fn is_intersection_type(types: &dyn TypeDatabase, type_id: TypeId) -> bool {
125 matches!(types.lookup(type_id), Some(TypeData::Intersection(_)))
126}
127
128pub fn is_array_type(types: &dyn TypeDatabase, type_id: TypeId) -> bool {
130 matches!(types.lookup(type_id), Some(TypeData::Array(_)))
131}
132
133pub fn is_tuple_type(types: &dyn TypeDatabase, type_id: TypeId) -> bool {
135 matches!(types.lookup(type_id), Some(TypeData::Tuple(_)))
136}
137
138pub fn is_type_parameter(types: &dyn TypeDatabase, type_id: TypeId) -> bool {
140 matches!(
141 types.lookup(type_id),
142 Some(TypeData::TypeParameter(_) | TypeData::Infer(_))
143 )
144}
145
146pub fn is_conditional_type(types: &dyn TypeDatabase, type_id: TypeId) -> bool {
148 matches!(types.lookup(type_id), Some(TypeData::Conditional(_)))
149}
150
151pub fn is_mapped_type(types: &dyn TypeDatabase, type_id: TypeId) -> bool {
153 matches!(types.lookup(type_id), Some(TypeData::Mapped(_)))
154}
155
156pub fn is_index_access_type(types: &dyn TypeDatabase, type_id: TypeId) -> bool {
158 matches!(types.lookup(type_id), Some(TypeData::IndexAccess(_, _)))
159}
160
161pub fn is_template_literal_type(types: &dyn TypeDatabase, type_id: TypeId) -> bool {
163 matches!(types.lookup(type_id), Some(TypeData::TemplateLiteral(_)))
164}
165
166pub fn is_type_reference(types: &dyn TypeDatabase, type_id: TypeId) -> bool {
168 matches!(
169 types.lookup(type_id),
170 Some(TypeData::Lazy(_) | TypeData::Recursive(_))
171 )
172}
173
174pub fn is_generic_application(types: &dyn TypeDatabase, type_id: TypeId) -> bool {
176 matches!(types.lookup(type_id), Some(TypeData::Application(_)))
177}
178
179pub fn is_identity_comparable_type(types: &dyn TypeDatabase, type_id: TypeId) -> bool {
201 is_identity_comparable_type_impl(types, type_id, 0)
202}
203
204const MAX_IDENTITY_COMPARABLE_DEPTH: u32 = 10;
205
206fn is_identity_comparable_type_impl(types: &dyn TypeDatabase, type_id: TypeId, depth: u32) -> bool {
207 if depth > MAX_IDENTITY_COMPARABLE_DEPTH {
209 return false;
210 }
211
212 if type_id == TypeId::NULL
214 || type_id == TypeId::UNDEFINED
215 || type_id == TypeId::VOID
216 || type_id == TypeId::NEVER
217 {
218 return true;
219 }
220
221 match types.lookup(type_id) {
222 Some(TypeData::Literal(_))
224 | Some(TypeData::Enum(_, _))
225 | Some(TypeData::UniqueSymbol(_)) => true,
226
227 Some(TypeData::Tuple(list_id)) => {
229 let elements = types.tuple_list(list_id);
230 if elements.iter().any(|e| e.rest) {
232 return false;
233 }
234 elements
236 .iter()
237 .all(|e| is_identity_comparable_type_impl(types, e.type_id, depth + 1))
238 }
239
240 _ => false,
244 }
245}
246
247pub fn contains_type_parameters(types: &dyn TypeDatabase, type_id: TypeId) -> bool {
253 contains_type_matching(types, type_id, |key| {
254 matches!(key, TypeData::TypeParameter(_) | TypeData::Infer(_))
255 })
256}
257
258pub fn contains_infer_types(types: &dyn TypeDatabase, type_id: TypeId) -> bool {
260 contains_type_matching(types, type_id, |key| matches!(key, TypeData::Infer(_)))
261}
262
263pub fn contains_error_type(types: &dyn TypeDatabase, type_id: TypeId) -> bool {
265 if type_id == TypeId::ERROR {
266 return true;
267 }
268 contains_type_matching(types, type_id, |key| matches!(key, TypeData::Error))
269}
270
271pub fn contains_this_type(types: &dyn TypeDatabase, type_id: TypeId) -> bool {
273 contains_type_matching(types, type_id, |key| matches!(key, TypeData::ThisType))
274}
275
276pub fn contains_type_matching<F>(types: &dyn TypeDatabase, type_id: TypeId, predicate: F) -> bool
278where
279 F: Fn(&TypeData) -> bool,
280{
281 let mut checker = ContainsTypeChecker {
282 types,
283 predicate,
284 memo: FxHashMap::default(),
285 guard: crate::recursion::RecursionGuard::with_profile(
286 crate::recursion::RecursionProfile::ShallowTraversal,
287 ),
288 };
289 checker.check(type_id)
290}
291
292struct ContainsTypeChecker<'a, F>
293where
294 F: Fn(&TypeData) -> bool,
295{
296 types: &'a dyn TypeDatabase,
297 predicate: F,
298 memo: FxHashMap<TypeId, bool>,
299 guard: crate::recursion::RecursionGuard<TypeId>,
300}
301
302impl<'a, F> ContainsTypeChecker<'a, F>
303where
304 F: Fn(&TypeData) -> bool,
305{
306 fn check(&mut self, type_id: TypeId) -> bool {
307 if let Some(&cached) = self.memo.get(&type_id) {
308 return cached;
309 }
310
311 match self.guard.enter(type_id) {
312 crate::recursion::RecursionResult::Entered => {}
313 _ => return false,
314 }
315
316 let Some(key) = self.types.lookup(type_id) else {
317 self.guard.leave(type_id);
318 return false;
319 };
320
321 if (self.predicate)(&key) {
322 self.guard.leave(type_id);
323 self.memo.insert(type_id, true);
324 return true;
325 }
326
327 let result = self.check_key(&key);
328
329 self.guard.leave(type_id);
330 self.memo.insert(type_id, result);
331
332 result
333 }
334
335 fn check_key(&mut self, key: &TypeData) -> bool {
336 match key {
337 TypeData::Intrinsic(_)
338 | TypeData::Literal(_)
339 | TypeData::Error
340 | TypeData::ThisType
341 | TypeData::BoundParameter(_)
342 | TypeData::Lazy(_)
343 | TypeData::Recursive(_)
344 | TypeData::TypeQuery(_)
345 | TypeData::UniqueSymbol(_)
346 | TypeData::ModuleNamespace(_) => false,
347 TypeData::Object(shape_id) | TypeData::ObjectWithIndex(shape_id) => {
348 let shape = self.types.object_shape(*shape_id);
349 shape.properties.iter().any(|p| self.check(p.type_id))
350 || shape
351 .string_index
352 .as_ref()
353 .is_some_and(|i| self.check(i.value_type))
354 || shape
355 .number_index
356 .as_ref()
357 .is_some_and(|i| self.check(i.value_type))
358 }
359 TypeData::Union(list_id) | TypeData::Intersection(list_id) => {
360 let members = self.types.type_list(*list_id);
361 members.iter().any(|&m| self.check(m))
362 }
363 TypeData::Array(elem) => self.check(*elem),
364 TypeData::Tuple(list_id) => {
365 let elements = self.types.tuple_list(*list_id);
366 elements.iter().any(|e| self.check(e.type_id))
367 }
368 TypeData::Function(shape_id) => {
369 let shape = self.types.function_shape(*shape_id);
370 shape.params.iter().any(|p| self.check(p.type_id))
371 || self.check(shape.return_type)
372 || shape.this_type.is_some_and(|t| self.check(t))
373 }
374 TypeData::Callable(shape_id) => {
375 let shape = self.types.callable_shape(*shape_id);
376 shape.call_signatures.iter().any(|s| {
377 s.params.iter().any(|p| self.check(p.type_id)) || self.check(s.return_type)
378 }) || shape.construct_signatures.iter().any(|s| {
379 s.params.iter().any(|p| self.check(p.type_id)) || self.check(s.return_type)
380 }) || shape.properties.iter().any(|p| self.check(p.type_id))
381 }
382 TypeData::TypeParameter(info) | TypeData::Infer(info) => {
383 info.constraint.is_some_and(|c| self.check(c))
384 || info.default.is_some_and(|d| self.check(d))
385 }
386 TypeData::Application(app_id) => {
387 let app = self.types.type_application(*app_id);
388 self.check(app.base) || app.args.iter().any(|&a| self.check(a))
389 }
390 TypeData::Conditional(cond_id) => {
391 let cond = self.types.conditional_type(*cond_id);
392 self.check(cond.check_type)
393 || self.check(cond.extends_type)
394 || self.check(cond.true_type)
395 || self.check(cond.false_type)
396 }
397 TypeData::Mapped(mapped_id) => {
398 let mapped = self.types.mapped_type(*mapped_id);
399 mapped.type_param.constraint.is_some_and(|c| self.check(c))
400 || mapped.type_param.default.is_some_and(|d| self.check(d))
401 || self.check(mapped.constraint)
402 || self.check(mapped.template)
403 || mapped.name_type.is_some_and(|n| self.check(n))
404 }
405 TypeData::IndexAccess(obj, idx) => self.check(*obj) || self.check(*idx),
406 TypeData::TemplateLiteral(list_id) => {
407 let spans = self.types.template_list(*list_id);
408 spans.iter().any(|span| {
409 if let crate::types::TemplateSpan::Type(type_id) = span {
410 self.check(*type_id)
411 } else {
412 false
413 }
414 })
415 }
416 TypeData::KeyOf(inner) | TypeData::ReadonlyType(inner) | TypeData::NoInfer(inner) => {
417 self.check(*inner)
418 }
419 TypeData::StringIntrinsic { type_arg, .. } => self.check(*type_arg),
420 TypeData::Enum(_def_id, member_type) => self.check(*member_type),
421 }
422 }
423}
424
425pub fn is_literal_type_db(types: &dyn TypeDatabase, type_id: TypeId) -> bool {
431 LiteralTypeChecker::check(types, type_id)
432}
433
434pub fn is_function_type_db(types: &dyn TypeDatabase, type_id: TypeId) -> bool {
436 FunctionTypeChecker::check(types, type_id)
437}
438
439pub fn is_object_like_type_db(types: &dyn TypeDatabase, type_id: TypeId) -> bool {
441 ObjectTypeChecker::check(types, type_id)
442}
443
444pub fn is_empty_object_type_db(types: &dyn TypeDatabase, type_id: TypeId) -> bool {
446 let checker = EmptyObjectChecker::new(types);
447 checker.check(type_id)
448}
449
450pub enum ObjectTypeKind {
456 Object(ObjectShapeId),
458 ObjectWithIndex(ObjectShapeId),
460 NotObject,
462}
463
464pub fn classify_object_type(types: &dyn TypeDatabase, type_id: TypeId) -> ObjectTypeKind {
469 match types.lookup(type_id) {
470 Some(TypeData::Object(shape_id)) => ObjectTypeKind::Object(shape_id),
471 Some(TypeData::ObjectWithIndex(shape_id)) => ObjectTypeKind::ObjectWithIndex(shape_id),
472 _ => ObjectTypeKind::NotObject,
473 }
474}
475
476struct LiteralTypeChecker;
482
483impl LiteralTypeChecker {
484 fn check(types: &dyn TypeDatabase, type_id: TypeId) -> bool {
485 match types.lookup(type_id) {
486 Some(TypeData::Literal(_)) => true,
487 Some(TypeData::ReadonlyType(inner) | TypeData::NoInfer(inner)) => {
488 Self::check(types, inner)
489 }
490 Some(TypeData::TypeParameter(info) | TypeData::Infer(info)) => {
491 info.constraint.is_some_and(|c| Self::check(types, c))
492 }
493 _ => false,
494 }
495 }
496}
497
498struct FunctionTypeChecker;
500
501impl FunctionTypeChecker {
502 fn check(types: &dyn TypeDatabase, type_id: TypeId) -> bool {
503 match types.lookup(type_id) {
504 Some(TypeData::Function(_) | TypeData::Callable(_)) => true,
505 Some(TypeData::Intersection(members)) => {
506 let members = types.type_list(members);
507 members.iter().any(|&member| Self::check(types, member))
508 }
509 Some(TypeData::TypeParameter(info) | TypeData::Infer(info)) => {
510 info.constraint.is_some_and(|c| Self::check(types, c))
511 }
512 _ => false,
513 }
514 }
515}
516
517struct ObjectTypeChecker;
519
520impl ObjectTypeChecker {
521 fn check(types: &dyn TypeDatabase, type_id: TypeId) -> bool {
522 match types.lookup(type_id) {
523 Some(
524 TypeData::Object(_)
525 | TypeData::ObjectWithIndex(_)
526 | TypeData::Array(_)
527 | TypeData::Tuple(_)
528 | TypeData::Mapped(_),
529 ) => true,
530 Some(TypeData::ReadonlyType(inner) | TypeData::NoInfer(inner)) => {
531 Self::check(types, inner)
532 }
533 Some(TypeData::Intersection(members)) => {
534 let members = types.type_list(members);
535 members.iter().all(|&member| Self::check(types, member))
536 }
537 Some(TypeData::TypeParameter(info) | TypeData::Infer(info)) => info
538 .constraint
539 .is_some_and(|constraint| Self::check(types, constraint)),
540 _ => false,
541 }
542 }
543}
544
545struct EmptyObjectChecker<'a> {
547 db: &'a dyn TypeDatabase,
548}
549
550impl<'a> EmptyObjectChecker<'a> {
551 fn new(db: &'a dyn TypeDatabase) -> Self {
552 Self { db }
553 }
554
555 fn check(&self, type_id: TypeId) -> bool {
556 match self.db.lookup(type_id) {
557 Some(TypeData::Object(shape_id)) => {
558 let shape = self.db.object_shape(shape_id);
559 shape.properties.is_empty()
560 }
561 Some(TypeData::ObjectWithIndex(shape_id)) => {
562 let shape = self.db.object_shape(shape_id);
563 shape.properties.is_empty()
564 && shape.string_index.is_none()
565 && shape.number_index.is_none()
566 }
567 Some(TypeData::ReadonlyType(inner) | TypeData::NoInfer(inner)) => self.check(inner),
568 Some(TypeData::TypeParameter(info) | TypeData::Infer(info)) => {
569 info.constraint.is_some_and(|c| self.check(c))
570 }
571 _ => false,
572 }
573 }
574}