Skip to main content

tsz_solver/
infer.rs

1//! Type inference engine using Union-Find.
2//!
3//! This module implements type inference for generic functions using
4//! the `ena` crate's Union-Find data structure.
5//!
6//! Key features:
7//! - Inference variables for generic type parameters
8//! - Constraint collection during type checking
9//! - Bounds checking (L <: α <: U)
10//! - Best common type calculation
11//! - Efficient unification with path compression
12
13use crate::TypeDatabase;
14#[cfg(test)]
15use crate::types::*;
16use crate::types::{
17    CallableShapeId, FunctionShapeId, InferencePriority, IntrinsicKind, LiteralValue, MappedTypeId,
18    ObjectShapeId, TemplateLiteralId, TemplateSpan, TupleElement, TupleListId, TypeApplicationId,
19    TypeData, TypeId, TypeListId,
20};
21use crate::visitor::is_literal_type;
22use ena::unify::{InPlaceUnificationTable, NoError, UnifyKey, UnifyValue};
23use rustc_hash::{FxHashMap, FxHashSet};
24use std::cell::RefCell;
25use tsz_common::interner::Atom;
26
27/// Helper function to extend a vector with deduplicated items.
28/// Uses a `HashSet` for O(1) lookups instead of O(n) contains checks.
29fn extend_dedup<T>(target: &mut Vec<T>, items: &[T])
30where
31    T: Clone + Eq + std::hash::Hash,
32{
33    if items.is_empty() {
34        return;
35    }
36
37    // Hot path for inference: most merges add a single item.
38    // Avoid allocating/hash-building a set for that case.
39    if items.len() == 1 {
40        let item = &items[0];
41        if !target.contains(item) {
42            target.push(item.clone());
43        }
44        return;
45    }
46
47    let mut existing: FxHashSet<_> = target.iter().cloned().collect();
48    for item in items {
49        if existing.insert(item.clone()) {
50            target.push(item.clone());
51        }
52    }
53}
54
55/// An inference variable representing an unknown type.
56/// These are created when instantiating generic functions.
57#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
58pub struct InferenceVar(pub u32);
59
60// Uses TypeScript-standard InferencePriority from types.rs
61
62/// A candidate type for an inference variable.
63#[derive(Clone, Debug, PartialEq, Eq, Hash)]
64pub struct InferenceCandidate {
65    pub type_id: TypeId,
66    pub priority: InferencePriority,
67    pub is_fresh_literal: bool,
68}
69
70/// Value stored for each inference variable root.
71#[derive(Clone, Debug, Default)]
72pub struct InferenceInfo {
73    pub candidates: Vec<InferenceCandidate>,
74    pub upper_bounds: Vec<TypeId>,
75    pub resolved: Option<TypeId>,
76}
77
78impl InferenceInfo {
79    pub const fn is_empty(&self) -> bool {
80        self.candidates.is_empty() && self.upper_bounds.is_empty()
81    }
82}
83
84impl UnifyKey for InferenceVar {
85    type Value = InferenceInfo;
86
87    fn index(&self) -> u32 {
88        self.0
89    }
90
91    fn from_index(u: u32) -> Self {
92        Self(u)
93    }
94
95    fn tag() -> &'static str {
96        "InferenceVar"
97    }
98}
99
100impl UnifyValue for InferenceInfo {
101    type Error = NoError;
102
103    fn unify_values(a: &Self, b: &Self) -> Result<Self, Self::Error> {
104        let mut merged = a.clone();
105
106        // Deduplicate candidates using helper
107        extend_dedup(&mut merged.candidates, &b.candidates);
108
109        // Deduplicate upper bounds using helper
110        extend_dedup(&mut merged.upper_bounds, &b.upper_bounds);
111
112        if merged.resolved.is_none() {
113            merged.resolved = b.resolved;
114        }
115        Ok(merged)
116    }
117}
118
119/// Inference error
120#[derive(Clone, Debug)]
121pub enum InferenceError {
122    /// Two incompatible types were unified
123    Conflict(TypeId, TypeId),
124    /// Inference variable was not resolved
125    Unresolved(InferenceVar),
126    /// Circular unification detected (occurs-check)
127    OccursCheck { var: InferenceVar, ty: TypeId },
128    /// Lower bound is not subtype of upper bound
129    BoundsViolation {
130        var: InferenceVar,
131        lower: TypeId,
132        upper: TypeId,
133    },
134    /// Variance violation detected
135    VarianceViolation {
136        var: InferenceVar,
137        expected_variance: &'static str,
138        position: TypeId,
139    },
140}
141
142/// Constraint set for an inference variable.
143/// Tracks both lower bounds (L <: α) and upper bounds (α <: U).
144#[derive(Clone, Debug, Default)]
145pub struct ConstraintSet {
146    /// Lower bounds: types that must be subtypes of this variable
147    /// e.g., from argument types being assigned to a parameter
148    pub lower_bounds: Vec<TypeId>,
149    /// Upper bounds: types that this variable must be a subtype of
150    /// e.g., from `extends` constraints on type parameters
151    pub upper_bounds: Vec<TypeId>,
152}
153
154impl ConstraintSet {
155    pub const fn new() -> Self {
156        Self {
157            lower_bounds: Vec::new(),
158            upper_bounds: Vec::new(),
159        }
160    }
161
162    pub fn from_info(info: &InferenceInfo) -> Self {
163        let mut lower_bounds = Vec::new();
164        let mut upper_bounds = Vec::new();
165        let mut seen_lower = FxHashSet::default();
166        let mut seen_upper = FxHashSet::default();
167
168        for candidate in &info.candidates {
169            if seen_lower.insert(candidate.type_id) {
170                lower_bounds.push(candidate.type_id);
171            }
172        }
173
174        for &upper in &info.upper_bounds {
175            if seen_upper.insert(upper) {
176                upper_bounds.push(upper);
177            }
178        }
179
180        Self {
181            lower_bounds,
182            upper_bounds,
183        }
184    }
185
186    /// Add a lower bound constraint: L <: α
187    pub fn add_lower_bound(&mut self, ty: TypeId) {
188        if !self.lower_bounds.contains(&ty) {
189            self.lower_bounds.push(ty);
190        }
191    }
192
193    /// Add an upper bound constraint: α <: U
194    pub fn add_upper_bound(&mut self, ty: TypeId) {
195        if !self.upper_bounds.contains(&ty) {
196            self.upper_bounds.push(ty);
197        }
198    }
199
200    /// Check if there are any constraints
201    pub const fn is_empty(&self) -> bool {
202        self.lower_bounds.is_empty() && self.upper_bounds.is_empty()
203    }
204
205    pub fn merge_from(&mut self, other: Self) {
206        for ty in other.lower_bounds {
207            self.add_lower_bound(ty);
208        }
209        for ty in other.upper_bounds {
210            self.add_upper_bound(ty);
211        }
212    }
213
214    /// Perform transitive reduction on upper bounds to remove redundant constraints.
215    ///
216    /// If we have constraints (T <: A) and (T <: B) and we know (A <: B),
217    /// then (T <: B) is redundant and can be removed.
218    ///
219    /// This reduces N² pairwise checks in `detect_conflicts` to O(N * `reduced_N`).
220    pub fn transitive_reduction(&mut self, interner: &dyn TypeDatabase) {
221        if self.upper_bounds.len() < 2 {
222            return;
223        }
224
225        let mut redundant = FxHashSet::default();
226        let bounds = &self.upper_bounds;
227
228        for (i, &u1) in bounds.iter().enumerate() {
229            for (j, &u2) in bounds.iter().enumerate() {
230                if i == j || redundant.contains(&u1) || redundant.contains(&u2) {
231                    continue;
232                }
233
234                // If u1 <: u2, then u2 is redundant (u1 is a stricter constraint)
235                if crate::subtype::is_subtype_of(interner, u1, u2) {
236                    redundant.insert(u2);
237                }
238            }
239        }
240
241        if !redundant.is_empty() {
242            self.upper_bounds.retain(|ty| !redundant.contains(ty));
243        }
244    }
245
246    /// Detect early conflicts between collected constraints.
247    /// This allows failing fast before full resolution.
248    pub fn detect_conflicts(&self, interner: &dyn TypeDatabase) -> Option<ConstraintConflict> {
249        // PERF: Transitive reduction of upper bounds to minimize N² checks.
250        let mut reduced_upper = self.upper_bounds.clone();
251        if reduced_upper.len() >= 2 {
252            let mut redundant = FxHashSet::default();
253            for (i, &u1) in reduced_upper.iter().enumerate() {
254                for (j, &u2) in reduced_upper.iter().enumerate() {
255                    if i == j || redundant.contains(&u1) || redundant.contains(&u2) {
256                        continue;
257                    }
258                    if crate::subtype::is_subtype_of(interner, u1, u2) {
259                        redundant.insert(u2);
260                    }
261                }
262            }
263            if !redundant.is_empty() {
264                reduced_upper.retain(|ty| !redundant.contains(ty));
265            }
266        }
267
268        // 1. Check for mutually exclusive upper bounds
269        for (i, &u1) in reduced_upper.iter().enumerate() {
270            for &u2 in &reduced_upper[i + 1..] {
271                if are_disjoint(interner, u1, u2) {
272                    return Some(ConstraintConflict::DisjointUpperBounds(u1, u2));
273                }
274            }
275        }
276
277        // 2. Check if any lower bound is incompatible with any upper bound
278        for &lower in &self.lower_bounds {
279            for &upper in &reduced_upper {
280                // Ignore ERROR and ANY for conflict detection
281                if lower == TypeId::ERROR
282                    || upper == TypeId::ERROR
283                    || lower == TypeId::ANY
284                    || upper == TypeId::ANY
285                {
286                    continue;
287                }
288                if !crate::subtype::is_subtype_of(interner, lower, upper) {
289                    return Some(ConstraintConflict::LowerExceedsUpper(lower, upper));
290                }
291            }
292        }
293
294        None
295    }
296}
297
298/// Conflict detected between constraints on an inference variable.
299#[derive(Clone, Debug)]
300pub enum ConstraintConflict {
301    /// Mutually exclusive upper bounds (e.g., string AND number)
302    DisjointUpperBounds(TypeId, TypeId),
303    /// A lower bound is not a subtype of an upper bound
304    LowerExceedsUpper(TypeId, TypeId),
305}
306
307/// Helper to determine if two types are definitely disjoint (no common inhabitants).
308fn are_disjoint(interner: &dyn TypeDatabase, a: TypeId, b: TypeId) -> bool {
309    if a == b {
310        return false;
311    }
312    if a.is_any_or_unknown() || b.is_any_or_unknown() {
313        return false;
314    }
315
316    let key_a = interner.lookup(a);
317    let key_b = interner.lookup(b);
318
319    match (key_a, key_b) {
320        (Some(TypeData::Intrinsic(k1)), Some(TypeData::Intrinsic(k2))) => {
321            use IntrinsicKind::*;
322            // Basic primitives are disjoint (ignoring object/Function which are more complex)
323            k1 != k2 && !matches!((k1, k2), (Object | Function, _) | (_, Object | Function))
324        }
325        (Some(TypeData::Literal(l1)), Some(TypeData::Literal(l2))) => l1 != l2,
326        (Some(TypeData::Literal(l1)), Some(TypeData::Intrinsic(k2))) => {
327            !is_literal_compatible_with_intrinsic(&l1, k2)
328        }
329        (Some(TypeData::Intrinsic(k1)), Some(TypeData::Literal(l2))) => {
330            !is_literal_compatible_with_intrinsic(&l2, k1)
331        }
332        _ => false,
333    }
334}
335
336fn is_literal_compatible_with_intrinsic(lit: &LiteralValue, kind: IntrinsicKind) -> bool {
337    match lit {
338        LiteralValue::String(_) => kind == IntrinsicKind::String,
339        LiteralValue::Number(_) => kind == IntrinsicKind::Number,
340        LiteralValue::BigInt(_) => kind == IntrinsicKind::Bigint,
341        LiteralValue::Boolean(_) => kind == IntrinsicKind::Boolean,
342    }
343}
344
345/// Maximum iterations for constraint strengthening loops to prevent infinite loops.
346pub const MAX_CONSTRAINT_ITERATIONS: usize = 100;
347
348/// Maximum recursion depth for type containment checks.
349pub const MAX_TYPE_RECURSION_DEPTH: usize = 100;
350
351/// Type inference context for a single function call or expression.
352pub struct InferenceContext<'a> {
353    pub(crate) interner: &'a dyn TypeDatabase,
354    /// Type resolver for semantic lookups (e.g., base class queries)
355    pub(crate) resolver: Option<&'a dyn crate::TypeResolver>,
356    /// Memoized subtype checks used by BCT and bound validation.
357    pub(crate) subtype_cache: RefCell<FxHashMap<(TypeId, TypeId), bool>>,
358    /// Unification table for inference variables
359    pub(crate) table: InPlaceUnificationTable<InferenceVar>,
360    /// Map from type parameter names to inference variables, with const flag
361    pub(crate) type_params: Vec<(Atom, InferenceVar, bool)>,
362}
363
364impl<'a> InferenceContext<'a> {
365    pub(crate) const UPPER_BOUND_INTERSECTION_FAST_PATH_LIMIT: usize = 8;
366    pub(crate) const UPPER_BOUND_INTERSECTION_LARGE_SET_THRESHOLD: usize = 64;
367
368    pub fn new(interner: &'a dyn TypeDatabase) -> Self {
369        InferenceContext {
370            interner,
371            resolver: None,
372            subtype_cache: RefCell::new(FxHashMap::default()),
373            table: InPlaceUnificationTable::new(),
374            type_params: Vec::new(),
375        }
376    }
377
378    pub fn with_resolver(
379        interner: &'a dyn TypeDatabase,
380        resolver: &'a dyn crate::TypeResolver,
381    ) -> Self {
382        InferenceContext {
383            interner,
384            resolver: Some(resolver),
385            subtype_cache: RefCell::new(FxHashMap::default()),
386            table: InPlaceUnificationTable::new(),
387            type_params: Vec::new(),
388        }
389    }
390
391    /// Create a fresh inference variable
392    pub fn fresh_var(&mut self) -> InferenceVar {
393        self.table.new_key(InferenceInfo::default())
394    }
395
396    /// Create an inference variable for a type parameter
397    pub fn fresh_type_param(&mut self, name: Atom, is_const: bool) -> InferenceVar {
398        let var = self.fresh_var();
399        self.type_params.push((name, var, is_const));
400        var
401    }
402
403    /// Register an existing inference variable as representing a type parameter.
404    ///
405    /// This is useful when the caller needs to compute a unique placeholder name
406    /// (and corresponding placeholder `TypeId`) after allocating the inference variable.
407    pub fn register_type_param(&mut self, name: Atom, var: InferenceVar, is_const: bool) {
408        self.type_params.push((name, var, is_const));
409    }
410
411    /// Look up an inference variable by type parameter name
412    pub fn find_type_param(&self, name: Atom) -> Option<InferenceVar> {
413        self.type_params
414            .iter()
415            .find(|(n, _, _)| *n == name)
416            .map(|(_, v, _)| *v)
417    }
418
419    /// Check if an inference variable is a const type parameter
420    pub fn is_var_const(&mut self, var: InferenceVar) -> bool {
421        let root = self.table.find(var);
422        self.type_params
423            .iter()
424            .any(|(_, v, is_const)| self.table.find(*v) == root && *is_const)
425    }
426
427    /// Probe the current value of an inference variable
428    pub fn probe(&mut self, var: InferenceVar) -> Option<TypeId> {
429        self.table.probe_value(var).resolved
430    }
431
432    /// Unify an inference variable with a concrete type
433    pub fn unify_var_type(&mut self, var: InferenceVar, ty: TypeId) -> Result<(), InferenceError> {
434        // Get the root variable
435        let root = self.table.find(var);
436
437        if self.occurs_in(root, ty) {
438            return Err(InferenceError::OccursCheck { var: root, ty });
439        }
440
441        // Check current value
442        match self.table.probe_value(root).resolved {
443            None => {
444                self.table.union_value(
445                    root,
446                    InferenceInfo {
447                        resolved: Some(ty),
448                        ..InferenceInfo::default()
449                    },
450                );
451                Ok(())
452            }
453            Some(existing) => {
454                if self.types_compatible(existing, ty) {
455                    Ok(())
456                } else {
457                    Err(InferenceError::Conflict(existing, ty))
458                }
459            }
460        }
461    }
462
463    /// Unify two inference variables
464    pub fn unify_vars(&mut self, a: InferenceVar, b: InferenceVar) -> Result<(), InferenceError> {
465        let root_a = self.table.find(a);
466        let root_b = self.table.find(b);
467
468        if root_a == root_b {
469            return Ok(());
470        }
471
472        let value_a = self.table.probe_value(root_a).resolved;
473        let value_b = self.table.probe_value(root_b).resolved;
474        if let (Some(a_ty), Some(b_ty)) = (value_a, value_b)
475            && !self.types_compatible(a_ty, b_ty)
476        {
477            return Err(InferenceError::Conflict(a_ty, b_ty));
478        }
479
480        self.table
481            .unify_var_var(root_a, root_b)
482            .map_err(|_| InferenceError::Conflict(TypeId::ERROR, TypeId::ERROR))?;
483        Ok(())
484    }
485
486    /// Check if two types are compatible for unification
487    fn types_compatible(&self, a: TypeId, b: TypeId) -> bool {
488        if a == b {
489            return true;
490        }
491
492        // Any is compatible with everything
493        if a == TypeId::ANY || b == TypeId::ANY {
494            return true;
495        }
496
497        // Unknown is compatible with everything
498        if a == TypeId::UNKNOWN || b == TypeId::UNKNOWN {
499            return true;
500        }
501
502        // Never is compatible with everything
503        if a == TypeId::NEVER || b == TypeId::NEVER {
504            return true;
505        }
506
507        false
508    }
509
510    pub(crate) fn occurs_in(&mut self, var: InferenceVar, ty: TypeId) -> bool {
511        let root = self.table.find(var);
512        if self.type_params.is_empty() {
513            return false;
514        }
515
516        let mut visited = FxHashSet::default();
517        for &(atom, param_var, _) in &self.type_params {
518            if self.table.find(param_var) == root
519                && self.type_contains_param(ty, atom, &mut visited)
520            {
521                return true;
522            }
523        }
524        false
525    }
526
527    pub(crate) fn type_param_names_for_root(&mut self, root: InferenceVar) -> Vec<Atom> {
528        self.type_params
529            .iter()
530            .filter(|&(_name, var, _)| self.table.find(*var) == root)
531            .map(|(name, _var, _)| *name)
532            .collect()
533    }
534
535    pub(crate) fn upper_bound_cycles_param(&mut self, bound: TypeId, targets: &[Atom]) -> bool {
536        let mut params = FxHashSet::default();
537        let mut visited = FxHashSet::default();
538        self.collect_type_params(bound, &mut params, &mut visited);
539
540        for name in params {
541            let mut seen = FxHashSet::default();
542            if self.param_depends_on_targets(name, targets, &mut seen) {
543                return true;
544            }
545        }
546
547        false
548    }
549
550    pub(crate) fn expand_cyclic_upper_bound(
551        &mut self,
552        root: InferenceVar,
553        bound: TypeId,
554        target_names: &[Atom],
555        candidates: &mut Vec<InferenceCandidate>,
556        upper_bounds: &mut Vec<TypeId>,
557    ) {
558        let name = match self.interner.lookup(bound) {
559            Some(TypeData::TypeParameter(info) | TypeData::Infer(info)) => info.name,
560            _ => return,
561        };
562
563        let Some(var) = self.find_type_param(name) else {
564            return;
565        };
566
567        if let Some(resolved) = self.probe(var) {
568            if !upper_bounds.contains(&resolved) {
569                upper_bounds.push(resolved);
570            }
571            return;
572        }
573
574        let bound_root = self.table.find(var);
575        let info = self.table.probe_value(bound_root);
576
577        for candidate in info.candidates {
578            if self.occurs_in(root, candidate.type_id) {
579                continue;
580            }
581            candidates.push(InferenceCandidate {
582                type_id: candidate.type_id,
583                priority: InferencePriority::Circular,
584                is_fresh_literal: candidate.is_fresh_literal,
585            });
586        }
587
588        for ty in info.upper_bounds {
589            if self.occurs_in(root, ty) {
590                continue;
591            }
592            if !target_names.is_empty() && self.upper_bound_cycles_param(ty, target_names) {
593                continue;
594            }
595            if !upper_bounds.contains(&ty) {
596                upper_bounds.push(ty);
597            }
598        }
599    }
600
601    fn collect_type_params(
602        &self,
603        ty: TypeId,
604        params: &mut FxHashSet<Atom>,
605        visited: &mut FxHashSet<TypeId>,
606    ) {
607        if !visited.insert(ty) {
608            return;
609        }
610        let Some(key) = self.interner.lookup(ty) else {
611            return;
612        };
613
614        match key {
615            TypeData::TypeParameter(info) | TypeData::Infer(info) => {
616                params.insert(info.name);
617            }
618            TypeData::Array(elem) => {
619                self.collect_type_params(elem, params, visited);
620            }
621            TypeData::Tuple(elements) => {
622                let elements = self.interner.tuple_list(elements);
623                for element in elements.iter() {
624                    self.collect_type_params(element.type_id, params, visited);
625                }
626            }
627            TypeData::Union(members) | TypeData::Intersection(members) => {
628                let members = self.interner.type_list(members);
629                for &member in members.iter() {
630                    self.collect_type_params(member, params, visited);
631                }
632            }
633            TypeData::Object(shape_id) => {
634                let shape = self.interner.object_shape(shape_id);
635                for prop in &shape.properties {
636                    self.collect_type_params(prop.type_id, params, visited);
637                }
638            }
639            TypeData::ObjectWithIndex(shape_id) => {
640                let shape = self.interner.object_shape(shape_id);
641                for prop in &shape.properties {
642                    self.collect_type_params(prop.type_id, params, visited);
643                }
644                if let Some(index) = shape.string_index.as_ref() {
645                    self.collect_type_params(index.key_type, params, visited);
646                    self.collect_type_params(index.value_type, params, visited);
647                }
648                if let Some(index) = shape.number_index.as_ref() {
649                    self.collect_type_params(index.key_type, params, visited);
650                    self.collect_type_params(index.value_type, params, visited);
651                }
652            }
653            TypeData::Application(app_id) => {
654                let app = self.interner.type_application(app_id);
655                self.collect_type_params(app.base, params, visited);
656                for &arg in &app.args {
657                    self.collect_type_params(arg, params, visited);
658                }
659            }
660            TypeData::Function(shape_id) => {
661                let shape = self.interner.function_shape(shape_id);
662                for param in &shape.params {
663                    self.collect_type_params(param.type_id, params, visited);
664                }
665                if let Some(this_type) = shape.this_type {
666                    self.collect_type_params(this_type, params, visited);
667                }
668                self.collect_type_params(shape.return_type, params, visited);
669            }
670            TypeData::Callable(shape_id) => {
671                let shape = self.interner.callable_shape(shape_id);
672                for sig in &shape.call_signatures {
673                    for param in &sig.params {
674                        self.collect_type_params(param.type_id, params, visited);
675                    }
676                    if let Some(this_type) = sig.this_type {
677                        self.collect_type_params(this_type, params, visited);
678                    }
679                    self.collect_type_params(sig.return_type, params, visited);
680                }
681                for sig in &shape.construct_signatures {
682                    for param in &sig.params {
683                        self.collect_type_params(param.type_id, params, visited);
684                    }
685                    if let Some(this_type) = sig.this_type {
686                        self.collect_type_params(this_type, params, visited);
687                    }
688                    self.collect_type_params(sig.return_type, params, visited);
689                }
690                for prop in &shape.properties {
691                    self.collect_type_params(prop.type_id, params, visited);
692                }
693            }
694            TypeData::Conditional(cond_id) => {
695                let cond = self.interner.conditional_type(cond_id);
696                self.collect_type_params(cond.check_type, params, visited);
697                self.collect_type_params(cond.extends_type, params, visited);
698                self.collect_type_params(cond.true_type, params, visited);
699                self.collect_type_params(cond.false_type, params, visited);
700            }
701            TypeData::Mapped(mapped_id) => {
702                let mapped = self.interner.mapped_type(mapped_id);
703                self.collect_type_params(mapped.constraint, params, visited);
704                if let Some(name_type) = mapped.name_type {
705                    self.collect_type_params(name_type, params, visited);
706                }
707                self.collect_type_params(mapped.template, params, visited);
708            }
709            TypeData::IndexAccess(obj, idx) => {
710                self.collect_type_params(obj, params, visited);
711                self.collect_type_params(idx, params, visited);
712            }
713            TypeData::KeyOf(operand) | TypeData::ReadonlyType(operand) => {
714                self.collect_type_params(operand, params, visited);
715            }
716            TypeData::TemplateLiteral(spans) => {
717                let spans = self.interner.template_list(spans);
718                for span in spans.iter() {
719                    if let TemplateSpan::Type(inner) = span {
720                        self.collect_type_params(*inner, params, visited);
721                    }
722                }
723            }
724            TypeData::StringIntrinsic { type_arg, .. } => {
725                self.collect_type_params(type_arg, params, visited);
726            }
727            TypeData::Enum(_def_id, member_type) => {
728                // Recurse into the structural member type
729                self.collect_type_params(member_type, params, visited);
730            }
731            TypeData::Intrinsic(_)
732            | TypeData::Literal(_)
733            | TypeData::Lazy(_)
734            | TypeData::Recursive(_)
735            | TypeData::BoundParameter(_)
736            | TypeData::TypeQuery(_)
737            | TypeData::UniqueSymbol(_)
738            | TypeData::ThisType
739            | TypeData::ModuleNamespace(_)
740            | TypeData::Error => {}
741            TypeData::NoInfer(inner) => {
742                self.collect_type_params(inner, params, visited);
743            }
744        }
745    }
746
747    fn param_depends_on_targets(
748        &mut self,
749        name: Atom,
750        targets: &[Atom],
751        visited: &mut FxHashSet<Atom>,
752    ) -> bool {
753        if targets.contains(&name) {
754            return true;
755        }
756        if !visited.insert(name) {
757            return false;
758        }
759        let Some(var) = self.find_type_param(name) else {
760            return false;
761        };
762        let root = self.table.find(var);
763        let upper_bounds = self.table.probe_value(root).upper_bounds;
764
765        for bound in upper_bounds {
766            for target in targets {
767                let mut seen = FxHashSet::default();
768                if self.type_contains_param(bound, *target, &mut seen) {
769                    return true;
770                }
771            }
772            if let Some(TypeData::TypeParameter(info)) = self.interner.lookup(bound)
773                && self.param_depends_on_targets(info.name, targets, visited)
774            {
775                return true;
776            }
777        }
778
779        false
780    }
781
782    fn type_contains_param(
783        &self,
784        ty: TypeId,
785        target: Atom,
786        visited: &mut FxHashSet<TypeId>,
787    ) -> bool {
788        if !visited.insert(ty) {
789            return false;
790        }
791
792        let key = match self.interner.lookup(ty) {
793            Some(key) => key,
794            None => return false,
795        };
796
797        match key {
798            TypeData::TypeParameter(info) | TypeData::Infer(info) => info.name == target,
799            TypeData::Array(elem) => self.type_contains_param(elem, target, visited),
800            TypeData::Tuple(elements) => {
801                let elements = self.interner.tuple_list(elements);
802                elements
803                    .iter()
804                    .any(|e| self.type_contains_param(e.type_id, target, visited))
805            }
806            TypeData::Union(members) | TypeData::Intersection(members) => {
807                let members = self.interner.type_list(members);
808                members
809                    .iter()
810                    .any(|&member| self.type_contains_param(member, target, visited))
811            }
812            TypeData::Object(shape_id) => {
813                let shape = self.interner.object_shape(shape_id);
814                shape
815                    .properties
816                    .iter()
817                    .any(|p| self.type_contains_param(p.type_id, target, visited))
818            }
819            TypeData::ObjectWithIndex(shape_id) => {
820                let shape = self.interner.object_shape(shape_id);
821                shape
822                    .properties
823                    .iter()
824                    .any(|p| self.type_contains_param(p.type_id, target, visited))
825                    || shape.string_index.as_ref().is_some_and(|idx| {
826                        self.type_contains_param(idx.key_type, target, visited)
827                            || self.type_contains_param(idx.value_type, target, visited)
828                    })
829                    || shape.number_index.as_ref().is_some_and(|idx| {
830                        self.type_contains_param(idx.key_type, target, visited)
831                            || self.type_contains_param(idx.value_type, target, visited)
832                    })
833            }
834            TypeData::Application(app_id) => {
835                let app = self.interner.type_application(app_id);
836                self.type_contains_param(app.base, target, visited)
837                    || app
838                        .args
839                        .iter()
840                        .any(|&arg| self.type_contains_param(arg, target, visited))
841            }
842            TypeData::Function(shape_id) => {
843                let shape = self.interner.function_shape(shape_id);
844                if shape.type_params.iter().any(|tp| tp.name == target) {
845                    return false;
846                }
847                shape
848                    .this_type
849                    .is_some_and(|this_type| self.type_contains_param(this_type, target, visited))
850                    || shape
851                        .params
852                        .iter()
853                        .any(|p| self.type_contains_param(p.type_id, target, visited))
854                    || self.type_contains_param(shape.return_type, target, visited)
855            }
856            TypeData::Callable(shape_id) => {
857                let shape = self.interner.callable_shape(shape_id);
858                let in_call = shape.call_signatures.iter().any(|sig| {
859                    if sig.type_params.iter().any(|tp| tp.name == target) {
860                        false
861                    } else {
862                        sig.this_type.is_some_and(|this_type| {
863                            self.type_contains_param(this_type, target, visited)
864                        }) || sig
865                            .params
866                            .iter()
867                            .any(|p| self.type_contains_param(p.type_id, target, visited))
868                            || self.type_contains_param(sig.return_type, target, visited)
869                    }
870                });
871                if in_call {
872                    return true;
873                }
874                let in_construct = shape.construct_signatures.iter().any(|sig| {
875                    if sig.type_params.iter().any(|tp| tp.name == target) {
876                        false
877                    } else {
878                        sig.this_type.is_some_and(|this_type| {
879                            self.type_contains_param(this_type, target, visited)
880                        }) || sig
881                            .params
882                            .iter()
883                            .any(|p| self.type_contains_param(p.type_id, target, visited))
884                            || self.type_contains_param(sig.return_type, target, visited)
885                    }
886                });
887                if in_construct {
888                    return true;
889                }
890                shape
891                    .properties
892                    .iter()
893                    .any(|p| self.type_contains_param(p.type_id, target, visited))
894            }
895            TypeData::Conditional(cond_id) => {
896                let cond = self.interner.conditional_type(cond_id);
897                self.type_contains_param(cond.check_type, target, visited)
898                    || self.type_contains_param(cond.extends_type, target, visited)
899                    || self.type_contains_param(cond.true_type, target, visited)
900                    || self.type_contains_param(cond.false_type, target, visited)
901            }
902            TypeData::Mapped(mapped_id) => {
903                let mapped = self.interner.mapped_type(mapped_id);
904                if mapped.type_param.name == target {
905                    return false;
906                }
907                self.type_contains_param(mapped.constraint, target, visited)
908                    || self.type_contains_param(mapped.template, target, visited)
909            }
910            TypeData::IndexAccess(obj, idx) => {
911                self.type_contains_param(obj, target, visited)
912                    || self.type_contains_param(idx, target, visited)
913            }
914            TypeData::KeyOf(operand) | TypeData::ReadonlyType(operand) => {
915                self.type_contains_param(operand, target, visited)
916            }
917            TypeData::TemplateLiteral(spans) => {
918                let spans = self.interner.template_list(spans);
919                spans.iter().any(|span| match span {
920                    TemplateSpan::Text(_) => false,
921                    TemplateSpan::Type(inner) => self.type_contains_param(*inner, target, visited),
922                })
923            }
924            TypeData::StringIntrinsic { type_arg, .. } => {
925                self.type_contains_param(type_arg, target, visited)
926            }
927            TypeData::Enum(_def_id, member_type) => {
928                // Recurse into the structural member type
929                self.type_contains_param(member_type, target, visited)
930            }
931            TypeData::Intrinsic(_)
932            | TypeData::Literal(_)
933            | TypeData::Lazy(_)
934            | TypeData::Recursive(_)
935            | TypeData::BoundParameter(_)
936            | TypeData::TypeQuery(_)
937            | TypeData::UniqueSymbol(_)
938            | TypeData::ThisType
939            | TypeData::ModuleNamespace(_)
940            | TypeData::Error => false,
941            TypeData::NoInfer(inner) => self.type_contains_param(inner, target, visited),
942        }
943    }
944
945    /// Resolve all type parameters to concrete types
946    pub fn resolve_all(&mut self) -> Result<Vec<(Atom, TypeId)>, InferenceError> {
947        // Clone type_params to avoid borrow conflict
948        let type_params: Vec<_> = self.type_params.clone();
949        let mut results = Vec::new();
950        for (name, var, _) in type_params {
951            match self.probe(var) {
952                Some(ty) => results.push((name, ty)),
953                None => return Err(InferenceError::Unresolved(var)),
954            }
955        }
956        Ok(results)
957    }
958
959    /// Get the interner reference
960    pub fn interner(&self) -> &dyn TypeDatabase {
961        self.interner
962    }
963
964    // =========================================================================
965    // Constraint Collection
966    // =========================================================================
967
968    /// Add a lower bound constraint: ty <: var
969    /// This is used when an argument type flows into a type parameter.
970    /// Updated to use `NakedTypeVariable` (highest priority) for direct argument inference.
971    pub fn add_lower_bound(&mut self, var: InferenceVar, ty: TypeId) {
972        self.add_candidate(var, ty, InferencePriority::NakedTypeVariable);
973    }
974
975    /// Add an inference candidate for a variable.
976    pub fn add_candidate(&mut self, var: InferenceVar, ty: TypeId, priority: InferencePriority) {
977        let root = self.table.find(var);
978        let candidate = InferenceCandidate {
979            type_id: ty,
980            priority,
981            is_fresh_literal: is_literal_type(self.interner, ty),
982        };
983        self.table.union_value(
984            root,
985            InferenceInfo {
986                candidates: vec![candidate],
987                ..InferenceInfo::default()
988            },
989        );
990    }
991
992    /// Add an upper bound constraint: var <: ty
993    /// This is used for `extends` constraints on type parameters.
994    pub fn add_upper_bound(&mut self, var: InferenceVar, ty: TypeId) {
995        let root = self.table.find(var);
996        self.table.union_value(
997            root,
998            InferenceInfo {
999                upper_bounds: vec![ty],
1000                ..InferenceInfo::default()
1001            },
1002        );
1003    }
1004
1005    /// Get the constraints for a variable
1006    pub fn get_constraints(&mut self, var: InferenceVar) -> Option<ConstraintSet> {
1007        let root = self.table.find(var);
1008        let info = self.table.probe_value(root);
1009        if info.is_empty() {
1010            None
1011        } else {
1012            Some(ConstraintSet::from_info(&info))
1013        }
1014    }
1015
1016    /// Check if all inference candidates for a variable have `ReturnType` priority.
1017    /// This indicates the type was inferred from callback return types (Round 2),
1018    /// not from direct arguments (Round 1).
1019    pub fn all_candidates_are_return_type(&mut self, var: InferenceVar) -> bool {
1020        let root = self.table.find(var);
1021        let info = self.table.probe_value(root);
1022        !info.candidates.is_empty()
1023            && info
1024                .candidates
1025                .iter()
1026                .all(|c| c.priority == InferencePriority::ReturnType)
1027    }
1028
1029    /// Get the original un-widened literal candidate types for an inference variable.
1030    pub fn get_literal_candidates(&mut self, var: InferenceVar) -> Vec<TypeId> {
1031        let root = self.table.find(var);
1032        let info = self.table.probe_value(root);
1033        info.candidates
1034            .iter()
1035            .filter(|c| c.is_fresh_literal)
1036            .map(|c| c.type_id)
1037            .collect()
1038    }
1039
1040    /// Collect a constraint from an assignment: source flows into target
1041    /// If target is an inference variable, source becomes a lower bound.
1042    /// If source is an inference variable, target becomes an upper bound.
1043    pub const fn collect_constraint(&mut self, _source: TypeId, _target: TypeId) {
1044        // Check if target is an inference variable (via TypeData lookup)
1045        // For now, we rely on the caller to call add_lower_bound/add_upper_bound directly
1046        // This is a placeholder for more sophisticated constraint collection
1047    }
1048
1049    /// Perform structural type inference from a source type to a target type.
1050    ///
1051    /// This is the core algorithm for inferring type parameters from function arguments.
1052    /// It walks the structure of both types, collecting constraints for type parameters.
1053    ///
1054    /// # Arguments
1055    /// * `source` - The type from the value argument (e.g., `string` from `identity("hello")`)
1056    /// * `target` - The type from the parameter (e.g., `T` from `function identity<T>(x: T)`)
1057    /// * `priority` - The inference priority (e.g., `NakedTypeVariable` for direct arguments)
1058    ///
1059    /// # Type Inference Algorithm
1060    ///
1061    /// TypeScript uses structural type inference with the following rules:
1062    ///
1063    /// 1. **Direct Parameter Match**: If target is a type parameter `T` we're inferring,
1064    ///    add source as a lower bound candidate for `T`.
1065    ///
1066    /// 2. **Structural Recursion**: For complex types, recurse into the structure:
1067    ///    - Objects: Match properties recursively
1068    ///    - Arrays: Match element types
1069    ///    - Functions: Match parameters (contravariant) and return types (covariant)
1070    ///
1071    /// 3. **Variance Handling**:
1072    ///    - Covariant positions (properties, arrays, return types): `infer(source, target)`
1073    ///    - Contravariant positions (function parameters): `infer(target, source)` (swapped!)
1074    ///
1075    /// # Example
1076    /// ```ignore
1077    /// let mut ctx = InferenceContext::new(&interner);
1078    /// let t_var = ctx.fresh_type_param(interner.intern_string("T"), false);
1079    ///
1080    /// // Inference: identity("hello") should infer T = string
1081    /// ctx.infer_from_types(string_type, t_type, InferencePriority::NakedTypeVariable)?;
1082    /// ```
1083    pub fn infer_from_types(
1084        &mut self,
1085        source: TypeId,
1086        target: TypeId,
1087        priority: InferencePriority,
1088    ) -> Result<(), InferenceError> {
1089        // Resolve the types to their actual TypeDatas
1090        let source_key = self.interner.lookup(source);
1091        let target_key = self.interner.lookup(target);
1092
1093        // Block inference if target is NoInfer<T> (TypeScript 5.4+)
1094        // NoInfer prevents inference from flowing through this type position
1095        if let Some(TypeData::NoInfer(_)) = target_key {
1096            return Ok(()); // Stop inference - don't descend into NoInfer
1097        }
1098
1099        // Unwrap NoInfer from source if present (rare but possible)
1100        let source_key = if let Some(TypeData::NoInfer(inner)) = source_key {
1101            self.interner.lookup(inner)
1102        } else {
1103            source_key
1104        };
1105
1106        // Case 1: Target is a TypeParameter we're inferring (Lower Bound: source <: T)
1107        if let Some(TypeData::TypeParameter(ref param_info)) = target_key
1108            && let Some(var) = self.find_type_param(param_info.name)
1109        {
1110            // Add source as a lower bound candidate for this type parameter
1111            self.add_candidate(var, source, priority);
1112            return Ok(());
1113        }
1114
1115        // Case 2: Source is a TypeParameter we're inferring (Upper Bound: T <: target)
1116        // CRITICAL: This handles contravariance! When function parameters are swapped,
1117        // the TypeParameter moves to source position and becomes an upper bound.
1118        if let Some(TypeData::TypeParameter(ref param_info)) = source_key
1119            && let Some(var) = self.find_type_param(param_info.name)
1120        {
1121            // T <: target, so target is an UPPER bound
1122            self.add_upper_bound(var, target);
1123            return Ok(());
1124        }
1125
1126        // Case 3: Structural recursion - match based on type structure
1127        match (source_key, target_key) {
1128            // Object types: recurse into properties
1129            (Some(TypeData::Object(source_shape)), Some(TypeData::Object(target_shape))) => {
1130                self.infer_objects(source_shape, target_shape, priority)?;
1131            }
1132
1133            // Function types: handle variance (parameters are contravariant, return is covariant)
1134            (Some(TypeData::Function(source_func)), Some(TypeData::Function(target_func))) => {
1135                self.infer_functions(source_func, target_func, priority)?;
1136            }
1137
1138            // Callable types: infer across signatures and properties
1139            (Some(TypeData::Callable(source_call)), Some(TypeData::Callable(target_call))) => {
1140                self.infer_callables(source_call, target_call, priority)?;
1141            }
1142
1143            // Array types: recurse into element types
1144            (Some(TypeData::Array(source_elem)), Some(TypeData::Array(target_elem))) => {
1145                self.infer_from_types(source_elem, target_elem, priority)?;
1146            }
1147
1148            // Tuple types: recurse into elements
1149            (Some(TypeData::Tuple(source_elems)), Some(TypeData::Tuple(target_elems))) => {
1150                self.infer_tuples(source_elems, target_elems, priority)?;
1151            }
1152
1153            // Union types: try to infer against each member
1154            (Some(TypeData::Union(source_members)), Some(TypeData::Union(target_members))) => {
1155                self.infer_unions(source_members, target_members, priority)?;
1156            }
1157
1158            // Intersection types
1159            (
1160                Some(TypeData::Intersection(source_members)),
1161                Some(TypeData::Intersection(target_members)),
1162            ) => {
1163                self.infer_intersections(source_members, target_members, priority)?;
1164            }
1165
1166            // TypeApplication: recurse into instantiated type
1167            (Some(TypeData::Application(source_app)), Some(TypeData::Application(target_app))) => {
1168                self.infer_applications(source_app, target_app, priority)?;
1169            }
1170
1171            // Index access types: infer both object and index types
1172            (
1173                Some(TypeData::IndexAccess(source_obj, source_idx)),
1174                Some(TypeData::IndexAccess(target_obj, target_idx)),
1175            ) => {
1176                self.infer_from_types(source_obj, target_obj, priority)?;
1177                self.infer_from_types(source_idx, target_idx, priority)?;
1178            }
1179
1180            // ReadonlyType: unwrap if both are readonly (e.g. readonly [T] vs readonly [number])
1181            (
1182                Some(TypeData::ReadonlyType(source_inner)),
1183                Some(TypeData::ReadonlyType(target_inner)),
1184            ) => {
1185                self.infer_from_types(source_inner, target_inner, priority)?;
1186            }
1187
1188            // Unwrap ReadonlyType when only target is readonly (mutable source is compatible)
1189            (_, Some(TypeData::ReadonlyType(target_inner))) => {
1190                self.infer_from_types(source, target_inner, priority)?;
1191            }
1192
1193            // Task #40: Template literal deconstruction for infer patterns
1194            // Handles: source extends `prefix${infer T}suffix` ? true : false
1195            (Some(source_key), Some(TypeData::TemplateLiteral(target_id))) => {
1196                self.infer_from_template_literal(source, Some(&source_key), target_id, priority)?;
1197            }
1198
1199            // Mapped type inference: infer from object properties against mapped type
1200            // Handles: source { a: string, b: number } against target { [P in K]: T }
1201            // Infers K from property names and T from property value types
1202            (Some(TypeData::Object(source_shape)), Some(TypeData::Mapped(mapped_id))) => {
1203                self.infer_from_mapped_type(source_shape, mapped_id, priority)?;
1204            }
1205
1206            // If we can't match structurally, that's okay - it might mean the types are incompatible
1207            // The Checker will handle this with proper error reporting
1208            _ => {
1209                // No structural match possible
1210                // This is not an error - the Checker will verify assignability separately
1211            }
1212        }
1213
1214        Ok(())
1215    }
1216
1217    /// Infer from object types by matching properties
1218    fn infer_objects(
1219        &mut self,
1220        source_shape: ObjectShapeId,
1221        target_shape: ObjectShapeId,
1222        priority: InferencePriority,
1223    ) -> Result<(), InferenceError> {
1224        let source_shape = self.interner.object_shape(source_shape);
1225        let target_shape = self.interner.object_shape(target_shape);
1226
1227        // For each property in the target, try to find a matching property in the source
1228        for target_prop in &target_shape.properties {
1229            if let Some(source_prop) = source_shape
1230                .properties
1231                .iter()
1232                .find(|p| p.name == target_prop.name)
1233            {
1234                self.infer_from_types(source_prop.type_id, target_prop.type_id, priority)?;
1235            }
1236        }
1237
1238        // Also check index signatures for inference
1239        // If target has a string index signature, infer from source's string index
1240        if let (Some(target_string_idx), Some(source_string_idx)) =
1241            (&target_shape.string_index, &source_shape.string_index)
1242        {
1243            self.infer_from_types(
1244                source_string_idx.value_type,
1245                target_string_idx.value_type,
1246                priority,
1247            )?;
1248        }
1249
1250        // If target has a number index signature, infer from source's number index
1251        if let (Some(target_number_idx), Some(source_number_idx)) =
1252            (&target_shape.number_index, &source_shape.number_index)
1253        {
1254            self.infer_from_types(
1255                source_number_idx.value_type,
1256                target_number_idx.value_type,
1257                priority,
1258            )?;
1259        }
1260
1261        Ok(())
1262    }
1263
1264    /// Infer type arguments from an object type matched against a mapped type.
1265    ///
1266    /// When source is `{ a: string, b: number }` and target is `{ [P in K]: T }`:
1267    /// - Infer K from the union of source property name literals ("a" | "b")
1268    /// - Infer T from each source property value type against the mapped template
1269    fn infer_from_mapped_type(
1270        &mut self,
1271        source_shape: ObjectShapeId,
1272        mapped_id: MappedTypeId,
1273        priority: InferencePriority,
1274    ) -> Result<(), InferenceError> {
1275        let mapped = self.interner.mapped_type(mapped_id);
1276        let source = self.interner.object_shape(source_shape);
1277
1278        if source.properties.is_empty() {
1279            return Ok(());
1280        }
1281
1282        // Infer the constraint type (K) from the union of source property names
1283        // e.g., for { foo: string, bar: number }, K = "foo" | "bar"
1284        let name_literals: Vec<TypeId> = source
1285            .properties
1286            .iter()
1287            .map(|p| self.interner.literal_string_atom(p.name))
1288            .collect();
1289        let names_union = if name_literals.len() == 1 {
1290            name_literals[0]
1291        } else {
1292            self.interner.union(name_literals)
1293        };
1294        self.infer_from_types(names_union, mapped.constraint, priority)?;
1295
1296        // Infer the template type (T) from each source property value type
1297        for prop in &source.properties {
1298            self.infer_from_types(prop.type_id, mapped.template, priority)?;
1299        }
1300
1301        Ok(())
1302    }
1303
1304    /// Infer from function types, handling variance correctly
1305    fn infer_functions(
1306        &mut self,
1307        source_func: FunctionShapeId,
1308        target_func: FunctionShapeId,
1309        priority: InferencePriority,
1310    ) -> Result<(), InferenceError> {
1311        let source_sig = self.interner.function_shape(source_func);
1312        let target_sig = self.interner.function_shape(target_func);
1313
1314        tracing::trace!(
1315            source_params = source_sig.params.len(),
1316            target_params = target_sig.params.len(),
1317            "infer_functions called"
1318        );
1319
1320        // Parameters are contravariant: swap source and target
1321        let mut source_params = source_sig.params.iter().peekable();
1322        let mut target_params = target_sig.params.iter().peekable();
1323
1324        loop {
1325            let source_rest = source_params.peek().is_some_and(|p| p.rest);
1326            let target_rest = target_params.peek().is_some_and(|p| p.rest);
1327
1328            tracing::trace!(
1329                source_rest,
1330                target_rest,
1331                "Checking rest params in loop iteration"
1332            );
1333
1334            // If both have rest params, infer the rest element types
1335            if source_rest && target_rest {
1336                let source_param = source_params.next().unwrap();
1337                let target_param = target_params.next().unwrap();
1338                self.infer_from_types(target_param.type_id, source_param.type_id, priority)?;
1339                break;
1340            }
1341
1342            // If source has rest param, infer all remaining target params into it
1343            if source_rest {
1344                let source_param = source_params.next().unwrap();
1345                for target_param in target_params.by_ref() {
1346                    self.infer_from_types(target_param.type_id, source_param.type_id, priority)?;
1347                }
1348                break;
1349            }
1350
1351            // If target has rest param, infer all remaining source params into it
1352            if target_rest {
1353                let target_param = target_params.next().unwrap();
1354
1355                // CRITICAL: Check if target rest param is a type parameter (like A extends any[])
1356                // If so, we need to infer it as a TUPLE of all remaining source params,
1357                // not as individual param types.
1358                //
1359                // Example: wrap<A extends any[], R>(fn: (...args: A) => R)
1360                //          with add(a: number, b: number): number
1361                //          should infer A = [number, number], not A = number
1362                let target_is_type_param = matches!(
1363                    self.interner.lookup(target_param.type_id),
1364                    Some(TypeData::TypeParameter(_) | TypeData::Infer(_))
1365                );
1366
1367                tracing::trace!(
1368                    target_is_type_param,
1369                    target_param_type = ?target_param.type_id,
1370                    "Rest parameter inference - target is type param check"
1371                );
1372
1373                if target_is_type_param {
1374                    // Collect all remaining source params into a tuple
1375                    let mut tuple_elements = Vec::new();
1376                    for source_param in source_params.by_ref() {
1377                        tuple_elements.push(TupleElement {
1378                            type_id: source_param.type_id,
1379                            name: source_param.name,
1380                            optional: source_param.optional,
1381                            rest: source_param.rest,
1382                        });
1383                    }
1384
1385                    tracing::trace!(
1386                        num_elements = tuple_elements.len(),
1387                        "Collected source params into tuple"
1388                    );
1389
1390                    // Infer the tuple type against the type parameter
1391                    // Note: Parameters are contravariant, so target comes first
1392                    if !tuple_elements.is_empty() {
1393                        let tuple_type = self.interner.tuple(tuple_elements);
1394                        tracing::trace!(
1395                            tuple_type = ?tuple_type,
1396                            target_param = ?target_param.type_id,
1397                            "Inferring tuple against type parameter"
1398                        );
1399                        self.infer_from_types(target_param.type_id, tuple_type, priority)?;
1400                    }
1401                } else {
1402                    // Target rest param is not a type parameter (e.g., number[] or Array<string>)
1403                    // Infer each source param individually against the rest element type
1404                    for source_param in source_params.by_ref() {
1405                        self.infer_from_types(
1406                            target_param.type_id,
1407                            source_param.type_id,
1408                            priority,
1409                        )?;
1410                    }
1411                }
1412                break;
1413            }
1414
1415            // Neither has rest param, do normal pairwise comparison
1416            match (source_params.next(), target_params.next()) {
1417                (Some(source_param), Some(target_param)) => {
1418                    // Note the swapped arguments! This is the key to handling contravariance.
1419                    self.infer_from_types(target_param.type_id, source_param.type_id, priority)?;
1420                }
1421                _ => break, // Mismatch in arity - stop here
1422            }
1423        }
1424
1425        // Return type is covariant: normal order
1426        self.infer_from_types(source_sig.return_type, target_sig.return_type, priority)?;
1427
1428        // This type is contravariant
1429        if let (Some(source_this), Some(target_this)) = (source_sig.this_type, target_sig.this_type)
1430        {
1431            self.infer_from_types(target_this, source_this, priority)?;
1432        }
1433
1434        // Type predicates are covariant
1435        if let (Some(source_pred), Some(target_pred)) =
1436            (&source_sig.type_predicate, &target_sig.type_predicate)
1437        {
1438            // Compare targets by index if possible
1439            let targets_match = match (source_pred.parameter_index, target_pred.parameter_index) {
1440                (Some(s_idx), Some(t_idx)) => s_idx == t_idx,
1441                _ => source_pred.target == target_pred.target,
1442            };
1443
1444            tracing::trace!(
1445                targets_match,
1446                ?source_pred.parameter_index,
1447                ?target_pred.parameter_index,
1448                "Inferring from type predicates"
1449            );
1450
1451            if targets_match
1452                && source_pred.asserts == target_pred.asserts
1453                && let (Some(source_ty), Some(target_ty)) =
1454                    (source_pred.type_id, target_pred.type_id)
1455            {
1456                self.infer_from_types(source_ty, target_ty, priority)?;
1457            }
1458        }
1459
1460        Ok(())
1461    }
1462
1463    /// Infer from tuple types
1464    fn infer_tuples(
1465        &mut self,
1466        source_elems: TupleListId,
1467        target_elems: TupleListId,
1468        priority: InferencePriority,
1469    ) -> Result<(), InferenceError> {
1470        let source_list = self.interner.tuple_list(source_elems);
1471        let target_list = self.interner.tuple_list(target_elems);
1472
1473        for (source_elem, target_elem) in source_list.iter().zip(target_list.iter()) {
1474            self.infer_from_types(source_elem.type_id, target_elem.type_id, priority)?;
1475        }
1476
1477        Ok(())
1478    }
1479
1480    /// Infer from callable types, handling signatures and properties
1481    fn infer_callables(
1482        &mut self,
1483        source_id: CallableShapeId,
1484        target_id: CallableShapeId,
1485        priority: InferencePriority,
1486    ) -> Result<(), InferenceError> {
1487        let source = self.interner.callable_shape(source_id);
1488        let target = self.interner.callable_shape(target_id);
1489
1490        // For each call signature in the target, try to find a compatible one in the source
1491        for target_sig in &target.call_signatures {
1492            for source_sig in &source.call_signatures {
1493                if source_sig.params.len() == target_sig.params.len() {
1494                    for (s_param, t_param) in source_sig.params.iter().zip(target_sig.params.iter())
1495                    {
1496                        self.infer_from_types(t_param.type_id, s_param.type_id, priority)?;
1497                    }
1498                    self.infer_from_types(
1499                        source_sig.return_type,
1500                        target_sig.return_type,
1501                        priority,
1502                    )?;
1503                    break;
1504                }
1505            }
1506        }
1507
1508        // For each construct signature
1509        for target_sig in &target.construct_signatures {
1510            for source_sig in &source.construct_signatures {
1511                if source_sig.params.len() == target_sig.params.len() {
1512                    for (s_param, t_param) in source_sig.params.iter().zip(target_sig.params.iter())
1513                    {
1514                        self.infer_from_types(t_param.type_id, s_param.type_id, priority)?;
1515                    }
1516                    self.infer_from_types(
1517                        source_sig.return_type,
1518                        target_sig.return_type,
1519                        priority,
1520                    )?;
1521                    break;
1522                }
1523            }
1524        }
1525
1526        // Properties
1527        for target_prop in &target.properties {
1528            if let Some(source_prop) = source
1529                .properties
1530                .iter()
1531                .find(|p| p.name == target_prop.name)
1532            {
1533                self.infer_from_types(source_prop.type_id, target_prop.type_id, priority)?;
1534            }
1535        }
1536
1537        // String index
1538        if let (Some(target_idx), Some(source_idx)) = (&target.string_index, &source.string_index) {
1539            self.infer_from_types(source_idx.value_type, target_idx.value_type, priority)?;
1540        }
1541
1542        // Number index
1543        if let (Some(target_idx), Some(source_idx)) = (&target.number_index, &source.number_index) {
1544            self.infer_from_types(source_idx.value_type, target_idx.value_type, priority)?;
1545        }
1546
1547        Ok(())
1548    }
1549
1550    /// Infer from union types
1551    fn infer_unions(
1552        &mut self,
1553        source_members: TypeListId,
1554        target_members: TypeListId,
1555        priority: InferencePriority,
1556    ) -> Result<(), InferenceError> {
1557        let source_list = self.interner.type_list(source_members);
1558        let target_list = self.interner.type_list(target_members);
1559
1560        // TypeScript inference filtering: when the target union contains both
1561        // type parameters and fixed types (e.g., `T | undefined`), strip source
1562        // members that match fixed target members before inferring against the
1563        // parameterized members. This prevents `undefined` in `number | undefined`
1564        // from being inferred as a candidate for `T` in `T | undefined`.
1565        let (parameterized, fixed): (Vec<TypeId>, Vec<TypeId>) = target_list
1566            .iter()
1567            .partition(|&&t| self.target_contains_inference_param(t));
1568
1569        if !parameterized.is_empty() && !fixed.is_empty() {
1570            // Filter source: only infer members not already covered by fixed targets
1571            for &source_ty in source_list.iter() {
1572                let matches_fixed = fixed.contains(&source_ty);
1573                if !matches_fixed {
1574                    for &target_ty in &parameterized {
1575                        self.infer_from_types(source_ty, target_ty, priority)?;
1576                    }
1577                }
1578            }
1579        } else {
1580            // No filtering needed — fall back to exhaustive inference
1581            for source_ty in source_list.iter() {
1582                for target_ty in target_list.iter() {
1583                    self.infer_from_types(*source_ty, *target_ty, priority)?;
1584                }
1585            }
1586        }
1587
1588        Ok(())
1589    }
1590
1591    /// Check if a target type directly is or contains an inference type parameter.
1592    fn target_contains_inference_param(&self, target: TypeId) -> bool {
1593        let Some(key) = self.interner.lookup(target) else {
1594            return false;
1595        };
1596        match key {
1597            TypeData::TypeParameter(ref info) => self.find_type_param(info.name).is_some(),
1598            _ => false,
1599        }
1600    }
1601
1602    /// Infer from intersection types
1603    fn infer_intersections(
1604        &mut self,
1605        source_members: TypeListId,
1606        target_members: TypeListId,
1607        priority: InferencePriority,
1608    ) -> Result<(), InferenceError> {
1609        let source_list = self.interner.type_list(source_members);
1610        let target_list = self.interner.type_list(target_members);
1611
1612        // For intersections, we can pick any member that matches
1613        for source_ty in source_list.iter() {
1614            for target_ty in target_list.iter() {
1615                // Don't fail if one member doesn't match
1616                let _ = self.infer_from_types(*source_ty, *target_ty, priority);
1617            }
1618        }
1619
1620        Ok(())
1621    }
1622
1623    /// Infer from `TypeApplication` (generic type instantiations)
1624    fn infer_applications(
1625        &mut self,
1626        source_app: TypeApplicationId,
1627        target_app: TypeApplicationId,
1628        priority: InferencePriority,
1629    ) -> Result<(), InferenceError> {
1630        let source_info = self.interner.type_application(source_app);
1631        let target_info = self.interner.type_application(target_app);
1632
1633        // The base types must match for inference to work
1634        if source_info.base != target_info.base {
1635            return Ok(());
1636        }
1637
1638        // Recurse into the type arguments
1639        for (source_arg, target_arg) in source_info.args.iter().zip(target_info.args.iter()) {
1640            self.infer_from_types(*source_arg, *target_arg, priority)?;
1641        }
1642
1643        Ok(())
1644    }
1645
1646    // =========================================================================
1647    // Task #40: Template Literal Deconstruction
1648    // =========================================================================
1649
1650    /// Infer from template literal patterns with `infer` placeholders.
1651    ///
1652    /// This implements the "Reverse String Matcher" for extracting type information
1653    /// from string literals that match template patterns like `user_${infer ID}`.
1654    ///
1655    /// # Example
1656    ///
1657    /// ```typescript
1658    /// type GetID<T> = T extends `user_${infer ID}` ? ID : never;
1659    /// // GetID<"user_123"> should infer ID = "123"
1660    /// ```
1661    ///
1662    /// # Algorithm
1663    ///
1664    /// The matching is **non-greedy** for all segments except the last:
1665    /// 1. Scan through template spans sequentially
1666    /// 2. For text spans: match literal text at current position
1667    /// 3. For infer type spans: capture text until next literal anchor (non-greedy)
1668    /// 4. For the last span: capture all remaining text (greedy)
1669    ///
1670    /// # Arguments
1671    ///
1672    /// * `source` - The source type being checked (e.g., `"user_123"`)
1673    /// * `source_key` - The `TypeData` of the source (cached for efficiency)
1674    /// * `target_template` - The template literal pattern to match against
1675    /// * `priority` - Inference priority for the extracted candidates
1676    fn infer_from_template_literal(
1677        &mut self,
1678        source: TypeId,
1679        source_key: Option<&TypeData>,
1680        target_template: TemplateLiteralId,
1681        priority: InferencePriority,
1682    ) -> Result<(), InferenceError> {
1683        let spans = self.interner.template_list(target_template);
1684
1685        // Special case: if source is `any` or the intrinsic `string` type, all infer vars get that type
1686        if source == TypeId::ANY
1687            || matches!(source_key, Some(TypeData::Intrinsic(IntrinsicKind::String)))
1688        {
1689            for span in spans.iter() {
1690                if let TemplateSpan::Type(type_id) = span
1691                    && let Some(TypeData::Infer(param_info)) = self.interner.lookup(*type_id)
1692                    && let Some(var) = self.find_type_param(param_info.name)
1693                {
1694                    // Source is `any` or `string`, so infer that for all variables
1695                    self.add_candidate(var, source, priority);
1696                }
1697            }
1698            return Ok(());
1699        }
1700
1701        // If source is a union, try to match each member against the template
1702        if let Some(TypeData::Union(source_members)) = source_key {
1703            let members = self.interner.type_list(*source_members);
1704            for &member in members.iter() {
1705                let member_key = self.interner.lookup(member);
1706                self.infer_from_template_literal(
1707                    member,
1708                    member_key.as_ref(),
1709                    target_template,
1710                    priority,
1711                )?;
1712            }
1713            return Ok(());
1714        }
1715
1716        // For literal string types, perform the actual pattern matching
1717        if let Some(source_str) = self.extract_string_literal(source)
1718            && let Some(captures) = self.match_template_pattern(&source_str, &spans)
1719        {
1720            // Convert captured strings to literal types and add as candidates
1721            for (infer_var, captured_string) in captures {
1722                let literal_type = self.interner.literal_string(&captured_string);
1723                self.add_candidate(infer_var, literal_type, priority);
1724            }
1725        }
1726
1727        Ok(())
1728    }
1729
1730    /// Extract a string literal value from a `TypeId`.
1731    ///
1732    /// Returns None if the type is not a literal string.
1733    fn extract_string_literal(&self, type_id: TypeId) -> Option<String> {
1734        match self.interner.lookup(type_id) {
1735            Some(TypeData::Literal(LiteralValue::String(s))) => Some(self.interner.resolve_atom(s)),
1736            _ => None,
1737        }
1738    }
1739
1740    /// Match a source string against a template pattern, extracting infer variable bindings.
1741    ///
1742    /// # Arguments
1743    ///
1744    /// * `source` - The source string to match (e.g., `"user_123"`)
1745    /// * `spans` - The template spans (e.g., `[Text("user_"), Type(ID), Text("_")]`)
1746    ///
1747    /// # Returns
1748    ///
1749    /// * `Some(bindings)` - Mapping from inference variables to captured strings
1750    /// * `None` - The source doesn't match the pattern
1751    fn match_template_pattern(
1752        &self,
1753        source: &str,
1754        spans: &[TemplateSpan],
1755    ) -> Option<Vec<(InferenceVar, String)>> {
1756        let mut bindings = Vec::new();
1757        let mut pos = 0;
1758
1759        for (i, span) in spans.iter().enumerate() {
1760            let is_last = i == spans.len() - 1;
1761
1762            match span {
1763                TemplateSpan::Text(text_atom) => {
1764                    // Match literal text at current position
1765                    let text = self.interner.resolve_atom(*text_atom).to_string();
1766                    if !source.get(pos..)?.starts_with(&text) {
1767                        return None; // Text doesn't match
1768                    }
1769                    pos += text.len();
1770                }
1771
1772                TemplateSpan::Type(type_id) => {
1773                    // Check if this is an infer variable
1774                    if let Some(TypeData::Infer(param_info)) = self.interner.lookup(*type_id)
1775                        && let Some(var) = self.find_type_param(param_info.name)
1776                    {
1777                        if is_last {
1778                            // Last span: capture all remaining text (greedy)
1779                            let captured = source[pos..].to_string();
1780                            bindings.push((var, captured));
1781                            pos = source.len();
1782                        } else {
1783                            // Non-last span: capture until next literal anchor (non-greedy)
1784                            // Find the next text span to use as an anchor
1785                            if let Some(anchor_text) = self.find_next_text_anchor(spans, i) {
1786                                let anchor = self.interner.resolve_atom(anchor_text).to_string();
1787                                // Find the first occurrence of the anchor (non-greedy)
1788                                let capture_end = source[pos..].find(&anchor)? + pos;
1789                                let captured = source[pos..capture_end].to_string();
1790                                bindings.push((var, captured));
1791                                pos = capture_end;
1792                            } else {
1793                                // No text anchor found (e.g., `${infer A}${infer B}`)
1794                                // Capture empty string for non-greedy match and continue
1795                                bindings.push((var, String::new()));
1796                                // pos remains unchanged - next infer var starts here
1797                            }
1798                        }
1799                    }
1800                }
1801            }
1802        }
1803
1804        // Must have consumed the entire source string
1805        (pos == source.len()).then_some(bindings)
1806    }
1807
1808    /// Find the next text span after a given index to use as a matching anchor.
1809    fn find_next_text_anchor(&self, spans: &[TemplateSpan], start_idx: usize) -> Option<Atom> {
1810        spans.iter().skip(start_idx + 1).find_map(|span| {
1811            if let TemplateSpan::Text(text) = span {
1812                Some(*text)
1813            } else {
1814                None
1815            }
1816        })
1817    }
1818}
1819
1820// DISABLED: Tests use deprecated add_candidate / resolve_with_constraints API
1821// The inference system has been refactored to use unification-based inference.
1822#[cfg(test)]
1823#[path = "../tests/infer_tests.rs"]
1824mod tests;