1use crate::caches::db::QueryDatabase;
4use crate::diagnostics::SubtypeFailureReason;
5use crate::relations::subtype::{NoopResolver, SubtypeChecker, TypeResolver};
6use crate::types::{IntrinsicKind, LiteralValue, PropertyInfo, TypeData, TypeId};
7use crate::visitor::{TypeVisitor, intrinsic_kind, is_empty_object_type_db, lazy_def_id};
8use crate::{AnyPropagationRules, AssignabilityChecker, TypeDatabase};
9use rustc_hash::FxHashMap;
10use tsz_common::interner::Atom;
11
12pub(crate) struct ShapeExtractor<'a, R: TypeResolver> {
18 db: &'a dyn TypeDatabase,
19 resolver: &'a R,
20 guard: crate::recursion::RecursionGuard<TypeId>,
21}
22
23impl<'a, R: TypeResolver> ShapeExtractor<'a, R> {
24 pub(crate) fn new(db: &'a dyn TypeDatabase, resolver: &'a R) -> Self {
25 Self {
26 db,
27 resolver,
28 guard: crate::recursion::RecursionGuard::with_profile(
29 crate::recursion::RecursionProfile::ShapeExtraction,
30 ),
31 }
32 }
33
34 pub(crate) fn extract(&mut self, type_id: TypeId) -> Option<u32> {
36 match self.guard.enter(type_id) {
37 crate::recursion::RecursionResult::Entered => {}
38 _ => return None, }
40 let result = self.visit_type(self.db, type_id);
41 self.guard.leave(type_id);
42 result
43 }
44}
45
46pub(crate) struct StringLikeVisitor<'a> {
48 pub(crate) db: &'a dyn TypeDatabase,
49}
50
51impl<'a> TypeVisitor for StringLikeVisitor<'a> {
52 type Output = bool;
53
54 fn visit_intrinsic(&mut self, kind: IntrinsicKind) -> Self::Output {
55 kind == IntrinsicKind::String
56 }
57
58 fn visit_literal(&mut self, value: &LiteralValue) -> Self::Output {
59 matches!(value, LiteralValue::String(_))
60 }
61
62 fn visit_template_literal(&mut self, _template_id: u32) -> Self::Output {
63 true
64 }
65
66 fn visit_type_parameter(&mut self, info: &crate::types::TypeParamInfo) -> Self::Output {
67 info.constraint.is_some_and(|c| self.visit_type(self.db, c))
68 }
69
70 fn visit_ref(&mut self, _symbol_ref: u32) -> Self::Output {
71 false
73 }
74
75 fn visit_lazy(&mut self, _def_id: u32) -> Self::Output {
76 false
78 }
79
80 fn default_output() -> Self::Output {
81 false
82 }
83}
84
85impl<'a, R: TypeResolver> TypeVisitor for ShapeExtractor<'a, R> {
86 type Output = Option<u32>;
87
88 fn visit_intrinsic(&mut self, _kind: crate::types::IntrinsicKind) -> Self::Output {
89 None
90 }
91
92 fn visit_literal(&mut self, _value: &crate::LiteralValue) -> Self::Output {
93 None
94 }
95
96 fn visit_object(&mut self, shape_id: u32) -> Self::Output {
97 Some(shape_id)
98 }
99
100 fn visit_object_with_index(&mut self, shape_id: u32) -> Self::Output {
101 Some(shape_id)
102 }
103
104 fn visit_lazy(&mut self, def_id: u32) -> Self::Output {
105 let def_id = crate::def::DefId(def_id);
106 if let Some(resolved) = self.resolver.resolve_lazy(def_id, self.db) {
107 return self.extract(resolved);
108 }
109 None
110 }
111
112 fn visit_ref(&mut self, symbol_ref: u32) -> Self::Output {
113 let symbol_ref = crate::types::SymbolRef(symbol_ref);
114 if let Some(def_id) = self.resolver.symbol_to_def_id(symbol_ref) {
116 return self.visit_lazy(def_id.0);
117 }
118 if let Some(resolved) = self.resolver.resolve_symbol_ref(symbol_ref, self.db) {
119 return self.extract(resolved);
120 }
121 None
122 }
123
124 fn visit_intersection(&mut self, list_id: u32) -> Self::Output {
127 let member_list = self.db.type_list(crate::types::TypeListId(list_id));
128 for member in member_list.iter() {
131 if let Some(shape) = self.visit_type(self.db, *member) {
132 return Some(shape);
133 }
134 }
135 None
136 }
137
138 fn default_output() -> Self::Output {
139 None
140 }
141}
142
143pub trait AssignabilityOverrideProvider {
149 fn enum_assignability_override(&self, source: TypeId, target: TypeId) -> Option<bool>;
152
153 fn abstract_constructor_assignability_override(
156 &self,
157 source: TypeId,
158 target: TypeId,
159 ) -> Option<bool>;
160
161 fn constructor_accessibility_override(&self, source: TypeId, target: TypeId) -> Option<bool>;
164}
165
166pub struct NoopOverrideProvider;
168
169impl AssignabilityOverrideProvider for NoopOverrideProvider {
170 fn enum_assignability_override(&self, _source: TypeId, _target: TypeId) -> Option<bool> {
171 None
172 }
173
174 fn abstract_constructor_assignability_override(
175 &self,
176 _source: TypeId,
177 _target: TypeId,
178 ) -> Option<bool> {
179 None
180 }
181
182 fn constructor_accessibility_override(&self, _source: TypeId, _target: TypeId) -> Option<bool> {
183 None
184 }
185}
186
187pub struct CompatChecker<'a, R: TypeResolver = NoopResolver> {
193 pub(crate) interner: &'a dyn TypeDatabase,
194 query_db: Option<&'a dyn QueryDatabase>,
196 pub(crate) subtype: SubtypeChecker<'a, R>,
197 lawyer: AnyPropagationRules,
199 strict_function_types: bool,
200 strict_null_checks: bool,
201 no_unchecked_indexed_access: bool,
202 exact_optional_property_types: bool,
203 strict_subtype_checking: bool,
205 cache: FxHashMap<(TypeId, TypeId), bool>,
206}
207
208impl<'a> CompatChecker<'a, NoopResolver> {
209 pub fn new(interner: &'a dyn TypeDatabase) -> Self {
212 CompatChecker {
213 interner,
214 query_db: None,
215 subtype: SubtypeChecker::new(interner),
216 lawyer: AnyPropagationRules::new(),
217 strict_function_types: false,
220 strict_null_checks: true,
221 no_unchecked_indexed_access: false,
222 exact_optional_property_types: false,
223 strict_subtype_checking: false,
224 cache: FxHashMap::default(),
225 }
226 }
227}
228
229impl<'a, R: TypeResolver> CompatChecker<'a, R> {
230 fn normalize_assignability_operand(&mut self, mut type_id: TypeId) -> TypeId {
231 for _ in 0..8 {
233 let next = match self.interner.lookup(type_id) {
234 Some(TypeData::Lazy(def_id)) => self
235 .subtype
236 .resolver
237 .resolve_lazy(def_id, self.interner)
238 .unwrap_or(type_id),
239 Some(TypeData::Mapped(_) | TypeData::Application(_)) => {
240 self.subtype.evaluate_type(type_id)
241 }
242 _ => type_id,
243 };
244
245 if next == type_id {
246 break;
247 }
248 type_id = next;
249 }
250 type_id
251 }
252
253 pub(crate) fn normalize_assignability_operands(
254 &mut self,
255 source: TypeId,
256 target: TypeId,
257 ) -> (TypeId, TypeId) {
258 (
259 self.normalize_assignability_operand(source),
260 self.normalize_assignability_operand(target),
261 )
262 }
263
264 fn is_function_target_member(&self, member: TypeId) -> bool {
265 let is_function_object_shape = match self.interner.lookup(member) {
266 Some(TypeData::Object(shape_id) | TypeData::ObjectWithIndex(shape_id)) => {
267 let shape = self.interner.object_shape(shape_id);
268 let apply = self.interner.intern_string("apply");
269 let call = self.interner.intern_string("call");
270 let has_apply = shape.properties.iter().any(|prop| prop.name == apply);
271 let has_call = shape.properties.iter().any(|prop| prop.name == call);
272 has_apply && has_call
273 }
274 _ => false,
275 };
276
277 intrinsic_kind(self.interner, member) == Some(IntrinsicKind::Function)
278 || is_function_object_shape
279 || self
280 .subtype
281 .resolver
282 .get_boxed_type(IntrinsicKind::Function)
283 .is_some_and(|boxed| boxed == member)
284 || lazy_def_id(self.interner, member).is_some_and(|def_id| {
285 self.subtype
286 .resolver
287 .is_boxed_def_id(def_id, IntrinsicKind::Function)
288 })
289 }
290
291 pub fn with_resolver(interner: &'a dyn TypeDatabase, resolver: &'a R) -> Self {
294 CompatChecker {
295 interner,
296 query_db: None,
297 subtype: SubtypeChecker::with_resolver(interner, resolver),
298 lawyer: AnyPropagationRules::new(),
299 strict_function_types: false,
302 strict_null_checks: true,
303 no_unchecked_indexed_access: false,
304 exact_optional_property_types: false,
305 strict_subtype_checking: false,
306 cache: FxHashMap::default(),
307 }
308 }
309
310 pub fn set_query_db(&mut self, db: &'a dyn QueryDatabase) {
313 self.query_db = Some(db);
314 self.subtype.query_db = Some(db);
315 }
316
317 pub const fn set_inheritance_graph(
320 &mut self,
321 graph: Option<&'a crate::classes::inheritance::InheritanceGraph>,
322 ) {
323 self.subtype.inheritance_graph = graph;
324 }
325
326 pub fn set_strict_function_types(&mut self, strict: bool) {
329 if self.strict_function_types != strict {
330 self.strict_function_types = strict;
331 self.cache.clear();
332 }
333 }
334
335 pub fn set_strict_null_checks(&mut self, strict: bool) {
337 if self.strict_null_checks != strict {
338 self.strict_null_checks = strict;
339 self.cache.clear();
340 }
341 }
342
343 pub fn set_no_unchecked_indexed_access(&mut self, enabled: bool) {
345 if self.no_unchecked_indexed_access != enabled {
346 self.no_unchecked_indexed_access = enabled;
347 self.cache.clear();
348 }
349 }
350
351 pub fn set_exact_optional_property_types(&mut self, exact: bool) {
354 if self.exact_optional_property_types != exact {
355 self.exact_optional_property_types = exact;
356 self.cache.clear();
357 }
358 }
359
360 pub fn set_strict_subtype_checking(&mut self, strict: bool) {
367 if self.strict_subtype_checking != strict {
368 self.strict_subtype_checking = strict;
369 self.cache.clear();
370 }
371 }
372
373 pub fn apply_flags(&mut self, flags: u16) {
388 let strict_null_checks = (flags & (1 << 0)) != 0;
390 let strict_function_types = (flags & (1 << 1)) != 0;
391 let exact_optional_property_types = (flags & (1 << 2)) != 0;
392 let no_unchecked_indexed_access = (flags & (1 << 3)) != 0;
393 let disable_method_bivariance = (flags & (1 << 4)) != 0;
394
395 self.set_strict_null_checks(strict_null_checks);
396 self.set_strict_function_types(strict_function_types);
397 self.set_exact_optional_property_types(exact_optional_property_types);
398 self.set_no_unchecked_indexed_access(no_unchecked_indexed_access);
399 self.set_strict_subtype_checking(disable_method_bivariance);
400
401 self.subtype.strict_null_checks = strict_null_checks;
404 self.subtype.strict_function_types = strict_function_types;
405 self.subtype.exact_optional_property_types = exact_optional_property_types;
406 self.subtype.no_unchecked_indexed_access = no_unchecked_indexed_access;
407 self.subtype.disable_method_bivariance = disable_method_bivariance;
408 self.subtype.allow_void_return = (flags & (1 << 5)) != 0;
409 self.subtype.allow_bivariant_rest = (flags & (1 << 6)) != 0;
410 self.subtype.allow_bivariant_param_count = (flags & (1 << 7)) != 0;
411 }
412
413 pub fn set_strict_any_propagation(&mut self, strict: bool) {
418 self.lawyer.set_allow_any_suppression(!strict);
419 self.cache.clear();
420 }
421
422 pub const fn lawyer(&self) -> &AnyPropagationRules {
424 &self.lawyer
425 }
426
427 pub fn lawyer_mut(&mut self) -> &mut AnyPropagationRules {
429 self.cache.clear();
430 &mut self.lawyer
431 }
432
433 pub fn apply_config(&mut self, config: &crate::judge::JudgeConfig) {
438 self.strict_function_types = config.strict_function_types;
439 self.strict_null_checks = config.strict_null_checks;
440 self.exact_optional_property_types = config.exact_optional_property_types;
441 self.no_unchecked_indexed_access = config.no_unchecked_indexed_access;
442
443 self.lawyer.allow_any_suppression = !config.strict_function_types && !config.sound_mode;
445
446 self.cache.clear();
448 }
449
450 pub fn is_assignable(&mut self, source: TypeId, target: TypeId) -> bool {
452 if !self.strict_null_checks && target.is_nullish() {
456 return true;
457 }
458
459 let key = (source, target);
460 if let Some(&cached) = self.cache.get(&key) {
461 return cached;
462 }
463
464 let result = self.is_assignable_impl(source, target, self.strict_function_types);
465
466 self.cache.insert(key, result);
467 result
468 }
469
470 fn check_excess_properties(&mut self, source: TypeId, target: TypeId) -> bool {
483 use super::freshness::is_fresh_object_type;
484 use crate::visitor::{ObjectTypeKind, classify_object_type};
485
486 if !is_fresh_object_type(self.interner, source) {
488 return true;
489 }
490
491 let source_shape_id = match classify_object_type(self.interner, source) {
493 ObjectTypeKind::Object(shape_id) | ObjectTypeKind::ObjectWithIndex(shape_id) => {
494 shape_id
495 }
496 ObjectTypeKind::NotObject => return true,
497 };
498
499 let source_shape = self.interner.object_shape(source_shape_id);
500
501 let (has_string_index, has_number_index) = self.check_index_signatures(target);
502
503 if has_string_index {
505 return true;
506 }
507
508 let target_properties = self.collect_target_properties(target);
510
511 if target_properties.is_empty() && !has_number_index {
514 return true;
515 }
516
517 for prop_info in &source_shape.properties {
519 if !target_properties.contains(&prop_info.name) {
520 if has_number_index {
522 let name_str = self.interner.resolve_atom(prop_info.name);
523 if name_str.parse::<f64>().is_ok() {
524 continue;
525 }
526 }
527 return false;
529 }
530 }
531
532 true
533 }
534
535 fn find_excess_property(&mut self, source: TypeId, target: TypeId) -> Option<Atom> {
540 use super::freshness::is_fresh_object_type;
541 use crate::visitor::{ObjectTypeKind, classify_object_type};
542
543 if !is_fresh_object_type(self.interner, source) {
545 return None;
546 }
547
548 let source_shape_id = match classify_object_type(self.interner, source) {
550 ObjectTypeKind::Object(shape_id) | ObjectTypeKind::ObjectWithIndex(shape_id) => {
551 shape_id
552 }
553 ObjectTypeKind::NotObject => return None,
554 };
555
556 let source_shape = self.interner.object_shape(source_shape_id);
557
558 let target_key = self.interner.lookup(target);
560 let resolved_target = match target_key {
561 Some(TypeData::Lazy(def_id)) => {
562 self.subtype.resolver.resolve_lazy(def_id, self.interner)?
564 }
565 Some(TypeData::Mapped(_) | TypeData::Application(_)) => {
566 self.subtype.evaluate_type(target)
568 }
569 _ => target,
570 };
571
572 let (has_string_index, has_number_index) = self.check_index_signatures(resolved_target);
573
574 if has_string_index {
576 return None;
577 }
578
579 let target_properties = self.collect_target_properties(resolved_target);
581
582 if target_properties.is_empty() && !has_number_index {
584 return None;
585 }
586
587 for prop_info in &source_shape.properties {
589 if !target_properties.contains(&prop_info.name) {
590 if has_number_index {
592 let name_str = self.interner.resolve_atom(prop_info.name);
593 if name_str.parse::<f64>().is_ok() {
594 continue;
595 }
596 }
597 return Some(prop_info.name);
599 }
600 }
601
602 None
603 }
604
605 fn check_index_signatures(&mut self, type_id: TypeId) -> (bool, bool) {
612 if type_id == TypeId::ANY || type_id == TypeId::UNKNOWN || type_id == TypeId::ERROR {
613 return (true, true);
614 }
615
616 let type_id = match self.interner.lookup(type_id) {
617 Some(TypeData::Lazy(def_id)) => self
618 .subtype
619 .resolver
620 .resolve_lazy(def_id, self.interner)
621 .unwrap_or(type_id),
622 Some(TypeData::Mapped(_) | TypeData::Application(_)) => {
623 self.subtype.evaluate_type(type_id)
624 }
625 _ => type_id,
626 };
627
628 if type_id == TypeId::ANY || type_id == TypeId::UNKNOWN || type_id == TypeId::ERROR {
629 return (true, true);
630 }
631
632 match self.interner.lookup(type_id) {
633 Some(TypeData::Object(shape_id) | TypeData::ObjectWithIndex(shape_id)) => {
634 let shape = self.interner.object_shape(shape_id);
635 (shape.string_index.is_some(), shape.number_index.is_some())
636 }
637 Some(TypeData::Intersection(members_id)) | Some(TypeData::Union(members_id)) => {
638 let members = self.interner.type_list(members_id);
639 let mut has_str = false;
640 let mut has_num = false;
641 for &member in members.iter() {
642 let (s, n) = self.check_index_signatures(member);
643 has_str |= s;
644 has_num |= n;
645 }
646 (has_str, has_num)
647 }
648 _ => (false, false),
649 }
650 }
651
652 fn collect_target_properties(&mut self, type_id: TypeId) -> rustc_hash::FxHashSet<Atom> {
653 let type_id = match self.interner.lookup(type_id) {
656 Some(TypeData::Mapped(_) | TypeData::Application(_)) => {
657 self.subtype.evaluate_type(type_id)
658 }
659 _ => type_id,
660 };
661
662 let mut properties = rustc_hash::FxHashSet::default();
663
664 match self.interner.lookup(type_id) {
665 Some(TypeData::Intersection(members_id)) => {
666 let members = self.interner.type_list(members_id);
667 for &member in members.iter() {
669 let member_props = self.collect_target_properties(member);
670 properties.extend(member_props);
671 }
672 }
673 Some(TypeData::Union(members_id)) => {
674 let members = self.interner.type_list(members_id);
675 if members.is_empty() {
676 return properties;
677 }
678 let mut all_props = self.collect_target_properties(members[0]);
681 for &member in members.iter().skip(1) {
683 let member_props = self.collect_target_properties(member);
684 all_props = all_props.intersection(&member_props).cloned().collect();
685 }
686 properties = all_props;
687 }
688 Some(TypeData::Object(shape_id) | TypeData::ObjectWithIndex(shape_id)) => {
689 let shape = self.interner.object_shape(shape_id);
690 for prop_info in &shape.properties {
691 properties.insert(prop_info.name);
692 }
693 }
694 _ => {}
695 }
696
697 properties
698 }
699
700 fn is_assignable_impl(
703 &mut self,
704 source: TypeId,
705 target: TypeId,
706 strict_function_types: bool,
707 ) -> bool {
708 let (source, target) = self.normalize_assignability_operands(source, target);
709
710 if let Some(result) = self.check_assignable_fast_path(source, target) {
712 return result;
713 }
714
715 if let Some(result) = self.enum_assignability_override(source, target) {
718 return result;
719 }
720
721 if self.violates_weak_union(source, target) {
723 return false;
724 }
725 if self.violates_weak_type(source, target) {
726 return false;
727 }
728
729 if !self.check_excess_properties(source, target) {
731 return false;
732 }
733
734 if self.is_empty_object_target(target) {
736 return self.is_assignable_to_empty_object(source);
737 }
738
739 if let (Some(TypeData::Mapped(s_mapped_id)), Some(TypeData::Mapped(t_mapped_id))) =
743 (self.interner.lookup(source), self.interner.lookup(target))
744 {
745 let result = self.check_mapped_to_mapped_assignability(s_mapped_id, t_mapped_id);
746 if let Some(assignable) = result {
747 return assignable;
748 }
749 }
750
751 self.configure_subtype(strict_function_types);
753 self.subtype.is_subtype_of(source, target)
754 }
755
756 fn check_mapped_to_mapped_assignability(
765 &mut self,
766 s_mapped_id: crate::types::MappedTypeId,
767 t_mapped_id: crate::types::MappedTypeId,
768 ) -> Option<bool> {
769 use crate::types::MappedModifier;
770 use crate::visitor::mapped_type_id;
771
772 let s_mapped = self.interner.mapped_type(s_mapped_id);
773 let t_mapped = self.interner.mapped_type(t_mapped_id);
774
775 if s_mapped.constraint != t_mapped.constraint {
777 return None;
778 }
779
780 let source_template = s_mapped.template;
781 let mut target_template = t_mapped.template;
782
783 let target_adds_optional = t_mapped.optional_modifier == Some(MappedModifier::Add);
786 let source_adds_optional = s_mapped.optional_modifier == Some(MappedModifier::Add);
787
788 if target_adds_optional && !source_adds_optional {
789 target_template = self.interner.union2(target_template, TypeId::UNDEFINED);
790 }
791
792 let target_removes_optional = t_mapped.optional_modifier == Some(MappedModifier::Remove);
795 if target_removes_optional && !source_adds_optional && s_mapped.optional_modifier.is_none()
796 {
797 return None;
798 }
799
800 if let (Some(s_inner), Some(t_inner)) = (
802 mapped_type_id(self.interner, source_template),
803 mapped_type_id(self.interner, target_template),
804 ) {
805 return self.check_mapped_to_mapped_assignability(s_inner, t_inner);
806 }
807
808 self.configure_subtype(self.strict_function_types);
810 Some(self.subtype.is_subtype_of(source_template, target_template))
811 }
812
813 fn check_assignable_fast_path(&self, source: TypeId, target: TypeId) -> Option<bool> {
816 if let Some(TypeData::Lazy(def_id)) = self.interner.lookup(target)
817 && let Some(resolved_target) = self.subtype.resolver.resolve_lazy(def_id, self.interner)
818 && resolved_target != target
819 {
820 return self.check_assignable_fast_path(source, resolved_target);
821 }
822
823 if source == target {
825 return Some(true);
826 }
827
828 if source == TypeId::ANY || target == TypeId::ANY {
831 if source == target {
835 return Some(true);
836 }
837 if self.lawyer.allow_any_suppression {
839 return Some(true);
840 }
841 return None;
843 }
844
845 if !self.strict_null_checks && source.is_nullish() {
847 return Some(true);
848 }
849
850 if target == TypeId::UNKNOWN {
852 return Some(true);
853 }
854
855 if source == TypeId::NEVER {
857 return Some(true);
858 }
859
860 if source == TypeId::ERROR || target == TypeId::ERROR {
863 return Some(true);
864 }
865
866 if source == TypeId::UNKNOWN {
868 return Some(false);
869 }
870
871 if let Some(TypeData::Union(members_id)) = self.interner.lookup(target) {
874 let members = self.interner.type_list(members_id);
875 if members
876 .iter()
877 .any(|&member| self.is_function_target_member(member))
878 && crate::type_queries::is_callable_type(self.interner, source)
879 {
880 return Some(true);
881 }
882 }
883
884 None }
886
887 pub fn is_assignable_strict(&mut self, source: TypeId, target: TypeId) -> bool {
888 if let Some(TypeData::Lazy(def_id)) = self.interner.lookup(target)
889 && let Some(resolved_target) = self.subtype.resolver.resolve_lazy(def_id, self.interner)
890 && resolved_target != target
891 {
892 return self.is_assignable_strict(source, resolved_target);
893 }
894
895 if source == target {
897 return true;
898 }
899 if !self.strict_null_checks && source.is_nullish() {
900 return true;
901 }
902 if !self.strict_null_checks && target.is_nullish() {
905 return true;
906 }
907 if target == TypeId::UNKNOWN {
908 return true;
909 }
910 if source == TypeId::NEVER {
911 return true;
912 }
913 if source == TypeId::ERROR || target == TypeId::ERROR {
915 return true;
916 }
917 if source == TypeId::UNKNOWN {
918 return false;
919 }
920 if let Some(TypeData::Union(members_id)) = self.interner.lookup(target) {
921 let members = self.interner.type_list(members_id);
922 if members
923 .iter()
924 .any(|&member| self.is_function_target_member(member))
925 && crate::type_queries::is_callable_type(self.interner, source)
926 {
927 return true;
928 }
929 }
930 if self.is_empty_object_target(target) {
931 return self.is_assignable_to_empty_object(source);
932 }
933
934 let prev = self.subtype.strict_function_types;
935 self.configure_subtype(true);
936 let result = self.subtype.is_subtype_of(source, target);
937 self.subtype.strict_function_types = prev;
938 result
939 }
940
941 pub fn explain_failure(
943 &mut self,
944 source: TypeId,
945 target: TypeId,
946 ) -> Option<SubtypeFailureReason> {
947 if source == target {
949 return None;
950 }
951 if target == TypeId::UNKNOWN {
952 return None;
953 }
954 if !self.strict_null_checks && source.is_nullish() {
955 return None;
956 }
957 if !self.strict_null_checks && (target == TypeId::NULL || target == TypeId::UNDEFINED) {
959 return None;
960 }
961 if source == TypeId::NEVER {
962 return None;
963 }
964 if source == TypeId::UNKNOWN {
965 return Some(SubtypeFailureReason::TypeMismatch {
966 source_type: source,
967 target_type: target,
968 });
969 }
970
971 if source == TypeId::ERROR || target == TypeId::ERROR {
974 return None;
975 }
976
977 let violates = self.violates_weak_union(source, target);
979 if violates {
980 return Some(SubtypeFailureReason::TypeMismatch {
981 source_type: source,
982 target_type: target,
983 });
984 }
985 if self.violates_weak_type(source, target) {
986 return Some(SubtypeFailureReason::NoCommonProperties {
987 source_type: source,
988 target_type: target,
989 });
990 }
991
992 if let Some(excess_prop) = self.find_excess_property(source, target) {
994 return Some(SubtypeFailureReason::ExcessProperty {
995 property_name: excess_prop,
996 target_type: target,
997 });
998 }
999
1000 if let Some(false) = self.private_brand_assignability_override(source, target) {
1003 return Some(SubtypeFailureReason::TypeMismatch {
1004 source_type: source,
1005 target_type: target,
1006 });
1007 }
1008
1009 if self.is_empty_object_target(target) && self.is_assignable_to_empty_object(source) {
1011 return None;
1012 }
1013
1014 self.configure_subtype(self.strict_function_types);
1015 self.subtype.explain_failure(source, target)
1016 }
1017
1018 const fn configure_subtype(&mut self, strict_function_types: bool) {
1019 self.subtype.strict_function_types = strict_function_types;
1020 self.subtype.allow_void_return = true;
1021 self.subtype.allow_bivariant_rest = true;
1022 self.subtype.exact_optional_property_types = self.exact_optional_property_types;
1023 self.subtype.strict_null_checks = self.strict_null_checks;
1024 self.subtype.no_unchecked_indexed_access = self.no_unchecked_indexed_access;
1025 self.subtype.any_propagation = self.lawyer.any_propagation_mode();
1029 self.subtype.disable_method_bivariance = self.strict_subtype_checking;
1031 }
1032
1033 fn violates_weak_type(&self, source: TypeId, target: TypeId) -> bool {
1034 let mut extractor = ShapeExtractor::new(self.interner, self.subtype.resolver);
1035
1036 let target_shape_id = match extractor.extract(target) {
1037 Some(id) => id,
1038 None => return false,
1039 };
1040
1041 let target_shape = self
1042 .interner
1043 .object_shape(crate::types::ObjectShapeId(target_shape_id));
1044
1045 if let Some(TypeData::ObjectWithIndex(_)) = self.interner.lookup(target)
1047 && (target_shape.string_index.is_some() || target_shape.number_index.is_some())
1048 {
1049 return false;
1050 }
1051
1052 let target_props = target_shape.properties.as_slice();
1053 if target_props.is_empty() || target_props.iter().any(|prop| !prop.optional) {
1054 return false;
1055 }
1056
1057 self.violates_weak_type_with_target_props(source, target_props)
1058 }
1059
1060 fn violates_weak_union(&self, source: TypeId, target: TypeId) -> bool {
1061 let target_key = match self.interner.lookup(target) {
1064 Some(TypeData::Union(members)) => members,
1065 _ => {
1066 return false;
1067 }
1068 };
1069
1070 let members = self.interner.type_list(target_key);
1071 if members.is_empty() {
1072 return false;
1073 }
1074
1075 let mut extractor = ShapeExtractor::new(self.interner, self.subtype.resolver);
1076 let mut has_weak_member = false;
1077
1078 for member in members.iter() {
1079 let resolved_member = self.resolve_weak_type_ref(*member);
1080 let member_shape_id = match extractor.extract(resolved_member) {
1084 Some(id) => id,
1085 None => return false,
1086 };
1087
1088 let member_shape = self
1089 .interner
1090 .object_shape(crate::types::ObjectShapeId(member_shape_id));
1091
1092 if member_shape.properties.is_empty()
1093 || member_shape.string_index.is_some()
1094 || member_shape.number_index.is_some()
1095 {
1096 return false;
1097 }
1098
1099 if member_shape.properties.iter().all(|prop| prop.optional) {
1100 has_weak_member = true;
1101 }
1102 }
1103
1104 if !has_weak_member {
1105 return false;
1106 }
1107
1108 self.source_lacks_union_common_property(source, members.as_ref())
1109 }
1110
1111 pub fn is_weak_union_violation(&self, source: TypeId, target: TypeId) -> bool {
1112 self.violates_weak_union(source, target)
1113 }
1114
1115 fn violates_weak_type_with_target_props(
1116 &self,
1117 source: TypeId,
1118 target_props: &[PropertyInfo],
1119 ) -> bool {
1120 if let Some(TypeData::Union(members)) = self.interner.lookup(source) {
1122 let members = self.interner.type_list(members);
1123 return members
1124 .iter()
1125 .all(|member| self.violates_weak_type_with_target_props(*member, target_props));
1126 }
1127
1128 let mut extractor = ShapeExtractor::new(self.interner, self.subtype.resolver);
1129 let source_shape_id = match extractor.extract(source) {
1130 Some(id) => id,
1131 None => return false,
1132 };
1133
1134 let source_shape = self
1135 .interner
1136 .object_shape(crate::types::ObjectShapeId(source_shape_id));
1137 let source_props = source_shape.properties.as_slice();
1138
1139 !source_props.is_empty() && !self.has_common_property(source_props, target_props)
1142 }
1143
1144 fn source_lacks_union_common_property(
1145 &self,
1146 source: TypeId,
1147 target_members: &[TypeId],
1148 ) -> bool {
1149 let source = self.resolve_weak_type_ref(source);
1150
1151 if let Some(TypeData::Union(members)) = self.interner.lookup(source) {
1153 let members = self.interner.type_list(members);
1154 return members
1155 .iter()
1156 .all(|member| self.source_lacks_union_common_property(*member, target_members));
1157 }
1158
1159 if let Some(TypeData::TypeParameter(param)) = self.interner.lookup(source) {
1161 return match param.constraint {
1162 Some(constraint) => {
1163 self.source_lacks_union_common_property(constraint, target_members)
1164 }
1165 None => false,
1166 };
1167 }
1168
1169 let mut extractor = ShapeExtractor::new(self.interner, self.subtype.resolver);
1171 let source_shape_id = match extractor.extract(source) {
1172 Some(id) => id,
1173 None => return false,
1174 };
1175
1176 let source_shape = self
1177 .interner
1178 .object_shape(crate::types::ObjectShapeId(source_shape_id));
1179 if source_shape.string_index.is_some() || source_shape.number_index.is_some() {
1180 return false;
1181 }
1182 let source_props = source_shape.properties.as_slice();
1183 if source_props.is_empty() {
1184 return false;
1185 }
1186
1187 let mut has_common = false;
1188 for member in target_members {
1189 let resolved_member = self.resolve_weak_type_ref(*member);
1190 let member_shape_id = match extractor.extract(resolved_member) {
1191 Some(id) => id,
1192 None => continue,
1193 };
1194
1195 let member_shape = self
1196 .interner
1197 .object_shape(crate::types::ObjectShapeId(member_shape_id));
1198 if member_shape.string_index.is_some() || member_shape.number_index.is_some() {
1199 return false;
1200 }
1201 if self.has_common_property(source_props, member_shape.properties.as_slice()) {
1202 has_common = true;
1203 break;
1204 }
1205 }
1206
1207 !has_common
1208 }
1209
1210 fn has_common_property(
1211 &self,
1212 source_props: &[PropertyInfo],
1213 target_props: &[PropertyInfo],
1214 ) -> bool {
1215 let mut source_idx = 0;
1216 let mut target_idx = 0;
1217
1218 while source_idx < source_props.len() && target_idx < target_props.len() {
1219 let source_name = source_props[source_idx].name;
1220 let target_name = target_props[target_idx].name;
1221 if source_name == target_name {
1222 return true;
1223 }
1224 if source_name < target_name {
1225 source_idx += 1;
1226 } else {
1227 target_idx += 1;
1228 }
1229 }
1230
1231 false
1232 }
1233
1234 fn resolve_weak_type_ref(&self, type_id: TypeId) -> TypeId {
1235 self.subtype.resolve_ref_type(type_id)
1236 }
1237
1238 fn is_empty_object_target(&self, target: TypeId) -> bool {
1241 is_empty_object_type_db(self.interner, target)
1242 }
1243
1244 fn is_assignable_to_empty_object(&self, source: TypeId) -> bool {
1245 if source == TypeId::ANY || source == TypeId::NEVER {
1246 return true;
1247 }
1248 if source == TypeId::ERROR {
1250 return true;
1251 }
1252 if !self.strict_null_checks && source.is_nullish() {
1253 return true;
1254 }
1255 if source == TypeId::UNKNOWN
1256 || source == TypeId::NULL
1257 || source == TypeId::UNDEFINED
1258 || source == TypeId::VOID
1259 {
1260 return false;
1261 }
1262
1263 let key = match self.interner.lookup(source) {
1264 Some(key) => key,
1265 None => return false,
1266 };
1267
1268 match key {
1269 TypeData::Union(members) => {
1270 let members = self.interner.type_list(members);
1271 members
1272 .iter()
1273 .all(|member| self.is_assignable_to_empty_object(*member))
1274 }
1275 TypeData::Intersection(members) => {
1276 let members = self.interner.type_list(members);
1277 members
1278 .iter()
1279 .any(|member| self.is_assignable_to_empty_object(*member))
1280 }
1281 TypeData::TypeParameter(param) => match param.constraint {
1282 Some(constraint) => self.is_assignable_to_empty_object(constraint),
1283 None => false,
1284 },
1285 _ => true,
1286 }
1287 }
1288}
1289
1290impl<'a, R: TypeResolver> AssignabilityChecker for CompatChecker<'a, R> {
1291 fn is_assignable_to(&mut self, source: TypeId, target: TypeId) -> bool {
1292 self.is_assignable(source, target)
1293 }
1294
1295 fn is_assignable_to_strict(&mut self, source: TypeId, target: TypeId) -> bool {
1296 self.is_assignable_strict(source, target)
1297 }
1298
1299 fn is_assignable_to_bivariant_callback(&mut self, source: TypeId, target: TypeId) -> bool {
1300 self.is_assignable_impl(source, target, false)
1302 }
1303
1304 fn evaluate_type(&mut self, type_id: TypeId) -> TypeId {
1305 self.subtype.evaluate_type(type_id)
1306 }
1307}
1308
1309#[cfg(test)]
1310#[path = "../../tests/compat_tests.rs"]
1311mod tests;