Skip to main content

tsz_solver/relations/
subtype.rs

1//! Structural subtype checking.
2//!
3//! This module implements the core logic engine for TypeScript's structural
4//! subtyping. It uses coinductive semantics to handle recursive types.
5//!
6//! Key features:
7//! - O(1) equality check via `TypeId` comparison
8//! - Cycle detection for recursive types (coinductive)
9//! - Set-theoretic operations for unions and intersections
10//! - `TypeResolver` trait for lazy symbol resolution
11//! - Tracer pattern for zero-cost diagnostic abstraction
12
13use crate::AssignabilityChecker;
14use crate::TypeDatabase;
15use crate::caches::db::QueryDatabase;
16use crate::def::DefId;
17use crate::diagnostics::{DynSubtypeTracer, SubtypeFailureReason};
18#[cfg(test)]
19use crate::types::*;
20use crate::types::{
21    IntrinsicKind, LiteralValue, ObjectFlags, ObjectShape, SymbolRef, TypeData, TypeId, TypeListId,
22};
23use crate::visitor::{
24    TypeVisitor, application_id, array_element_type, callable_shape_id, conditional_type_id,
25    enum_components, function_shape_id, intersection_list_id, intrinsic_kind, is_this_type,
26    keyof_inner_type, lazy_def_id, literal_value, mapped_type_id, object_shape_id,
27    object_with_index_shape_id, readonly_inner_type, ref_symbol, template_literal_id,
28    tuple_list_id, type_param_info, type_query_symbol, union_list_id, unique_symbol_ref,
29};
30use rustc_hash::{FxHashMap, FxHashSet};
31use tsz_common::limits;
32
33/// Maximum recursion depth for subtype checking.
34/// This prevents OOM/stack overflow from infinitely expanding recursive types.
35/// Examples: `interface AA<T extends AA<T>>`, `interface List<T> { next: List<T> }`
36pub(crate) const MAX_SUBTYPE_DEPTH: u32 = limits::MAX_SUBTYPE_DEPTH;
37pub(crate) const INTERSECTION_OBJECT_FAST_PATH_THRESHOLD: usize = 8;
38
39/// Result of a subtype check
40#[derive(Copy, Clone, Debug, PartialEq, Eq)]
41pub enum SubtypeResult {
42    /// The relationship is definitely true
43    True,
44    /// The relationship is definitely false
45    False,
46    /// We're in a valid cycle (coinductive recursion)
47    ///
48    /// This represents finite/cyclic recursion like `interface List { next: List }`.
49    /// The type graph forms a closed loop, which is valid in TypeScript.
50    CycleDetected,
51    /// We've exceeded the recursion depth limit
52    ///
53    /// This represents expansive recursion that grows indefinitely like
54    /// `type T<X> = T<Box<X>>`. TypeScript rejects these as "excessively deep".
55    ///
56    /// This is treated as `false` for soundness - if we can't prove subtyping within
57    /// reasonable limits, we reject the relationship rather than accepting unsoundly.
58    DepthExceeded,
59}
60
61impl SubtypeResult {
62    pub const fn is_true(self) -> bool {
63        matches!(self, Self::True | Self::CycleDetected)
64    }
65
66    pub const fn is_false(self) -> bool {
67        matches!(self, Self::False)
68    }
69}
70
71/// Returns true for unit types where `source != target` implies disjointness.
72///
73/// This intentionally excludes:
74/// - null/undefined/void/never (special-cased assignability semantics)
75/// - Tuples (labeled tuples like [a: 1] vs [b: 1] are compatible despite different `TypeIds`)
76///
77/// Only safe for primitives where identity implies structural equality.
78pub(crate) fn is_disjoint_unit_type(types: &dyn TypeDatabase, ty: TypeId) -> bool {
79    match types.lookup(ty) {
80        Some(TypeData::Literal(_) | TypeData::UniqueSymbol(_)) => true,
81        // Note: Tuples removed to avoid labeled tuple bug
82        // TypeScript treats [a: 1] and [b: 1] as compatible even though they have different TypeIds
83        _ => false,
84    }
85}
86
87/// Controls how `any` is treated during subtype checks.
88#[derive(Copy, Clone, Debug, PartialEq, Eq)]
89pub enum AnyPropagationMode {
90    /// `any` is treated as top/bottom everywhere (TypeScript default).
91    All,
92    /// `any` is treated as top/bottom only at the top-level comparison.
93    TopLevelOnly,
94}
95
96impl AnyPropagationMode {
97    #[inline]
98    pub(crate) const fn allows_any_at_depth(self, depth: u32) -> bool {
99        match self {
100            Self::All => true,
101            Self::TopLevelOnly => depth == 0,
102        }
103    }
104}
105
106// TypeResolver, NoopResolver, and TypeEnvironment are defined in type_resolver.rs
107pub use crate::type_resolver::{NoopResolver, TypeEnvironment, TypeResolver};
108
109// SubtypeVisitor is defined in subtype_visitor.rs
110pub use crate::relations::subtype_visitor::SubtypeVisitor;
111
112/// Subtype checking context.
113/// Maintains the "seen" set for cycle detection.
114pub struct SubtypeChecker<'a, R: TypeResolver = NoopResolver> {
115    pub(crate) interner: &'a dyn TypeDatabase,
116    /// Optional query database for Salsa-backed memoization.
117    /// When set, routes `evaluate_type` and `is_subtype_of` through Salsa.
118    pub(crate) query_db: Option<&'a dyn QueryDatabase>,
119    pub(crate) resolver: &'a R,
120    /// Unified recursion guard for TypeId-pair cycle detection, depth, and iteration limits.
121    pub(crate) guard: crate::recursion::RecursionGuard<(TypeId, TypeId)>,
122    /// Active `SymbolRef` pairs being checked (for DefId-level cycle detection)
123    /// This catches cycles in Ref types before they're resolved, preventing
124    /// infinite expansion of recursive type aliases and interfaces.
125    pub(crate) seen_refs: FxHashSet<(SymbolRef, SymbolRef)>,
126    /// Unified recursion guard for DefId-pair cycle detection.
127    /// Catches cycles in Lazy(DefId) types before they're resolved.
128    pub(crate) def_guard: crate::recursion::RecursionGuard<(DefId, DefId)>,
129    /// Symbol-pair visiting set for Object-level cycle detection.
130    /// Catches cycles when comparing evaluated Object types with symbols
131    /// (e.g., `Promise<X>` vs `PromiseLike<Y>`) where `DefId` information is lost
132    /// after type evaluation. Without this, recursive interfaces like `Promise`
133    /// cause infinite expansion when comparing `then` method return types.
134    sym_visiting: FxHashSet<(tsz_binder::SymbolId, tsz_binder::SymbolId)>,
135    /// Whether to use strict function types (contravariant parameters).
136    /// Default: true (sound, correct behavior)
137    pub strict_function_types: bool,
138    /// Whether to allow any return type when the target return is void.
139    pub allow_void_return: bool,
140    /// Whether rest parameters of any/unknown should be treated as bivariant.
141    /// See <https://github.com/microsoft/TypeScript/issues/20007>.
142    pub allow_bivariant_rest: bool,
143    /// When true, skip the `evaluate_type()` call in `check_subtype`.
144    /// This prevents infinite recursion when `TypeEvaluator` calls `SubtypeChecker`
145    /// for simplification, since `TypeEvaluator` has already evaluated the types.
146    pub bypass_evaluation: bool,
147    /// Maximum recursion depth for subtype checking.
148    /// Used by `TypeEvaluator` simplification to prevent stack overflow.
149    /// Default: `MAX_SUBTYPE_DEPTH` (100)
150    pub max_depth: u32,
151    /// Whether required parameter count mismatches are allowed for bivariant methods.
152    pub allow_bivariant_param_count: bool,
153    /// Whether optional properties are exact (exclude implicit `undefined`).
154    /// Default: false (legacy TS behavior).
155    pub exact_optional_property_types: bool,
156    /// Whether null/undefined are treated as separate types.
157    /// Default: true (strict null checks).
158    pub strict_null_checks: bool,
159    /// Whether indexed access includes `undefined`.
160    /// Default: false (legacy TS behavior).
161    pub no_unchecked_indexed_access: bool,
162    // When true, disables method bivariance (methods use contravariance).
163    // Default: false (methods are bivariant in TypeScript for compatibility).
164    pub disable_method_bivariance: bool,
165    /// Optional inheritance graph for O(1) nominal class subtype checking.
166    /// When provided, enables fast nominal checks for class inheritance.
167    pub inheritance_graph: Option<&'a crate::classes::inheritance::InheritanceGraph>,
168    /// Optional callback to check if a symbol is a class (for nominal subtyping).
169    /// Returns true if the symbol has the CLASS flag set.
170    pub is_class_symbol: Option<&'a dyn Fn(SymbolRef) -> bool>,
171    /// Controls how `any` is treated during subtype checks.
172    pub any_propagation: AnyPropagationMode,
173    /// Cache for `evaluate_type` results within this `SubtypeChecker`'s lifetime.
174    /// This prevents O(n²) behavior when the same type (e.g., a large union) is
175    /// evaluated multiple times across different subtype checks.
176    /// Key is (`TypeId`, `no_unchecked_indexed_access`) since that flag affects evaluation.
177    pub(crate) eval_cache: FxHashMap<(TypeId, bool), TypeId>,
178    /// Optional tracer for collecting subtype failure diagnostics.
179    /// When `Some`, enables detailed failure reason collection for error messages.
180    /// When `None`, disables tracing for maximum performance (default).
181    pub tracer: Option<&'a mut dyn DynSubtypeTracer>,
182}
183
184impl<'a> SubtypeChecker<'a, NoopResolver> {
185    /// Create a new `SubtypeChecker` without a resolver (basic mode).
186    pub fn new(interner: &'a dyn TypeDatabase) -> Self {
187        static NOOP: NoopResolver = NoopResolver;
188        SubtypeChecker {
189            interner,
190            query_db: None,
191            resolver: &NOOP,
192            guard: crate::recursion::RecursionGuard::with_profile(
193                crate::recursion::RecursionProfile::SubtypeCheck,
194            ),
195            seen_refs: FxHashSet::default(),
196            def_guard: crate::recursion::RecursionGuard::with_profile(
197                crate::recursion::RecursionProfile::SubtypeCheck,
198            ),
199            sym_visiting: FxHashSet::default(),
200            strict_function_types: true, // Default to strict (sound) behavior
201            allow_void_return: false,
202            allow_bivariant_rest: false,
203            allow_bivariant_param_count: false,
204            exact_optional_property_types: false,
205            strict_null_checks: true,
206            no_unchecked_indexed_access: false,
207            disable_method_bivariance: false,
208            inheritance_graph: None,
209            is_class_symbol: None,
210            any_propagation: AnyPropagationMode::All,
211            bypass_evaluation: false,
212            max_depth: MAX_SUBTYPE_DEPTH,
213            eval_cache: FxHashMap::default(),
214            tracer: None,
215        }
216    }
217}
218
219impl<'a, R: TypeResolver> SubtypeChecker<'a, R> {
220    /// Create a new `SubtypeChecker` with a custom resolver.
221    pub fn with_resolver(interner: &'a dyn TypeDatabase, resolver: &'a R) -> Self {
222        SubtypeChecker {
223            interner,
224            query_db: None,
225            resolver,
226            guard: crate::recursion::RecursionGuard::with_profile(
227                crate::recursion::RecursionProfile::SubtypeCheck,
228            ),
229            seen_refs: FxHashSet::default(),
230            def_guard: crate::recursion::RecursionGuard::with_profile(
231                crate::recursion::RecursionProfile::SubtypeCheck,
232            ),
233            sym_visiting: FxHashSet::default(),
234            strict_function_types: true,
235            allow_void_return: false,
236            allow_bivariant_rest: false,
237            allow_bivariant_param_count: false,
238            exact_optional_property_types: false,
239            strict_null_checks: true,
240            no_unchecked_indexed_access: false,
241            disable_method_bivariance: false,
242            inheritance_graph: None,
243            is_class_symbol: None,
244            any_propagation: AnyPropagationMode::All,
245            bypass_evaluation: false,
246            max_depth: MAX_SUBTYPE_DEPTH,
247            eval_cache: FxHashMap::default(),
248            tracer: None,
249        }
250    }
251
252    /// Set the inheritance graph for O(1) nominal class subtype checking.
253    pub const fn with_inheritance_graph(
254        mut self,
255        graph: &'a crate::classes::inheritance::InheritanceGraph,
256    ) -> Self {
257        self.inheritance_graph = Some(graph);
258        self
259    }
260
261    /// Set the callback to check if a symbol is a class.
262    pub fn with_class_check(mut self, check: &'a dyn Fn(SymbolRef) -> bool) -> Self {
263        self.is_class_symbol = Some(check);
264        self
265    }
266
267    /// Configure how `any` is treated during subtype checks.
268    pub const fn with_any_propagation_mode(mut self, mode: AnyPropagationMode) -> Self {
269        self.any_propagation = mode;
270        self
271    }
272
273    /// Set the tracer for collecting subtype failure diagnostics.
274    /// When set, enables detailed failure reason collection for error messages.
275    pub fn with_tracer(mut self, tracer: &'a mut dyn DynSubtypeTracer) -> Self {
276        self.tracer = Some(tracer);
277        self
278    }
279
280    /// Set the query database for Salsa-backed memoization.
281    /// When set, routes `evaluate_type` and `is_subtype_of` through Salsa.
282    pub fn with_query_db(mut self, db: &'a dyn QueryDatabase) -> Self {
283        self.query_db = Some(db);
284        self
285    }
286
287    /// Set whether strict null checks are enabled.
288    /// When false, null and undefined are assignable to any type.
289    pub const fn with_strict_null_checks(mut self, strict_null_checks: bool) -> Self {
290        self.strict_null_checks = strict_null_checks;
291        self
292    }
293
294    /// Reset per-check state so this checker can be reused for another subtype check.
295    ///
296    /// This clears cycle detection sets and counters while preserving configuration
297    /// (`strict_function_types`, `allow_void_return`, etc.) and borrowed references
298    /// (interner, resolver, `inheritance_graph`, etc.).
299    ///
300    /// Uses `.clear()` instead of re-allocating, so hash set memory is reused.
301    #[inline]
302    pub fn reset(&mut self) {
303        self.guard.reset();
304        self.seen_refs.clear();
305        self.def_guard.reset();
306        self.sym_visiting.clear();
307        self.eval_cache.clear();
308    }
309
310    /// Whether the recursion depth was exceeded during subtype checking.
311    pub const fn depth_exceeded(&self) -> bool {
312        self.guard.is_exceeded()
313    }
314
315    /// Apply compiler flags from a packed u16 bitmask.
316    ///
317    /// This unpacks the flags used by `RelationCacheKey` and applies them to the checker.
318    /// The bit layout matches the cache key definition in types.rs:
319    /// - bit 0: `strict_null_checks`
320    /// - bit 1: `strict_function_types`
321    /// - bit 2: `exact_optional_property_types`
322    /// - bit 3: `no_unchecked_indexed_access`
323    /// - bit 4: `disable_method_bivariance`
324    /// - bit 5: `allow_void_return`
325    /// - bit 6: `allow_bivariant_rest`
326    /// - bit 7: `allow_bivariant_param_count`
327    pub(crate) const fn apply_flags(mut self, flags: u16) -> Self {
328        self.strict_null_checks = (flags & (1 << 0)) != 0;
329        self.strict_function_types = (flags & (1 << 1)) != 0;
330        self.exact_optional_property_types = (flags & (1 << 2)) != 0;
331        self.no_unchecked_indexed_access = (flags & (1 << 3)) != 0;
332        self.disable_method_bivariance = (flags & (1 << 4)) != 0;
333        self.allow_void_return = (flags & (1 << 5)) != 0;
334        self.allow_bivariant_rest = (flags & (1 << 6)) != 0;
335        self.allow_bivariant_param_count = (flags & (1 << 7)) != 0;
336        self
337    }
338
339    pub(crate) fn resolve_ref_type(&self, type_id: TypeId) -> TypeId {
340        // Handle DefId-based Lazy types (new API)
341        if let Some(def_id) = lazy_def_id(self.interner, type_id) {
342            return self
343                .resolver
344                .resolve_lazy(def_id, self.interner)
345                .unwrap_or(type_id);
346        }
347
348        // Handle legacy SymbolRef-based types (old API)
349        if let Some(symbol) = ref_symbol(self.interner, type_id) {
350            self.resolver
351                .resolve_symbol_ref(symbol, self.interner)
352                .unwrap_or(type_id)
353        } else {
354            type_id
355        }
356    }
357
358    pub(crate) fn resolve_lazy_type(&self, type_id: TypeId) -> TypeId {
359        if let Some(def_id) = lazy_def_id(self.interner, type_id) {
360            self.resolver
361                .resolve_lazy(def_id, self.interner)
362                .unwrap_or(type_id)
363        } else {
364            type_id
365        }
366    }
367
368    /// Inner subtype check (after cycle detection and type evaluation)
369    pub(crate) fn check_subtype_inner(&mut self, source: TypeId, target: TypeId) -> SubtypeResult {
370        // Types are already evaluated in check_subtype, so no need to re-evaluate here
371
372        if !self.strict_null_checks && source.is_nullish() {
373            return SubtypeResult::True;
374        }
375
376        // Note: Canonicalization-based structural identity (Task #36) was previously
377        // called here as a "fast path", but it was actually SLOWER than the normal path
378        // because it allocated a fresh Canonicalizer per call (FxHashMap + Vecs) and
379        // triggered O(n²) union reduction via interner.union(). The existing QueryCache
380        // already provides O(1) memoization for repeated subtype checks.
381        // The Canonicalizer remains available for its intended purpose: detecting
382        // structural identity of recursive type aliases (graph isomorphism).
383        // See: are_types_structurally_identical() and isomorphism_tests.rs
384
385        // Note: Weak type checking is handled by CompatChecker (compat.rs:167-170).
386        // Removed redundant check here to avoid double-checking which caused false positives.
387
388        if let Some(shape) = self.apparent_primitive_shape_for_type(source) {
389            if let Some(t_shape_id) = object_shape_id(self.interner, target) {
390                let t_shape = self.interner.object_shape(t_shape_id);
391                return self.check_object_subtype(&shape, None, &t_shape);
392            }
393            if let Some(t_shape_id) = object_with_index_shape_id(self.interner, target) {
394                let t_shape = self.interner.object_shape(t_shape_id);
395                return self.check_object_with_index_subtype(&shape, None, &t_shape);
396            }
397        }
398
399        if let Some(source_cond_id) = conditional_type_id(self.interner, source) {
400            if let Some(target_cond_id) = conditional_type_id(self.interner, target) {
401                let source_cond = self.interner.conditional_type(source_cond_id);
402                let target_cond = self.interner.conditional_type(target_cond_id);
403                return self.check_conditional_subtype(source_cond.as_ref(), target_cond.as_ref());
404            }
405
406            let source_cond = self.interner.conditional_type(source_cond_id);
407            return self.conditional_branches_subtype(source_cond.as_ref(), target);
408        }
409
410        if let Some(target_cond_id) = conditional_type_id(self.interner, target) {
411            let target_cond = self.interner.conditional_type(target_cond_id);
412            return self.subtype_of_conditional_target(source, target_cond.as_ref());
413        }
414
415        // Note: Source union/intersection handling is consolidated as follows:
416        //
417        // 1. Source union: Kept here (not moved to visitor) because it must run BEFORE
418        //    the target union check. This order dependency is critical for correct
419        //    union-to-union semantics: Union(A,B) <: Union(C,D) means ALL members of
420        //    source must be subtypes of the target union (delegating to target union check).
421        //
422        // 2. Source intersection: Moved to visitor pattern (visit_intersection) which
423        //    handles both the "at least one member" check AND the property merging logic
424        //    for object targets. This removed ~50 lines of duplicate code.
425        //
426        // Source union check must run BEFORE target union check to handle union-to-union cases:
427        // Union(A, B) <: Union(C, D) means (A <: Union(C, D)) AND (B <: Union(C, D))
428        // This is different from the target union check which does: Source <: C OR Source <: D
429        if let Some(members) = union_list_id(self.interner, source) {
430            let member_list = self.interner.type_list(members);
431            for &member in member_list.iter() {
432                if !self.check_subtype(member, target).is_true() {
433                    // Trace: No union member matches target
434                    if let Some(tracer) = &mut self.tracer
435                        && !tracer.on_mismatch_dyn(SubtypeFailureReason::NoUnionMemberMatches {
436                            source_type: source,
437                            target_union_members: vec![target],
438                        })
439                    {
440                        return SubtypeResult::False;
441                    }
442                    return SubtypeResult::False;
443                }
444            }
445            return SubtypeResult::True;
446        }
447
448        if let Some(members) = union_list_id(self.interner, target) {
449            if keyof_inner_type(self.interner, source).is_some()
450                && self.is_keyof_subtype_of_string_number_symbol_union(members)
451            {
452                return SubtypeResult::True;
453            }
454
455            // Rule #7: Open Numeric Enums - number is assignable to unions containing numeric enums
456            if source == TypeId::NUMBER {
457                let member_list = self.interner.type_list(members);
458                for &member in member_list.iter() {
459                    let def_id = lazy_def_id(self.interner, member)
460                        .or_else(|| enum_components(self.interner, member).map(|(d, _)| d));
461                    if let Some(def_id) = def_id
462                        && self.resolver.is_numeric_enum(def_id)
463                    {
464                        return SubtypeResult::True;
465                    }
466                }
467            }
468
469            let member_list = self.interner.type_list(members);
470
471            // Fast path: TypeId equality pre-scan before expensive structural checks.
472            // If source has the same TypeId as any union member, it's trivially a subtype.
473            // This avoids O(n × cost) structural comparisons when the match is by identity.
474            for &member in member_list.iter() {
475                if source == member {
476                    return SubtypeResult::True;
477                }
478            }
479
480            for &member in member_list.iter() {
481                if self.check_subtype(source, member).is_true() {
482                    return SubtypeResult::True;
483                }
484            }
485
486            // Type parameter constraint check: if source is a type parameter with a constraint,
487            // check if its constraint is assignable to the entire target union.
488            // e.g., Bottom extends T | U should be assignable to T | U
489            if let Some(s_info) = type_param_info(self.interner, source)
490                && let Some(constraint) = s_info.constraint
491                && self.check_subtype(constraint, target).is_true()
492            {
493                return SubtypeResult::True;
494            }
495
496            // Distributive intersection factoring:
497            // S <: (A & S) | (B & S) is equivalent to S <: A | B
498            let s_arc;
499            let source_members: &[TypeId] =
500                if let Some(s_list) = intersection_list_id(self.interner, source) {
501                    s_arc = self.interner.type_list(s_list);
502                    &s_arc
503                } else {
504                    std::slice::from_ref(&source)
505                };
506
507            let mut factored_members = Vec::new();
508            let mut all_contain_source = true;
509            for &member in member_list.iter() {
510                let i_arc;
511                let i_list: &[TypeId] =
512                    if let Some(i_members) = intersection_list_id(self.interner, member) {
513                        i_arc = self.interner.type_list(i_members);
514                        &i_arc
515                    } else {
516                        std::slice::from_ref(&member)
517                    };
518
519                let mut contains_all = true;
520                for &s_m in source_members.iter() {
521                    if !i_list.contains(&s_m) {
522                        contains_all = false;
523                        break;
524                    }
525                }
526
527                if contains_all {
528                    let mut rem = Vec::new();
529                    for &i_m in i_list.iter() {
530                        if !source_members.contains(&i_m) {
531                            rem.push(i_m);
532                        }
533                    }
534                    factored_members.push(self.interner.intersection(rem));
535                } else {
536                    all_contain_source = false;
537                    break;
538                }
539            }
540
541            if all_contain_source && !factored_members.is_empty() {
542                let factored_target = self.interner.union(factored_members);
543                if self.check_subtype(source, factored_target).is_true() {
544                    return SubtypeResult::True;
545                }
546            }
547
548            // Discriminated union check: if the source has discriminant properties
549            // that distinguish between target union members, check each discriminant
550            // value against the matching target members with a narrowed source.
551            // See TypeScript's typeRelatedToDiscriminatedType.
552            if self
553                .type_related_to_discriminated_type(source, &member_list)
554                .is_true()
555            {
556                return SubtypeResult::True;
557            }
558
559            // Intersection source check: if source is an intersection, check if any
560            // member is assignable to the target union as a whole.
561            // e.g., (A & B) <: C | D if A <: C | D
562            if let Some(s_list) = intersection_list_id(self.interner, source) {
563                let s_member_list = self.interner.type_list(s_list);
564                for &s_member in s_member_list.iter() {
565                    if self.check_subtype(s_member, target).is_true() {
566                        return SubtypeResult::True;
567                    }
568                }
569            }
570
571            // Trace: Source is not a subtype of any union member
572            if let Some(tracer) = &mut self.tracer
573                && !tracer.on_mismatch_dyn(SubtypeFailureReason::NoUnionMemberMatches {
574                    source_type: source,
575                    target_union_members: member_list.iter().copied().collect(),
576                })
577            {
578                return SubtypeResult::False;
579            }
580            return SubtypeResult::False;
581        }
582
583        // Note: Source intersection check removed - handled by visitor pattern dispatch
584        // at the end of this function. The visitor includes the property merging logic.
585
586        if let Some(members) = intersection_list_id(self.interner, target) {
587            let member_list = self.interner.type_list(members);
588
589            // Keep diagnostic precision when collecting mismatch reasons via tracer.
590            if self.tracer.is_none()
591                && self.can_use_object_intersection_fast_path(&member_list)
592                && let Some(merged_target) = self.build_object_intersection_target(target)
593            {
594                return self.check_subtype(source, merged_target);
595            }
596
597            for &member in member_list.iter() {
598                if !self.check_subtype(source, member).is_true() {
599                    return SubtypeResult::False;
600                }
601            }
602            return SubtypeResult::True;
603        }
604
605        if let (Some(s_kind), Some(t_kind)) = (
606            intrinsic_kind(self.interner, source),
607            intrinsic_kind(self.interner, target),
608        ) {
609            return self.check_intrinsic_subtype(s_kind, t_kind);
610        }
611
612        // Type parameter checks BEFORE boxed primitive check
613        // Unconstrained type parameters should be handled before other checks
614        if let Some(s_info) = type_param_info(self.interner, source) {
615            return self.check_type_parameter_subtype(&s_info, target);
616        }
617
618        if let Some(_t_info) = type_param_info(self.interner, target) {
619            // Special case: T & SomeType <: T
620            // If source is an intersection containing the target type parameter,
621            // the intersection is a more specific version (excluding null/undefined)
622            // and is assignable. This handles the common pattern: T & {} <: T.
623            if let Some(members) = intersection_list_id(self.interner, source) {
624                let member_list = self.interner.type_list(members);
625                for &member in member_list.iter() {
626                    if member == target {
627                        return SubtypeResult::True;
628                    }
629                }
630            }
631
632            // A concrete type is never a subtype of an opaque type parameter.
633            // The type parameter T could be instantiated as any type satisfying its constraint,
634            // so we cannot guarantee that source <: T unless source is never/any (handled above).
635            //
636            // This is the correct TypeScript behavior:
637            // - "hello" is NOT assignable to T extends string (T could be "world")
638            // - { value: number } is NOT assignable to unconstrained T (T defaults to unknown)
639            //
640            // Note: When the type parameter is the SOURCE (e.g., T <: string), we check
641            // against its constraint. But as TARGET, we return False.
642
643            // Trace: Concrete type not assignable to type parameter
644            if let Some(tracer) = &mut self.tracer
645                && !tracer.on_mismatch_dyn(SubtypeFailureReason::TypeMismatch {
646                    source_type: source,
647                    target_type: target,
648                })
649            {
650                return SubtypeResult::False;
651            }
652            return SubtypeResult::False;
653        }
654
655        if let Some(s_kind) = intrinsic_kind(self.interner, source) {
656            if self.is_boxed_primitive_subtype(s_kind, target) {
657                return SubtypeResult::True;
658            }
659            // `object` keyword is structurally equivalent to `{}` (empty object).
660            // It's assignable to any object type where all properties are optional,
661            // since no required properties need to be satisfied.
662            if s_kind == IntrinsicKind::Object {
663                let target_shape = object_shape_id(self.interner, target)
664                    .or_else(|| object_with_index_shape_id(self.interner, target));
665                if let Some(t_shape_id) = target_shape {
666                    let t_shape = self.interner.object_shape(t_shape_id);
667                    if t_shape.properties.iter().all(|p| p.optional) {
668                        return SubtypeResult::True;
669                    }
670                }
671            }
672            // Trace: Intrinsic type mismatch (boxed primitive check failed)
673            if let Some(tracer) = &mut self.tracer
674                && !tracer.on_mismatch_dyn(SubtypeFailureReason::TypeMismatch {
675                    source_type: source,
676                    target_type: target,
677                })
678            {
679                return SubtypeResult::False;
680            }
681            return SubtypeResult::False;
682        }
683
684        if let (Some(lit), Some(t_kind)) = (
685            literal_value(self.interner, source),
686            intrinsic_kind(self.interner, target),
687        ) {
688            return self.check_literal_to_intrinsic(&lit, t_kind);
689        }
690
691        if let (Some(s_lit), Some(t_lit)) = (
692            literal_value(self.interner, source),
693            literal_value(self.interner, target),
694        ) {
695            if s_lit == t_lit {
696                return SubtypeResult::True;
697            }
698            // Trace: Literal type mismatch
699            if let Some(tracer) = &mut self.tracer
700                && !tracer.on_mismatch_dyn(SubtypeFailureReason::LiteralTypeMismatch {
701                    source_type: source,
702                    target_type: target,
703                })
704            {
705                return SubtypeResult::False;
706            }
707            return SubtypeResult::False;
708        }
709
710        if let (Some(LiteralValue::String(s_lit)), Some(t_spans)) = (
711            literal_value(self.interner, source),
712            template_literal_id(self.interner, target),
713        ) {
714            return self.check_literal_matches_template_literal(s_lit, t_spans);
715        }
716
717        if intrinsic_kind(self.interner, target) == Some(IntrinsicKind::Object) {
718            if self.is_object_keyword_type(source) {
719                return SubtypeResult::True;
720            }
721            // Trace: Source is not object-compatible
722            if let Some(tracer) = &mut self.tracer
723                && !tracer.on_mismatch_dyn(SubtypeFailureReason::TypeMismatch {
724                    source_type: source,
725                    target_type: target,
726                })
727            {
728                return SubtypeResult::False;
729            }
730            return SubtypeResult::False;
731        }
732
733        // Check if target is the Function intrinsic (TypeId::FUNCTION) or the
734        // Function interface from lib.d.ts. We check three ways:
735        // 1. Target is the Function intrinsic (TypeId::FUNCTION)
736        // 2. Target matches the registered boxed Function TypeId
737        // 3. Target was resolved from a Lazy(DefId) whose DefId is a known Function DefId
738        //    (handles the case where get_type_of_symbol and resolve_lib_type_by_name
739        //    produce different TypeIds for the same Function interface)
740        let is_function_target = intrinsic_kind(self.interner, target)
741            == Some(IntrinsicKind::Function)
742            || self
743                .resolver
744                .is_boxed_type_id(target, IntrinsicKind::Function)
745            || self
746                .resolver
747                .get_boxed_type(IntrinsicKind::Function)
748                .is_some_and(|boxed| boxed == target)
749            || lazy_def_id(self.interner, target).is_some_and(|def_id| {
750                self.resolver
751                    .is_boxed_def_id(def_id, IntrinsicKind::Function)
752            });
753        if is_function_target {
754            if self.is_callable_type(source) {
755                return SubtypeResult::True;
756            }
757            // Trace: Source is not function-compatible
758            if let Some(tracer) = &mut self.tracer
759                && !tracer.on_mismatch_dyn(SubtypeFailureReason::TypeMismatch {
760                    source_type: source,
761                    target_type: target,
762                })
763            {
764                return SubtypeResult::False;
765            }
766            return SubtypeResult::False;
767        }
768
769        // Check if target is the global `Object` interface from lib.d.ts.
770        // This is a separate path from intrinsic `object`:
771        // - `object` (lowercase) includes callable values.
772        // - `Object` (capitalized interface) should follow TS structural rules and
773        //   exclude bare callable types from primitive-style object assignability.
774        let is_global_object_target = self
775            .resolver
776            .is_boxed_type_id(target, IntrinsicKind::Object)
777            || self
778                .resolver
779                .get_boxed_type(IntrinsicKind::Object)
780                .is_some_and(|boxed| boxed == target)
781            || lazy_def_id(self.interner, target)
782                .is_some_and(|t_def| self.resolver.is_boxed_def_id(t_def, IntrinsicKind::Object));
783        if is_global_object_target {
784            let source_eval = self.evaluate_type(source);
785            if self.is_global_object_interface_type(source_eval) {
786                return SubtypeResult::True;
787            }
788
789            if let Some(tracer) = &mut self.tracer
790                && !tracer.on_mismatch_dyn(SubtypeFailureReason::TypeMismatch {
791                    source_type: source,
792                    target_type: target,
793                })
794            {
795                return SubtypeResult::False;
796            }
797            return SubtypeResult::False;
798        }
799
800        if let (Some(s_elem), Some(t_elem)) = (
801            array_element_type(self.interner, source),
802            array_element_type(self.interner, target),
803        ) {
804            return self.check_subtype(s_elem, t_elem);
805        }
806
807        if let (Some(s_elems), Some(t_elems)) = (
808            tuple_list_id(self.interner, source),
809            tuple_list_id(self.interner, target),
810        ) {
811            // OPTIMIZATION: Unit-tuple disjointness fast-path (O(1) cached lookup)
812            // Two different identity-comparable tuples are guaranteed disjoint.
813            // Since we already checked source == target at the top and returned True,
814            // reaching here means source != target. If both are identity-comparable, they're disjoint.
815            // This avoids O(N) structural recursion for each comparison in BCT's O(N²) loop.
816            if self.interner.is_identity_comparable_type(source)
817                && self.interner.is_identity_comparable_type(target)
818            {
819                return SubtypeResult::False;
820            }
821            let s_elems = self.interner.tuple_list(s_elems);
822            let t_elems = self.interner.tuple_list(t_elems);
823            return self.check_tuple_subtype(&s_elems, &t_elems);
824        }
825
826        if let (Some(s_elems), Some(t_elem)) = (
827            tuple_list_id(self.interner, source),
828            array_element_type(self.interner, target),
829        ) {
830            return self.check_tuple_to_array_subtype(s_elems, t_elem);
831        }
832
833        if let (Some(s_elem), Some(t_elems)) = (
834            array_element_type(self.interner, source),
835            tuple_list_id(self.interner, target),
836        ) {
837            let t_elems = self.interner.tuple_list(t_elems);
838            return self.check_array_to_tuple_subtype(s_elem, &t_elems);
839        }
840
841        if let (Some(s_shape_id), Some(t_shape_id)) = (
842            object_shape_id(self.interner, source),
843            object_shape_id(self.interner, target),
844        ) {
845            let s_shape = self.interner.object_shape(s_shape_id);
846            let t_shape = self.interner.object_shape(t_shape_id);
847
848            // Symbol-level cycle detection for recursive interface types.
849            // When both objects have symbols (e.g., Promise<X> vs PromiseLike<Y>),
850            // check if we're already comparing objects with the same symbol pair.
851            // This catches cycles where type evaluation loses DefId identity:
852            // Promise<never> evaluates to Object(51) which has no DefId, but its
853            // `then` method returns Promise<TResult> which, after instantiation and
854            // evaluation, produces another Object with the same Promise symbol.
855            if let (Some(s_sym), Some(t_sym)) = (s_shape.symbol, t_shape.symbol)
856                && s_sym != t_sym
857            {
858                let sym_pair = (s_sym, t_sym);
859                if !self.sym_visiting.insert(sym_pair) {
860                    // Already visiting this symbol pair — coinductive cycle
861                    return SubtypeResult::CycleDetected;
862                }
863                let result = self.check_object_subtype(&s_shape, Some(s_shape_id), &t_shape);
864                self.sym_visiting.remove(&sym_pair);
865                return result;
866            }
867
868            return self.check_object_subtype(&s_shape, Some(s_shape_id), &t_shape);
869        }
870
871        if let (Some(s_shape_id), Some(t_shape_id)) = (
872            object_with_index_shape_id(self.interner, source),
873            object_with_index_shape_id(self.interner, target),
874        ) {
875            let s_shape = self.interner.object_shape(s_shape_id);
876            let t_shape = self.interner.object_shape(t_shape_id);
877            return self.check_object_with_index_subtype(&s_shape, Some(s_shape_id), &t_shape);
878        }
879
880        if let (Some(s_shape_id), Some(t_shape_id)) = (
881            object_with_index_shape_id(self.interner, source),
882            object_shape_id(self.interner, target),
883        ) {
884            let s_shape = self.interner.object_shape(s_shape_id);
885            let t_shape = self.interner.object_shape(t_shape_id);
886            return self.check_object_with_index_to_object(
887                &s_shape,
888                s_shape_id,
889                &t_shape.properties,
890            );
891        }
892
893        if let (Some(s_shape_id), Some(t_shape_id)) = (
894            object_shape_id(self.interner, source),
895            object_with_index_shape_id(self.interner, target),
896        ) {
897            let s_shape = self.interner.object_shape(s_shape_id);
898            let t_shape = self.interner.object_shape(t_shape_id);
899            return self.check_object_to_indexed(&s_shape.properties, Some(s_shape_id), &t_shape);
900        }
901
902        if let (Some(s_fn_id), Some(t_fn_id)) = (
903            function_shape_id(self.interner, source),
904            function_shape_id(self.interner, target),
905        ) {
906            let s_fn = self.interner.function_shape(s_fn_id);
907            let t_fn = self.interner.function_shape(t_fn_id);
908            return self.check_function_subtype(&s_fn, &t_fn);
909        }
910
911        // Compatibility bridge: function-like values are assignable to interfaces
912        // that only require Function members like `call`/`apply`.
913        // This aligns with tsc behavior for:
914        //   interface Callable { call(blah: any): any }
915        //   const x: Callable = () => {}
916        let source_function_like = function_shape_id(self.interner, source).is_some()
917            || callable_shape_id(self.interner, source).is_some_and(|sid| {
918                let shape = self.interner.callable_shape(sid);
919                !shape.call_signatures.is_empty()
920            })
921            || source == TypeId::FUNCTION;
922        if source_function_like {
923            if let Some(t_callable_id) = callable_shape_id(self.interner, target) {
924                let t_shape = self.interner.callable_shape(t_callable_id);
925                if t_shape.call_signatures.is_empty() && t_shape.construct_signatures.is_empty() {
926                    let required_props: Vec<_> =
927                        t_shape.properties.iter().filter(|p| !p.optional).collect();
928                    if required_props.len() == 1 {
929                        let name = self.interner.resolve_atom(required_props[0].name);
930                        if name == "call" || name == "apply" {
931                            return SubtypeResult::True;
932                        }
933                    }
934                }
935            }
936            if let Some(t_shape_id) = object_shape_id(self.interner, target)
937                .or_else(|| object_with_index_shape_id(self.interner, target))
938            {
939                let t_shape = self.interner.object_shape(t_shape_id);
940                let required_props: Vec<_> =
941                    t_shape.properties.iter().filter(|p| !p.optional).collect();
942                if required_props.len() == 1 {
943                    let name = self.interner.resolve_atom(required_props[0].name);
944                    if name == "call" || name == "apply" {
945                        return SubtypeResult::True;
946                    }
947                }
948            }
949        }
950
951        if let (Some(s_callable_id), Some(t_callable_id)) = (
952            callable_shape_id(self.interner, source),
953            callable_shape_id(self.interner, target),
954        ) {
955            let s_callable = self.interner.callable_shape(s_callable_id);
956            let t_callable = self.interner.callable_shape(t_callable_id);
957            return self.check_callable_subtype(&s_callable, &t_callable);
958        }
959
960        if let (Some(s_fn_id), Some(t_callable_id)) = (
961            function_shape_id(self.interner, source),
962            callable_shape_id(self.interner, target),
963        ) {
964            return self.check_function_to_callable_subtype(s_fn_id, t_callable_id);
965        }
966
967        if let (Some(s_callable_id), Some(t_fn_id)) = (
968            callable_shape_id(self.interner, source),
969            function_shape_id(self.interner, target),
970        ) {
971            return self.check_callable_to_function_subtype(s_callable_id, t_fn_id);
972        }
973
974        if let (Some(s_app_id), Some(t_app_id)) = (
975            application_id(self.interner, source),
976            application_id(self.interner, target),
977        ) {
978            return self.check_application_to_application_subtype(s_app_id, t_app_id);
979        }
980
981        // When both source and target are applications, try mapped-to-mapped
982        // comparison before falling through to one-sided expansion. This handles
983        // cases like Readonly<T> <: Partial<T> where both resolve to mapped types
984        // over a generic type parameter that can't be concretely expanded.
985        if let (Some(s_app_id), Some(t_app_id)) = (
986            application_id(self.interner, source),
987            application_id(self.interner, target),
988        ) {
989            let result = self.check_application_to_application(source, target, s_app_id, t_app_id);
990            if result != SubtypeResult::False {
991                return result;
992            }
993            // Fall through to one-sided expansion
994        }
995
996        if let Some(app_id) = application_id(self.interner, source) {
997            return self.check_application_expansion_target(source, target, app_id);
998        }
999
1000        if let Some(app_id) = application_id(self.interner, target) {
1001            return self.check_source_to_application_expansion(source, target, app_id);
1002        }
1003
1004        // Check mapped-to-mapped structural comparison (for raw mapped types).
1005        if let (Some(source_mapped_id), Some(target_mapped_id)) = (
1006            mapped_type_id(self.interner, source),
1007            mapped_type_id(self.interner, target),
1008        ) {
1009            let result =
1010                self.check_mapped_to_mapped(source, target, source_mapped_id, target_mapped_id);
1011            if result != SubtypeResult::False {
1012                return result;
1013            }
1014        }
1015
1016        if let Some(mapped_id) = mapped_type_id(self.interner, source) {
1017            return self.check_mapped_expansion_target(source, target, mapped_id);
1018        }
1019
1020        if let Some(mapped_id) = mapped_type_id(self.interner, target) {
1021            return self.check_source_to_mapped_expansion(source, target, mapped_id);
1022        }
1023
1024        // =======================================================================
1025        // ENUM TYPE CHECKING (Nominal Identity)
1026        // =======================================================================
1027        // Enums are nominal types - two different enums with the same member types
1028        // are NOT compatible. Enum(DefId, MemberType) preserves both:
1029        // - DefId: For nominal identity (E1 != E2)
1030        // - MemberType: For structural assignability to primitives (E1 <: number)
1031        // =======================================================================
1032
1033        if let (Some((s_def_id, _s_members)), Some((t_def_id, _t_members))) = (
1034            enum_components(self.interner, source),
1035            enum_components(self.interner, target),
1036        ) {
1037            // Enum to Enum: Nominal check - DefIds must match
1038            if s_def_id == t_def_id {
1039                return SubtypeResult::True;
1040            }
1041
1042            // Check for member-to-parent relationship (e.g., E.A -> E)
1043            // If source is a member of the target enum, it is a subtype
1044            if self.resolver.get_enum_parent_def_id(s_def_id) == Some(t_def_id) {
1045                // Source is a member of target enum
1046                // Only allow if target is the full enum type (not a different member)
1047                if self.resolver.is_enum_type(target, self.interner) {
1048                    return SubtypeResult::True;
1049                }
1050            }
1051
1052            // Different enums are NOT compatible (nominal typing)
1053            // Trace: Enum nominal mismatch
1054            if let Some(tracer) = &mut self.tracer
1055                && !tracer.on_mismatch_dyn(SubtypeFailureReason::TypeMismatch {
1056                    source_type: source,
1057                    target_type: target,
1058                })
1059            {
1060                return SubtypeResult::False;
1061            }
1062            return SubtypeResult::False;
1063        }
1064
1065        // Source is Enum, Target is not - check structural member type
1066        if let Some((_s_def_id, s_members)) = enum_components(self.interner, source) {
1067            return self.check_subtype(s_members, target);
1068        }
1069
1070        // Target is Enum, Source is not - check Rule #7 first, then structural member type
1071        if let Some((t_def_id, t_members)) = enum_components(self.interner, target) {
1072            // Rule #7: number is assignable to numeric enums
1073            if source == TypeId::NUMBER && self.resolver.is_numeric_enum(t_def_id) {
1074                return SubtypeResult::True;
1075            }
1076            // For number literals, fall through to structural check against t_members
1077            // so that only actual enum member values (e.g., 0|1|2) are accepted
1078            return self.check_subtype(source, t_members);
1079        }
1080
1081        // =======================================================================
1082        // PHASE 3.2: PRIORITIZE DefId (Lazy) OVER SymbolRef (Ref)
1083        // =======================================================================
1084        // We now check Lazy(DefId) types before Ref(SymbolRef) types to establish
1085        // DefId as the primary type identity system. The InheritanceGraph bridge
1086        // enables Lazy types to use O(1) nominal subtype checking.
1087        // =======================================================================
1088
1089        if let (Some(s_def), Some(t_def)) = (
1090            lazy_def_id(self.interner, source),
1091            lazy_def_id(self.interner, target),
1092        ) {
1093            // Use DefId-level cycle detection (checked before Ref types)
1094            return self.check_lazy_lazy_subtype(source, target, s_def, t_def);
1095        }
1096
1097        // =======================================================================
1098        // Rule #7: Open Numeric Enums - Number <-> Numeric Enum Assignability
1099        // =======================================================================
1100        // In TypeScript, numeric enums are "open" - they allow bidirectional
1101        // assignability with the number type. This is unsound but matches tsc behavior.
1102        // See docs/specs/TS_UNSOUNDNESS_CATALOG.md Item #7.
1103
1104        // Helper to extract DefId from Enum or Lazy types
1105        let get_enum_def_id = |type_id: TypeId| -> Option<DefId> {
1106            match self.interner.lookup(type_id) {
1107                Some(TypeData::Enum(def_id, _)) | Some(TypeData::Lazy(def_id)) => Some(def_id),
1108                _ => None,
1109            }
1110        };
1111
1112        // Check: source is numeric enum, target is Number
1113        if let Some(s_def) = get_enum_def_id(source)
1114            && target == TypeId::NUMBER
1115            && self.resolver.is_numeric_enum(s_def)
1116        {
1117            return SubtypeResult::True;
1118        }
1119
1120        // Check: source is Number (or numeric literal), target is numeric enum
1121        if let Some(t_def) = get_enum_def_id(target) {
1122            if source == TypeId::NUMBER && self.resolver.is_numeric_enum(t_def) {
1123                return SubtypeResult::True;
1124            }
1125            // Also check for numeric literals (subtypes of number)
1126            if matches!(
1127                self.interner.lookup(source),
1128                Some(TypeData::Literal(LiteralValue::Number(_)))
1129            ) && self.resolver.is_numeric_enum(t_def)
1130            {
1131                // For numeric literals, we need to check if they're assignable to the enum
1132                // Fall through to structural check (e.g., 0 -> E.A might succeed if E.A = 0)
1133                return self.check_subtype(source, self.resolve_lazy_type(target));
1134            }
1135        }
1136
1137        if lazy_def_id(self.interner, source).is_some() {
1138            let resolved = self.resolve_lazy_type(source);
1139            return if resolved != source {
1140                self.check_subtype(resolved, target)
1141            } else {
1142                SubtypeResult::False
1143            };
1144        }
1145
1146        if lazy_def_id(self.interner, target).is_some() {
1147            let resolved = self.resolve_lazy_type(target);
1148            return if resolved != target {
1149                self.check_subtype(source, resolved)
1150            } else {
1151                SubtypeResult::False
1152            };
1153        }
1154
1155        // =======================================================================
1156        // Ref(SymbolRef) checks - now secondary to Lazy(DefId)
1157        // =======================================================================
1158
1159        if let (Some(s_sym), Some(t_sym)) = (
1160            ref_symbol(self.interner, source),
1161            ref_symbol(self.interner, target),
1162        ) {
1163            return self.check_ref_ref_subtype(source, target, s_sym, t_sym);
1164        }
1165
1166        if let Some(s_sym) = ref_symbol(self.interner, source) {
1167            return self.check_ref_subtype(source, target, s_sym);
1168        }
1169
1170        if let Some(t_sym) = ref_symbol(self.interner, target) {
1171            return self.check_to_ref_subtype(source, target, t_sym);
1172        }
1173
1174        if let (Some(s_sym), Some(t_sym)) = (
1175            type_query_symbol(self.interner, source),
1176            type_query_symbol(self.interner, target),
1177        ) {
1178            return self.check_typequery_typequery_subtype(source, target, s_sym, t_sym);
1179        }
1180
1181        if let Some(s_sym) = type_query_symbol(self.interner, source) {
1182            return self.check_typequery_subtype(source, target, s_sym);
1183        }
1184
1185        if let Some(t_sym) = type_query_symbol(self.interner, target) {
1186            return self.check_to_typequery_subtype(source, target, t_sym);
1187        }
1188
1189        if let (Some(s_inner), Some(t_inner)) = (
1190            keyof_inner_type(self.interner, source),
1191            keyof_inner_type(self.interner, target),
1192        ) {
1193            return self.check_subtype(t_inner, s_inner);
1194        }
1195
1196        if let (Some(s_inner), Some(t_inner)) = (
1197            readonly_inner_type(self.interner, source),
1198            readonly_inner_type(self.interner, target),
1199        ) {
1200            return self.check_subtype(s_inner, t_inner);
1201        }
1202
1203        // Readonly target peeling: T <: Readonly<U> if T <: U
1204        // A mutable type can always be treated as readonly (readonly is a supertype)
1205        // CRITICAL: Only peel if source is NOT Readonly. If source IS Readonly, we must
1206        // fall through to the visitor to compare Readonly<S> vs Readonly<T>.
1207        if let Some(t_inner) = readonly_inner_type(self.interner, target)
1208            && readonly_inner_type(self.interner, source).is_none()
1209        {
1210            return self.check_subtype(source, t_inner);
1211        }
1212
1213        // Readonly source to mutable target case is handled by SubtypeVisitor::visit_readonly_type
1214        // which returns False (correctly, because Readonly is not assignable to Mutable)
1215
1216        if let (Some(s_sym), Some(t_sym)) = (
1217            unique_symbol_ref(self.interner, source),
1218            unique_symbol_ref(self.interner, target),
1219        ) {
1220            return if s_sym == t_sym {
1221                SubtypeResult::True
1222            } else {
1223                SubtypeResult::False
1224            };
1225        }
1226
1227        if unique_symbol_ref(self.interner, source).is_some()
1228            && intrinsic_kind(self.interner, target) == Some(IntrinsicKind::Symbol)
1229        {
1230            return SubtypeResult::True;
1231        }
1232
1233        if is_this_type(self.interner, source) && is_this_type(self.interner, target) {
1234            return SubtypeResult::True;
1235        }
1236
1237        if let (Some(s_spans), Some(t_spans)) = (
1238            template_literal_id(self.interner, source),
1239            template_literal_id(self.interner, target),
1240        ) {
1241            return self.check_template_assignable_to_template(s_spans, t_spans);
1242        }
1243
1244        if template_literal_id(self.interner, source).is_some()
1245            && intrinsic_kind(self.interner, target) == Some(IntrinsicKind::String)
1246        {
1247            return SubtypeResult::True;
1248        }
1249
1250        let source_is_callable = function_shape_id(self.interner, source).is_some()
1251            || callable_shape_id(self.interner, source).is_some();
1252        if source_is_callable {
1253            // Build a source ObjectShape from callable properties for structural comparison.
1254            // IMPORTANT: Sort properties by name (Atom) to match the merge scan's expectation.
1255            let source_props = if let Some(callable_id) = callable_shape_id(self.interner, source) {
1256                let callable = self.interner.callable_shape(callable_id);
1257                let mut props = callable.properties.clone();
1258                props.sort_by_key(|a| a.name);
1259                Some(ObjectShape {
1260                    flags: ObjectFlags::empty(),
1261                    properties: props,
1262                    string_index: callable.string_index.clone(),
1263                    number_index: callable.number_index.clone(),
1264                    symbol: callable.symbol,
1265                })
1266            } else {
1267                None
1268            };
1269
1270            if let Some(t_shape_id) = object_shape_id(self.interner, target) {
1271                let t_shape = self.interner.object_shape(t_shape_id);
1272                if t_shape.properties.is_empty() {
1273                    return SubtypeResult::True;
1274                }
1275                // If source is a CallableShape with properties, check structural compatibility
1276                if let Some(ref s_shape) = source_props {
1277                    return self.check_object_subtype(s_shape, None, &t_shape);
1278                }
1279                // FunctionShape has no properties - not assignable to non-empty object
1280                if let Some(tracer) = &mut self.tracer
1281                    && !tracer.on_mismatch_dyn(SubtypeFailureReason::TypeMismatch {
1282                        source_type: source,
1283                        target_type: target,
1284                    })
1285                {
1286                    return SubtypeResult::False;
1287                }
1288                return SubtypeResult::False;
1289            }
1290            if let Some(t_shape_id) = object_with_index_shape_id(self.interner, target) {
1291                let t_shape = self.interner.object_shape(t_shape_id);
1292                if t_shape.properties.is_empty() && t_shape.string_index.is_none() {
1293                    return SubtypeResult::True;
1294                }
1295                // If source is a CallableShape with properties, check structural compatibility
1296                if let Some(ref s_shape) = source_props {
1297                    return self.check_object_subtype(s_shape, None, &t_shape);
1298                }
1299                // FunctionShape has no properties - not assignable to non-empty indexed object
1300                if let Some(tracer) = &mut self.tracer
1301                    && !tracer.on_mismatch_dyn(SubtypeFailureReason::TypeMismatch {
1302                        source_type: source,
1303                        target_type: target,
1304                    })
1305                {
1306                    return SubtypeResult::False;
1307                }
1308                return SubtypeResult::False;
1309            }
1310        }
1311
1312        let source_is_array_or_tuple = array_element_type(self.interner, source).is_some()
1313            || tuple_list_id(self.interner, source).is_some();
1314        if source_is_array_or_tuple {
1315            if let Some(t_shape_id) = object_shape_id(self.interner, target) {
1316                let t_shape = self.interner.object_shape(t_shape_id);
1317                if t_shape.properties.is_empty() {
1318                    return SubtypeResult::True;
1319                }
1320                // Check if all target properties are satisfiable by the array.
1321                // First try a quick check for length-only targets.
1322                let only_length = t_shape
1323                    .properties
1324                    .iter()
1325                    .all(|p| self.interner.resolve_atom(p.name) == "length");
1326                if only_length {
1327                    let all_ok = t_shape
1328                        .properties
1329                        .iter()
1330                        .all(|p| self.check_subtype(TypeId::NUMBER, p.type_id).is_true());
1331                    if all_ok {
1332                        return SubtypeResult::True;
1333                    }
1334                }
1335                // Try the Array<T> interface for full structural comparison.
1336                // This handles cases like: number[] <: { toString(): string }
1337                if let Some(elem) = array_element_type(self.interner, source)
1338                    && let Some(result) = self.check_array_interface_subtype(elem, target)
1339                {
1340                    return result;
1341                }
1342                // Trace: Array/tuple not compatible with object
1343                if let Some(tracer) = &mut self.tracer
1344                    && !tracer.on_mismatch_dyn(SubtypeFailureReason::TypeMismatch {
1345                        source_type: source,
1346                        target_type: target,
1347                    })
1348                {
1349                    return SubtypeResult::False;
1350                }
1351                return SubtypeResult::False;
1352            }
1353            if let Some(t_shape_id) = object_with_index_shape_id(self.interner, target) {
1354                let t_shape = self.interner.object_shape(t_shape_id);
1355                if t_shape.properties.is_empty() && t_shape.string_index.is_none() {
1356                    if let Some(ref num_idx) = t_shape.number_index {
1357                        let elem_type =
1358                            array_element_type(self.interner, source).unwrap_or(TypeId::ANY);
1359                        if !self.check_subtype(elem_type, num_idx.value_type).is_true() {
1360                            // Trace: Array element type mismatch with index signature
1361                            if let Some(tracer) = &mut self.tracer
1362                                && !tracer.on_mismatch_dyn(
1363                                    SubtypeFailureReason::IndexSignatureMismatch {
1364                                        index_kind: "number",
1365                                        source_value_type: elem_type,
1366                                        target_value_type: num_idx.value_type,
1367                                    },
1368                                )
1369                            {
1370                                return SubtypeResult::False;
1371                            }
1372                            return SubtypeResult::False;
1373                        }
1374                    }
1375                    return SubtypeResult::True;
1376                }
1377                // Target has non-empty properties + index signature.
1378                // Try the Array<T> interface for full structural comparison.
1379                if let Some(elem) = array_element_type(self.interner, source)
1380                    && let Some(result) = self.check_array_interface_subtype(elem, target)
1381                {
1382                    return result;
1383                }
1384                // Trace: Array/tuple not compatible with indexed object with non-empty properties
1385                if let Some(tracer) = &mut self.tracer
1386                    && !tracer.on_mismatch_dyn(SubtypeFailureReason::TypeMismatch {
1387                        source_type: source,
1388                        target_type: target,
1389                    })
1390                {
1391                    return SubtypeResult::False;
1392                }
1393                return SubtypeResult::False;
1394            }
1395        }
1396
1397        // =======================================================================
1398        // VISITOR PATTERN DISPATCH (Task #48.4)
1399        // =======================================================================
1400        // After all special-case checks above, dispatch to the visitor for
1401        // general structural type checking. The visitor implements double-
1402        // dispatch pattern to handle source type variants and their interaction
1403        // with the target type.
1404        // =======================================================================
1405
1406        // Extract the interner reference FIRST (Copy trait)
1407        // This must happen before creating the visitor which mutably borrows self
1408        let interner = self.interner;
1409
1410        // Create the visitor with a mutable reborrow of self
1411        let mut visitor = SubtypeVisitor {
1412            checker: self,
1413            source,
1414            target,
1415        };
1416
1417        // Dispatch to the visitor using the extracted interner
1418        let result = visitor.visit_type(interner, source);
1419
1420        if result == SubtypeResult::False && self.check_generic_index_access_subtype(source, target)
1421        {
1422            return SubtypeResult::True;
1423        }
1424
1425        // Trace: Generic fallback type mismatch (no specific reason matched above)
1426        if result == SubtypeResult::False
1427            && let Some(tracer) = &mut self.tracer
1428            && !tracer.on_mismatch_dyn(SubtypeFailureReason::TypeMismatch {
1429                source_type: source,
1430                target_type: target,
1431            })
1432        {
1433            return SubtypeResult::False;
1434        }
1435
1436        result
1437    }
1438
1439    /// Check if a deferred keyof type is a subtype of string | number | symbol.
1440    /// This handles the case where `keyof T` (T is a type parameter) should be
1441    /// considered a subtype of `string | number | symbol` because in TypeScript,
1442    /// keyof always produces a subtype of those three types.
1443    fn is_keyof_subtype_of_string_number_symbol_union(&self, members: TypeListId) -> bool {
1444        let member_list = self.interner.type_list(members);
1445        // Check if the union contains string, number, and symbol
1446        let mut has_string = false;
1447        let mut has_number = false;
1448        let mut has_symbol = false;
1449        for &member in member_list.iter() {
1450            if member == TypeId::STRING {
1451                has_string = true;
1452            } else if member == TypeId::NUMBER {
1453                has_number = true;
1454            } else if member == TypeId::SYMBOL {
1455                has_symbol = true;
1456            }
1457        }
1458        has_string && has_number && has_symbol
1459    }
1460}
1461
1462/// Convenience function for one-off subtype checks (without resolver)
1463pub fn is_subtype_of(interner: &dyn TypeDatabase, source: TypeId, target: TypeId) -> bool {
1464    let mut checker = SubtypeChecker::new(interner);
1465    checker.is_subtype_of(source, target)
1466}
1467
1468impl<'a, R: TypeResolver> AssignabilityChecker for SubtypeChecker<'a, R> {
1469    fn is_assignable_to(&mut self, source: TypeId, target: TypeId) -> bool {
1470        SubtypeChecker::is_assignable_to(self, source, target)
1471    }
1472
1473    fn is_assignable_to_bivariant_callback(&mut self, source: TypeId, target: TypeId) -> bool {
1474        let prev_strict = self.strict_function_types;
1475        let prev_param_count = self.allow_bivariant_param_count;
1476        self.strict_function_types = false;
1477        self.allow_bivariant_param_count = true;
1478        let result = SubtypeChecker::is_assignable_to(self, source, target);
1479        self.allow_bivariant_param_count = prev_param_count;
1480        self.strict_function_types = prev_strict;
1481        result
1482    }
1483
1484    fn evaluate_type(&mut self, type_id: TypeId) -> TypeId {
1485        SubtypeChecker::evaluate_type(self, type_id)
1486    }
1487}
1488
1489/// Check if two types are structurally identical using De Bruijn indices for cycles.
1490///
1491/// This is the O(1) alternative to bidirectional subtyping for identity checks.
1492/// It transforms cyclic graphs into trees to solve the Graph Isomorphism problem.
1493pub fn are_types_structurally_identical<R: TypeResolver>(
1494    interner: &dyn TypeDatabase,
1495    resolver: &R,
1496    a: TypeId,
1497    b: TypeId,
1498) -> bool {
1499    if a == b {
1500        return true;
1501    }
1502    let mut canonicalizer = crate::canonicalize::Canonicalizer::new(interner, resolver);
1503    let canon_a = canonicalizer.canonicalize(a);
1504    let canon_b = canonicalizer.canonicalize(b);
1505
1506    // After canonicalization, structural identity reduces to TypeId equality
1507    canon_a == canon_b
1508}
1509
1510/// Convenience function for one-off subtype checks routed through a `QueryDatabase`.
1511/// The `QueryDatabase` enables Salsa memoization when available.
1512pub fn is_subtype_of_with_db(db: &dyn QueryDatabase, source: TypeId, target: TypeId) -> bool {
1513    let mut checker = SubtypeChecker::new(db.as_type_database()).with_query_db(db);
1514    checker.is_subtype_of(source, target)
1515}
1516
1517/// Convenience function for one-off subtype checks with compiler flags.
1518/// The flags are a packed u16 bitmask matching RelationCacheKey.flags.
1519pub fn is_subtype_of_with_flags(
1520    interner: &dyn TypeDatabase,
1521    source: TypeId,
1522    target: TypeId,
1523    flags: u16,
1524) -> bool {
1525    let mut checker = SubtypeChecker::new(interner).apply_flags(flags);
1526    checker.is_subtype_of(source, target)
1527}
1528
1529// Re-enabled subtype tests - verifying API compatibility
1530#[cfg(test)]
1531#[path = "../../tests/subtype_tests.rs"]
1532mod tests;
1533
1534#[cfg(test)]
1535#[path = "../../tests/index_signature_tests.rs"]
1536mod index_signature_tests;
1537
1538#[cfg(test)]
1539#[path = "../../tests/generics_rules_tests.rs"]
1540mod generics_rules_tests;
1541
1542#[cfg(test)]
1543#[path = "../../tests/callable_tests.rs"]
1544mod callable_tests;
1545
1546#[cfg(test)]
1547#[path = "../../tests/union_tests.rs"]
1548mod union_tests;
1549
1550#[cfg(test)]
1551#[path = "../../tests/typescript_quirks_tests.rs"]
1552mod typescript_quirks_tests;
1553
1554#[cfg(test)]
1555#[path = "../../tests/type_predicate_tests.rs"]
1556mod type_predicate_tests;
1557
1558#[cfg(test)]
1559#[path = "../../tests/overlap_tests.rs"]
1560mod overlap_tests;