Skip to main content

tsz_solver/
infer_bct.rs

1//! Best Common Type (BCT) inference.
2//!
3//! Implements Rule #32: Best Common Type algorithm for determining the most
4//! specific type that is a supertype of all candidates. Used by array literal
5//! type inference, conditional expression type inference, etc.
6//!
7//! Algorithm:
8//! 1. Filter out duplicates and never types
9//! 2. Try to find a single candidate that is a supertype of all others
10//! 3. Try to find a common base class (e.g., Dog + Cat -> Animal)
11//! 4. If not found, create a union of all candidates
12
13use crate::types::{
14    CallSignature, CallableShape, FunctionShape, LiteralValue, ObjectShape, ObjectShapeId,
15    ParamInfo, PropertyInfo, TupleElement, TypeData, TypeId,
16};
17use crate::utils;
18use crate::visitor;
19use rustc_hash::FxHashSet;
20use tsz_common::interner::Atom;
21
22use super::InferenceContext;
23
24struct TupleRestExpansion {
25    /// Fixed elements before the variadic portion (prefix)
26    fixed: Vec<TupleElement>,
27    /// The variadic element type (e.g., T for ...T[])
28    variadic: Option<TypeId>,
29    /// Fixed elements after the variadic portion (suffix/tail)
30    tail: Vec<TupleElement>,
31}
32
33impl<'a> InferenceContext<'a> {
34    // =========================================================================
35    // Best Common Type
36    // =========================================================================
37
38    /// Calculate the best common type from a set of types.
39    /// This implements Rule #32: Best Common Type (BCT) Inference.
40    ///
41    /// Algorithm:
42    /// 1. Filter out duplicates and never types
43    /// 2. Try to find a single candidate that is a supertype of all others
44    /// 3. Try to find a common base class (e.g., Dog + Cat -> Animal)
45    /// 4. If not found, create a union of all candidates
46    pub fn best_common_type(&self, types: &[TypeId]) -> TypeId {
47        if types.is_empty() {
48            return TypeId::UNKNOWN;
49        }
50        if types.len() == 1 {
51            return types[0];
52        }
53
54        // HOMOGENEOUS FAST PATH: Zero-allocation check for arrays with identical types
55        // This is the most common case for array literals like [1, 2, 3] or ["a", "b", "c"]
56        let first = types[0];
57        if types.iter().all(|&t| t == first) {
58            return first;
59        }
60
61        // Filter out duplicates and special types
62        let mut seen = FxHashSet::default();
63        let mut unique: Vec<TypeId> = Vec::new();
64        let mut has_any = false;
65        for &ty in types {
66            if ty == TypeId::ANY {
67                has_any = true;
68            }
69            if ty == TypeId::NEVER {
70                continue; // never doesn't contribute to union
71            }
72            if seen.insert(ty) {
73                unique.push(ty);
74            }
75        }
76
77        // Rule: If any type is 'any', the best common type is 'any'
78        if has_any {
79            return TypeId::ANY;
80        }
81
82        if unique.is_empty() {
83            return TypeId::NEVER;
84        }
85        if unique.len() == 1 {
86            return unique[0];
87        }
88
89        // Step 1: Try to find a common base type for primitives/literals
90        // For example, [string, "hello"] -> string
91        let common_base = self.find_common_base_type(&unique);
92        if let Some(base) = common_base {
93            // All types share a common base type (e.g. all are strings or derived from Animal).
94            // Using the common base is more specific than a full union only when there's
95            // more than one unique candidate.
96            if unique.len() > 1 {
97                return base;
98            }
99        }
100
101        // Step 2: Tournament reduction — O(N) to find potential supertype candidate.
102        // Instead of O(N²) pairwise comparison, we find the "winner" of a tournament
103        // and then verify if it's truly a supertype of all in a second O(N) pass.
104        let mut best = unique[0];
105        for &candidate in &unique[1..] {
106            if self.is_subtype(best, candidate) {
107                best = candidate;
108            }
109        }
110        if self.is_suitable_common_type(best, &unique) {
111            return best;
112        }
113
114        // Step 3: Try to find a common base class for object types
115        // This handles cases like [Dog, Cat] -> Animal (if both extend Animal)
116        if let Some(common_class) = self.find_common_base_class(&unique) {
117            return common_class;
118        }
119
120        // Step 4: Create union of all types
121        self.interner.union(unique)
122    }
123
124    /// Find a common base type for a set of types.
125    /// For example, [string, "hello"] -> Some(string)
126    fn find_common_base_type(&self, types: &[TypeId]) -> Option<TypeId> {
127        if types.is_empty() {
128            return None;
129        }
130
131        // Get the base type of the first element
132        let first_base = self.get_base_type(types[0])?;
133
134        // Check if all other types have the same base
135        for &ty in types.iter().skip(1) {
136            let base = self.get_base_type(ty)?;
137            if base != first_base {
138                return None;
139            }
140        }
141
142        Some(first_base)
143    }
144
145    /// Get the base type of a type.
146    ///
147    /// This handles both:
148    /// 1. Literal widening: `"hello"` -> `string`, `42` -> `number`
149    /// 2. Nominal hierarchy: `Dog` -> `Animal` (via resolver)
150    pub(crate) fn get_base_type(&self, ty: TypeId) -> Option<TypeId> {
151        match self.interner.lookup(ty) {
152            // Literal widening: extract intrinsic type
153            Some(TypeData::Literal(_)) => {
154                match ty {
155                    TypeId::STRING | TypeId::NUMBER | TypeId::BOOLEAN | TypeId::BIGINT => Some(ty),
156                    _ => {
157                        // For literal values, extract their base type
158                        if let Some(TypeData::Literal(lit)) = self.interner.lookup(ty) {
159                            match lit {
160                                LiteralValue::String(_) => Some(TypeId::STRING),
161                                LiteralValue::Number(_) => Some(TypeId::NUMBER),
162                                LiteralValue::Boolean(_) => Some(TypeId::BOOLEAN),
163                                LiteralValue::BigInt(_) => Some(TypeId::BIGINT),
164                            }
165                        } else {
166                            Some(ty)
167                        }
168                    }
169                }
170            }
171            // Nominal hierarchy: use resolver to get base class
172            Some(TypeData::Lazy(_)) => {
173                // For class/interface types, try to get base class from resolver
174                if let Some(resolver) = self.resolver {
175                    resolver.get_base_type(ty, self.interner)
176                } else {
177                    // No resolver available - return type as-is
178                    Some(ty)
179                }
180            }
181            _ => Some(ty),
182        }
183    }
184
185    /// Find a common base class for object types.
186    /// This implements the optimization for BCT where [Dog, Cat] -> Animal
187    /// instead of Dog | Cat, if both Dog and Cat extend Animal.
188    ///
189    /// Returns None if no common base class exists or if types are not class types.
190    fn find_common_base_class(&self, types: &[TypeId]) -> Option<TypeId> {
191        if types.len() < 2 {
192            return None;
193        }
194
195        // 1. Initialize candidates from the FIRST type only.
196        // This is the only time we generate a full hierarchy.
197        let mut base_candidates = self.get_class_hierarchy(types[0])?;
198
199        // 2. For subsequent types, filter using is_subtype (cached and fast).
200        // No allocations, no hierarchy traversal - just subtype checks.
201        // This reduces complexity from O(N·Alloc(D)) to O(N·|Candidates|).
202        for &ty in types.iter().skip(1) {
203            // Optimization: If we run out of candidates, stop immediately.
204            if base_candidates.is_empty() {
205                return None;
206            }
207
208            // Filter: Keep base if 'ty' is a subtype of 'base'
209            // This preserves semantic correctness while being much faster.
210            base_candidates.retain(|&base| self.is_subtype(ty, base));
211        }
212
213        // Return the most specific base (first remaining candidate after filtering)
214        base_candidates.first().copied()
215    }
216
217    /// Get the class hierarchy for a type, from most derived to most base.
218    /// Returns None if the type is not a class/interface type.
219    fn get_class_hierarchy(&self, ty: TypeId) -> Option<Vec<TypeId>> {
220        let mut hierarchy = Vec::new();
221        self.collect_class_hierarchy(ty, &mut hierarchy);
222        if hierarchy.is_empty() {
223            None
224        } else {
225            Some(hierarchy)
226        }
227    }
228
229    /// Recursively collect the class hierarchy for a type.
230    fn collect_class_hierarchy(&self, ty: TypeId, hierarchy: &mut Vec<TypeId>) {
231        // Prevent infinite recursion
232        if hierarchy.contains(&ty) {
233            return;
234        }
235
236        // Add current type to hierarchy
237        hierarchy.push(ty);
238
239        // Get the type key
240        let Some(type_key) = self.interner.lookup(ty) else {
241            return;
242        };
243
244        match type_key {
245            // Intersection types: recurse into all members to extract commonality
246            // This enables BCT to find common members from intersections
247            // Example: [A & B, A & C] -> A (common member)
248            TypeData::Intersection(members_id) => {
249                let members = self.interner.type_list(members_id);
250                for &member in members.iter() {
251                    self.collect_class_hierarchy(member, hierarchy);
252                }
253            }
254            // Lazy types: add the type itself, then follow extends chain
255            // This enables BCT to work with classes/interfaces defined as Lazy(DefId)
256            TypeData::Lazy(_) => {
257                if let Some(base_type) = self.get_extends_clause(ty) {
258                    self.collect_class_hierarchy(base_type, hierarchy);
259                }
260            }
261            // For class/interface types, collect extends clauses
262            TypeData::Callable(shape_id) => {
263                let _shape = self.interner.callable_shape(shape_id);
264
265                // Check for base class (extends clause)
266                // In callable shapes, this is stored in the base_class property
267                if let Some(base_type) = self.get_extends_clause(ty) {
268                    self.collect_class_hierarchy(base_type, hierarchy);
269                }
270            }
271            TypeData::Object(shape_id) => {
272                let _shape = self.interner.object_shape(shape_id);
273
274                // Check for base class (extends clause)
275                if let Some(base_type) = self.get_extends_clause(ty) {
276                    self.collect_class_hierarchy(base_type, hierarchy);
277                }
278            }
279            _ => {
280                // Not a class/interface type, no hierarchy
281            }
282        }
283    }
284
285    /// Get the extends clause (base class) for a class/interface type.
286    ///
287    /// This uses the `TypeResolver` to bridge to the Binder's extends clause information.
288    /// For example, given Dog that extends Animal, this returns the Animal type.
289    fn get_extends_clause(&self, ty: TypeId) -> Option<TypeId> {
290        // If we have a resolver, use it to get the base type
291        if let Some(resolver) = self.resolver {
292            resolver.get_base_type(ty, self.interner)
293        } else {
294            // No resolver available - can't determine base class
295            None
296        }
297    }
298
299    /// Check if a candidate type is a suitable common type for all types.
300    /// A suitable common type must be a supertype of all types in the list.
301    fn is_suitable_common_type(&self, candidate: TypeId, types: &[TypeId]) -> bool {
302        types.iter().all(|&ty| self.is_subtype(ty, candidate))
303    }
304
305    /// Simple subtype check for bounds validation.
306    /// Uses a simplified check - for full checking, use `SubtypeChecker`.
307    pub(crate) fn is_subtype(&self, source: TypeId, target: TypeId) -> bool {
308        let key = (source, target);
309        if let Some(&cached) = self.subtype_cache.borrow().get(&key) {
310            return cached;
311        }
312
313        let result = self.is_subtype_uncached(source, target);
314        self.subtype_cache.borrow_mut().insert(key, result);
315        result
316    }
317
318    fn is_subtype_uncached(&self, source: TypeId, target: TypeId) -> bool {
319        // Same type
320        if source == target {
321            return true;
322        }
323
324        // never <: T for all T
325        if source == TypeId::NEVER {
326            return true;
327        }
328
329        // T <: unknown for all T
330        if target == TypeId::UNKNOWN {
331            return true;
332        }
333
334        // any <: T and T <: any (only if both are any)
335        if source == TypeId::ANY || target == TypeId::ANY {
336            return source == target;
337        }
338
339        // STRICT_ANY matches itself or unknown/any (only at top level)
340        if source == TypeId::STRICT_ANY || target == TypeId::STRICT_ANY {
341            return source == target
342                || target == TypeId::UNKNOWN
343                || target == TypeId::ANY
344                || source == TypeId::ANY;
345        }
346
347        // object keyword accepts any non-primitive type
348        if target == TypeId::OBJECT {
349            return self.is_object_keyword_type(source);
350        }
351
352        let source_key = self.interner.lookup(source);
353        let target_key = self.interner.lookup(target);
354
355        // OPTIMIZATION: Enum member disjointness fast-path
356        // Two different enum members are guaranteed disjoint (neither is subtype of the other).
357        // Since we already checked source == target at the top, reaching here means source != target.
358        // This avoids O(n²) structural recursion in enumLiteralsSubtypeReduction.ts
359        if let (Some(TypeData::Enum(..)), Some(TypeData::Enum(..))) =
360            (source_key.as_ref(), target_key.as_ref())
361        {
362            // Different enum members (or different enums) are always disjoint
363            return false;
364        }
365
366        // Check if source is literal of target intrinsic
367        if let Some(TypeData::Literal(lit)) = source_key.as_ref() {
368            match (lit, target) {
369                (LiteralValue::String(_), t) if t == TypeId::STRING => return true,
370                (LiteralValue::Number(_), t) if t == TypeId::NUMBER => return true,
371                (LiteralValue::Boolean(_), t) if t == TypeId::BOOLEAN => return true,
372                (LiteralValue::BigInt(_), t) if t == TypeId::BIGINT => return true,
373                _ => {}
374            }
375        }
376
377        // Array and tuple structural checks
378        if let (Some(TypeData::Array(s_elem)), Some(TypeData::Array(t_elem))) =
379            (source_key.as_ref(), target_key.as_ref())
380        {
381            return self.is_subtype(*s_elem, *t_elem);
382        }
383
384        if let (Some(TypeData::Tuple(_)), Some(TypeData::Tuple(_))) =
385            (source_key.as_ref(), target_key.as_ref())
386        {
387            // OPTIMIZATION: Unit-tuple disjointness fast-path
388            // Two different unit tuples (tuples of literals/enums only) are guaranteed disjoint.
389            // Since we already checked source == target at the top and returned true,
390            // reaching here means source != target. If both are unit tuples, they're disjoint.
391            // This avoids O(N) structural recursion for each comparison.
392            if visitor::is_unit_type(self.interner, source)
393                && visitor::is_unit_type(self.interner, target)
394            {
395                return false;
396            }
397            // Fall through to structural check for non-unit tuples
398            let (Some(TypeData::Tuple(s_elems)), Some(TypeData::Tuple(t_elems))) =
399                (source_key.as_ref(), target_key.as_ref())
400            else {
401                panic!("invariant violation: tuple subtype check expected tuple operands")
402            };
403            let s_elems = self.interner.tuple_list(*s_elems);
404            let t_elems = self.interner.tuple_list(*t_elems);
405            return self.tuple_subtype_of(&s_elems, &t_elems);
406        }
407
408        if let (Some(TypeData::Tuple(s_elems)), Some(TypeData::Array(t_elem))) =
409            (source_key.as_ref(), target_key.as_ref())
410        {
411            let s_elems = self.interner.tuple_list(*s_elems);
412            return self.tuple_subtype_array(&s_elems, *t_elem);
413        }
414
415        if let (Some(TypeData::Object(s_props)), Some(TypeData::Object(t_props))) =
416            (source_key.as_ref(), target_key.as_ref())
417        {
418            let s_shape = self.interner.object_shape(*s_props);
419            let t_shape = self.interner.object_shape(*t_props);
420            return self.object_subtype_of(
421                &s_shape.properties,
422                Some(*s_props),
423                &t_shape.properties,
424            );
425        }
426
427        if let (
428            Some(TypeData::ObjectWithIndex(s_shape_id)),
429            Some(TypeData::ObjectWithIndex(t_shape_id)),
430        ) = (source_key.as_ref(), target_key.as_ref())
431        {
432            let s_shape = self.interner.object_shape(*s_shape_id);
433            let t_shape = self.interner.object_shape(*t_shape_id);
434            return self.object_with_index_subtype_of(&s_shape, Some(*s_shape_id), &t_shape);
435        }
436
437        if let (Some(TypeData::Object(s_props)), Some(TypeData::ObjectWithIndex(t_shape))) =
438            (source_key.as_ref(), target_key.as_ref())
439        {
440            let s_shape = self.interner.object_shape(*s_props);
441            let t_shape = self.interner.object_shape(*t_shape);
442            return self.object_props_subtype_index(&s_shape.properties, Some(*s_props), &t_shape);
443        }
444
445        if let (Some(TypeData::ObjectWithIndex(s_shape_id)), Some(TypeData::Object(t_props))) =
446            (source_key.as_ref(), target_key.as_ref())
447        {
448            let s_shape = self.interner.object_shape(*s_shape_id);
449            let t_shape = self.interner.object_shape(*t_props);
450            return self.object_subtype_of(
451                &s_shape.properties,
452                Some(*s_shape_id),
453                &t_shape.properties,
454            );
455        }
456
457        if let (Some(TypeData::Function(s_fn)), Some(TypeData::Function(t_fn))) =
458            (source_key.as_ref(), target_key.as_ref())
459        {
460            let s_fn = self.interner.function_shape(*s_fn);
461            let t_fn = self.interner.function_shape(*t_fn);
462            return self.function_subtype_of(&s_fn, &t_fn);
463        }
464
465        if let (Some(TypeData::Callable(s_callable)), Some(TypeData::Callable(t_callable))) =
466            (source_key.as_ref(), target_key.as_ref())
467        {
468            let s_callable = self.interner.callable_shape(*s_callable);
469            let t_callable = self.interner.callable_shape(*t_callable);
470            return self.callable_subtype_of(&s_callable, &t_callable);
471        }
472
473        if let (Some(TypeData::Function(s_fn)), Some(TypeData::Callable(t_callable))) =
474            (source_key.as_ref(), target_key.as_ref())
475        {
476            let s_fn = self.interner.function_shape(*s_fn);
477            let t_callable = self.interner.callable_shape(*t_callable);
478            return self.function_subtype_callable(&s_fn, &t_callable);
479        }
480
481        if let (Some(TypeData::Callable(s_callable)), Some(TypeData::Function(t_fn))) =
482            (source_key.as_ref(), target_key.as_ref())
483        {
484            let s_callable = self.interner.callable_shape(*s_callable);
485            let t_fn = self.interner.function_shape(*t_fn);
486            return self.callable_subtype_function(&s_callable, &t_fn);
487        }
488
489        if let (Some(TypeData::Application(s_app)), Some(TypeData::Application(t_app))) =
490            (source_key.as_ref(), target_key.as_ref())
491        {
492            let s_app = self.interner.type_application(*s_app);
493            let t_app = self.interner.type_application(*t_app);
494            if s_app.args.len() != t_app.args.len() {
495                return false;
496            }
497            if !self.is_subtype(s_app.base, t_app.base) {
498                return false;
499            }
500            for (s_arg, t_arg) in s_app.args.iter().zip(t_app.args.iter()) {
501                if !self.is_subtype(*s_arg, *t_arg) {
502                    return false;
503                }
504            }
505            return true;
506        }
507
508        // Intersection: A & B <: T if either member is a subtype of T
509        if let Some(TypeData::Intersection(members)) = source_key.as_ref() {
510            let members = self.interner.type_list(*members);
511            return members
512                .iter()
513                .any(|&member| self.is_subtype(member, target));
514        }
515
516        // Union: A | B <: T if both A <: T and B <: T
517        if let Some(TypeData::Union(members)) = source_key.as_ref() {
518            let members = self.interner.type_list(*members);
519            return members
520                .iter()
521                .all(|&member| self.is_subtype(member, target));
522        }
523
524        // Target intersection: S <: (A & B) if S <: A and S <: B
525        if let Some(TypeData::Intersection(members)) = target_key.as_ref() {
526            let members = self.interner.type_list(*members);
527            return members
528                .iter()
529                .all(|&member| self.is_subtype(source, member));
530        }
531
532        // Target union: S <: (A | B) if S <: A or S <: B
533        if let Some(TypeData::Union(members)) = target_key.as_ref() {
534            let members = self.interner.type_list(*members);
535            return members
536                .iter()
537                .any(|&member| self.is_subtype(source, member));
538        }
539
540        // Object vs Object comparison
541        if let (Some(TypeData::Object(s_props)), Some(TypeData::Object(t_props))) =
542            (source_key.as_ref(), target_key.as_ref())
543        {
544            let s_shape = self.interner.object_shape(*s_props);
545            let t_shape = self.interner.object_shape(*t_props);
546            return self.object_subtype_of(
547                &s_shape.properties,
548                Some(*s_props),
549                &t_shape.properties,
550            );
551        }
552
553        false
554    }
555
556    fn is_object_keyword_type(&self, source: TypeId) -> bool {
557        match source {
558            TypeId::NEVER | TypeId::ERROR | TypeId::OBJECT => return true,
559            TypeId::ANY => {
560                // In BCT context, we want strict matching for ANY
561                return false;
562            }
563            TypeId::UNKNOWN
564            | TypeId::VOID
565            | TypeId::NULL
566            | TypeId::UNDEFINED
567            | TypeId::BOOLEAN
568            | TypeId::NUMBER
569            | TypeId::STRING
570            | TypeId::BIGINT
571            | TypeId::SYMBOL => return false,
572            _ => {}
573        }
574
575        let key = match self.interner.lookup(source) {
576            Some(key) => key,
577            None => return false,
578        };
579
580        match key {
581            TypeData::Object(_)
582            | TypeData::ObjectWithIndex(_)
583            | TypeData::Array(_)
584            | TypeData::Tuple(_)
585            | TypeData::Function(_)
586            | TypeData::Callable(_)
587            | TypeData::Mapped(_)
588            | TypeData::Application(_)
589            | TypeData::ThisType => true,
590            TypeData::ReadonlyType(inner) => self.is_subtype(inner, TypeId::OBJECT),
591            TypeData::TypeParameter(info) | TypeData::Infer(info) => info
592                .constraint
593                .is_some_and(|constraint| self.is_subtype(constraint, TypeId::OBJECT)),
594            _ => false,
595        }
596    }
597
598    fn optional_property_type(&self, prop: &PropertyInfo) -> TypeId {
599        if prop.optional {
600            self.interner.union2(prop.type_id, TypeId::UNDEFINED)
601        } else {
602            prop.type_id
603        }
604    }
605
606    fn optional_property_write_type(&self, prop: &PropertyInfo) -> TypeId {
607        if prop.optional {
608            self.interner.union2(prop.write_type, TypeId::UNDEFINED)
609        } else {
610            prop.write_type
611        }
612    }
613
614    fn is_subtype_with_method_variance(
615        &self,
616        source: TypeId,
617        target: TypeId,
618        allow_bivariant: bool,
619    ) -> bool {
620        if !allow_bivariant {
621            return self.is_subtype(source, target);
622        }
623
624        let source_key = self.interner.lookup(source);
625        let target_key = self.interner.lookup(target);
626
627        match (source_key.as_ref(), target_key.as_ref()) {
628            (Some(TypeData::Function(s_fn)), Some(TypeData::Function(t_fn))) => {
629                let s_fn = self.interner.function_shape(*s_fn);
630                let t_fn = self.interner.function_shape(*t_fn);
631                return self.function_like_subtype_of_with_variance(
632                    &s_fn.params,
633                    s_fn.return_type,
634                    &t_fn.params,
635                    t_fn.return_type,
636                    true,
637                );
638            }
639            (Some(TypeData::Callable(s_callable)), Some(TypeData::Callable(t_callable))) => {
640                let s_callable = self.interner.callable_shape(*s_callable);
641                let t_callable = self.interner.callable_shape(*t_callable);
642                return self.callable_subtype_of_with_variance(&s_callable, &t_callable, true);
643            }
644            (Some(TypeData::Function(s_fn)), Some(TypeData::Callable(t_callable))) => {
645                let s_fn = self.interner.function_shape(*s_fn);
646                let t_callable = self.interner.callable_shape(*t_callable);
647                return self.function_subtype_callable_with_variance(&s_fn, &t_callable, true);
648            }
649            (Some(TypeData::Callable(s_callable)), Some(TypeData::Function(t_fn))) => {
650                let s_callable = self.interner.callable_shape(*s_callable);
651                let t_fn = self.interner.function_shape(*t_fn);
652                return self.callable_subtype_function_with_variance(&s_callable, &t_fn, true);
653            }
654            _ => {}
655        }
656
657        self.is_subtype(source, target)
658    }
659
660    fn lookup_property<'props>(
661        &self,
662        props: &'props [PropertyInfo],
663        shape_id: Option<ObjectShapeId>,
664        name: Atom,
665    ) -> Option<&'props PropertyInfo> {
666        crate::utils::lookup_property(self.interner, props, shape_id, name)
667    }
668
669    fn object_subtype_of(
670        &self,
671        source: &[PropertyInfo],
672        source_shape_id: Option<ObjectShapeId>,
673        target: &[PropertyInfo],
674    ) -> bool {
675        for t_prop in target {
676            let s_prop = self.lookup_property(source, source_shape_id, t_prop.name);
677            match s_prop {
678                Some(sp) => {
679                    if sp.optional && !t_prop.optional {
680                        return false;
681                    }
682                    // NOTE: TypeScript allows readonly source to satisfy mutable target
683                    // (readonly is a constraint on the reference, not structural compatibility)
684                    let source_type = self.optional_property_type(sp);
685                    let target_type = self.optional_property_type(t_prop);
686                    if !self.is_subtype_with_method_variance(
687                        source_type,
688                        target_type,
689                        t_prop.is_method,
690                    ) {
691                        return false;
692                    }
693                    // Check write type compatibility for mutable targets
694                    // A readonly source cannot satisfy a mutable target (can't write to readonly)
695                    if !t_prop.readonly {
696                        // If source is readonly but target is mutable, this is a mismatch
697                        if sp.readonly {
698                            return false;
699                        }
700                        // If source is non-optional and target is optional, skip write type check
701                        // Non-optional source can always satisfy optional target for writing
702                        if !sp.optional && t_prop.optional {
703                            // Skip write type check - non-optional source satisfies optional target
704                        } else {
705                            let source_write = self.optional_property_write_type(sp);
706                            let target_write = self.optional_property_write_type(t_prop);
707                            if !self.is_subtype_with_method_variance(
708                                target_write,
709                                source_write,
710                                t_prop.is_method,
711                            ) {
712                                return false;
713                            }
714                        }
715                    }
716                }
717                None => {
718                    if !t_prop.optional {
719                        return false;
720                    }
721                }
722            }
723        }
724        true
725    }
726
727    fn object_props_subtype_index(
728        &self,
729        source: &[PropertyInfo],
730        source_shape_id: Option<ObjectShapeId>,
731        target: &ObjectShape,
732    ) -> bool {
733        if !self.object_subtype_of(source, source_shape_id, &target.properties) {
734            return false;
735        }
736        self.check_properties_against_index_signatures(source, target)
737    }
738
739    fn object_with_index_subtype_of(
740        &self,
741        source: &ObjectShape,
742        source_shape_id: Option<ObjectShapeId>,
743        target: &ObjectShape,
744    ) -> bool {
745        if !self.object_subtype_of(&source.properties, source_shape_id, &target.properties) {
746            return false;
747        }
748
749        if let Some(t_string_idx) = &target.string_index
750            && let Some(s_string_idx) = &source.string_index
751        {
752            if s_string_idx.readonly && !t_string_idx.readonly {
753                return false;
754            }
755            if !self.is_subtype(s_string_idx.value_type, t_string_idx.value_type) {
756                return false;
757            }
758        }
759
760        if let Some(t_number_idx) = &target.number_index
761            && let Some(s_number_idx) = &source.number_index
762        {
763            if s_number_idx.readonly && !t_number_idx.readonly {
764                return false;
765            }
766            if !self.is_subtype(s_number_idx.value_type, t_number_idx.value_type) {
767                return false;
768            }
769        }
770
771        if let (Some(s_string_idx), Some(s_number_idx)) =
772            (&source.string_index, &source.number_index)
773            && !self.is_subtype(s_number_idx.value_type, s_string_idx.value_type)
774        {
775            return false;
776        }
777
778        self.check_properties_against_index_signatures(&source.properties, target)
779    }
780
781    fn check_properties_against_index_signatures(
782        &self,
783        source: &[PropertyInfo],
784        target: &ObjectShape,
785    ) -> bool {
786        let string_index = target.string_index.as_ref();
787        let number_index = target.number_index.as_ref();
788
789        if string_index.is_none() && number_index.is_none() {
790            return true;
791        }
792
793        for prop in source {
794            let prop_type = self.optional_property_type(prop);
795
796            if let Some(number_idx) = number_index
797                && utils::is_numeric_property_name(self.interner, prop.name)
798            {
799                if !number_idx.readonly && prop.readonly {
800                    return false;
801                }
802                if !self.is_subtype(prop_type, number_idx.value_type) {
803                    return false;
804                }
805            }
806
807            if let Some(string_idx) = string_index {
808                if !string_idx.readonly && prop.readonly {
809                    return false;
810                }
811                if !self.is_subtype(prop_type, string_idx.value_type) {
812                    return false;
813                }
814            }
815        }
816
817        true
818    }
819
820    fn rest_element_type(&self, type_id: TypeId) -> TypeId {
821        if type_id == TypeId::ANY {
822            return TypeId::ANY;
823        }
824        match self.interner.lookup(type_id) {
825            Some(TypeData::Array(elem)) => elem,
826            _ => type_id,
827        }
828    }
829
830    fn are_parameters_compatible(&self, source: TypeId, target: TypeId, bivariant: bool) -> bool {
831        if bivariant {
832            self.is_subtype(target, source) || self.is_subtype(source, target)
833        } else {
834            self.is_subtype(target, source)
835        }
836    }
837
838    fn are_this_parameters_compatible(
839        &self,
840        source: Option<TypeId>,
841        target: Option<TypeId>,
842        bivariant: bool,
843    ) -> bool {
844        if source.is_none() && target.is_none() {
845            return true;
846        }
847        // If target has no explicit `this` parameter, always compatible.
848        // TypeScript only checks `this` when the target declares one.
849        if target.is_none() {
850            return true;
851        }
852        let source = source.unwrap_or(TypeId::UNKNOWN);
853        let target = target.unwrap();
854        self.are_parameters_compatible(source, target, bivariant)
855    }
856
857    fn function_like_subtype_of(
858        &self,
859        source_params: &[ParamInfo],
860        source_return: TypeId,
861        target_params: &[ParamInfo],
862        target_return: TypeId,
863    ) -> bool {
864        self.function_like_subtype_of_with_variance(
865            source_params,
866            source_return,
867            target_params,
868            target_return,
869            false,
870        )
871    }
872
873    fn function_like_subtype_of_with_variance(
874        &self,
875        source_params: &[ParamInfo],
876        source_return: TypeId,
877        target_params: &[ParamInfo],
878        target_return: TypeId,
879        bivariant: bool,
880    ) -> bool {
881        if !self.is_subtype(source_return, target_return) {
882            return false;
883        }
884
885        let target_has_rest = target_params.last().is_some_and(|p| p.rest);
886        let source_has_rest = source_params.last().is_some_and(|p| p.rest);
887        let target_fixed = if target_has_rest {
888            target_params.len().saturating_sub(1)
889        } else {
890            target_params.len()
891        };
892        let source_fixed = if source_has_rest {
893            source_params.len().saturating_sub(1)
894        } else {
895            source_params.len()
896        };
897
898        if !target_has_rest && source_params.len() > target_params.len() {
899            return false;
900        }
901
902        let fixed_compare = std::cmp::min(source_fixed, target_fixed);
903        for i in 0..fixed_compare {
904            let s_param = &source_params[i];
905            let t_param = &target_params[i];
906            if !self.are_parameters_compatible(s_param.type_id, t_param.type_id, bivariant) {
907                return false;
908            }
909        }
910
911        if target_has_rest {
912            let rest_param = match target_params.last() {
913                Some(param) => param,
914                None => return false,
915            };
916            let rest_elem = self.rest_element_type(rest_param.type_id);
917
918            for s_param in source_params
919                .iter()
920                .skip(target_fixed)
921                .take(source_fixed - target_fixed)
922            {
923                if !self.are_parameters_compatible(s_param.type_id, rest_elem, bivariant) {
924                    return false;
925                }
926            }
927
928            if source_has_rest {
929                let s_rest = match source_params.last() {
930                    Some(param) => param,
931                    None => return false,
932                };
933                let s_rest_elem = self.rest_element_type(s_rest.type_id);
934                if !self.are_parameters_compatible(s_rest_elem, rest_elem, bivariant) {
935                    return false;
936                }
937            }
938        }
939
940        true
941    }
942
943    fn function_subtype_of(&self, source: &FunctionShape, target: &FunctionShape) -> bool {
944        if source.is_constructor != target.is_constructor {
945            return false;
946        }
947        if !self.are_this_parameters_compatible(source.this_type, target.this_type, false) {
948            return false;
949        }
950
951        self.function_like_subtype_of(
952            &source.params,
953            source.return_type,
954            &target.params,
955            target.return_type,
956        )
957    }
958
959    fn call_signature_subtype_of(
960        &self,
961        source: &CallSignature,
962        target: &CallSignature,
963        bivariant: bool,
964    ) -> bool {
965        if !self.are_this_parameters_compatible(source.this_type, target.this_type, bivariant) {
966            return false;
967        }
968        self.function_like_subtype_of_with_variance(
969            &source.params,
970            source.return_type,
971            &target.params,
972            target.return_type,
973            bivariant,
974        )
975    }
976
977    fn callable_subtype_of(&self, source: &CallableShape, target: &CallableShape) -> bool {
978        self.callable_subtype_of_with_variance(source, target, false)
979    }
980
981    fn callable_subtype_of_with_variance(
982        &self,
983        source: &CallableShape,
984        target: &CallableShape,
985        bivariant: bool,
986    ) -> bool {
987        for t_sig in &target.call_signatures {
988            let mut found = false;
989            for s_sig in &source.call_signatures {
990                if self.call_signature_subtype_of(s_sig, t_sig, bivariant) {
991                    found = true;
992                    break;
993                }
994            }
995            if !found {
996                return false;
997            }
998        }
999
1000        for t_sig in &target.construct_signatures {
1001            let mut found = false;
1002            for s_sig in &source.construct_signatures {
1003                if self.call_signature_subtype_of(s_sig, t_sig, bivariant) {
1004                    found = true;
1005                    break;
1006                }
1007            }
1008            if !found {
1009                return false;
1010            }
1011        }
1012
1013        self.object_subtype_of(&source.properties, None, &target.properties)
1014    }
1015
1016    fn function_subtype_callable(&self, source: &FunctionShape, target: &CallableShape) -> bool {
1017        self.function_subtype_callable_with_variance(source, target, false)
1018    }
1019
1020    fn function_subtype_callable_with_variance(
1021        &self,
1022        source: &FunctionShape,
1023        target: &CallableShape,
1024        bivariant: bool,
1025    ) -> bool {
1026        for t_sig in &target.call_signatures {
1027            if !self.function_like_subtype_of_with_variance(
1028                &source.params,
1029                source.return_type,
1030                &t_sig.params,
1031                t_sig.return_type,
1032                bivariant,
1033            ) {
1034                return false;
1035            }
1036        }
1037        true
1038    }
1039
1040    fn callable_subtype_function(&self, source: &CallableShape, target: &FunctionShape) -> bool {
1041        self.callable_subtype_function_with_variance(source, target, false)
1042    }
1043
1044    fn callable_subtype_function_with_variance(
1045        &self,
1046        source: &CallableShape,
1047        target: &FunctionShape,
1048        bivariant: bool,
1049    ) -> bool {
1050        for s_sig in &source.call_signatures {
1051            if self.function_like_subtype_of_with_variance(
1052                &s_sig.params,
1053                s_sig.return_type,
1054                &target.params,
1055                target.return_type,
1056                bivariant,
1057            ) {
1058                return true;
1059            }
1060        }
1061        false
1062    }
1063
1064    fn tuple_subtype_array(&self, source: &[TupleElement], target_elem: TypeId) -> bool {
1065        for elem in source {
1066            if elem.rest {
1067                let expansion = self.expand_tuple_rest(elem.type_id);
1068                for fixed in expansion.fixed {
1069                    if !self.is_subtype(fixed.type_id, target_elem) {
1070                        return false;
1071                    }
1072                }
1073                if let Some(variadic) = expansion.variadic
1074                    && !self.is_subtype(variadic, target_elem)
1075                {
1076                    return false;
1077                }
1078                // Check tail elements from nested tuple spreads
1079                for tail_elem in expansion.tail {
1080                    if !self.is_subtype(tail_elem.type_id, target_elem) {
1081                        return false;
1082                    }
1083                }
1084            } else if !self.is_subtype(elem.type_id, target_elem) {
1085                return false;
1086            }
1087        }
1088        true
1089    }
1090
1091    fn tuple_subtype_of(&self, source: &[TupleElement], target: &[TupleElement]) -> bool {
1092        let source_required = source.iter().filter(|e| !e.optional && !e.rest).count();
1093        let target_required = target.iter().filter(|e| !e.optional && !e.rest).count();
1094
1095        if source_required < target_required {
1096            return false;
1097        }
1098
1099        for (i, t_elem) in target.iter().enumerate() {
1100            if t_elem.rest {
1101                let expansion = self.expand_tuple_rest(t_elem.type_id);
1102                let outer_tail = &target[i + 1..];
1103                // Combined suffix = expansion.tail + outer_tail
1104                let combined_suffix: Vec<_> = expansion
1105                    .tail
1106                    .iter()
1107                    .chain(outer_tail.iter())
1108                    .cloned()
1109                    .collect();
1110
1111                // Match combined suffix from the end
1112                let mut source_end = source.len();
1113                for tail_elem in combined_suffix.iter().rev() {
1114                    if source_end <= i {
1115                        if !tail_elem.optional {
1116                            return false;
1117                        }
1118                        break;
1119                    }
1120                    let s_elem = &source[source_end - 1];
1121                    if s_elem.rest {
1122                        if !tail_elem.optional {
1123                            return false;
1124                        }
1125                        break;
1126                    }
1127                    if !self.is_subtype(s_elem.type_id, tail_elem.type_id) {
1128                        if tail_elem.optional {
1129                            break;
1130                        }
1131                        return false;
1132                    }
1133                    source_end -= 1;
1134                }
1135
1136                let mut source_iter = source.iter().take(source_end).skip(i);
1137
1138                for t_fixed in &expansion.fixed {
1139                    match source_iter.next() {
1140                        Some(s_elem) => {
1141                            if s_elem.rest {
1142                                return false;
1143                            }
1144                            if !self.is_subtype(s_elem.type_id, t_fixed.type_id) {
1145                                return false;
1146                            }
1147                        }
1148                        None => {
1149                            if !t_fixed.optional {
1150                                return false;
1151                            }
1152                        }
1153                    }
1154                }
1155
1156                if let Some(variadic) = expansion.variadic {
1157                    let variadic_array = self.interner.array(variadic);
1158                    for s_elem in source_iter {
1159                        if s_elem.rest {
1160                            if !self.is_subtype(s_elem.type_id, variadic_array) {
1161                                return false;
1162                            }
1163                        } else if !self.is_subtype(s_elem.type_id, variadic) {
1164                            return false;
1165                        }
1166                    }
1167                    return true;
1168                }
1169
1170                if source_iter.next().is_some() {
1171                    return false;
1172                }
1173                return true;
1174            }
1175
1176            if let Some(s_elem) = source.get(i) {
1177                if s_elem.rest {
1178                    return false;
1179                }
1180                if !self.is_subtype(s_elem.type_id, t_elem.type_id) {
1181                    return false;
1182                }
1183            } else if !t_elem.optional {
1184                return false;
1185            }
1186        }
1187
1188        if source.len() > target.len() {
1189            return false;
1190        }
1191
1192        if source.iter().any(|elem| elem.rest) {
1193            return false;
1194        }
1195
1196        true
1197    }
1198
1199    fn expand_tuple_rest(&self, type_id: TypeId) -> TupleRestExpansion {
1200        match self.interner.lookup(type_id) {
1201            Some(TypeData::Array(elem)) => TupleRestExpansion {
1202                fixed: Vec::new(),
1203                variadic: Some(elem),
1204                tail: Vec::new(),
1205            },
1206            Some(TypeData::Tuple(elements)) => {
1207                let elements = self.interner.tuple_list(elements);
1208                let mut fixed = Vec::new();
1209                for (i, elem) in elements.iter().enumerate() {
1210                    if elem.rest {
1211                        let inner = self.expand_tuple_rest(elem.type_id);
1212                        fixed.extend(inner.fixed);
1213                        // Capture tail elements: inner.tail + elements after the rest
1214                        let mut tail = inner.tail;
1215                        tail.extend(elements[i + 1..].iter().cloned());
1216                        return TupleRestExpansion {
1217                            fixed,
1218                            variadic: inner.variadic,
1219                            tail,
1220                        };
1221                    }
1222                    fixed.push(elem.clone());
1223                }
1224                TupleRestExpansion {
1225                    fixed,
1226                    variadic: None,
1227                    tail: Vec::new(),
1228                }
1229            }
1230            _ => TupleRestExpansion {
1231                fixed: Vec::new(),
1232                variadic: Some(type_id),
1233                tail: Vec::new(),
1234            },
1235        }
1236    }
1237}