1use crate::query_boundaries::assignability::{
7 AssignabilityEvalKind, AssignabilityQueryInputs, ExcessPropertiesKind,
8 are_types_overlapping_with_env, check_assignable_gate_with_overrides,
9 classify_for_assignability_eval, classify_for_excess_properties,
10 is_assignable_bivariant_with_resolver, is_assignable_with_overrides, is_callable_type,
11 is_relation_cacheable, object_shape_for_type,
12};
13use crate::state::{CheckerOverrideProvider, CheckerState};
14use rustc_hash::FxHashSet;
15use tracing::trace;
16use tsz_common::interner::Atom;
17use tsz_parser::parser::NodeIndex;
18use tsz_parser::parser::node::NodeAccess;
19use tsz_parser::parser::node_flags;
20use tsz_parser::parser::syntax_kind_ext;
21use tsz_scanner::SyntaxKind;
22use tsz_solver::NarrowingContext;
23use tsz_solver::RelationCacheKey;
24use tsz_solver::TypeId;
25use tsz_solver::visitor::{collect_lazy_def_ids, collect_type_queries};
26
27impl<'a> CheckerState<'a> {
32 fn get_keyof_type_keys(
33 &mut self,
34 type_id: TypeId,
35 db: &dyn tsz_solver::TypeDatabase,
36 ) -> FxHashSet<Atom> {
37 if let Some(keyof_type) = tsz_solver::type_queries::get_keyof_type(db, type_id)
38 && let Some(key_type) =
39 tsz_solver::type_queries::keyof_object_properties(db, keyof_type)
40 && let Some(members) = tsz_solver::type_queries::get_union_members(db, key_type)
41 {
42 return members
43 .into_iter()
44 .filter_map(|m| {
45 if let Some(str_lit) = tsz_solver::type_queries::get_string_literal_value(db, m)
46 {
47 return Some(str_lit);
48 }
49 None
50 })
51 .collect();
52 }
53 FxHashSet::default()
54 }
55
56 fn typeof_this_comparison_literal(
57 &self,
58 left: NodeIndex,
59 right: NodeIndex,
60 this_ref: NodeIndex,
61 ) -> Option<&str> {
62 if self.is_typeof_this_target(left, this_ref) {
63 return self.string_literal_text(right);
64 }
65 if self.is_typeof_this_target(right, this_ref) {
66 return self.string_literal_text(left);
67 }
68 None
69 }
70
71 fn is_typeof_this_target(&self, expr: NodeIndex, this_ref: NodeIndex) -> bool {
72 let expr = self.ctx.arena.skip_parenthesized(expr);
73 let Some(node) = self.ctx.arena.get(expr) else {
74 return false;
75 };
76 if node.kind != syntax_kind_ext::PREFIX_UNARY_EXPRESSION {
77 return false;
78 }
79 let Some(unary) = self.ctx.arena.get_unary_expr(node) else {
80 return false;
81 };
82 if unary.operator != SyntaxKind::TypeOfKeyword as u16 {
83 return false;
84 }
85 let operand = self.ctx.arena.skip_parenthesized(unary.operand);
86 if operand == this_ref {
87 return true;
88 }
89 self.ctx
90 .arena
91 .get(operand)
92 .is_some_and(|n| n.kind == SyntaxKind::ThisKeyword as u16)
93 }
94
95 fn string_literal_text(&self, idx: NodeIndex) -> Option<&str> {
96 let idx = self.ctx.arena.skip_parenthesized(idx);
97 let node = self.ctx.arena.get(idx)?;
98 if node.kind == SyntaxKind::StringLiteral as u16
99 || node.kind == SyntaxKind::NoSubstitutionTemplateLiteral as u16
100 {
101 return self
102 .ctx
103 .arena
104 .get_literal(node)
105 .map(|lit| lit.text.as_str());
106 }
107 None
108 }
109
110 fn narrow_this_from_enclosing_typeof_guard(
111 &self,
112 source_idx: NodeIndex,
113 source: TypeId,
114 ) -> TypeId {
115 let is_this_source = self
116 .ctx
117 .arena
118 .get(source_idx)
119 .is_some_and(|n| n.kind == SyntaxKind::ThisKeyword as u16);
120 if !is_this_source {
121 return source;
122 }
123
124 let mut current = source_idx;
125 let mut depth = 0usize;
126 while depth < 256 {
127 depth += 1;
128 let Some(ext) = self.ctx.arena.get_extended(current) else {
129 break;
130 };
131 if ext.parent.is_none() {
132 break;
133 }
134 current = ext.parent;
135 let Some(parent_node) = self.ctx.arena.get(current) else {
136 break;
137 };
138 if parent_node.kind != syntax_kind_ext::IF_STATEMENT {
139 continue;
140 }
141 let Some(if_stmt) = self.ctx.arena.get_if_statement(parent_node) else {
142 continue;
143 };
144 if !self.is_node_within(source_idx, if_stmt.then_statement) {
145 continue;
146 }
147 let Some(cond_node) = self.ctx.arena.get(if_stmt.expression) else {
148 continue;
149 };
150 if cond_node.kind != syntax_kind_ext::BINARY_EXPRESSION {
151 continue;
152 }
153 let Some(bin) = self.ctx.arena.get_binary_expr(cond_node) else {
154 continue;
155 };
156 let is_equality = bin.operator_token == SyntaxKind::EqualsEqualsEqualsToken as u16
157 || bin.operator_token == SyntaxKind::EqualsEqualsToken as u16;
158 if !is_equality {
159 continue;
160 }
161 if let Some(type_name) =
162 self.typeof_this_comparison_literal(bin.left, bin.right, source_idx)
163 {
164 return NarrowingContext::new(self.ctx.types).narrow_by_typeof(source, type_name);
165 }
166 }
167
168 source
169 }
170
171 pub(crate) fn ensure_relation_input_ready(&mut self, type_id: TypeId) {
173 self.ensure_refs_resolved(type_id);
174 self.ensure_application_symbols_resolved(type_id);
175 }
176
177 pub(crate) fn ensure_relation_inputs_ready(&mut self, type_ids: &[TypeId]) {
179 for &type_id in type_ids {
180 self.ensure_relation_input_ready(type_id);
181 }
182 }
183
184 pub(crate) const fn should_suppress_assignability_diagnostic(
186 &self,
187 source: TypeId,
188 target: TypeId,
189 ) -> bool {
190 matches!(source, TypeId::ERROR | TypeId::ANY)
191 || matches!(target, TypeId::ERROR | TypeId::ANY)
192 }
193
194 fn should_suppress_assignability_for_parse_recovery(
199 &self,
200 source_idx: NodeIndex,
201 diag_idx: NodeIndex,
202 ) -> bool {
203 if !self.has_syntax_parse_errors() {
204 return false;
205 }
206
207 if self.ctx.syntax_parse_error_positions.is_empty() {
208 return false;
209 }
210
211 self.is_parse_recovery_anchor_node(source_idx)
212 || self.is_parse_recovery_anchor_node(diag_idx)
213 }
214
215 fn is_parse_recovery_anchor_node(&self, idx: NodeIndex) -> bool {
222 let Some(node) = self.ctx.arena.get(idx) else {
223 return false;
224 };
225
226 if self
228 .ctx
229 .arena
230 .get_identifier_text(idx)
231 .is_some_and(str::is_empty)
232 {
233 return true;
234 }
235
236 const DIAG_PARSE_DISTANCE: u32 = 16;
238 for &err_pos in &self.ctx.syntax_parse_error_positions {
239 let before = err_pos.saturating_sub(DIAG_PARSE_DISTANCE);
240 let after = err_pos.saturating_add(DIAG_PARSE_DISTANCE);
241 if (node.pos >= before && node.pos <= after)
242 || (node.end >= before && node.end <= after)
243 {
244 return true;
245 }
246 }
247
248 let mut current = idx;
249 let mut walk_guard = 0;
250 while current.is_some() {
251 walk_guard += 1;
252 if walk_guard > 512 {
253 break;
254 }
255
256 if let Some(current_node) = self.ctx.arena.get(current) {
257 let flags = current_node.flags as u32;
258 if (flags & node_flags::THIS_NODE_HAS_ERROR) != 0
259 || (flags & node_flags::THIS_NODE_OR_ANY_SUB_NODES_HAS_ERROR) != 0
260 {
261 return true;
262 }
263 } else {
264 break;
265 }
266
267 let Some(ext) = self.ctx.arena.get_extended(current) else {
268 break;
269 };
270 if ext.parent.is_none() {
271 break;
272 }
273 current = ext.parent;
274 }
275
276 false
277 }
278
279 pub(crate) fn ensure_refs_resolved(&mut self, type_id: TypeId) {
290 let mut visited_types = FxHashSet::default();
291 let mut visited_def_ids = FxHashSet::default();
292 let mut worklist = vec![type_id];
293
294 while let Some(current) = worklist.pop() {
295 if !visited_types.insert(current) {
296 continue;
297 }
298
299 for symbol_ref in collect_type_queries(self.ctx.types, current) {
300 let sym_id = tsz_binder::SymbolId(symbol_ref.0);
301 let _ = self.get_type_of_symbol(sym_id);
302 }
303
304 for def_id in collect_lazy_def_ids(self.ctx.types, current) {
305 if !visited_def_ids.insert(def_id) {
306 continue;
307 }
308 if let Some(result) = self.resolve_and_insert_def_type(def_id)
309 && result != TypeId::ERROR
310 && result != TypeId::ANY
311 {
312 worklist.push(result);
313 }
314 }
315 }
316 }
317
318 pub(crate) fn evaluate_type_for_assignability(&mut self, type_id: TypeId) -> TypeId {
323 let mut evaluated = match classify_for_assignability_eval(self.ctx.types, type_id) {
324 AssignabilityEvalKind::Application => self.evaluate_type_with_resolution(type_id),
325 AssignabilityEvalKind::NeedsEnvEval => self.evaluate_type_with_env(type_id),
326 AssignabilityEvalKind::Resolved => type_id,
327 };
328
329 if let Some(distributed) =
332 tsz_solver::type_queries::map_compound_members(self.ctx.types, evaluated, |member| {
333 self.evaluate_type_for_assignability(member)
334 })
335 {
336 evaluated = distributed;
337 }
338
339 evaluated
340 }
341
342 fn substitute_this_type_if_needed(&mut self, type_id: TypeId) -> TypeId {
354 if type_id.is_intrinsic() {
356 return type_id;
357 }
358
359 let needs_substitution = tsz_solver::is_this_type(self.ctx.types, type_id);
360
361 if !needs_substitution {
362 return type_id;
363 }
364
365 let Some(class_info) = &self.ctx.enclosing_class else {
366 return type_id;
367 };
368 let class_idx = class_info.class_idx;
369
370 let Some(node) = self.ctx.arena.get(class_idx) else {
371 return type_id;
372 };
373 let Some(class_data) = self.ctx.arena.get_class(node) else {
374 return type_id;
375 };
376
377 let instance_type = self.get_class_instance_type(class_idx, class_data);
378
379 if tsz_solver::is_this_type(self.ctx.types, type_id) {
380 instance_type
381 } else {
382 tsz_solver::substitute_this_type(self.ctx.types, type_id, instance_type)
383 }
384 }
385
386 pub fn is_assignable_to(&mut self, source: TypeId, target: TypeId) -> bool {
392 self.ensure_relation_input_ready(target);
396
397 let target = self.substitute_this_type_if_needed(target);
403
404 {
408 use tsz_solver::visitor::lazy_def_id;
409 let is_function_target = lazy_def_id(self.ctx.types, target).is_some_and(|t_def| {
410 self.ctx.type_env.try_borrow().ok().is_some_and(|env| {
411 env.is_boxed_def_id(t_def, tsz_solver::IntrinsicKind::Function)
412 })
413 });
414 if is_function_target {
415 let source_eval = self.evaluate_type_for_assignability(source);
416 if is_callable_type(self.ctx.types, source_eval) {
417 return true;
418 }
419 }
420 }
421
422 let source = self.evaluate_type_for_assignability(source);
423 let target = self.evaluate_type_for_assignability(target);
424
425 let is_cacheable = is_relation_cacheable(self.ctx.types, source, target);
429
430 let flags = self.ctx.pack_relation_flags();
431
432 if is_cacheable {
433 let cache_key = RelationCacheKey::assignability(source, target, flags, 0);
434
435 if let Some(cached) = self.ctx.types.lookup_assignability_cache(cache_key) {
436 return cached;
437 }
438 }
439
440 let overrides = CheckerOverrideProvider::new(self, None);
443 let result = is_assignable_with_overrides(
444 &AssignabilityQueryInputs {
445 db: self.ctx.types,
446 resolver: &self.ctx,
447 source,
448 target,
449 flags,
450 inheritance_graph: &self.ctx.inheritance_graph,
451 sound_mode: self.ctx.sound_mode(),
452 },
453 &overrides,
454 );
455
456 if is_cacheable {
457 let cache_key = RelationCacheKey::assignability(source, target, flags, 0);
458
459 self.ctx.types.insert_assignability_cache(cache_key, result);
460 }
461
462 trace!(
463 source = source.0,
464 target = target.0,
465 result,
466 "is_assignable_to"
467 );
468
469 if let Some(keyof_type) = tsz_solver::type_queries::get_keyof_type(self.ctx.types, target)
471 && let Some(source_atom) =
472 tsz_solver::type_queries::get_string_literal_value(self.ctx.types, source)
473 {
474 let source_str = self.ctx.types.resolve_atom(source_atom);
475 let allowed_keys =
476 tsz_solver::type_queries::get_allowed_keys(self.ctx.types, keyof_type);
477 if !allowed_keys.contains(&source_str) {
478 return false;
479 }
480 }
481
482 result
483 }
484
485 pub fn is_assignable_to_strict(&mut self, source: TypeId, target: TypeId) -> bool {
489 self.ensure_relation_input_ready(target);
490
491 let target = self.substitute_this_type_if_needed(target);
492 let source = self.evaluate_type_for_assignability(source);
493 let target = self.evaluate_type_for_assignability(target);
494
495 let is_cacheable = is_relation_cacheable(self.ctx.types, source, target);
496 let flags = self.ctx.pack_relation_flags() | RelationCacheKey::FLAG_STRICT_FUNCTION_TYPES;
497
498 if is_cacheable {
499 let cache_key = RelationCacheKey::assignability(source, target, flags, 0);
500 if let Some(cached) = self.ctx.types.lookup_assignability_cache(cache_key) {
501 return cached;
502 }
503 }
504
505 let overrides = CheckerOverrideProvider::new(self, None);
506 let result = is_assignable_with_overrides(
507 &AssignabilityQueryInputs {
508 db: self.ctx.types,
509 resolver: &self.ctx,
510 source,
511 target,
512 flags,
513 inheritance_graph: &self.ctx.inheritance_graph,
514 sound_mode: self.ctx.sound_mode(),
515 },
516 &overrides,
517 );
518
519 if is_cacheable {
520 let cache_key = RelationCacheKey::assignability(source, target, flags, 0);
521 self.ctx.types.insert_assignability_cache(cache_key, result);
522 }
523
524 trace!(
525 source = source.0,
526 target = target.0,
527 result,
528 "is_assignable_to_strict"
529 );
530 result
531 }
532
533 pub fn is_assignable_to_strict_null(&mut self, source: TypeId, target: TypeId) -> bool {
539 self.ensure_relation_input_ready(target);
540
541 let target = self.substitute_this_type_if_needed(target);
542 let source = self.evaluate_type_for_assignability(source);
543 let target = self.evaluate_type_for_assignability(target);
544
545 let is_cacheable = is_relation_cacheable(self.ctx.types, source, target);
546 let flags = self.ctx.pack_relation_flags() | RelationCacheKey::FLAG_STRICT_NULL_CHECKS;
547
548 if is_cacheable {
549 let cache_key = RelationCacheKey::assignability(source, target, flags, 0);
550 if let Some(cached) = self.ctx.types.lookup_assignability_cache(cache_key) {
551 return cached;
552 }
553 }
554
555 let overrides = CheckerOverrideProvider::new(self, None);
556 let result = is_assignable_with_overrides(
557 &AssignabilityQueryInputs {
558 db: self.ctx.types,
559 resolver: &self.ctx,
560 source,
561 target,
562 flags,
563 inheritance_graph: &self.ctx.inheritance_graph,
564 sound_mode: self.ctx.sound_mode(),
565 },
566 &overrides,
567 );
568
569 if is_cacheable {
570 let cache_key = RelationCacheKey::assignability(source, target, flags, 0);
571 self.ctx.types.insert_assignability_cache(cache_key, result);
572 }
573
574 trace!(
575 source = source.0,
576 target = target.0,
577 result,
578 "is_assignable_to_strict_null"
579 );
580 result
581 }
582
583 pub fn is_assignable_to_with_env(
587 &self,
588 source: TypeId,
589 target: TypeId,
590 env: &tsz_solver::TypeEnvironment,
591 ) -> bool {
592 let flags = self.ctx.pack_relation_flags();
593 let overrides = CheckerOverrideProvider::new(self, Some(env));
594 is_assignable_with_overrides(
595 &AssignabilityQueryInputs {
596 db: self.ctx.types,
597 resolver: env,
598 source,
599 target,
600 flags,
601 inheritance_graph: &self.ctx.inheritance_graph,
602 sound_mode: self.ctx.sound_mode(),
603 },
604 &overrides,
605 )
606 }
607
608 pub fn is_assignable_to_bivariant(&mut self, source: TypeId, target: TypeId) -> bool {
616 self.ensure_relation_input_ready(target);
620
621 let source = self.evaluate_type_for_assignability(source);
622 let target = self.evaluate_type_for_assignability(target);
623
624 let is_cacheable = is_relation_cacheable(self.ctx.types, source, target);
628
629 let flags = self.ctx.pack_relation_flags() & !RelationCacheKey::FLAG_STRICT_FUNCTION_TYPES;
632
633 if is_cacheable {
634 let cache_key = RelationCacheKey::assignability(source, target, flags, 0);
637
638 if let Some(cached) = self.ctx.types.lookup_assignability_cache(cache_key) {
639 return cached;
640 }
641 }
642
643 let env = self.ctx.type_env.borrow();
644 let result = is_assignable_bivariant_with_resolver(
646 self.ctx.types,
647 &*env,
648 source,
649 target,
650 flags,
651 &self.ctx.inheritance_graph,
652 self.ctx.sound_mode(),
653 );
654
655 if is_cacheable {
658 let cache_key = RelationCacheKey::assignability(source, target, flags, 0);
659
660 self.ctx.types.insert_assignability_cache(cache_key, result);
661 }
662
663 trace!(
664 source = source.0,
665 target = target.0,
666 result,
667 "is_assignable_to_bivariant"
668 );
669 result
670 }
671
672 pub fn are_types_overlapping(&mut self, left: TypeId, right: TypeId) -> bool {
680 self.ensure_relation_input_ready(left);
682 self.ensure_relation_input_ready(right);
683
684 let env = self.ctx.type_env.borrow();
685 are_types_overlapping_with_env(
686 self.ctx.types,
687 &env,
688 left,
689 right,
690 self.ctx.strict_null_checks(),
691 )
692 }
693
694 pub(crate) fn should_skip_weak_union_error(
703 &mut self,
704 source: TypeId,
705 target: TypeId,
706 source_idx: NodeIndex,
707 ) -> bool {
708 let Some(node) = self.ctx.arena.get(source_idx) else {
709 return false;
710 };
711 if node.kind != syntax_kind_ext::OBJECT_LITERAL_EXPRESSION {
712 return false;
713 }
714
715 if self.is_weak_union_violation(source, target) {
717 return true;
718 }
719
720 if !self.object_literal_has_excess_properties(source, target, source_idx) {
722 return false;
723 }
724
725 let Some(source_shape) = object_shape_for_type(self.ctx.types, source) else {
727 return true;
728 };
729
730 let resolved_target = self.resolve_type_for_property_access(target);
731 let Some(target_shape) = object_shape_for_type(self.ctx.types, resolved_target) else {
732 return true;
733 };
734
735 let source_props = source_shape.properties.as_slice();
736 let target_props = target_shape.properties.as_slice();
737
738 for source_prop in source_props {
740 if let Some(target_prop) = target_props.iter().find(|p| p.name == source_prop.name) {
741 let source_prop_type = source_prop.type_id;
742 let target_prop_type = target_prop.type_id;
743
744 let effective_target_type = if target_prop.optional {
745 self.ctx
746 .types
747 .union(vec![target_prop_type, TypeId::UNDEFINED])
748 } else {
749 target_prop_type
750 };
751
752 let is_assignable =
753 { self.is_assignable_to(source_prop_type, effective_target_type) };
754
755 if !is_assignable {
756 return false;
757 }
758 }
759 }
760
761 true
762 }
763
764 pub(crate) fn check_satisfies_assignable_or_report(
766 &mut self,
767 source: TypeId,
768 target: TypeId,
769 source_idx: NodeIndex,
770 ) -> bool {
771 let diag_idx = source_idx;
772 let source = self.narrow_this_from_enclosing_typeof_guard(source_idx, source);
773 if self.should_suppress_assignability_diagnostic(source, target) {
774 return true;
775 }
776 if self.should_suppress_assignability_for_parse_recovery(source_idx, diag_idx) {
777 return true;
778 }
779
780 if tsz_solver::type_queries::is_keyof_type(self.ctx.types, target)
781 && let Some(str_lit) =
782 tsz_solver::type_queries::get_string_literal_value(self.ctx.types, source)
783 {
784 let keyof_type =
785 tsz_solver::type_queries::get_keyof_type(self.ctx.types, target).unwrap();
786 let allowed_keys = self.get_keyof_type_keys(keyof_type, self.ctx.types);
787 if !allowed_keys.contains(&str_lit) {
788 self.error_type_does_not_satisfy_the_expected_type(source, target, diag_idx);
789 return false;
790 }
791 }
792
793 if let Some(node) = self.ctx.arena.get(source_idx)
794 && node.kind == syntax_kind_ext::OBJECT_LITERAL_EXPRESSION
795 {
796 self.check_object_literal_excess_properties(source, target, source_idx);
797 }
798
799 if self.is_assignable_to(source, target)
800 || self.should_skip_weak_union_error(source, target, source_idx)
801 {
802 return true;
803 }
804 self.error_type_does_not_satisfy_the_expected_type(source, target, diag_idx);
805 false
806 }
807
808 pub(crate) fn check_assignable_or_report(
812 &mut self,
813 source: TypeId,
814 target: TypeId,
815 source_idx: NodeIndex,
816 ) -> bool {
817 self.check_assignable_or_report_at(source, target, source_idx, source_idx)
818 }
819
820 pub(crate) fn check_assignable_or_report_at(
826 &mut self,
827 source: TypeId,
828 target: TypeId,
829 source_idx: NodeIndex,
830 diag_idx: NodeIndex,
831 ) -> bool {
832 let source = self.narrow_this_from_enclosing_typeof_guard(source_idx, source);
833 if self.should_suppress_assignability_diagnostic(source, target) {
834 return true;
835 }
836 if self.should_suppress_assignability_for_parse_recovery(source_idx, diag_idx) {
837 return true;
838 }
839
840 if tsz_solver::type_queries::is_keyof_type(self.ctx.types, target)
841 && let Some(str_lit) =
842 tsz_solver::type_queries::get_string_literal_value(self.ctx.types, source)
843 {
844 let keyof_type =
845 tsz_solver::type_queries::get_keyof_type(self.ctx.types, target).unwrap();
846 let allowed_keys = self.get_keyof_type_keys(keyof_type, self.ctx.types);
847 if !allowed_keys.contains(&str_lit) {
848 self.error_type_not_assignable_with_reason_at(source, target, diag_idx);
849 return false;
850 }
851 }
852
853 if self.is_assignable_to(source, target)
854 || self.should_skip_weak_union_error(source, target, source_idx)
855 {
856 return true;
857 }
858 self.error_type_not_assignable_with_reason_at(source, target, diag_idx);
859 false
860 }
861
862 pub(crate) fn check_assignable_or_report_generic_at(
867 &mut self,
868 source: TypeId,
869 target: TypeId,
870 source_idx: NodeIndex,
871 diag_idx: NodeIndex,
872 ) -> bool {
873 let source = self.narrow_this_from_enclosing_typeof_guard(source_idx, source);
874 if self.should_suppress_assignability_diagnostic(source, target) {
875 return true;
876 }
877 if self.should_suppress_assignability_for_parse_recovery(source_idx, diag_idx) {
878 return true;
879 }
880 if self.is_assignable_to(source, target)
881 || self.should_skip_weak_union_error(source, target, source_idx)
882 {
883 return true;
884 }
885 self.error_type_not_assignable_generic_at(source, target, diag_idx);
886 false
887 }
888
889 pub(crate) fn check_argument_assignable_or_report(
894 &mut self,
895 source: TypeId,
896 target: TypeId,
897 arg_idx: NodeIndex,
898 ) -> bool {
899 if self.should_suppress_assignability_diagnostic(source, target) {
900 return true;
901 }
902 if self.should_suppress_assignability_for_parse_recovery(arg_idx, arg_idx) {
903 return true;
904 }
905 if self.is_assignable_to(source, target) {
906 return true;
907 }
908 if self.should_skip_weak_union_error(source, target, arg_idx) {
909 return true;
910 }
911 self.error_argument_not_assignable_at(source, target, arg_idx);
912 false
913 }
914
915 pub(crate) fn should_report_assignability_mismatch(
920 &mut self,
921 source: TypeId,
922 target: TypeId,
923 source_idx: NodeIndex,
924 ) -> bool {
925 if self.should_suppress_assignability_diagnostic(source, target) {
926 return false;
927 }
928 if self.should_suppress_assignability_for_parse_recovery(source_idx, source_idx) {
929 return false;
930 }
931 !self.is_assignable_to(source, target)
932 && !self.should_skip_weak_union_error(source, target, source_idx)
933 }
934
935 pub(crate) fn should_report_assignability_mismatch_bivariant(
940 &mut self,
941 source: TypeId,
942 target: TypeId,
943 source_idx: NodeIndex,
944 ) -> bool {
945 if self.should_suppress_assignability_diagnostic(source, target) {
946 return false;
947 }
948 if self.should_suppress_assignability_for_parse_recovery(source_idx, source_idx) {
949 return false;
950 }
951 !self.is_assignable_to_bivariant(source, target)
952 && !self.should_skip_weak_union_error(source, target, source_idx)
953 }
954
955 pub(crate) fn are_mutually_assignable(&mut self, left: TypeId, right: TypeId) -> bool {
959 self.is_assignable_to(left, right) && self.is_assignable_to(right, left)
960 }
961
962 pub(crate) fn is_type_comparable_to(&mut self, source: TypeId, target: TypeId) -> bool {
971 if self.is_assignable_to(source, target) || self.is_assignable_to(target, source) {
973 return true;
974 }
975
976 use crate::query_boundaries::dispatch as query;
981
982 if let Some(members) = query::union_members(self.ctx.types, source) {
984 for member in &members {
985 if self.is_assignable_to(*member, target) || self.is_assignable_to(target, *member)
986 {
987 return true;
988 }
989 }
990 }
991
992 if let Some(members) = query::union_members(self.ctx.types, target) {
994 for member in &members {
995 if self.is_assignable_to(source, *member) || self.is_assignable_to(*member, source)
996 {
997 return true;
998 }
999 }
1000 }
1001
1002 false
1003 }
1004
1005 pub(crate) fn object_literal_has_excess_properties(
1009 &mut self,
1010 source: TypeId,
1011 target: TypeId,
1012 _source_idx: NodeIndex,
1013 ) -> bool {
1014 use tsz_solver::relations::freshness;
1015 if !freshness::is_fresh_object_type(self.ctx.types, source) {
1017 return false;
1018 }
1019
1020 let Some(source_shape) = object_shape_for_type(self.ctx.types, source) else {
1021 return false;
1022 };
1023
1024 let source_props = source_shape.properties.as_slice();
1025 if source_props.is_empty() {
1026 return false;
1027 }
1028
1029 let resolved_target = self.resolve_type_for_property_access(target);
1030
1031 match classify_for_excess_properties(self.ctx.types, resolved_target) {
1032 ExcessPropertiesKind::Object(shape_id) => {
1033 let target_shape = self.ctx.types.object_shape(shape_id);
1034 let target_props = target_shape.properties.as_slice();
1035
1036 if target_props.is_empty() {
1037 return false;
1038 }
1039
1040 if target_shape.string_index.is_some() || target_shape.number_index.is_some() {
1041 return false;
1042 }
1043
1044 source_props
1045 .iter()
1046 .any(|source_prop| !target_props.iter().any(|p| p.name == source_prop.name))
1047 }
1048 ExcessPropertiesKind::ObjectWithIndex(_shape_id) => false,
1049 ExcessPropertiesKind::Union(members) => {
1050 let mut target_shapes = Vec::new();
1051 let mut matched_shapes = Vec::new();
1052
1053 for member in members {
1054 let resolved_member = self.resolve_type_for_property_access(member);
1055 let Some(shape) = object_shape_for_type(self.ctx.types, resolved_member) else {
1056 if tsz_solver::type_queries::is_type_parameter_like(
1060 self.ctx.types,
1061 resolved_member,
1062 ) || resolved_member == TypeId::OBJECT
1063 {
1064 return false;
1065 }
1066 continue;
1067 };
1068
1069 if shape.properties.is_empty()
1070 || shape.string_index.is_some()
1071 || shape.number_index.is_some()
1072 {
1073 return false;
1074 }
1075
1076 target_shapes.push(shape.clone());
1077
1078 if self.is_subtype_of(source, resolved_member) {
1079 matched_shapes.push(shape);
1080 }
1081 }
1082
1083 if target_shapes.is_empty() {
1084 return false;
1085 }
1086
1087 let effective_shapes = if matched_shapes.is_empty() {
1088 target_shapes
1089 } else {
1090 matched_shapes
1091 };
1092
1093 source_props.iter().any(|source_prop| {
1094 !effective_shapes.iter().any(|shape| {
1095 shape
1096 .properties
1097 .iter()
1098 .any(|prop| prop.name == source_prop.name)
1099 })
1100 })
1101 }
1102 ExcessPropertiesKind::Intersection(members) => {
1103 let mut target_shapes = Vec::new();
1104
1105 for member in members {
1106 let resolved_member = self.resolve_type_for_property_access(member);
1107 let Some(shape) = object_shape_for_type(self.ctx.types, resolved_member) else {
1108 continue;
1109 };
1110
1111 if shape.string_index.is_some() || shape.number_index.is_some() {
1112 return false;
1113 }
1114
1115 target_shapes.push(shape);
1116 }
1117
1118 if target_shapes.is_empty() {
1119 return false;
1120 }
1121
1122 source_props.iter().any(|source_prop| {
1123 !target_shapes.iter().any(|shape| {
1124 shape
1125 .properties
1126 .iter()
1127 .any(|prop| prop.name == source_prop.name)
1128 })
1129 })
1130 }
1131 ExcessPropertiesKind::NotObject => false,
1132 }
1133 }
1134
1135 pub(crate) fn analyze_assignability_failure(
1136 &mut self,
1137 source: TypeId,
1138 target: TypeId,
1139 ) -> crate::query_boundaries::assignability::AssignabilityFailureAnalysis {
1140 let overrides = CheckerOverrideProvider::new(self, None);
1144 let inputs = AssignabilityQueryInputs {
1145 db: self.ctx.types,
1146 resolver: &self.ctx,
1147 source,
1148 target,
1149 flags: self.ctx.pack_relation_flags(),
1150 inheritance_graph: &self.ctx.inheritance_graph,
1151 sound_mode: self.ctx.sound_mode(),
1152 };
1153 let gate = check_assignable_gate_with_overrides(&inputs, &overrides, Some(&self.ctx), true);
1154 if gate.related {
1155 return crate::query_boundaries::assignability::AssignabilityFailureAnalysis {
1156 weak_union_violation: false,
1157 failure_reason: None,
1158 };
1159 }
1160 gate.analysis.unwrap_or(
1161 crate::query_boundaries::assignability::AssignabilityFailureAnalysis {
1162 weak_union_violation: false,
1163 failure_reason: None,
1164 },
1165 )
1166 }
1167
1168 pub(crate) fn is_weak_union_violation(&mut self, source: TypeId, target: TypeId) -> bool {
1169 self.analyze_assignability_failure(source, target)
1170 .weak_union_violation
1171 }
1172}