tsz_solver/evaluation/evaluate.rs
1//! Type evaluation for meta-types (conditional, mapped, index access).
2//!
3//! Meta-types are "type-level functions" that compute output types from input types.
4//! This module provides evaluation logic for:
5//! - Conditional types: T extends U ? X : Y
6//! - Distributive conditional types: (A | B) extends U ? X : Y
7//! - Index access types: T[K]
8//!
9//! Key design:
10//! - Lazy evaluation: only evaluate when needed for subtype checking
11//! - Handles deferred evaluation when type parameters are unknown
12//! - Supports distributivity for naked type parameters in unions
13
14use crate::TypeDatabase;
15use crate::caches::db::QueryDatabase;
16use crate::def::DefId;
17use crate::instantiation::instantiate::instantiate_generic;
18use crate::relations::subtype::{NoopResolver, TypeResolver};
19#[cfg(test)]
20use crate::types::*;
21use crate::types::{
22 ConditionalType, ConditionalTypeId, MappedType, MappedTypeId, StringIntrinsicKind,
23 TemplateLiteralId, TemplateSpan, TypeApplicationId, TypeData, TypeId, TypeListId,
24 TypeParamInfo,
25};
26use rustc_hash::{FxHashMap, FxHashSet};
27
28/// Result of conditional type evaluation
29#[derive(Clone, Debug, PartialEq, Eq)]
30pub enum ConditionalResult {
31 /// The condition was resolved to a definite type
32 Resolved(TypeId),
33 /// The condition could not be resolved (deferred)
34 /// This happens when `check_type` is a type parameter that hasn't been substituted
35 Deferred(TypeId),
36}
37
38/// Maximum number of unique types to track in the visiting set.
39/// Prevents unbounded memory growth in pathological cases.
40pub const MAX_VISITING_SET_SIZE: usize = 10_000;
41
42/// Controls which subtype direction makes a member redundant when simplifying
43/// a union or intersection.
44enum SubtypeDirection {
45 /// member[i] <: member[j] → member[i] is redundant (union semantics).
46 SourceSubsumedByOther,
47 /// member[j] <: member[i] → member[i] is redundant (intersection semantics).
48 OtherSubsumedBySource,
49}
50
51/// Type evaluator for meta-types.
52///
53/// # Salsa Preparation
54/// This struct uses `&mut self` methods instead of `RefCell` + `&self`.
55/// This makes the evaluator thread-safe (Send) and prepares for future
56/// Salsa integration where state is managed by the database runtime.
57pub struct TypeEvaluator<'a, R: TypeResolver = NoopResolver> {
58 interner: &'a dyn TypeDatabase,
59 /// Optional query database for Salsa-backed memoization.
60 query_db: Option<&'a dyn QueryDatabase>,
61 resolver: &'a R,
62 no_unchecked_indexed_access: bool,
63 cache: FxHashMap<TypeId, TypeId>,
64 /// Unified recursion guard for `TypeId` cycle detection, depth, and iteration limits.
65 guard: crate::recursion::RecursionGuard<TypeId>,
66 /// Per-DefId recursion depth counter.
67 /// Allows recursive type aliases (like `TrimRight`) to expand up to `MAX_DEF_DEPTH`
68 /// times before stopping, matching tsc's TS2589 "Type instantiation is excessively
69 /// deep and possibly infinite" behavior. Unlike a set-based cycle detector, this
70 /// permits legitimate bounded recursion where each expansion converges.
71 def_depth: FxHashMap<DefId, u32>,
72}
73
74/// Array methods that return any (used for apparent type computation).
75pub const ARRAY_METHODS_RETURN_ANY: &[&str] = &[
76 "concat",
77 "filter",
78 "flat",
79 "flatMap",
80 "map",
81 "reverse",
82 "slice",
83 "sort",
84 "splice",
85 "toReversed",
86 "toSorted",
87 "toSpliced",
88 "with",
89 "at",
90 "find",
91 "findLast",
92 "pop",
93 "shift",
94 "entries",
95 "keys",
96 "values",
97 "reduce",
98 "reduceRight",
99];
100/// Array methods that return boolean.
101pub const ARRAY_METHODS_RETURN_BOOLEAN: &[&str] = &["every", "includes", "some"];
102/// Array methods that return number.
103pub const ARRAY_METHODS_RETURN_NUMBER: &[&str] = &[
104 "findIndex",
105 "findLastIndex",
106 "indexOf",
107 "lastIndexOf",
108 "push",
109 "unshift",
110];
111/// Array methods that return void.
112pub const ARRAY_METHODS_RETURN_VOID: &[&str] = &["forEach", "copyWithin", "fill"];
113/// Array methods that return string.
114pub const ARRAY_METHODS_RETURN_STRING: &[&str] = &["join", "toLocaleString", "toString"];
115
116impl<'a> TypeEvaluator<'a, NoopResolver> {
117 /// Create a new evaluator without a resolver.
118 pub fn new(interner: &'a dyn TypeDatabase) -> Self {
119 static NOOP: NoopResolver = NoopResolver;
120 TypeEvaluator {
121 interner,
122 query_db: None,
123 resolver: &NOOP,
124 no_unchecked_indexed_access: false,
125 cache: FxHashMap::default(),
126 guard: crate::recursion::RecursionGuard::with_profile(
127 crate::recursion::RecursionProfile::TypeEvaluation,
128 ),
129 def_depth: FxHashMap::default(),
130 }
131 }
132}
133
134impl<'a, R: TypeResolver> TypeEvaluator<'a, R> {
135 /// Maximum recursive expansion depth for a single `DefId`.
136 /// Matches TypeScript's instantiation depth limit that triggers TS2589.
137 const MAX_DEF_DEPTH: u32 = 50;
138
139 /// Create a new evaluator with a custom resolver.
140 pub fn with_resolver(interner: &'a dyn TypeDatabase, resolver: &'a R) -> Self {
141 TypeEvaluator {
142 interner,
143 query_db: None,
144 resolver,
145 no_unchecked_indexed_access: false,
146 cache: FxHashMap::default(),
147 guard: crate::recursion::RecursionGuard::with_profile(
148 crate::recursion::RecursionProfile::TypeEvaluation,
149 ),
150 def_depth: FxHashMap::default(),
151 }
152 }
153
154 /// Set the query database for Salsa-backed memoization.
155 pub fn with_query_db(mut self, db: &'a dyn QueryDatabase) -> Self {
156 self.query_db = Some(db);
157 self
158 }
159
160 pub fn set_no_unchecked_indexed_access(&mut self, enabled: bool) {
161 if self.no_unchecked_indexed_access != enabled {
162 self.cache.clear();
163 }
164 self.no_unchecked_indexed_access = enabled;
165 }
166
167 /// Reset per-evaluation state so this evaluator can be reused.
168 ///
169 /// Clears the cache, cycle detection sets, and counters while preserving
170 /// configuration and borrowed references. Uses `.clear()` to reuse memory.
171 #[inline]
172 pub fn reset(&mut self) {
173 self.cache.clear();
174 self.guard.reset();
175 self.def_depth.clear();
176 }
177
178 // =========================================================================
179 // Accessor methods for evaluate_rules modules
180 // =========================================================================
181
182 /// Get the type interner.
183 #[inline]
184 pub(crate) fn interner(&self) -> &'a dyn TypeDatabase {
185 self.interner
186 }
187
188 /// Get the type resolver.
189 #[inline]
190 pub(crate) const fn resolver(&self) -> &'a R {
191 self.resolver
192 }
193
194 /// Check if `no_unchecked_indexed_access` is enabled.
195 #[inline]
196 pub(crate) const fn no_unchecked_indexed_access(&self) -> bool {
197 self.no_unchecked_indexed_access
198 }
199
200 /// Check if depth limit was exceeded.
201 #[inline]
202 pub const fn is_depth_exceeded(&self) -> bool {
203 self.guard.is_exceeded()
204 }
205
206 /// Mark the guard as exceeded, causing subsequent evaluations to bail out.
207 ///
208 /// Used when an external condition (e.g. mapped key count or distribution
209 /// size exceeds its limit) means further recursive evaluation should stop.
210 #[inline]
211 pub(crate) const fn mark_depth_exceeded(&mut self) {
212 self.guard.mark_exceeded();
213 }
214
215 /// Evaluate a type, resolving any meta-types if possible.
216 /// Returns the evaluated type (may be the same if no evaluation needed).
217 pub fn evaluate(&mut self, type_id: TypeId) -> TypeId {
218 use crate::recursion::RecursionResult;
219
220 // Fast path for intrinsics
221 if type_id.is_intrinsic() {
222 return type_id;
223 }
224
225 // Check if depth was already exceeded in a previous call
226 if self.guard.is_exceeded() {
227 return TypeId::ERROR;
228 }
229
230 if let Some(&cached) = self.cache.get(&type_id) {
231 return cached;
232 }
233
234 // Unified enter: checks iterations, depth, cycle detection, and visiting set size
235 match self.guard.enter(type_id) {
236 RecursionResult::Entered => {}
237 RecursionResult::Cycle => {
238 // Recursion guard for self-referential mapped/application types.
239 // Per TypeScript behavior, recursive mapped types evaluate to empty objects.
240 let key = self.interner.lookup(type_id);
241 if matches!(key, Some(TypeData::Mapped(_))) {
242 let empty = self.interner.object(vec![]);
243 self.cache.insert(type_id, empty);
244 return empty;
245 }
246 return type_id;
247 }
248 RecursionResult::DepthExceeded => {
249 self.cache.insert(type_id, TypeId::ERROR);
250 return TypeId::ERROR;
251 }
252 RecursionResult::IterationExceeded => {
253 self.cache.insert(type_id, type_id);
254 return type_id;
255 }
256 }
257
258 let key = match self.interner.lookup(type_id) {
259 Some(k) => k,
260 None => {
261 self.guard.leave(type_id);
262 return type_id;
263 }
264 };
265
266 // Visitor pattern: dispatch to appropriate visit_* method
267 let result = self.visit_type_key(type_id, &key);
268
269 // Symmetric cleanup: leave guard and cache result
270 self.guard.leave(type_id);
271 self.cache.insert(type_id, result);
272
273 result
274 }
275
276 /// Evaluate a generic type application: Base<Args>
277 ///
278 /// Algorithm:
279 /// 1. Look up the base type - if it's a Ref, resolve it
280 /// 2. Get the type parameters for the base symbol
281 /// 3. If we have type params, instantiate the resolved type with args
282 /// 4. Recursively evaluate the result
283 fn evaluate_application(&mut self, app_id: TypeApplicationId) -> TypeId {
284 let app = self.interner.type_application(app_id);
285
286 // Look up the base type
287 let base_key = match self.interner.lookup(app.base) {
288 Some(k) => k,
289 None => return self.interner.application(app.base, app.args.clone()),
290 };
291
292 // Task B: Resolve TypeQuery bases to DefId for expansion
293 // This fixes the "Ref(5)<error>" diagnostic issue where generic types
294 // aren't expanded to their underlying function/object types
295 // Note: Ref(SymbolRef) was migrated to Lazy(DefId)
296 let def_id = match base_key {
297 TypeData::Lazy(def_id) => Some(def_id),
298 TypeData::TypeQuery(sym_ref) => self.resolver.symbol_to_def_id(sym_ref),
299 _ => None,
300 };
301
302 tracing::trace!(
303 base = app.base.0,
304 base_key = ?base_key,
305 def_id = ?def_id,
306 args = ?app.args.iter().map(|a| a.0).collect::<Vec<_>>(),
307 "evaluate_application"
308 );
309
310 // If the base is a DefId (Lazy, Ref, or TypeQuery), try to resolve and instantiate
311 if let Some(def_id) = def_id {
312 // =======================================================================
313 // PER-DEFID DEPTH LIMITING
314 // =======================================================================
315 // This catches expansive recursion in type aliases like `type T<X> = T<Box<X>>`
316 // that produce new TypeIds on each evaluation, bypassing the `visiting` set.
317 //
318 // Unlike a set-based cycle detector (which blocks ANY re-entry), we use a
319 // per-DefId counter that allows up to MAX_DEF_DEPTH recursive expansions.
320 // This correctly handles legitimate recursive types like:
321 // type TrimRight<S> = S extends `${infer R} ` ? TrimRight<R> : S;
322 // which need multiple re-entries of the same DefId to converge.
323 // =======================================================================
324 let depth = self.def_depth.entry(def_id).or_insert(0);
325 if *depth >= Self::MAX_DEF_DEPTH {
326 self.guard.mark_exceeded();
327 return TypeId::ERROR;
328 }
329 *depth += 1;
330
331 // Try to get the type parameters for this DefId
332 let type_params = self.resolver.get_lazy_type_params(def_id);
333 let resolved = self.resolver.resolve_lazy(def_id, self.interner);
334
335 tracing::trace!(
336 ?def_id,
337 has_type_params = type_params.is_some(),
338 type_params_count = type_params.as_ref().map(std::vec::Vec::len),
339 has_resolved = resolved.is_some(),
340 resolved_key = ?resolved.and_then(|r| self.interner.lookup(r)),
341 "evaluate_application resolve"
342 );
343
344 let result = if let Some(type_params) = type_params {
345 // Resolve the base type to get the body
346 if let Some(resolved) = resolved {
347 // Pre-expand type arguments that are TypeQuery or Application.
348 // For conditional type bodies with Application extends containing infer,
349 // preserve Application args so the conditional evaluator can match
350 // at the Application level (e.g., Promise<string> vs Promise<infer U>).
351 let body_is_conditional_with_app_infer =
352 self.is_conditional_with_application_infer(resolved);
353 let expanded_args = if body_is_conditional_with_app_infer {
354 self.expand_type_args_preserve_applications(&app.args)
355 } else {
356 self.expand_type_args(&app.args)
357 };
358 let no_unchecked_indexed_access = self.no_unchecked_indexed_access;
359
360 if let Some(db) = self.query_db
361 && let Some(cached) = db.lookup_application_eval_cache(
362 def_id,
363 &expanded_args,
364 no_unchecked_indexed_access,
365 )
366 {
367 if let Some(d) = self.def_depth.get_mut(&def_id) {
368 *d = d.saturating_sub(1);
369 }
370 return cached;
371 }
372
373 // Instantiate the resolved type with the type arguments
374 let instantiated =
375 instantiate_generic(self.interner, resolved, &type_params, &expanded_args);
376 // Recursively evaluate the result
377 let evaluated = self.evaluate(instantiated);
378 if let Some(db) = self.query_db {
379 db.insert_application_eval_cache(
380 def_id,
381 &expanded_args,
382 no_unchecked_indexed_access,
383 evaluated,
384 );
385 }
386 evaluated
387 } else {
388 self.interner.application(app.base, app.args.clone())
389 }
390 } else if let Some(resolved) = resolved {
391 // Fallback: try to extract type params from the resolved type's properties
392 let extracted_params = self.extract_type_params_from_type(resolved);
393 if !extracted_params.is_empty() && extracted_params.len() == app.args.len() {
394 // Pre-expand type arguments
395 let expanded_args = self.expand_type_args(&app.args);
396 let no_unchecked_indexed_access = self.no_unchecked_indexed_access;
397
398 if let Some(db) = self.query_db
399 && let Some(cached) = db.lookup_application_eval_cache(
400 def_id,
401 &expanded_args,
402 no_unchecked_indexed_access,
403 )
404 {
405 if let Some(d) = self.def_depth.get_mut(&def_id) {
406 *d = d.saturating_sub(1);
407 }
408 return cached;
409 }
410
411 let instantiated = instantiate_generic(
412 self.interner,
413 resolved,
414 &extracted_params,
415 &expanded_args,
416 );
417 let evaluated = self.evaluate(instantiated);
418 if let Some(db) = self.query_db {
419 db.insert_application_eval_cache(
420 def_id,
421 &expanded_args,
422 no_unchecked_indexed_access,
423 evaluated,
424 );
425 }
426 evaluated
427 } else {
428 self.interner.application(app.base, app.args.clone())
429 }
430 } else {
431 self.interner.application(app.base, app.args.clone())
432 };
433
434 // Decrement per-DefId depth after evaluation
435 if let Some(d) = self.def_depth.get_mut(&def_id) {
436 *d = d.saturating_sub(1);
437 }
438
439 result
440 } else {
441 // If we can't expand, return the original application
442 self.interner.application(app.base, app.args.clone())
443 }
444 }
445
446 /// Check if a type is a Conditional whose `extends_type` is an Application containing infer.
447 /// This detects patterns like `T extends Promise<infer U> ? U : T`.
448 fn is_conditional_with_application_infer(&self, type_id: TypeId) -> bool {
449 let Some(TypeData::Conditional(cond_id)) = self.interner.lookup(type_id) else {
450 return false;
451 };
452 let cond = self.interner.conditional_type(cond_id);
453 matches!(
454 self.interner.lookup(cond.extends_type),
455 Some(TypeData::Application(_))
456 )
457 }
458
459 /// Like `expand_type_args` but preserves Application types without evaluating them.
460 /// Used for conditional type bodies so the conditional evaluator can match
461 /// at the Application level for infer pattern matching.
462 fn expand_type_args_preserve_applications(&mut self, args: &[TypeId]) -> Vec<TypeId> {
463 let mut expanded = Vec::with_capacity(args.len());
464 for &arg in args {
465 let Some(key) = self.interner.lookup(arg) else {
466 expanded.push(arg);
467 continue;
468 };
469 match key {
470 TypeData::Application(_) => {
471 // Preserve Application form for conditional infer matching
472 expanded.push(arg);
473 }
474 _ => expanded.push(self.try_expand_type_arg(arg)),
475 }
476 }
477 expanded
478 }
479
480 /// Expand type arguments by evaluating any that are `TypeQuery` or Application.
481 /// Uses a loop instead of closure to allow mutable self access.
482 fn expand_type_args(&mut self, args: &[TypeId]) -> Vec<TypeId> {
483 let mut expanded = Vec::with_capacity(args.len());
484 for &arg in args {
485 expanded.push(self.try_expand_type_arg(arg));
486 }
487 expanded
488 }
489
490 /// Extract type parameter infos from a type by scanning for `TypeParameter` types.
491 fn extract_type_params_from_type(&self, type_id: TypeId) -> Vec<TypeParamInfo> {
492 let mut seen = FxHashSet::default();
493 let mut params = Vec::new();
494 self.collect_type_params(type_id, &mut seen, &mut params);
495 params
496 }
497
498 /// Recursively collect `TypeParameter` types from a type.
499 fn collect_type_params(
500 &self,
501 type_id: TypeId,
502 seen: &mut FxHashSet<tsz_common::interner::Atom>,
503 params: &mut Vec<TypeParamInfo>,
504 ) {
505 if type_id.is_intrinsic() {
506 return;
507 }
508
509 let Some(key) = self.interner.lookup(type_id) else {
510 return;
511 };
512
513 match key {
514 TypeData::TypeParameter(ref info) => {
515 if !seen.contains(&info.name) {
516 seen.insert(info.name);
517 params.push(info.clone());
518 }
519 }
520 TypeData::Object(shape_id) | TypeData::ObjectWithIndex(shape_id) => {
521 let shape = self.interner.object_shape(shape_id);
522 for prop in &shape.properties {
523 self.collect_type_params(prop.type_id, seen, params);
524 }
525 }
526 TypeData::Function(shape_id) => {
527 let shape = self.interner.function_shape(shape_id);
528 for param in &shape.params {
529 self.collect_type_params(param.type_id, seen, params);
530 }
531 self.collect_type_params(shape.return_type, seen, params);
532 }
533 TypeData::Union(members) | TypeData::Intersection(members) => {
534 let members = self.interner.type_list(members);
535 for &member in members.iter() {
536 self.collect_type_params(member, seen, params);
537 }
538 }
539 TypeData::Array(elem) => {
540 self.collect_type_params(elem, seen, params);
541 }
542 TypeData::Conditional(cond_id) => {
543 let cond = self.interner.conditional_type(cond_id);
544 self.collect_type_params(cond.check_type, seen, params);
545 self.collect_type_params(cond.extends_type, seen, params);
546 self.collect_type_params(cond.true_type, seen, params);
547 self.collect_type_params(cond.false_type, seen, params);
548 }
549 TypeData::Application(app_id) => {
550 let app = self.interner.type_application(app_id);
551 self.collect_type_params(app.base, seen, params);
552 for &arg in &app.args {
553 self.collect_type_params(arg, seen, params);
554 }
555 }
556 TypeData::Mapped(mapped_id) => {
557 let mapped = self.interner.mapped_type(mapped_id);
558 // Note: mapped.type_param is the iteration variable (e.g., K in "K in keyof T")
559 // We should NOT add it directly - the outer type param (T) is found in the constraint.
560 // For DeepPartial<T> = { [K in keyof T]?: DeepPartial<T[K]> }:
561 // - type_param is K (iteration var, NOT the outer param)
562 // - constraint is "keyof T" (contains T, the actual param to extract)
563 // - template is DeepPartial<T[K]> (also contains T)
564 self.collect_type_params(mapped.constraint, seen, params);
565 self.collect_type_params(mapped.template, seen, params);
566 if let Some(name_type) = mapped.name_type {
567 self.collect_type_params(name_type, seen, params);
568 }
569 }
570 TypeData::KeyOf(operand) => {
571 // Extract type params from the operand of keyof
572 // e.g., keyof T -> extract T
573 self.collect_type_params(operand, seen, params);
574 }
575 TypeData::IndexAccess(obj, idx) => {
576 // Extract type params from both object and index
577 // e.g., T[K] -> extract T and K
578 self.collect_type_params(obj, seen, params);
579 self.collect_type_params(idx, seen, params);
580 }
581 TypeData::TemplateLiteral(spans) => {
582 // Extract type params from template literal interpolations
583 let spans = self.interner.template_list(spans);
584 for span in spans.iter() {
585 if let TemplateSpan::Type(inner) = span {
586 self.collect_type_params(*inner, seen, params);
587 }
588 }
589 }
590 _ => {}
591 }
592 }
593
594 /// Try to expand a type argument that may be a `TypeQuery` or Application.
595 /// Returns the expanded type, or the original if it can't be expanded.
596 /// This ensures type arguments are resolved before instantiation.
597 ///
598 /// NOTE: This method uses `self.evaluate()` for Application, Conditional, Mapped,
599 /// and `TemplateLiteral` types to ensure recursion depth limits are enforced.
600 fn try_expand_type_arg(&mut self, arg: TypeId) -> TypeId {
601 let Some(key) = self.interner.lookup(arg) else {
602 return arg;
603 };
604 match key {
605 TypeData::TypeQuery(sym_ref) => {
606 // Resolve the TypeQuery to get the VALUE type (constructor for classes).
607 // Use resolve_ref, not resolve_symbol_ref, to avoid the resolve_lazy path
608 // which returns instance types for classes.
609 if let Some(resolved) = self.resolver.resolve_ref(sym_ref, self.interner) {
610 resolved
611 } else if let Some(def_id) = self.resolver.symbol_to_def_id(sym_ref) {
612 self.resolver
613 .resolve_lazy(def_id, self.interner)
614 .unwrap_or(arg)
615 } else {
616 arg
617 }
618 }
619 TypeData::Application(_)
620 | TypeData::Conditional(_)
621 | TypeData::Mapped(_)
622 | TypeData::TemplateLiteral(_) => {
623 // Use evaluate() to ensure depth limits are enforced
624 self.evaluate(arg)
625 }
626 TypeData::Lazy(def_id) => {
627 // Resolve Lazy types in type arguments
628 // This helps with generic instantiation accuracy
629 self.resolver
630 .resolve_lazy(def_id, self.interner)
631 .unwrap_or(arg)
632 }
633 _ => arg,
634 }
635 }
636
637 /// Check if a type is "complex" and requires full evaluation for identity.
638 ///
639 /// Complex types are those whose structural identity depends on evaluation context:
640 /// - `TypeParameter`: Opaque until instantiation
641 /// - Lazy: Requires resolution
642 /// - Conditional: Requires evaluation of extends clause
643 /// - Mapped: Requires evaluation of mapped type
644 /// - `IndexAccess`: Requires evaluation of T[K]
645 /// - `KeyOf`: Requires evaluation of keyof
646 /// - Application: Requires expansion of Base<Args>
647 /// - `TypeQuery`: Requires resolution of typeof
648 /// - `TemplateLiteral`: Requires evaluation of template parts
649 /// - `ReadonlyType`: Wraps another type
650 /// - `StringIntrinsic`: Uppercase, Lowercase, Capitalize, Uncapitalize
651 ///
652 /// These types are NOT safe for simplification because bypassing evaluation
653 /// would produce incorrect results (e.g., treating T[K] as a distinct type from
654 /// the value it evaluates to).
655 ///
656 /// ## Task #37: Deep Structural Simplification
657 ///
658 /// After implementing the Canonicalizer (Task #32), we can now safely handle
659 /// `Lazy` (type aliases) and `Application` (generics) structurally. These types
660 /// are now "unlocked" for simplification because:
661 /// - `Lazy` types are canonicalized using De Bruijn indices
662 /// - `Application` types are recursively canonicalized
663 /// - The `SubtypeChecker`'s fast-path (Task #36) uses O(1) structural identity
664 ///
665 /// Types that remain "complex" are those that are **inherently deferred**:
666 /// - `TypeParameter`, `Infer`: Waiting for generic substitution
667 /// - `Conditional`, `Mapped`, `IndexAccess`, `KeyOf`: Require type-level computation
668 /// - These cannot be compared structurally until they are fully evaluated
669 fn is_complex_type(&self, type_id: TypeId) -> bool {
670 let Some(key) = self.interner.lookup(type_id) else {
671 return false;
672 };
673
674 matches!(
675 key,
676 TypeData::TypeParameter(_)
677 | TypeData::Infer(_) // Type parameter for conditional types
678 | TypeData::Conditional(_)
679 | TypeData::Mapped(_)
680 | TypeData::IndexAccess(_, _)
681 | TypeData::KeyOf(_)
682 | TypeData::TypeQuery(_)
683 | TypeData::TemplateLiteral(_)
684 | TypeData::ReadonlyType(_)
685 | TypeData::StringIntrinsic { .. }
686 | TypeData::ThisType // Context-dependent polymorphic type
687 // Note: Lazy and Application are REMOVED (Task #37)
688 // They are now handled by the Canonicalizer (Task #32)
689 )
690 }
691
692 /// Evaluate an intersection type by recursively evaluating members and re-interning.
693 /// This enables "deferred reduction" where intersections containing meta-types
694 /// (e.g., `string & T[K]`) are reduced after the meta-types are evaluated.
695 ///
696 /// Example: `string & T[K]` where `T[K]` evaluates to `number` will become
697 /// `string & number`, which then reduces to `never` via the interner's normalization.
698 fn evaluate_intersection(&mut self, list_id: TypeListId) -> TypeId {
699 let members = self.interner.type_list(list_id);
700 let mut evaluated_members = Vec::with_capacity(members.len());
701
702 for &member in members.iter() {
703 evaluated_members.push(self.evaluate(member));
704 }
705
706 // Deep structural simplification using SubtypeChecker
707 self.simplify_intersection_members(&mut evaluated_members);
708
709 self.interner.intersection(evaluated_members)
710 }
711
712 /// Evaluate a union type by recursively evaluating members and re-interning.
713 /// This enables "deferred reduction" where unions containing meta-types
714 /// (e.g., `string | T[K]`) are reduced after the meta-types are evaluated.
715 ///
716 /// Example: `string | T[K]` where `T[K]` evaluates to `string` will become
717 /// `string | string`, which then reduces to `string` via the interner's normalization.
718 fn evaluate_union(&mut self, list_id: TypeListId) -> TypeId {
719 let members = self.interner.type_list(list_id);
720 let mut evaluated_members = Vec::with_capacity(members.len());
721
722 for &member in members.iter() {
723 evaluated_members.push(self.evaluate(member));
724 }
725
726 // Deep structural simplification using SubtypeChecker
727 self.simplify_union_members(&mut evaluated_members);
728
729 self.interner.union(evaluated_members)
730 }
731
732 /// Simplify union members by removing redundant types using deep subtype checks.
733 /// If A <: B, then A | B = B (A is redundant in the union).
734 ///
735 /// This uses `SubtypeChecker` with `bypass_evaluation=true` to prevent infinite
736 /// recursion, since `TypeEvaluator` has already evaluated all members.
737 ///
738 /// Performance: O(N²) where N is the number of members. We skip simplification
739 /// if the union has more than 25 members to avoid excessive computation.
740 ///
741 /// ## Strategy
742 ///
743 /// 1. **Early exit for large unions** (>25 members) to avoid O(N²) explosion
744 /// 2. **Skip complex types** that require full resolution:
745 /// - `TypeParameter`, Infer, Conditional, Mapped, `IndexAccess`, `KeyOf`, `TypeQuery`
746 /// - `TemplateLiteral`, `ReadonlyType`, String manipulation types
747 /// - Note: Lazy and Application are NOW safe (Task #37: handled by Canonicalizer)
748 /// 3. **Fast-path for any/unknown**: If any member is any, entire union becomes any
749 /// 4. **Identity check**: O(1) structural identity via `SubtypeChecker` (Task #36 fast-path)
750 /// 5. **Depth limit**: `MAX_SUBTYPE_DEPTH` enables deep recursive type simplification (Task #37)
751 ///
752 /// ## Example Reductions
753 ///
754 /// - `"a" | string` → `string` (literal absorbed by primitive)
755 /// - `number | 1 | 2` → `number` (literals absorbed by primitive)
756 /// - `{ a: string } | { a: string; b: number }` → `{ a: string; b: number }`
757 fn simplify_union_members(&mut self, members: &mut Vec<TypeId>) {
758 // Union-specific early exits
759 if members.iter().any(|&id| id.is_unknown()) {
760 return;
761 }
762 // Skip if all members are identity-comparable — they're disjoint, so the O(n²) loop
763 // would find nothing. The interner's reduce_union_subtypes handles shallow cases.
764 if members
765 .iter()
766 .all(|&id| self.interner.is_identity_comparable_type(id))
767 {
768 return;
769 }
770 // In a union, A <: B means A is redundant (B subsumes it).
771 // E.g. `"a" | string` => "a" is redundant, result: `string`
772 self.remove_redundant_members(members, SubtypeDirection::SourceSubsumedByOther);
773 }
774
775 /// Simplify intersection members by removing redundant types using deep subtype checks.
776 /// If A <: B, then A & B = A (B is redundant in the intersection).
777 ///
778 /// ## Example Reductions
779 ///
780 /// - `{ a: string } & { a: string; b: number }` → `{ a: string; b: number }`
781 /// - `{ readonly a: string } & { a: string }` → `{ readonly a: string }`
782 /// - `number & 1` → `1` (literal is more specific)
783 fn simplify_intersection_members(&mut self, members: &mut Vec<TypeId>) {
784 // In an intersection, A <: B means B is redundant (A is more specific).
785 // We check if other members are subtypes of the candidate to remove the supertype.
786 self.remove_redundant_members(members, SubtypeDirection::OtherSubsumedBySource);
787 }
788
789 /// Remove redundant members from a type list using subtype checks.
790 ///
791 /// This is the shared O(n²) core for both union and intersection simplification.
792 /// The `direction` parameter controls which subtype relationship makes a member
793 /// redundant:
794 /// - `SourceSubsumedByOther`: member[i] <: member[j] → i is redundant (union semantics)
795 /// - `OtherSubsumedBySource`: member[j] <: member[i] → i is redundant (intersection semantics)
796 ///
797 /// Common early exits (size guards, `any` check, complex-type check) are applied here.
798 fn remove_redundant_members(&mut self, members: &mut Vec<TypeId>, direction: SubtypeDirection) {
799 // Performance guard: skip small or very large type lists
800 const MAX_SIMPLIFICATION_SIZE: usize = 25;
801 if members.len() < 2 || members.len() > MAX_SIMPLIFICATION_SIZE {
802 return;
803 }
804
805 if members.iter().any(|&id| id.is_any()) {
806 return;
807 }
808
809 if members.iter().any(|&id| self.is_complex_type(id)) {
810 return;
811 }
812
813 use crate::relations::subtype::{MAX_SUBTYPE_DEPTH, SubtypeChecker};
814 let mut checker = SubtypeChecker::with_resolver(self.interner, self.resolver);
815 checker.bypass_evaluation = true;
816 checker.max_depth = MAX_SUBTYPE_DEPTH;
817 checker.no_unchecked_indexed_access = self.no_unchecked_indexed_access;
818
819 let mut i = 0;
820 while i < members.len() {
821 let mut redundant = false;
822 for j in 0..members.len() {
823 if i == j {
824 continue;
825 }
826 if members[i] == members[j] {
827 continue;
828 }
829
830 let is_subtype = match direction {
831 SubtypeDirection::SourceSubsumedByOther => {
832 checker.is_subtype_of(members[i], members[j])
833 }
834 SubtypeDirection::OtherSubsumedBySource => {
835 checker.is_subtype_of(members[j], members[i])
836 }
837 };
838 if is_subtype {
839 redundant = true;
840 break;
841 }
842 }
843 if redundant {
844 members.remove(i);
845 } else {
846 i += 1;
847 }
848 }
849 }
850
851 // =========================================================================
852 // Visitor Pattern Implementation (North Star Rule 2)
853 // =========================================================================
854
855 /// Visit a `TypeData` and return its evaluated form.
856 ///
857 /// This is the visitor dispatch method that routes to specific visit_* methods.
858 /// The `visiting.remove()` and `cache.insert()` are handled in `evaluate()` for symmetry.
859 fn visit_type_key(&mut self, type_id: TypeId, key: &TypeData) -> TypeId {
860 match key {
861 TypeData::Conditional(cond_id) => self.visit_conditional(*cond_id),
862 TypeData::IndexAccess(obj, idx) => self.visit_index_access(*obj, *idx),
863 TypeData::Mapped(mapped_id) => self.visit_mapped(*mapped_id),
864 TypeData::KeyOf(operand) => self.visit_keyof(*operand),
865 TypeData::TypeQuery(symbol) => self.visit_type_query(symbol.0, type_id),
866 TypeData::Application(app_id) => self.visit_application(*app_id),
867 TypeData::TemplateLiteral(spans) => self.visit_template_literal(*spans),
868 TypeData::Lazy(def_id) => self.visit_lazy(*def_id, type_id),
869 TypeData::StringIntrinsic { kind, type_arg } => {
870 self.visit_string_intrinsic(*kind, *type_arg)
871 }
872 TypeData::Intersection(list_id) => self.visit_intersection(*list_id),
873 TypeData::Union(list_id) => self.visit_union(*list_id),
874 TypeData::NoInfer(inner) => {
875 // NoInfer<T> evaluates to T (strip wrapper, evaluate inner)
876 self.evaluate(*inner)
877 }
878 // All other types pass through unchanged (default behavior)
879 _ => type_id,
880 }
881 }
882
883 /// Visit a conditional type: T extends U ? X : Y
884 fn visit_conditional(&mut self, cond_id: ConditionalTypeId) -> TypeId {
885 let cond = self.interner.conditional_type(cond_id);
886 self.evaluate_conditional(cond.as_ref())
887 }
888
889 /// Visit an index access type: T[K]
890 fn visit_index_access(&mut self, object_type: TypeId, index_type: TypeId) -> TypeId {
891 self.evaluate_index_access(object_type, index_type)
892 }
893
894 /// Visit a mapped type: { [K in Keys]: V }
895 fn visit_mapped(&mut self, mapped_id: MappedTypeId) -> TypeId {
896 let mapped = self.interner.mapped_type(mapped_id);
897 self.evaluate_mapped(mapped.as_ref())
898 }
899
900 /// Visit a keyof type: keyof T
901 fn visit_keyof(&mut self, operand: TypeId) -> TypeId {
902 self.evaluate_keyof(operand)
903 }
904
905 /// Visit a type query: typeof expr
906 ///
907 /// `TypeQuery` represents `typeof X` which must resolve to the VALUE-space type
908 /// (constructor type for classes). We use `resolve_ref` which returns the
909 /// constructor type stored under `SymbolRef`, NOT `resolve_lazy` which returns
910 /// the instance type for classes. This distinction is critical: `typeof A`
911 /// for a class A should give the constructor type (with static members and
912 /// construct signatures), not the instance type.
913 fn visit_type_query(&mut self, symbol_ref: u32, original_type_id: TypeId) -> TypeId {
914 use crate::types::SymbolRef;
915 let symbol = SymbolRef(symbol_ref);
916
917 // Prefer resolve_ref which returns the VALUE type (constructor for classes).
918 // This is correct for TypeQuery (typeof) which is a value-space query.
919 if let Some(resolved) = self.resolver.resolve_ref(symbol, self.interner) {
920 return resolved;
921 }
922
923 // Fallback: try DefId-based resolution if no SymbolRef mapping exists
924 if let Some(def_id) = self.resolver.symbol_to_def_id(symbol)
925 && let Some(resolved) = self.resolver.resolve_lazy(def_id, self.interner)
926 {
927 return resolved;
928 }
929
930 original_type_id
931 }
932
933 /// Visit a generic type application: Base<Args>
934 fn visit_application(&mut self, app_id: TypeApplicationId) -> TypeId {
935 self.evaluate_application(app_id)
936 }
937
938 /// Visit a template literal type: `hello${T}world`
939 fn visit_template_literal(&mut self, spans: TemplateLiteralId) -> TypeId {
940 self.evaluate_template_literal(spans)
941 }
942
943 /// Visit a lazy type reference: Lazy(DefId)
944 fn visit_lazy(&mut self, def_id: DefId, original_type_id: TypeId) -> TypeId {
945 if let Some(resolved) = self.resolver.resolve_lazy(def_id, self.interner) {
946 // Re-evaluate the resolved type in case it needs further evaluation
947 self.evaluate(resolved)
948 } else {
949 original_type_id
950 }
951 }
952
953 /// Visit a string manipulation intrinsic type: Uppercase<T>, Lowercase<T>, etc.
954 fn visit_string_intrinsic(&mut self, kind: StringIntrinsicKind, type_arg: TypeId) -> TypeId {
955 self.evaluate_string_intrinsic(kind, type_arg)
956 }
957
958 /// Visit an intersection type: A & B & C
959 fn visit_intersection(&mut self, list_id: TypeListId) -> TypeId {
960 self.evaluate_intersection(list_id)
961 }
962
963 /// Visit a union type: A | B | C
964 fn visit_union(&mut self, list_id: TypeListId) -> TypeId {
965 self.evaluate_union(list_id)
966 }
967}
968
969/// Convenience function for evaluating conditional types
970pub fn evaluate_conditional(interner: &dyn TypeDatabase, cond: &ConditionalType) -> TypeId {
971 let mut evaluator = TypeEvaluator::new(interner);
972 evaluator.evaluate_conditional(cond)
973}
974
975/// Convenience function for evaluating index access types
976pub fn evaluate_index_access(
977 interner: &dyn TypeDatabase,
978 object_type: TypeId,
979 index_type: TypeId,
980) -> TypeId {
981 let mut evaluator = TypeEvaluator::new(interner);
982 evaluator.evaluate_index_access(object_type, index_type)
983}
984
985/// Convenience function for evaluating index access types with options.
986pub fn evaluate_index_access_with_options(
987 interner: &dyn TypeDatabase,
988 object_type: TypeId,
989 index_type: TypeId,
990 no_unchecked_indexed_access: bool,
991) -> TypeId {
992 let mut evaluator = TypeEvaluator::new(interner);
993 evaluator.set_no_unchecked_indexed_access(no_unchecked_indexed_access);
994 evaluator.evaluate_index_access(object_type, index_type)
995}
996
997/// Convenience function for full type evaluation
998pub fn evaluate_type(interner: &dyn TypeDatabase, type_id: TypeId) -> TypeId {
999 let mut evaluator = TypeEvaluator::new(interner);
1000 evaluator.evaluate(type_id)
1001}
1002
1003/// Convenience function for evaluating mapped types
1004pub fn evaluate_mapped(interner: &dyn TypeDatabase, mapped: &MappedType) -> TypeId {
1005 let mut evaluator = TypeEvaluator::new(interner);
1006 evaluator.evaluate_mapped(mapped)
1007}
1008
1009/// Convenience function for evaluating keyof types
1010pub fn evaluate_keyof(interner: &dyn TypeDatabase, operand: TypeId) -> TypeId {
1011 let mut evaluator = TypeEvaluator::new(interner);
1012 evaluator.evaluate_keyof(operand)
1013}
1014
1015// Re-enabled evaluate tests - verifying API compatibility
1016#[cfg(test)]
1017#[path = "../../tests/evaluate_tests.rs"]
1018mod tests;