Skip to main content

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