1use crate::db::QueryDatabase;
4use crate::diagnostics::SubtypeFailureReason;
5use crate::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
12struct 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 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 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
46struct StringLikeVisitor<'a> {
48 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 let _symbol_ref = crate::types::SymbolRef(symbol_ref);
72 false
75 }
76
77 fn visit_lazy(&mut self, _def_id: u32) -> Self::Output {
78 false
80 }
81
82 fn default_output() -> Self::Output {
83 false
84 }
85}
86
87impl<'a, R: TypeResolver> TypeVisitor for ShapeExtractor<'a, R> {
88 type Output = Option<u32>;
89
90 fn visit_intrinsic(&mut self, _kind: crate::types::IntrinsicKind) -> Self::Output {
91 None
92 }
93
94 fn visit_literal(&mut self, _value: &crate::LiteralValue) -> Self::Output {
95 None
96 }
97
98 fn visit_object(&mut self, shape_id: u32) -> Self::Output {
99 Some(shape_id)
100 }
101
102 fn visit_object_with_index(&mut self, shape_id: u32) -> Self::Output {
103 Some(shape_id)
104 }
105
106 fn visit_lazy(&mut self, def_id: u32) -> Self::Output {
107 let def_id = crate::def::DefId(def_id);
108 if let Some(resolved) = self.resolver.resolve_lazy(def_id, self.db) {
109 return self.extract(resolved);
110 }
111 None
112 }
113
114 fn visit_ref(&mut self, symbol_ref: u32) -> Self::Output {
115 let symbol_ref = crate::types::SymbolRef(symbol_ref);
116 if let Some(def_id) = self.resolver.symbol_to_def_id(symbol_ref) {
118 return self.visit_lazy(def_id.0);
119 }
120 if let Some(resolved) = self.resolver.resolve_symbol_ref(symbol_ref, self.db) {
121 return self.extract(resolved);
122 }
123 None
124 }
125
126 fn visit_intersection(&mut self, list_id: u32) -> Self::Output {
129 let member_list = self.db.type_list(crate::types::TypeListId(list_id));
130 for member in member_list.iter() {
133 if let Some(shape) = self.visit_type(self.db, *member) {
134 return Some(shape);
135 }
136 }
137 None
138 }
139
140 fn default_output() -> Self::Output {
141 None
142 }
143}
144
145pub trait AssignabilityOverrideProvider {
151 fn enum_assignability_override(&self, source: TypeId, target: TypeId) -> Option<bool>;
154
155 fn abstract_constructor_assignability_override(
158 &self,
159 source: TypeId,
160 target: TypeId,
161 ) -> Option<bool>;
162
163 fn constructor_accessibility_override(&self, source: TypeId, target: TypeId) -> Option<bool>;
166}
167
168pub struct NoopOverrideProvider;
170
171impl AssignabilityOverrideProvider for NoopOverrideProvider {
172 fn enum_assignability_override(&self, _source: TypeId, _target: TypeId) -> Option<bool> {
173 None
174 }
175
176 fn abstract_constructor_assignability_override(
177 &self,
178 _source: TypeId,
179 _target: TypeId,
180 ) -> Option<bool> {
181 None
182 }
183
184 fn constructor_accessibility_override(&self, _source: TypeId, _target: TypeId) -> Option<bool> {
185 None
186 }
187}
188
189pub struct CompatChecker<'a, R: TypeResolver = NoopResolver> {
195 interner: &'a dyn TypeDatabase,
196 query_db: Option<&'a dyn QueryDatabase>,
198 subtype: SubtypeChecker<'a, R>,
199 lawyer: AnyPropagationRules,
201 strict_function_types: bool,
202 strict_null_checks: bool,
203 no_unchecked_indexed_access: bool,
204 exact_optional_property_types: bool,
205 strict_subtype_checking: bool,
207 cache: FxHashMap<(TypeId, TypeId), bool>,
208}
209
210impl<'a> CompatChecker<'a, NoopResolver> {
211 pub fn new(interner: &'a dyn TypeDatabase) -> Self {
214 CompatChecker {
215 interner,
216 query_db: None,
217 subtype: SubtypeChecker::new(interner),
218 lawyer: AnyPropagationRules::new(),
219 strict_function_types: false,
222 strict_null_checks: true,
223 no_unchecked_indexed_access: false,
224 exact_optional_property_types: false,
225 strict_subtype_checking: false,
226 cache: FxHashMap::default(),
227 }
228 }
229}
230
231impl<'a, R: TypeResolver> CompatChecker<'a, R> {
232 fn normalize_assignability_operand(&mut self, mut type_id: TypeId) -> TypeId {
233 for _ in 0..8 {
235 let next = match self.interner.lookup(type_id) {
236 Some(TypeData::Lazy(def_id)) => self
237 .subtype
238 .resolver
239 .resolve_lazy(def_id, self.interner)
240 .unwrap_or(type_id),
241 Some(TypeData::Mapped(_) | TypeData::Application(_)) => {
242 self.subtype.evaluate_type(type_id)
243 }
244 _ => type_id,
245 };
246
247 if next == type_id {
248 break;
249 }
250 type_id = next;
251 }
252 type_id
253 }
254
255 fn normalize_assignability_operands(
256 &mut self,
257 source: TypeId,
258 target: TypeId,
259 ) -> (TypeId, TypeId) {
260 (
261 self.normalize_assignability_operand(source),
262 self.normalize_assignability_operand(target),
263 )
264 }
265
266 fn is_function_target_member(&self, member: TypeId) -> bool {
267 let is_function_object_shape = match self.interner.lookup(member) {
268 Some(TypeData::Object(shape_id) | TypeData::ObjectWithIndex(shape_id)) => {
269 let shape = self.interner.object_shape(shape_id);
270 let apply = self.interner.intern_string("apply");
271 let call = self.interner.intern_string("call");
272 let has_apply = shape.properties.iter().any(|prop| prop.name == apply);
273 let has_call = shape.properties.iter().any(|prop| prop.name == call);
274 has_apply && has_call
275 }
276 _ => false,
277 };
278
279 intrinsic_kind(self.interner, member) == Some(IntrinsicKind::Function)
280 || is_function_object_shape
281 || self
282 .subtype
283 .resolver
284 .get_boxed_type(IntrinsicKind::Function)
285 .is_some_and(|boxed| boxed == member)
286 || lazy_def_id(self.interner, member).is_some_and(|def_id| {
287 self.subtype
288 .resolver
289 .is_boxed_def_id(def_id, IntrinsicKind::Function)
290 })
291 }
292
293 pub fn with_resolver(interner: &'a dyn TypeDatabase, resolver: &'a R) -> Self {
296 CompatChecker {
297 interner,
298 query_db: None,
299 subtype: SubtypeChecker::with_resolver(interner, resolver),
300 lawyer: AnyPropagationRules::new(),
301 strict_function_types: false,
304 strict_null_checks: true,
305 no_unchecked_indexed_access: false,
306 exact_optional_property_types: false,
307 strict_subtype_checking: false,
308 cache: FxHashMap::default(),
309 }
310 }
311
312 pub fn set_query_db(&mut self, db: &'a dyn QueryDatabase) {
315 self.query_db = Some(db);
316 self.subtype.query_db = Some(db);
317 }
318
319 pub const fn set_inheritance_graph(
322 &mut self,
323 graph: Option<&'a crate::inheritance::InheritanceGraph>,
324 ) {
325 self.subtype.inheritance_graph = graph;
326 }
327
328 pub fn set_strict_function_types(&mut self, strict: bool) {
331 if self.strict_function_types != strict {
332 self.strict_function_types = strict;
333 self.cache.clear();
334 }
335 }
336
337 pub fn set_strict_null_checks(&mut self, strict: bool) {
339 if self.strict_null_checks != strict {
340 self.strict_null_checks = strict;
341 self.cache.clear();
342 }
343 }
344
345 pub fn set_no_unchecked_indexed_access(&mut self, enabled: bool) {
347 if self.no_unchecked_indexed_access != enabled {
348 self.no_unchecked_indexed_access = enabled;
349 self.cache.clear();
350 }
351 }
352
353 pub fn set_exact_optional_property_types(&mut self, exact: bool) {
356 if self.exact_optional_property_types != exact {
357 self.exact_optional_property_types = exact;
358 self.cache.clear();
359 }
360 }
361
362 pub fn set_strict_subtype_checking(&mut self, strict: bool) {
369 if self.strict_subtype_checking != strict {
370 self.strict_subtype_checking = strict;
371 self.cache.clear();
372 }
373 }
374
375 pub fn apply_flags(&mut self, flags: u16) {
390 let strict_null_checks = (flags & (1 << 0)) != 0;
392 let strict_function_types = (flags & (1 << 1)) != 0;
393 let exact_optional_property_types = (flags & (1 << 2)) != 0;
394 let no_unchecked_indexed_access = (flags & (1 << 3)) != 0;
395 let disable_method_bivariance = (flags & (1 << 4)) != 0;
396
397 self.set_strict_null_checks(strict_null_checks);
398 self.set_strict_function_types(strict_function_types);
399 self.set_exact_optional_property_types(exact_optional_property_types);
400 self.set_no_unchecked_indexed_access(no_unchecked_indexed_access);
401 self.set_strict_subtype_checking(disable_method_bivariance);
402
403 self.subtype.strict_null_checks = strict_null_checks;
406 self.subtype.strict_function_types = strict_function_types;
407 self.subtype.exact_optional_property_types = exact_optional_property_types;
408 self.subtype.no_unchecked_indexed_access = no_unchecked_indexed_access;
409 self.subtype.disable_method_bivariance = disable_method_bivariance;
410 self.subtype.allow_void_return = (flags & (1 << 5)) != 0;
411 self.subtype.allow_bivariant_rest = (flags & (1 << 6)) != 0;
412 self.subtype.allow_bivariant_param_count = (flags & (1 << 7)) != 0;
413 }
414
415 pub fn set_strict_any_propagation(&mut self, strict: bool) {
420 self.lawyer.set_allow_any_suppression(!strict);
421 self.cache.clear();
422 }
423
424 pub const fn lawyer(&self) -> &AnyPropagationRules {
426 &self.lawyer
427 }
428
429 pub fn lawyer_mut(&mut self) -> &mut AnyPropagationRules {
431 self.cache.clear();
432 &mut self.lawyer
433 }
434
435 pub fn apply_config(&mut self, config: &crate::judge::JudgeConfig) {
440 self.strict_function_types = config.strict_function_types;
441 self.strict_null_checks = config.strict_null_checks;
442 self.exact_optional_property_types = config.exact_optional_property_types;
443 self.no_unchecked_indexed_access = config.no_unchecked_indexed_access;
444
445 self.lawyer.allow_any_suppression = !config.strict_function_types && !config.sound_mode;
447
448 self.cache.clear();
450 }
451
452 pub fn is_assignable(&mut self, source: TypeId, target: TypeId) -> bool {
454 if !self.strict_null_checks && target.is_nullish() {
458 return true;
459 }
460
461 let key = (source, target);
462 if let Some(&cached) = self.cache.get(&key) {
463 return cached;
464 }
465
466 let result = self.is_assignable_impl(source, target, self.strict_function_types);
467
468 self.cache.insert(key, result);
469 result
470 }
471
472 fn check_excess_properties(&mut self, source: TypeId, target: TypeId) -> bool {
485 use crate::freshness::is_fresh_object_type;
486 use crate::visitor::{ObjectTypeKind, classify_object_type};
487
488 if !is_fresh_object_type(self.interner, source) {
490 return true;
491 }
492
493 let source_shape_id = match classify_object_type(self.interner, source) {
495 ObjectTypeKind::Object(shape_id) | ObjectTypeKind::ObjectWithIndex(shape_id) => {
496 shape_id
497 }
498 ObjectTypeKind::NotObject => return true,
499 };
500
501 let source_shape = self.interner.object_shape(source_shape_id);
502
503 let (has_string_index, has_number_index) = self.check_index_signatures(target);
504
505 if has_string_index {
507 return true;
508 }
509
510 let target_properties = self.collect_target_properties(target);
512
513 if target_properties.is_empty() && !has_number_index {
516 return true;
517 }
518
519 for prop_info in &source_shape.properties {
521 if !target_properties.contains(&prop_info.name) {
522 if has_number_index {
524 let name_str = self.interner.resolve_atom(prop_info.name);
525 if name_str.parse::<f64>().is_ok() {
526 continue;
527 }
528 }
529 return false;
531 }
532 }
533
534 true
535 }
536
537 fn find_excess_property(&mut self, source: TypeId, target: TypeId) -> Option<Atom> {
542 use crate::freshness::is_fresh_object_type;
543 use crate::visitor::{ObjectTypeKind, classify_object_type};
544
545 if !is_fresh_object_type(self.interner, source) {
547 return None;
548 }
549
550 let source_shape_id = match classify_object_type(self.interner, source) {
552 ObjectTypeKind::Object(shape_id) | ObjectTypeKind::ObjectWithIndex(shape_id) => {
553 shape_id
554 }
555 ObjectTypeKind::NotObject => return None,
556 };
557
558 let source_shape = self.interner.object_shape(source_shape_id);
559
560 let target_key = self.interner.lookup(target);
562 let resolved_target = match target_key {
563 Some(TypeData::Lazy(def_id)) => {
564 self.subtype.resolver.resolve_lazy(def_id, self.interner)?
566 }
567 Some(TypeData::Mapped(_) | TypeData::Application(_)) => {
568 self.subtype.evaluate_type(target)
570 }
571 _ => target,
572 };
573
574 let (has_string_index, has_number_index) = self.check_index_signatures(resolved_target);
575
576 if has_string_index {
578 return None;
579 }
580
581 let target_properties = self.collect_target_properties(resolved_target);
583
584 if target_properties.is_empty() && !has_number_index {
586 return None;
587 }
588
589 for prop_info in &source_shape.properties {
591 if !target_properties.contains(&prop_info.name) {
592 if has_number_index {
594 let name_str = self.interner.resolve_atom(prop_info.name);
595 if name_str.parse::<f64>().is_ok() {
596 continue;
597 }
598 }
599 return Some(prop_info.name);
601 }
602 }
603
604 None
605 }
606
607 fn check_index_signatures(&mut self, type_id: TypeId) -> (bool, bool) {
614 if type_id == TypeId::ANY || type_id == TypeId::UNKNOWN || type_id == TypeId::ERROR {
615 return (true, true);
616 }
617
618 let type_id = match self.interner.lookup(type_id) {
619 Some(TypeData::Lazy(def_id)) => self
620 .subtype
621 .resolver
622 .resolve_lazy(def_id, self.interner)
623 .unwrap_or(type_id),
624 Some(TypeData::Mapped(_) | TypeData::Application(_)) => {
625 self.subtype.evaluate_type(type_id)
626 }
627 _ => type_id,
628 };
629
630 if type_id == TypeId::ANY || type_id == TypeId::UNKNOWN || type_id == TypeId::ERROR {
631 return (true, true);
632 }
633
634 match self.interner.lookup(type_id) {
635 Some(TypeData::Object(shape_id) | TypeData::ObjectWithIndex(shape_id)) => {
636 let shape = self.interner.object_shape(shape_id);
637 (shape.string_index.is_some(), shape.number_index.is_some())
638 }
639 Some(TypeData::Intersection(members_id)) | Some(TypeData::Union(members_id)) => {
640 let members = self.interner.type_list(members_id);
641 let mut has_str = false;
642 let mut has_num = false;
643 for &member in members.iter() {
644 let (s, n) = self.check_index_signatures(member);
645 has_str |= s;
646 has_num |= n;
647 }
648 (has_str, has_num)
649 }
650 _ => (false, false),
651 }
652 }
653
654 fn collect_target_properties(&mut self, type_id: TypeId) -> rustc_hash::FxHashSet<Atom> {
655 let type_id = match self.interner.lookup(type_id) {
658 Some(TypeData::Mapped(_) | TypeData::Application(_)) => {
659 self.subtype.evaluate_type(type_id)
660 }
661 _ => type_id,
662 };
663
664 let mut properties = rustc_hash::FxHashSet::default();
665
666 match self.interner.lookup(type_id) {
667 Some(TypeData::Intersection(members_id)) => {
668 let members = self.interner.type_list(members_id);
669 for &member in members.iter() {
671 let member_props = self.collect_target_properties(member);
672 properties.extend(member_props);
673 }
674 }
675 Some(TypeData::Union(members_id)) => {
676 let members = self.interner.type_list(members_id);
677 if members.is_empty() {
678 return properties;
679 }
680 let mut all_props = self.collect_target_properties(members[0]);
683 for &member in members.iter().skip(1) {
685 let member_props = self.collect_target_properties(member);
686 all_props = all_props.intersection(&member_props).cloned().collect();
687 }
688 properties = all_props;
689 }
690 Some(TypeData::Object(shape_id) | TypeData::ObjectWithIndex(shape_id)) => {
691 let shape = self.interner.object_shape(shape_id);
692 for prop_info in &shape.properties {
693 properties.insert(prop_info.name);
694 }
695 }
696 _ => {}
697 }
698
699 properties
700 }
701
702 fn is_assignable_impl(
705 &mut self,
706 source: TypeId,
707 target: TypeId,
708 strict_function_types: bool,
709 ) -> bool {
710 let (source, target) = self.normalize_assignability_operands(source, target);
711
712 if let Some(result) = self.check_assignable_fast_path(source, target) {
714 return result;
715 }
716
717 if let Some(result) = self.enum_assignability_override(source, target) {
720 return result;
721 }
722
723 if self.violates_weak_union(source, target) {
725 return false;
726 }
727 if self.violates_weak_type(source, target) {
728 return false;
729 }
730
731 if !self.check_excess_properties(source, target) {
733 return false;
734 }
735
736 if self.is_empty_object_target(target) {
738 return self.is_assignable_to_empty_object(source);
739 }
740
741 if let (Some(TypeData::Mapped(s_mapped_id)), Some(TypeData::Mapped(t_mapped_id))) =
745 (self.interner.lookup(source), self.interner.lookup(target))
746 {
747 let result = self.check_mapped_to_mapped_assignability(s_mapped_id, t_mapped_id);
748 if let Some(assignable) = result {
749 return assignable;
750 }
751 }
752
753 self.configure_subtype(strict_function_types);
755 self.subtype.is_subtype_of(source, target)
756 }
757
758 fn check_mapped_to_mapped_assignability(
767 &mut self,
768 s_mapped_id: crate::types::MappedTypeId,
769 t_mapped_id: crate::types::MappedTypeId,
770 ) -> Option<bool> {
771 use crate::types::MappedModifier;
772 use crate::visitor::mapped_type_id;
773
774 let s_mapped = self.interner.mapped_type(s_mapped_id);
775 let t_mapped = self.interner.mapped_type(t_mapped_id);
776
777 if s_mapped.constraint != t_mapped.constraint {
779 return None;
780 }
781
782 let source_template = s_mapped.template;
783 let mut target_template = t_mapped.template;
784
785 let target_adds_optional = t_mapped.optional_modifier == Some(MappedModifier::Add);
788 let source_adds_optional = s_mapped.optional_modifier == Some(MappedModifier::Add);
789
790 if target_adds_optional && !source_adds_optional {
791 target_template = self.interner.union2(target_template, TypeId::UNDEFINED);
792 }
793
794 let target_removes_optional = t_mapped.optional_modifier == Some(MappedModifier::Remove);
797 if target_removes_optional && !source_adds_optional && s_mapped.optional_modifier.is_none()
798 {
799 return None;
800 }
801
802 if let (Some(s_inner), Some(t_inner)) = (
804 mapped_type_id(self.interner, source_template),
805 mapped_type_id(self.interner, target_template),
806 ) {
807 return self.check_mapped_to_mapped_assignability(s_inner, t_inner);
808 }
809
810 self.configure_subtype(self.strict_function_types);
812 Some(self.subtype.is_subtype_of(source_template, target_template))
813 }
814
815 fn check_assignable_fast_path(&self, source: TypeId, target: TypeId) -> Option<bool> {
818 if let Some(TypeData::Lazy(def_id)) = self.interner.lookup(target)
819 && let Some(resolved_target) = self.subtype.resolver.resolve_lazy(def_id, self.interner)
820 && resolved_target != target
821 {
822 return self.check_assignable_fast_path(source, resolved_target);
823 }
824
825 if source == target {
827 return Some(true);
828 }
829
830 if source == TypeId::ANY || target == TypeId::ANY {
833 if source == target {
837 return Some(true);
838 }
839 if self.lawyer.allow_any_suppression {
841 return Some(true);
842 }
843 return None;
845 }
846
847 if !self.strict_null_checks && source.is_nullish() {
849 return Some(true);
850 }
851
852 if target == TypeId::UNKNOWN {
854 return Some(true);
855 }
856
857 if source == TypeId::NEVER {
859 return Some(true);
860 }
861
862 if source == TypeId::ERROR || target == TypeId::ERROR {
865 return Some(true);
866 }
867
868 if source == TypeId::UNKNOWN {
870 return Some(false);
871 }
872
873 if let Some(TypeData::Union(members_id)) = self.interner.lookup(target) {
876 let members = self.interner.type_list(members_id);
877 if members
878 .iter()
879 .any(|&member| self.is_function_target_member(member))
880 && crate::type_queries::is_callable_type(self.interner, source)
881 {
882 return Some(true);
883 }
884 }
885
886 None }
888
889 pub fn is_assignable_strict(&mut self, source: TypeId, target: TypeId) -> bool {
890 if let Some(TypeData::Lazy(def_id)) = self.interner.lookup(target)
891 && let Some(resolved_target) = self.subtype.resolver.resolve_lazy(def_id, self.interner)
892 && resolved_target != target
893 {
894 return self.is_assignable_strict(source, resolved_target);
895 }
896
897 if source == target {
899 return true;
900 }
901 if !self.strict_null_checks && source.is_nullish() {
902 return true;
903 }
904 if !self.strict_null_checks && target.is_nullish() {
907 return true;
908 }
909 if target == TypeId::UNKNOWN {
910 return true;
911 }
912 if source == TypeId::NEVER {
913 return true;
914 }
915 if source == TypeId::ERROR || target == TypeId::ERROR {
917 return true;
918 }
919 if source == TypeId::UNKNOWN {
920 return false;
921 }
922 if let Some(TypeData::Union(members_id)) = self.interner.lookup(target) {
923 let members = self.interner.type_list(members_id);
924 if members
925 .iter()
926 .any(|&member| self.is_function_target_member(member))
927 && crate::type_queries::is_callable_type(self.interner, source)
928 {
929 return true;
930 }
931 }
932 if self.is_empty_object_target(target) {
933 return self.is_assignable_to_empty_object(source);
934 }
935
936 let prev = self.subtype.strict_function_types;
937 self.configure_subtype(true);
938 let result = self.subtype.is_subtype_of(source, target);
939 self.subtype.strict_function_types = prev;
940 result
941 }
942
943 pub fn explain_failure(
945 &mut self,
946 source: TypeId,
947 target: TypeId,
948 ) -> Option<SubtypeFailureReason> {
949 if source == target {
951 return None;
952 }
953 if target == TypeId::UNKNOWN {
954 return None;
955 }
956 if !self.strict_null_checks && source.is_nullish() {
957 return None;
958 }
959 if !self.strict_null_checks && (target == TypeId::NULL || target == TypeId::UNDEFINED) {
961 return None;
962 }
963 if source == TypeId::NEVER {
964 return None;
965 }
966 if source == TypeId::UNKNOWN {
967 return Some(SubtypeFailureReason::TypeMismatch {
968 source_type: source,
969 target_type: target,
970 });
971 }
972
973 if source == TypeId::ERROR || target == TypeId::ERROR {
976 return None;
977 }
978
979 let violates = self.violates_weak_union(source, target);
981 if violates {
982 return Some(SubtypeFailureReason::TypeMismatch {
983 source_type: source,
984 target_type: target,
985 });
986 }
987 if self.violates_weak_type(source, target) {
988 return Some(SubtypeFailureReason::NoCommonProperties {
989 source_type: source,
990 target_type: target,
991 });
992 }
993
994 if let Some(excess_prop) = self.find_excess_property(source, target) {
996 return Some(SubtypeFailureReason::ExcessProperty {
997 property_name: excess_prop,
998 target_type: target,
999 });
1000 }
1001
1002 if let Some(false) = self.private_brand_assignability_override(source, target) {
1005 return Some(SubtypeFailureReason::TypeMismatch {
1006 source_type: source,
1007 target_type: target,
1008 });
1009 }
1010
1011 if self.is_empty_object_target(target) && self.is_assignable_to_empty_object(source) {
1013 return None;
1014 }
1015
1016 self.configure_subtype(self.strict_function_types);
1017 self.subtype.explain_failure(source, target)
1018 }
1019
1020 const fn configure_subtype(&mut self, strict_function_types: bool) {
1021 self.subtype.strict_function_types = strict_function_types;
1022 self.subtype.allow_void_return = true;
1023 self.subtype.allow_bivariant_rest = true;
1024 self.subtype.exact_optional_property_types = self.exact_optional_property_types;
1025 self.subtype.strict_null_checks = self.strict_null_checks;
1026 self.subtype.no_unchecked_indexed_access = self.no_unchecked_indexed_access;
1027 self.subtype.any_propagation = self.lawyer.any_propagation_mode();
1031 self.subtype.disable_method_bivariance = self.strict_subtype_checking;
1033 }
1034
1035 fn violates_weak_type(&self, source: TypeId, target: TypeId) -> bool {
1036 let mut extractor = ShapeExtractor::new(self.interner, self.subtype.resolver);
1037
1038 let target_shape_id = match extractor.extract(target) {
1039 Some(id) => id,
1040 None => return false,
1041 };
1042
1043 let target_shape = self
1044 .interner
1045 .object_shape(crate::types::ObjectShapeId(target_shape_id));
1046
1047 if let Some(TypeData::ObjectWithIndex(_)) = self.interner.lookup(target)
1049 && (target_shape.string_index.is_some() || target_shape.number_index.is_some())
1050 {
1051 return false;
1052 }
1053
1054 let target_props = target_shape.properties.as_slice();
1055 if target_props.is_empty() || target_props.iter().any(|prop| !prop.optional) {
1056 return false;
1057 }
1058
1059 self.violates_weak_type_with_target_props(source, target_props)
1060 }
1061
1062 fn violates_weak_union(&self, source: TypeId, target: TypeId) -> bool {
1063 let target_key = match self.interner.lookup(target) {
1066 Some(TypeData::Union(members)) => members,
1067 _ => {
1068 return false;
1069 }
1070 };
1071
1072 let members = self.interner.type_list(target_key);
1073 if members.is_empty() {
1074 return false;
1075 }
1076
1077 let mut extractor = ShapeExtractor::new(self.interner, self.subtype.resolver);
1078 let mut has_weak_member = false;
1079
1080 for member in members.iter() {
1081 let resolved_member = self.resolve_weak_type_ref(*member);
1082 let member_shape_id = match extractor.extract(resolved_member) {
1086 Some(id) => id,
1087 None => return false,
1088 };
1089
1090 let member_shape = self
1091 .interner
1092 .object_shape(crate::types::ObjectShapeId(member_shape_id));
1093
1094 if member_shape.properties.is_empty()
1095 || member_shape.string_index.is_some()
1096 || member_shape.number_index.is_some()
1097 {
1098 return false;
1099 }
1100
1101 if member_shape.properties.iter().all(|prop| prop.optional) {
1102 has_weak_member = true;
1103 }
1104 }
1105
1106 if !has_weak_member {
1107 return false;
1108 }
1109
1110 self.source_lacks_union_common_property(source, members.as_ref())
1111 }
1112
1113 pub fn is_weak_union_violation(&self, source: TypeId, target: TypeId) -> bool {
1114 self.violates_weak_union(source, target)
1115 }
1116
1117 fn violates_weak_type_with_target_props(
1118 &self,
1119 source: TypeId,
1120 target_props: &[PropertyInfo],
1121 ) -> bool {
1122 if let Some(TypeData::Union(members)) = self.interner.lookup(source) {
1124 let members = self.interner.type_list(members);
1125 return members
1126 .iter()
1127 .all(|member| self.violates_weak_type_with_target_props(*member, target_props));
1128 }
1129
1130 let mut extractor = ShapeExtractor::new(self.interner, self.subtype.resolver);
1131 let source_shape_id = match extractor.extract(source) {
1132 Some(id) => id,
1133 None => return false,
1134 };
1135
1136 let source_shape = self
1137 .interner
1138 .object_shape(crate::types::ObjectShapeId(source_shape_id));
1139 let source_props = source_shape.properties.as_slice();
1140
1141 !source_props.is_empty() && !self.has_common_property(source_props, target_props)
1144 }
1145
1146 fn source_lacks_union_common_property(
1147 &self,
1148 source: TypeId,
1149 target_members: &[TypeId],
1150 ) -> bool {
1151 let source = self.resolve_weak_type_ref(source);
1152
1153 if let Some(TypeData::Union(members)) = self.interner.lookup(source) {
1155 let members = self.interner.type_list(members);
1156 return members
1157 .iter()
1158 .all(|member| self.source_lacks_union_common_property(*member, target_members));
1159 }
1160
1161 if let Some(TypeData::TypeParameter(param)) = self.interner.lookup(source) {
1163 return match param.constraint {
1164 Some(constraint) => {
1165 self.source_lacks_union_common_property(constraint, target_members)
1166 }
1167 None => false,
1168 };
1169 }
1170
1171 let mut extractor = ShapeExtractor::new(self.interner, self.subtype.resolver);
1173 let source_shape_id = match extractor.extract(source) {
1174 Some(id) => id,
1175 None => return false,
1176 };
1177
1178 let source_shape = self
1179 .interner
1180 .object_shape(crate::types::ObjectShapeId(source_shape_id));
1181 if source_shape.string_index.is_some() || source_shape.number_index.is_some() {
1182 return false;
1183 }
1184 let source_props = source_shape.properties.as_slice();
1185 if source_props.is_empty() {
1186 return false;
1187 }
1188
1189 let mut has_common = false;
1190 for member in target_members {
1191 let resolved_member = self.resolve_weak_type_ref(*member);
1192 let member_shape_id = match extractor.extract(resolved_member) {
1193 Some(id) => id,
1194 None => continue,
1195 };
1196
1197 let member_shape = self
1198 .interner
1199 .object_shape(crate::types::ObjectShapeId(member_shape_id));
1200 if member_shape.string_index.is_some() || member_shape.number_index.is_some() {
1201 return false;
1202 }
1203 if self.has_common_property(source_props, member_shape.properties.as_slice()) {
1204 has_common = true;
1205 break;
1206 }
1207 }
1208
1209 !has_common
1210 }
1211
1212 fn has_common_property(
1213 &self,
1214 source_props: &[PropertyInfo],
1215 target_props: &[PropertyInfo],
1216 ) -> bool {
1217 let mut source_idx = 0;
1218 let mut target_idx = 0;
1219
1220 while source_idx < source_props.len() && target_idx < target_props.len() {
1221 let source_name = source_props[source_idx].name;
1222 let target_name = target_props[target_idx].name;
1223 if source_name == target_name {
1224 return true;
1225 }
1226 if source_name < target_name {
1227 source_idx += 1;
1228 } else {
1229 target_idx += 1;
1230 }
1231 }
1232
1233 false
1234 }
1235
1236 fn resolve_weak_type_ref(&self, type_id: TypeId) -> TypeId {
1237 self.subtype.resolve_ref_type(type_id)
1238 }
1239
1240 fn is_empty_object_target(&self, target: TypeId) -> bool {
1243 is_empty_object_type_db(self.interner, target)
1244 }
1245
1246 fn is_assignable_to_empty_object(&self, source: TypeId) -> bool {
1247 if source == TypeId::ANY || source == TypeId::NEVER {
1248 return true;
1249 }
1250 if source == TypeId::ERROR {
1252 return true;
1253 }
1254 if !self.strict_null_checks && source.is_nullish() {
1255 return true;
1256 }
1257 if source == TypeId::UNKNOWN
1258 || source == TypeId::NULL
1259 || source == TypeId::UNDEFINED
1260 || source == TypeId::VOID
1261 {
1262 return false;
1263 }
1264
1265 let key = match self.interner.lookup(source) {
1266 Some(key) => key,
1267 None => return false,
1268 };
1269
1270 match key {
1271 TypeData::Union(members) => {
1272 let members = self.interner.type_list(members);
1273 members
1274 .iter()
1275 .all(|member| self.is_assignable_to_empty_object(*member))
1276 }
1277 TypeData::Intersection(members) => {
1278 let members = self.interner.type_list(members);
1279 members
1280 .iter()
1281 .any(|member| self.is_assignable_to_empty_object(*member))
1282 }
1283 TypeData::TypeParameter(param) => match param.constraint {
1284 Some(constraint) => self.is_assignable_to_empty_object(constraint),
1285 None => false,
1286 },
1287 _ => true,
1288 }
1289 }
1290}
1291
1292impl<'a, R: TypeResolver> AssignabilityChecker for CompatChecker<'a, R> {
1293 fn is_assignable_to(&mut self, source: TypeId, target: TypeId) -> bool {
1294 self.is_assignable(source, target)
1295 }
1296
1297 fn is_assignable_to_strict(&mut self, source: TypeId, target: TypeId) -> bool {
1298 self.is_assignable_strict(source, target)
1299 }
1300
1301 fn is_assignable_to_bivariant_callback(&mut self, source: TypeId, target: TypeId) -> bool {
1302 self.is_assignable_impl(source, target, false)
1304 }
1305
1306 fn evaluate_type(&mut self, type_id: TypeId) -> TypeId {
1307 self.subtype.evaluate_type(type_id)
1308 }
1309}
1310
1311impl<'a, R: TypeResolver> CompatChecker<'a, R> {
1316 pub fn is_assignable_with_overrides<P: AssignabilityOverrideProvider + ?Sized>(
1321 &mut self,
1322 source: TypeId,
1323 target: TypeId,
1324 overrides: &P,
1325 ) -> bool {
1326 if let Some(result) = overrides.enum_assignability_override(source, target) {
1328 return result;
1329 }
1330
1331 if let Some(result) = overrides.abstract_constructor_assignability_override(source, target)
1333 {
1334 return result;
1335 }
1336
1337 if let Some(result) = overrides.constructor_accessibility_override(source, target) {
1339 return result;
1340 }
1341
1342 if let Some(result) = self.private_brand_assignability_override(source, target) {
1344 return result;
1345 }
1346
1347 self.is_assignable(source, target)
1349 }
1350
1351 pub fn private_brand_assignability_override(
1359 &self,
1360 source: TypeId,
1361 target: TypeId,
1362 ) -> Option<bool> {
1363 use crate::types::Visibility;
1364
1365 if source == target {
1368 return None;
1369 }
1370
1371 if let Some(TypeData::Union(members)) = self.interner.lookup(target) {
1374 let members = self.interner.type_list(members);
1375 for &member in members.iter() {
1377 match self.private_brand_assignability_override(source, member) {
1378 Some(true) | None => return None, Some(false) => {} }
1381 }
1382 return Some(false); }
1384
1385 if let Some(TypeData::Union(members)) = self.interner.lookup(source) {
1388 let members = self.interner.type_list(members);
1389 for &member in members.iter() {
1390 if let Some(false) = self.private_brand_assignability_override(member, target) {
1391 return Some(false); }
1393 }
1394 return None; }
1396
1397 if let Some(TypeData::Intersection(members)) = self.interner.lookup(target) {
1400 let members = self.interner.type_list(members);
1401 for &member in members.iter() {
1402 if let Some(false) = self.private_brand_assignability_override(source, member) {
1403 return Some(false); }
1405 }
1406 return None; }
1408
1409 if let Some(TypeData::Intersection(members)) = self.interner.lookup(source) {
1412 let members = self.interner.type_list(members);
1413 for &member in members.iter() {
1414 match self.private_brand_assignability_override(member, target) {
1415 Some(true) | None => return None, Some(false) => {} }
1418 }
1419 return Some(false); }
1421
1422 if let Some(TypeData::Lazy(def_id)) = self.interner.lookup(source)
1424 && let Some(resolved) = self.subtype.resolver.resolve_lazy(def_id, self.interner)
1425 {
1426 if resolved == source {
1429 return None;
1430 }
1431 return self.private_brand_assignability_override(resolved, target);
1432 }
1433
1434 if let Some(TypeData::Lazy(def_id)) = self.interner.lookup(target)
1435 && let Some(resolved) = self.subtype.resolver.resolve_lazy(def_id, self.interner)
1436 {
1437 if resolved == target {
1439 return None;
1440 }
1441 return self.private_brand_assignability_override(source, resolved);
1442 }
1443
1444 let mut extractor = ShapeExtractor::new(self.interner, self.subtype.resolver);
1446
1447 let source_shape_id = extractor.extract(source)?;
1449 let source_shape = self
1450 .interner
1451 .object_shape(crate::types::ObjectShapeId(source_shape_id));
1452
1453 let mut extractor = ShapeExtractor::new(self.interner, self.subtype.resolver);
1455 let target_shape_id = extractor.extract(target)?;
1456 let target_shape = self
1457 .interner
1458 .object_shape(crate::types::ObjectShapeId(target_shape_id));
1459
1460 let mut has_private_brands = false;
1461
1462 for target_prop in &target_shape.properties {
1465 if target_prop.visibility == Visibility::Private
1466 || target_prop.visibility == Visibility::Protected
1467 {
1468 has_private_brands = true;
1469 let source_prop = source_shape
1470 .properties
1471 .binary_search_by_key(&target_prop.name, |p| p.name)
1472 .ok()
1473 .map(|idx| &source_shape.properties[idx]);
1474
1475 match source_prop {
1476 Some(sp) => {
1477 if sp.parent_id != target_prop.parent_id {
1479 return Some(false);
1480 }
1481 }
1482 None => {
1483 return Some(false);
1484 }
1485 }
1486 }
1487 }
1488
1489 for source_prop in &source_shape.properties {
1493 if source_prop.visibility == Visibility::Private
1494 || source_prop.visibility == Visibility::Protected
1495 {
1496 has_private_brands = true;
1497 if let Some(target_prop) = target_shape
1498 .properties
1499 .binary_search_by_key(&source_prop.name, |p| p.name)
1500 .ok()
1501 .map(|idx| &target_shape.properties[idx])
1502 && target_prop.visibility == Visibility::Public
1503 {
1504 return Some(false);
1505 }
1506 }
1507 }
1508
1509 has_private_brands.then_some(true)
1510 }
1511
1512 pub fn enum_assignability_override(&self, source: TypeId, target: TypeId) -> Option<bool> {
1522 use crate::type_queries;
1523 use crate::visitor;
1524
1525 if let Some((t_def, _)) = visitor::enum_components(self.interner, target)
1530 && type_queries::is_union_type(self.interner, source)
1531 {
1532 let union_members = type_queries::get_union_members(self.interner, source)?;
1533
1534 let mut all_same_enum = true;
1535 let mut has_non_enum = false;
1536 for &member in &union_members {
1537 if let Some((member_def, _)) = visitor::enum_components(self.interner, member) {
1538 let member_parent = self.subtype.resolver.get_enum_parent_def_id(member_def);
1542 if member_def != t_def && member_parent != Some(t_def) {
1543 return Some(false);
1545 }
1546 } else {
1547 all_same_enum = false;
1548 has_non_enum = true;
1549 }
1550 }
1551
1552 if all_same_enum && !has_non_enum && !union_members.is_empty() {
1556 return Some(true);
1557 }
1558 }
1560
1561 if let (Some((s_def, _)), Some((t_def, _))) = (
1567 visitor::enum_components(self.interner, source),
1568 visitor::enum_components(self.interner, target),
1569 ) && s_def == t_def
1570 && source != target
1571 {
1572 if self.is_literal_enum_member(source) && self.is_literal_enum_member(target) {
1575 return Some(false);
1578 }
1579 }
1580
1581 let source_def = self.get_enum_def_id(source);
1582 let target_def = self.get_enum_def_id(target);
1583
1584 match (source_def, target_def) {
1585 (Some(s_def), Some(t_def)) => {
1588 if s_def == t_def {
1589 return Some(true);
1591 }
1592
1593 let s_parent = self.subtype.resolver.get_enum_parent_def_id(s_def);
1596 let t_parent = self.subtype.resolver.get_enum_parent_def_id(t_def);
1597
1598 match (s_parent, t_parent) {
1599 (Some(sp), Some(tp)) if sp == tp => {
1600 if self.subtype.resolver.is_enum_type(target, self.interner) {
1603 return None;
1604 }
1605 Some(false)
1608 }
1609 (Some(sp), None) => {
1610 if t_def == sp {
1613 return Some(true);
1616 }
1617 Some(false)
1619 }
1620 _ => {
1621 Some(false)
1624 }
1625 }
1626 }
1627
1628 (None, Some(t_def)) => {
1630 if self.subtype.resolver.is_numeric_enum(t_def) {
1632 let is_source_number = source == TypeId::NUMBER
1639 || matches!(
1640 self.interner.lookup(source),
1641 Some(TypeData::Literal(LiteralValue::Number(_)))
1642 );
1643
1644 if is_source_number {
1645 if self.subtype.resolver.is_enum_type(target, self.interner) {
1647 return Some(true);
1648 }
1649
1650 return None;
1655 }
1656
1657 None
1658 } else {
1659 if self.is_string_like(source) {
1662 return Some(false);
1663 }
1664 None
1665 }
1666 }
1667
1668 (Some(s_def), None) => {
1671 if !self.subtype.resolver.is_numeric_enum(s_def) {
1673 if target == TypeId::STRING {
1675 return None;
1678 }
1679 }
1680 None
1682 }
1683
1684 (None, None) => None,
1686 }
1687 }
1688
1689 fn is_string_like(&self, type_id: TypeId) -> bool {
1692 if type_id == TypeId::STRING {
1693 return true;
1694 }
1695 let mut visitor = StringLikeVisitor { db: self.interner };
1697 visitor.visit_type(self.interner, type_id)
1698 }
1699
1700 fn is_literal_enum_member(&self, type_id: TypeId) -> bool {
1703 matches!(
1704 self.interner.lookup(type_id),
1705 Some(TypeData::Enum(_, member_type))
1706 if matches!(
1707 self.interner.lookup(member_type),
1708 Some(TypeData::Literal(LiteralValue::Number(_) | LiteralValue::String(_)))
1709 )
1710 )
1711 }
1712
1713 fn get_enum_def_id(&self, type_id: TypeId) -> Option<crate::def::DefId> {
1716 use crate::{type_queries, visitor};
1717
1718 let resolved =
1720 if let Some(lazy_def_id) = type_queries::get_lazy_def_id(self.interner, type_id) {
1721 if let Some(resolved_type) = self
1723 .subtype
1724 .resolver
1725 .resolve_lazy(lazy_def_id, self.interner)
1726 {
1727 if resolved_type == type_id {
1729 return None;
1730 }
1731 return self.get_enum_def_id(resolved_type);
1733 }
1734 return None;
1736 } else {
1737 type_id
1738 };
1739
1740 if visitor::intrinsic_kind(self.interner, resolved).is_some() {
1744 return None;
1745 }
1746
1747 if let Some((def_id, _inner)) = visitor::enum_components(self.interner, resolved) {
1749 if self.subtype.resolver.is_user_enum_def(def_id) {
1752 return Some(def_id);
1753 }
1754 return None;
1756 }
1757
1758 if let Some(members) = visitor::union_list_id(self.interner, resolved) {
1760 let members = self.interner.type_list(members);
1761 if members.is_empty() {
1762 return None;
1763 }
1764
1765 let first_def = self.get_enum_def_id(members[0])?;
1766 for &member in members.iter().skip(1) {
1767 if self.get_enum_def_id(member) != Some(first_def) {
1768 return None; }
1770 }
1771 return Some(first_def);
1772 }
1773
1774 None
1775 }
1776
1777 pub fn are_types_identical_for_redeclaration(&mut self, a: TypeId, b: TypeId) -> bool {
1790 if a == b {
1792 return true;
1793 }
1794
1795 if a == TypeId::ERROR || b == TypeId::ERROR {
1797 return true;
1798 }
1799
1800 if a == TypeId::ANY || b == TypeId::ANY {
1803 return false;
1804 }
1805
1806 if let Some(res) = self.enum_redeclaration_check(a, b) {
1810 return res;
1811 }
1812
1813 let (a_norm, b_norm) = self.normalize_assignability_operands(a, b);
1817 tracing::trace!(
1818 a = a.0,
1819 b = b.0,
1820 a_norm = a_norm.0,
1821 b_norm = b_norm.0,
1822 a_changed = a != a_norm,
1823 b_changed = b != b_norm,
1824 "are_types_identical_for_redeclaration: normalized"
1825 );
1826 let a = a_norm;
1827 let b = b_norm;
1828
1829 let fwd = self.subtype.is_subtype_of(a, b);
1832 let bwd = self.subtype.is_subtype_of(b, a);
1833 tracing::trace!(
1834 a = a.0,
1835 b = b.0,
1836 fwd,
1837 bwd,
1838 "are_types_identical_for_redeclaration: result"
1839 );
1840 fwd && bwd
1841 }
1842
1843 fn enum_redeclaration_check(&self, a: TypeId, b: TypeId) -> Option<bool> {
1849 let a_def = self.get_enum_def_id(a);
1850 let b_def = self.get_enum_def_id(b);
1851
1852 match (a_def, b_def) {
1853 (Some(def_a), Some(def_b)) => {
1854 if def_a != def_b {
1856 Some(false)
1857 } else {
1858 Some(true)
1862 }
1863 }
1864 (Some(_), None) | (None, Some(_)) => {
1865 Some(false)
1868 }
1869 (None, None) => None,
1870 }
1871 }
1872}
1873
1874#[cfg(test)]
1875#[path = "../tests/compat_tests.rs"]
1876mod tests;