Skip to main content

tsz_solver/
instantiate.rs

1//! Generic type instantiation and substitution.
2//!
3//! This module implements type parameter substitution for generic types.
4//! When a generic function/type is instantiated, we replace type parameters
5//! with concrete types throughout the type structure.
6//!
7//! Key features:
8//! - Type substitution map (type parameter name -> `TypeId`)
9//! - Deep recursive substitution through nested types
10//! - Handling of constraints and defaults
11
12use crate::TypeDatabase;
13#[cfg(test)]
14use crate::types::*;
15use crate::types::{
16    CallSignature, CallableShape, ConditionalType, FunctionShape, IndexSignature, IntrinsicKind,
17    LiteralValue, MappedType, ObjectShape, ParamInfo, PropertyInfo, TemplateSpan, TupleElement,
18    TypeData, TypeId, TypeParamInfo, TypePredicate,
19};
20use rustc_hash::FxHashMap;
21use tsz_common::interner::Atom;
22
23/// Maximum depth for recursive type instantiation.
24pub const MAX_INSTANTIATION_DEPTH: u32 = 50;
25
26/// A substitution map from type parameter names to concrete types.
27#[derive(Clone, Debug, Default)]
28pub struct TypeSubstitution {
29    /// Maps type parameter names to their substituted types
30    map: FxHashMap<Atom, TypeId>,
31}
32
33impl TypeSubstitution {
34    /// Create an empty substitution.
35    pub fn new() -> Self {
36        Self {
37            map: FxHashMap::default(),
38        }
39    }
40
41    /// Create a substitution from type parameters and arguments.
42    ///
43    /// `type_params` - The declared type parameters (e.g., `<T, U>`)
44    /// `type_args` - The provided type arguments (e.g., `<string, number>`)
45    ///
46    /// When `type_args` has fewer elements than `type_params`, default values
47    /// from the type parameters are used for the remaining parameters.
48    ///
49    /// IMPORTANT: Defaults may reference earlier type parameters, so they need
50    /// to be instantiated with the substitution built so far.
51    pub fn from_args(
52        interner: &dyn TypeDatabase,
53        type_params: &[TypeParamInfo],
54        type_args: &[TypeId],
55    ) -> Self {
56        let mut map = FxHashMap::default();
57        for (i, param) in type_params.iter().enumerate() {
58            let type_id = if i < type_args.len() {
59                type_args[i]
60            } else {
61                // Use default value if type argument not provided
62                match param.default {
63                    Some(default) => {
64                        // Defaults may reference earlier type parameters, so instantiate them
65                        if i > 0 && !map.is_empty() {
66                            let subst = Self { map: map.clone() };
67                            instantiate_type(interner, default, &subst)
68                        } else {
69                            default
70                        }
71                    }
72                    None => {
73                        // No default and no argument - leave this parameter unsubstituted
74                        // It will remain as a TypeParameter in the result
75                        continue;
76                    }
77                }
78            };
79            map.insert(param.name, type_id);
80        }
81        Self { map }
82    }
83
84    /// Add a single substitution.
85    pub fn insert(&mut self, name: Atom, type_id: TypeId) {
86        self.map.insert(name, type_id);
87    }
88
89    /// Look up a substitution.
90    pub fn get(&self, name: Atom) -> Option<TypeId> {
91        self.map.get(&name).copied()
92    }
93
94    /// Check if substitution is empty.
95    pub fn is_empty(&self) -> bool {
96        self.map.is_empty()
97    }
98
99    /// Number of substitutions.
100    pub fn len(&self) -> usize {
101        self.map.len()
102    }
103
104    /// Get a reference to the internal substitution map.
105    ///
106    /// This is useful for building new substitutions based on existing ones.
107    pub const fn map(&self) -> &FxHashMap<Atom, TypeId> {
108        &self.map
109    }
110}
111
112/// Instantiator for applying type substitutions.
113pub struct TypeInstantiator<'a> {
114    interner: &'a dyn TypeDatabase,
115    substitution: &'a TypeSubstitution,
116    /// Track visited types to handle cycles
117    visiting: FxHashMap<TypeId, TypeId>,
118    /// Type parameter names that are shadowed in the current scope.
119    shadowed: Vec<Atom>,
120    substitute_infer: bool,
121    /// When set, substitutes `ThisType` with this concrete type.
122    pub this_type: Option<TypeId>,
123    depth: u32,
124    max_depth: u32,
125    depth_exceeded: bool,
126}
127
128impl<'a> TypeInstantiator<'a> {
129    /// Create a new instantiator.
130    pub fn new(interner: &'a dyn TypeDatabase, substitution: &'a TypeSubstitution) -> Self {
131        TypeInstantiator {
132            interner,
133            substitution,
134            visiting: FxHashMap::default(),
135            shadowed: Vec::new(),
136            substitute_infer: false,
137            this_type: None,
138            depth: 0,
139            max_depth: MAX_INSTANTIATION_DEPTH,
140            depth_exceeded: false,
141        }
142    }
143
144    fn is_shadowed(&self, name: Atom) -> bool {
145        self.shadowed.contains(&name)
146    }
147
148    /// Apply the substitution to a type, returning the instantiated type.
149    pub fn instantiate(&mut self, type_id: TypeId) -> TypeId {
150        // Fast path: intrinsic types don't need instantiation
151        if type_id.is_intrinsic() {
152            return type_id;
153        }
154
155        if self.depth_exceeded {
156            return TypeId::ERROR;
157        }
158
159        if self.depth >= self.max_depth {
160            self.depth_exceeded = true;
161            return TypeId::ERROR;
162        }
163
164        self.depth += 1;
165        let result = self.instantiate_inner(type_id);
166        self.depth -= 1;
167        result
168    }
169
170    fn instantiate_inner(&mut self, type_id: TypeId) -> TypeId {
171        // Check if we're already processing this type (cycle detection)
172        if let Some(&cached) = self.visiting.get(&type_id) {
173            return cached;
174        }
175
176        // Look up the type structure
177        let key = match self.interner.lookup(type_id) {
178            Some(k) => k,
179            None => return type_id,
180        };
181
182        // Mark as visiting (with original ID as placeholder for cycles)
183        self.visiting.insert(type_id, type_id);
184
185        let result = self.instantiate_key(&key);
186
187        // Update the cache with the actual result
188        self.visiting.insert(type_id, result);
189
190        result
191    }
192
193    /// Instantiate a call signature.
194    fn instantiate_call_signature(&mut self, sig: &CallSignature) -> CallSignature {
195        let shadowed_len = self.shadowed.len();
196
197        // Save the visiting cache before entering a shadowing scope.
198        // The cache maps TypeId→TypeId, but TypeParameter substitution depends on
199        // the current `shadowed` set. If we cache T→string when T is not shadowed,
200        // then enter a scope where T IS shadowed (method <T>), the stale cache
201        // would incorrectly return string instead of T. Conversely, if T→T is
202        // cached while shadowed, it would persist after leaving the scope.
203        // Saving/restoring ensures correctness across shadowing boundaries.
204        let saved_visiting = (!sig.type_params.is_empty()).then(|| {
205            let saved = self.visiting.clone();
206            // Remove cached entries for TypeParameters being shadowed.
207            // Without this, instantiate_inner returns cached results before
208            // instantiate_key gets to check is_shadowed.
209            for tp in &sig.type_params {
210                let tp_id = self.interner.type_param(tp.clone());
211                self.visiting.remove(&tp_id);
212            }
213            saved
214        });
215
216        self.shadowed
217            .extend(sig.type_params.iter().map(|tp| tp.name));
218
219        let type_predicate = sig
220            .type_predicate
221            .as_ref()
222            .map(|predicate| self.instantiate_type_predicate(predicate));
223        let this_type = sig.this_type.map(|type_id| self.instantiate(type_id));
224        let type_params: Vec<TypeParamInfo> = sig
225            .type_params
226            .iter()
227            .map(|tp| TypeParamInfo {
228                is_const: false,
229                name: tp.name,
230                constraint: tp.constraint.map(|c| self.instantiate(c)),
231                default: tp.default.map(|d| self.instantiate(d)),
232            })
233            .collect();
234        let params: Vec<ParamInfo> = sig
235            .params
236            .iter()
237            .map(|p| ParamInfo {
238                name: p.name,
239                type_id: self.instantiate(p.type_id),
240                optional: p.optional,
241                rest: p.rest,
242            })
243            .collect();
244        let return_type = self.instantiate(sig.return_type);
245
246        self.shadowed.truncate(shadowed_len);
247
248        // Restore the visiting cache to discard any entries produced under the
249        // shadowed scope that would be stale now.
250        if let Some(saved) = saved_visiting {
251            self.visiting = saved;
252        }
253
254        CallSignature {
255            type_params,
256            params,
257            this_type,
258            return_type,
259            type_predicate,
260            is_method: sig.is_method,
261        }
262    }
263
264    fn instantiate_type_predicate(&mut self, predicate: &TypePredicate) -> TypePredicate {
265        TypePredicate {
266            asserts: predicate.asserts,
267            target: predicate.target.clone(),
268            type_id: predicate.type_id.map(|type_id| self.instantiate(type_id)),
269            parameter_index: predicate.parameter_index,
270        }
271    }
272
273    /// Instantiate a `TypeData`.
274    fn instantiate_key(&mut self, key: &TypeData) -> TypeId {
275        match key {
276            // Type parameters get substituted
277            TypeData::TypeParameter(info) => {
278                if self.is_shadowed(info.name) {
279                    return self.interner.intern(key.clone());
280                }
281                if let Some(substituted) = self.substitution.get(info.name) {
282                    substituted
283                } else {
284                    // No direct substitution found. If the type parameter has a constraint
285                    // that references substituted type parameters, instantiate the constraint.
286                    // Example: Actions extends ActionsObject<State>, with {State: number}
287                    // → use ActionsObject<number> instead of Actions
288                    if let Some(constraint) = info.constraint {
289                        let instantiated_constraint = self.instantiate(constraint);
290                        // Only use the constraint if instantiation changed it
291                        if instantiated_constraint != constraint {
292                            return instantiated_constraint;
293                        }
294                    }
295                    // No substitution and no instantiated constraint, return original
296                    self.interner.intern(key.clone())
297                }
298            }
299
300            // Intrinsics don't change
301            TypeData::Intrinsic(_) | TypeData::Literal(_) | TypeData::Error => {
302                self.interner.intern(key.clone())
303            }
304
305            // Lazy types might resolve to something that needs substitution
306            TypeData::Lazy(_)
307            | TypeData::Recursive(_)
308            | TypeData::BoundParameter(_)
309            | TypeData::TypeQuery(_)
310            | TypeData::UniqueSymbol(_)
311            | TypeData::ModuleNamespace(_) => self.interner.intern(key.clone()),
312
313            // Enum types: instantiate the member type (structural part)
314            // The DefId (nominal identity) stays the same
315            TypeData::Enum(def_id, member_type) => {
316                let instantiated_member = self.instantiate(*member_type);
317                self.interner.enum_type(*def_id, instantiated_member)
318            }
319
320            // Application: instantiate base and args
321            TypeData::Application(app_id) => {
322                let app = self.interner.type_application(*app_id);
323                let base = self.instantiate(app.base);
324                let args: Vec<TypeId> = app.args.iter().map(|&arg| self.instantiate(arg)).collect();
325                self.interner.application(base, args)
326            }
327
328            // This type: substitute with concrete this_type if provided
329            TypeData::ThisType => {
330                if let Some(this_type) = self.this_type {
331                    this_type
332                } else {
333                    self.interner.intern(key.clone())
334                }
335            }
336
337            // Union: instantiate all members
338            TypeData::Union(members) => {
339                let members = self.interner.type_list(*members);
340                let instantiated: Vec<TypeId> =
341                    members.iter().map(|&m| self.instantiate(m)).collect();
342                self.interner.union(instantiated)
343            }
344
345            // Intersection: instantiate all members
346            TypeData::Intersection(members) => {
347                let members = self.interner.type_list(*members);
348                let instantiated: Vec<TypeId> =
349                    members.iter().map(|&m| self.instantiate(m)).collect();
350                self.interner.intersection(instantiated)
351            }
352
353            // Array: instantiate element type
354            TypeData::Array(elem) => {
355                let instantiated_elem = self.instantiate(*elem);
356                self.interner.array(instantiated_elem)
357            }
358
359            // Tuple: instantiate all elements
360            TypeData::Tuple(elements) => {
361                let elements = self.interner.tuple_list(*elements);
362                let instantiated: Vec<TupleElement> = elements
363                    .iter()
364                    .map(|e| TupleElement {
365                        type_id: self.instantiate(e.type_id),
366                        name: e.name,
367                        optional: e.optional,
368                        rest: e.rest,
369                    })
370                    .collect();
371                self.interner.tuple(instantiated)
372            }
373
374            // Object: instantiate all property types
375            TypeData::Object(shape_id) => {
376                let shape = self.interner.object_shape(*shape_id);
377                let instantiated: Vec<PropertyInfo> = shape
378                    .properties
379                    .iter()
380                    .map(|p| PropertyInfo {
381                        name: p.name,
382                        type_id: self.instantiate(p.type_id),
383                        write_type: self.instantiate(p.write_type),
384                        optional: p.optional,
385                        readonly: p.readonly,
386                        is_method: p.is_method,
387                        visibility: p.visibility,
388                        parent_id: p.parent_id,
389                    })
390                    .collect();
391                self.interner
392                    .object_with_flags_and_symbol(instantiated, shape.flags, shape.symbol)
393            }
394
395            // Object with index signatures: instantiate all types
396            TypeData::ObjectWithIndex(shape_id) => {
397                let shape = self.interner.object_shape(*shape_id);
398                let instantiated_props: Vec<PropertyInfo> = shape
399                    .properties
400                    .iter()
401                    .map(|p| PropertyInfo {
402                        name: p.name,
403                        type_id: self.instantiate(p.type_id),
404                        write_type: self.instantiate(p.write_type),
405                        optional: p.optional,
406                        readonly: p.readonly,
407                        is_method: p.is_method,
408                        visibility: p.visibility,
409                        parent_id: p.parent_id,
410                    })
411                    .collect();
412                let instantiated_string_idx =
413                    shape.string_index.as_ref().map(|idx| IndexSignature {
414                        key_type: self.instantiate(idx.key_type),
415                        value_type: self.instantiate(idx.value_type),
416                        readonly: idx.readonly,
417                    });
418                let instantiated_number_idx =
419                    shape.number_index.as_ref().map(|idx| IndexSignature {
420                        key_type: self.instantiate(idx.key_type),
421                        value_type: self.instantiate(idx.value_type),
422                        readonly: idx.readonly,
423                    });
424                self.interner.object_with_index(ObjectShape {
425                    flags: shape.flags,
426                    properties: instantiated_props,
427                    string_index: instantiated_string_idx,
428                    number_index: instantiated_number_idx,
429                    symbol: shape.symbol,
430                })
431            }
432
433            // Function: instantiate params and return type
434            // Note: Type params in the function create a new scope - don't substitute those
435            TypeData::Function(shape_id) => {
436                let shape = self.interner.function_shape(*shape_id);
437                let shadowed_len = self.shadowed.len();
438                let saved_visiting = (!shape.type_params.is_empty()).then(|| {
439                    let saved = self.visiting.clone();
440                    // Remove cached entries for TypeParameters being shadowed.
441                    // The cache might already contain T→string from instantiating
442                    // a sibling property in the same object. Without removal,
443                    // instantiate_inner returns the cached result before
444                    // instantiate_key gets to check is_shadowed.
445                    for tp in &shape.type_params {
446                        let tp_id = self.interner.type_param(tp.clone());
447                        self.visiting.remove(&tp_id);
448                    }
449                    saved
450                });
451                self.shadowed
452                    .extend(shape.type_params.iter().map(|tp| tp.name));
453
454                let type_predicate = shape
455                    .type_predicate
456                    .as_ref()
457                    .map(|predicate| self.instantiate_type_predicate(predicate));
458                let this_type = shape.this_type.map(|type_id| self.instantiate(type_id));
459                let instantiated_type_params: Vec<TypeParamInfo> = shape
460                    .type_params
461                    .iter()
462                    .map(|tp| TypeParamInfo {
463                        is_const: false,
464                        name: tp.name,
465                        constraint: tp.constraint.map(|c| self.instantiate(c)),
466                        default: tp.default.map(|d| self.instantiate(d)),
467                    })
468                    .collect();
469                let instantiated_params: Vec<ParamInfo> = shape
470                    .params
471                    .iter()
472                    .map(|p| ParamInfo {
473                        name: p.name,
474                        type_id: self.instantiate(p.type_id),
475                        optional: p.optional,
476                        rest: p.rest,
477                    })
478                    .collect();
479                let instantiated_return = self.instantiate(shape.return_type);
480
481                self.shadowed.truncate(shadowed_len);
482                if let Some(saved) = saved_visiting {
483                    self.visiting = saved;
484                }
485
486                self.interner.function(FunctionShape {
487                    type_params: instantiated_type_params,
488                    params: instantiated_params,
489                    this_type,
490                    return_type: instantiated_return,
491                    type_predicate,
492                    is_constructor: shape.is_constructor,
493                    is_method: shape.is_method,
494                })
495            }
496
497            // Callable: instantiate all signatures and properties
498            TypeData::Callable(shape_id) => {
499                let shape = self.interner.callable_shape(*shape_id);
500                let instantiated_call: Vec<CallSignature> = shape
501                    .call_signatures
502                    .iter()
503                    .map(|sig| self.instantiate_call_signature(sig))
504                    .collect();
505                let instantiated_construct: Vec<CallSignature> = shape
506                    .construct_signatures
507                    .iter()
508                    .map(|sig| self.instantiate_call_signature(sig))
509                    .collect();
510                let instantiated_props = shape
511                    .properties
512                    .iter()
513                    .map(|p| PropertyInfo {
514                        name: p.name,
515                        type_id: self.instantiate(p.type_id),
516                        write_type: self.instantiate(p.write_type),
517                        optional: p.optional,
518                        readonly: p.readonly,
519                        is_method: p.is_method,
520                        visibility: p.visibility,
521                        parent_id: p.parent_id,
522                    })
523                    .collect();
524
525                self.interner.callable(CallableShape {
526                    call_signatures: instantiated_call,
527                    construct_signatures: instantiated_construct,
528                    properties: instantiated_props,
529                    string_index: shape.string_index.clone(),
530                    number_index: shape.number_index.clone(),
531                    symbol: shape.symbol,
532                })
533            }
534
535            // Conditional: instantiate all parts
536            TypeData::Conditional(cond_id) => {
537                let cond = self.interner.conditional_type(*cond_id);
538                if cond.is_distributive
539                    && let Some(TypeData::TypeParameter(info)) =
540                        self.interner.lookup(cond.check_type)
541                    && !self.is_shadowed(info.name)
542                    && let Some(substituted) = self.substitution.get(info.name)
543                {
544                    // When substituting with `never`, the result is `never`
545                    if substituted == crate::types::TypeId::NEVER {
546                        return substituted;
547                    }
548                    // For `any`, we need to let evaluation handle it properly
549                    // so it can distribute to both branches
550                    // TypeScript treats `boolean` as `true | false` for distributive conditionals
551                    if substituted == TypeId::BOOLEAN {
552                        let cond_type = self.interner.conditional(cond.as_ref().clone());
553                        let mut results = Vec::with_capacity(2);
554                        for &member in &[TypeId::BOOLEAN_TRUE, TypeId::BOOLEAN_FALSE] {
555                            if self.depth_exceeded {
556                                return TypeId::ERROR;
557                            }
558                            let mut member_subst = self.substitution.clone();
559                            member_subst.insert(info.name, member);
560                            let instantiated =
561                                instantiate_type(self.interner, cond_type, &member_subst);
562                            if instantiated == TypeId::ERROR {
563                                self.depth_exceeded = true;
564                                return TypeId::ERROR;
565                            }
566                            let evaluated =
567                                crate::evaluate::evaluate_type(self.interner, instantiated);
568                            if evaluated == TypeId::ERROR {
569                                self.depth_exceeded = true;
570                                return TypeId::ERROR;
571                            }
572                            results.push(evaluated);
573                        }
574                        return self.interner.union(results);
575                    }
576                    if let Some(TypeData::Union(members)) = self.interner.lookup(substituted) {
577                        let members = self.interner.type_list(members);
578                        // Limit distribution to prevent OOM with large unions
579                        // (e.g., string literal unions with thousands of members)
580                        const MAX_DISTRIBUTION_SIZE: usize = 100;
581                        if members.len() > MAX_DISTRIBUTION_SIZE {
582                            self.depth_exceeded = true;
583                            return TypeId::ERROR;
584                        }
585                        let cond_type = self.interner.conditional(cond.as_ref().clone());
586                        let mut results = Vec::with_capacity(members.len());
587                        for &member in members.iter() {
588                            // Check depth before each distribution step
589                            if self.depth_exceeded {
590                                return TypeId::ERROR;
591                            }
592                            let mut member_subst = self.substitution.clone();
593                            member_subst.insert(info.name, member);
594                            let instantiated =
595                                instantiate_type(self.interner, cond_type, &member_subst);
596                            // Check if instantiation hit depth limit
597                            if instantiated == TypeId::ERROR {
598                                self.depth_exceeded = true;
599                                return TypeId::ERROR;
600                            }
601                            let evaluated =
602                                crate::evaluate::evaluate_type(self.interner, instantiated);
603                            // Check if evaluation hit depth limit
604                            if evaluated == TypeId::ERROR {
605                                self.depth_exceeded = true;
606                                return TypeId::ERROR;
607                            }
608                            results.push(evaluated);
609                        }
610                        return self.interner.union(results);
611                    }
612                }
613                let instantiated = ConditionalType {
614                    check_type: self.instantiate(cond.check_type),
615                    extends_type: self.instantiate(cond.extends_type),
616                    true_type: self.instantiate(cond.true_type),
617                    false_type: self.instantiate(cond.false_type),
618                    is_distributive: cond.is_distributive,
619                };
620                self.interner.conditional(instantiated)
621            }
622
623            // Mapped: instantiate constraint and template
624            TypeData::Mapped(mapped_id) => {
625                let mapped = self.interner.mapped_type(*mapped_id);
626                let shadowed_len = self.shadowed.len();
627                let saved = self.visiting.clone();
628                let tp_id = self.interner.type_param(mapped.type_param.clone());
629                self.visiting.remove(&tp_id);
630                let saved_visiting = Some(saved);
631                self.shadowed.push(mapped.type_param.name);
632
633                let new_constraint = self.instantiate(mapped.constraint);
634                let new_template = self.instantiate(mapped.template);
635                let new_name_type = mapped.name_type.map(|t| self.instantiate(t));
636                let new_param_constraint =
637                    mapped.type_param.constraint.map(|c| self.instantiate(c));
638                let new_param_default = mapped.type_param.default.map(|d| self.instantiate(d));
639
640                self.shadowed.truncate(shadowed_len);
641                if let Some(saved) = saved_visiting {
642                    self.visiting = saved;
643                }
644
645                // If the mapped type is unchanged after substitution (e.g., because
646                // the mapped type's own type parameter shadowed the outer substitution),
647                // return the original to avoid eager evaluation that would collapse it.
648                let unchanged = new_constraint == mapped.constraint
649                    && new_template == mapped.template
650                    && new_name_type == mapped.name_type
651                    && new_param_constraint == mapped.type_param.constraint
652                    && new_param_default == mapped.type_param.default;
653
654                if unchanged {
655                    return self.interner.mapped((*mapped).clone());
656                }
657
658                let instantiated = MappedType {
659                    type_param: TypeParamInfo {
660                        is_const: false,
661                        name: mapped.type_param.name,
662                        constraint: new_param_constraint,
663                        default: new_param_default,
664                    },
665                    constraint: new_constraint,
666                    name_type: new_name_type,
667                    template: new_template,
668                    readonly_modifier: mapped.readonly_modifier,
669                    optional_modifier: mapped.optional_modifier,
670                };
671
672                // Trigger evaluation immediately for changed mapped types.
673                // This converts MappedType { constraint: "host"|"port", ... }
674                // into Object { host?: string, port?: number }
675                // Without this, the MappedType is returned unevaluated, causing subtype checks to fail.
676                let mapped_type = self.interner.mapped(instantiated);
677                crate::evaluate::evaluate_type(self.interner, mapped_type)
678            }
679
680            // Index access: instantiate both parts and evaluate immediately
681            // Task #46: Meta-type reduction for O(1) equality
682            TypeData::IndexAccess(obj, idx) => {
683                let inst_obj = self.instantiate(*obj);
684                let inst_idx = self.instantiate(*idx);
685                // Don't eagerly evaluate if either part still contains type parameters.
686                // This prevents premature evaluation of `T[K]` or `T[keyof T]` where T
687                // is an inference placeholder, which would resolve through the constraint
688                // instead of waiting for the actual inferred type.
689                if crate::visitor::contains_type_parameters(self.interner, inst_obj)
690                    || crate::visitor::contains_type_parameters(self.interner, inst_idx)
691                {
692                    return self.interner.index_access(inst_obj, inst_idx);
693                }
694                // Evaluate immediately to achieve O(1) equality
695                crate::evaluate::evaluate_index_access(self.interner, inst_obj, inst_idx)
696            }
697
698            // KeyOf: instantiate the operand and evaluate immediately
699            // Task #46: Meta-type reduction for O(1) equality
700            TypeData::KeyOf(operand) => {
701                let inst_operand = self.instantiate(*operand);
702                // Don't eagerly evaluate if the operand still contains type parameters.
703                // This prevents premature evaluation of `keyof T` where T is an inference
704                // placeholder (e.g. during compute_contextual_types), which would resolve
705                // to `keyof <constraint>` instead of waiting for T to be inferred.
706                // Without this, mapped types like `{ [P in keyof T]: ... }` collapse to `{}`
707                // because `keyof object` = `never`.
708                if crate::visitor::contains_type_parameters(self.interner, inst_operand) {
709                    return self.interner.keyof(inst_operand);
710                }
711                // Evaluate immediately to expand keyof { a: 1 } -> "a"
712                crate::evaluate::evaluate_keyof(self.interner, inst_operand)
713            }
714
715            // ReadonlyType: instantiate the operand
716            TypeData::ReadonlyType(operand) => {
717                let inst_operand = self.instantiate(*operand);
718                self.interner.readonly_type(inst_operand)
719            }
720
721            // NoInfer: preserve wrapper, instantiate inner
722            TypeData::NoInfer(inner) => {
723                let inst_inner = self.instantiate(*inner);
724                self.interner.no_infer(inst_inner)
725            }
726
727            // Template literal: instantiate embedded types
728            // After substitution, if any type span becomes a union of string literals,
729            // we trigger evaluation to expand the template literal into a union of strings.
730            TypeData::TemplateLiteral(spans) => {
731                let spans = self.interner.template_list(*spans);
732                let mut instantiated: Vec<TemplateSpan> = Vec::with_capacity(spans.len());
733                let mut needs_evaluation = false;
734
735                for span in spans.iter() {
736                    match span {
737                        TemplateSpan::Text(t) => instantiated.push(TemplateSpan::Text(*t)),
738                        TemplateSpan::Type(t) => {
739                            let inst_type = self.instantiate(*t);
740                            // Check if this type became something that can be evaluated:
741                            // - A union of string literals
742                            // - A single string literal
743                            // - The string intrinsic type
744                            if let Some(
745                                TypeData::Union(_)
746                                | TypeData::Literal(
747                                    LiteralValue::String(_)
748                                    | LiteralValue::Number(_)
749                                    | LiteralValue::Boolean(_),
750                                )
751                                | TypeData::Intrinsic(
752                                    IntrinsicKind::String
753                                    | IntrinsicKind::Number
754                                    | IntrinsicKind::Boolean,
755                                ),
756                            ) = self.interner.lookup(inst_type)
757                            {
758                                needs_evaluation = true;
759                            }
760                            instantiated.push(TemplateSpan::Type(inst_type));
761                        }
762                    }
763                }
764
765                let template_type = self.interner.template_literal(instantiated);
766
767                // If we detected types that can be evaluated, trigger evaluation
768                // to potentially expand the template literal to a union of string literals
769                if needs_evaluation {
770                    crate::evaluate::evaluate_type(self.interner, template_type)
771                } else {
772                    template_type
773                }
774            }
775
776            // StringIntrinsic: instantiate the type argument
777            // After substitution, if the type argument becomes a concrete type that can
778            // be evaluated (like a string literal or union), trigger evaluation.
779            TypeData::StringIntrinsic { kind, type_arg } => {
780                let inst_arg = self.instantiate(*type_arg);
781                let string_intrinsic = self.interner.string_intrinsic(*kind, inst_arg);
782
783                // Check if we can evaluate the result
784                if let Some(key) = self.interner.lookup(inst_arg) {
785                    match key {
786                        TypeData::Union(_)
787                        | TypeData::Literal(LiteralValue::String(_))
788                        | TypeData::TemplateLiteral(_)
789                        | TypeData::Intrinsic(IntrinsicKind::String) => {
790                            crate::evaluate::evaluate_type(self.interner, string_intrinsic)
791                        }
792                        _ => string_intrinsic,
793                    }
794                } else {
795                    string_intrinsic
796                }
797            }
798
799            // Infer: keep as-is unless explicitly substituting inference variables
800            TypeData::Infer(info) => {
801                if self.substitute_infer
802                    && !self.is_shadowed(info.name)
803                    && let Some(substituted) = self.substitution.get(info.name)
804                {
805                    return substituted;
806                }
807                self.interner.infer(info.clone())
808            }
809        }
810    }
811}
812
813/// Convenience function for instantiating a type with a substitution.
814pub fn instantiate_type(
815    interner: &dyn TypeDatabase,
816    type_id: TypeId,
817    substitution: &TypeSubstitution,
818) -> TypeId {
819    if substitution.is_empty() {
820        return type_id;
821    }
822    let mut instantiator = TypeInstantiator::new(interner, substitution);
823    let result = instantiator.instantiate(type_id);
824    if instantiator.depth_exceeded {
825        TypeId::ERROR
826    } else {
827        result
828    }
829}
830
831/// Convenience function for instantiating a type while substituting infer variables.
832pub fn instantiate_type_with_infer(
833    interner: &dyn TypeDatabase,
834    type_id: TypeId,
835    substitution: &TypeSubstitution,
836) -> TypeId {
837    if substitution.is_empty() {
838        return type_id;
839    }
840    let mut instantiator = TypeInstantiator::new(interner, substitution);
841    instantiator.substitute_infer = true;
842    let result = instantiator.instantiate(type_id);
843    if instantiator.depth_exceeded {
844        TypeId::ERROR
845    } else {
846        result
847    }
848}
849
850/// Convenience function for instantiating a generic type with type arguments.
851pub fn instantiate_generic(
852    interner: &dyn TypeDatabase,
853    type_id: TypeId,
854    type_params: &[TypeParamInfo],
855    type_args: &[TypeId],
856) -> TypeId {
857    if type_params.is_empty() || type_args.is_empty() {
858        return type_id;
859    }
860    let substitution = TypeSubstitution::from_args(interner, type_params, type_args);
861    instantiate_type(interner, type_id, &substitution)
862}
863
864/// Substitute `ThisType` with a concrete type throughout a type.
865///
866/// Used for method call return types where `this` refers to the receiver's type.
867/// For example, in a fluent builder pattern:
868/// ```typescript
869/// class Builder { setName(n: string): this { ... } }
870/// const b: Builder = new Builder().setName("foo"); // this → Builder
871/// ```
872pub fn substitute_this_type(
873    interner: &dyn TypeDatabase,
874    type_id: TypeId,
875    this_type: TypeId,
876) -> TypeId {
877    // Quick check: if the type is intrinsic, no substitution needed
878    if type_id.is_intrinsic() {
879        return type_id;
880    }
881    let empty_subst = TypeSubstitution::new();
882    let mut instantiator = TypeInstantiator::new(interner, &empty_subst);
883    instantiator.this_type = Some(this_type);
884    let result = instantiator.instantiate(type_id);
885    if instantiator.depth_exceeded {
886        TypeId::ERROR
887    } else {
888        result
889    }
890}
891
892#[cfg(test)]
893#[path = "../tests/instantiate_tests.rs"]
894mod tests;