Skip to main content

tsz_solver/
narrowing.rs

1//! Type narrowing for discriminated unions and type guards.
2//!
3//! Discriminated unions are unions where each member has a common "discriminant"
4//! property with a literal type that uniquely identifies that member.
5//!
6//! Example:
7//! ```typescript
8//! type Action =
9//!   | { type: "add", value: number }
10//!   | { type: "remove", id: string }
11//!   | { type: "clear" };
12//!
13//! function handle(action: Action) {
14//!   if (action.type === "add") {
15//!     // action is narrowed to { type: "add", value: number }
16//!   }
17//! }
18//! ```
19//!
20//! ## `TypeGuard` Abstraction
21//!
22//! The `TypeGuard` enum provides an AST-agnostic representation of narrowing
23//! conditions. This allows the Solver to perform pure type algebra without
24//! depending on AST nodes.
25//!
26//! Architecture:
27//! - **Checker**: Extracts `TypeGuard` from AST nodes (WHERE)
28//! - **Solver**: Applies `TypeGuard` to types (WHAT)
29
30// Re-export utility functions that were extracted to narrowing_utils
31pub use crate::narrowing_utils::{
32    can_be_nullish, find_discriminants, is_definitely_nullish, is_nullish_type,
33    narrow_by_discriminant, narrow_by_typeof, remove_definitely_falsy_types, remove_nullish,
34    split_nullish_type, type_contains_nullish, type_contains_undefined,
35};
36
37use crate::subtype::{SubtypeChecker, TypeResolver};
38use crate::type_queries::{UnionMembersKind, classify_for_union_members};
39#[cfg(test)]
40use crate::types::*;
41use crate::types::{FunctionShape, LiteralValue, ParamInfo, TypeData, TypeId};
42use crate::utils::{TypeIdExt, intersection_or_single, union_or_single};
43use crate::visitor::{
44    index_access_parts, intersection_list_id, is_function_type_db, is_object_like_type_db,
45    lazy_def_id, literal_value, object_shape_id, object_with_index_shape_id, template_literal_id,
46    type_param_info, union_list_id,
47};
48use crate::{QueryDatabase, TypeDatabase};
49use rustc_hash::FxHashMap;
50use std::cell::RefCell;
51use tracing::{Level, span, trace};
52use tsz_common::interner::Atom;
53
54/// The result of a `typeof` expression, restricted to the 8 standard JavaScript types.
55///
56/// Using an enum instead of `String` eliminates heap allocation per typeof guard.
57/// TypeScript's `typeof` operator only returns these 8 values.
58#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
59pub enum TypeofKind {
60    String,
61    Number,
62    Boolean,
63    BigInt,
64    Symbol,
65    Undefined,
66    Object,
67    Function,
68}
69
70impl TypeofKind {
71    /// Parse a typeof result string into a `TypeofKind`.
72    /// Returns None for non-standard typeof strings (which don't narrow).
73    pub fn parse(s: &str) -> Option<Self> {
74        match s {
75            "string" => Some(Self::String),
76            "number" => Some(Self::Number),
77            "boolean" => Some(Self::Boolean),
78            "bigint" => Some(Self::BigInt),
79            "symbol" => Some(Self::Symbol),
80            "undefined" => Some(Self::Undefined),
81            "object" => Some(Self::Object),
82            "function" => Some(Self::Function),
83            _ => None,
84        }
85    }
86
87    /// Get the string representation of this typeof kind.
88    pub const fn as_str(&self) -> &'static str {
89        match self {
90            Self::String => "string",
91            Self::Number => "number",
92            Self::Boolean => "boolean",
93            Self::BigInt => "bigint",
94            Self::Symbol => "symbol",
95            Self::Undefined => "undefined",
96            Self::Object => "object",
97            Self::Function => "function",
98        }
99    }
100}
101
102/// AST-agnostic representation of a type narrowing condition.
103///
104/// This enum represents various guards that can narrow a type, without
105/// depending on AST nodes like `NodeIndex` or `SyntaxKind`.
106///
107/// # Examples
108/// ```typescript
109/// typeof x === "string"     -> TypeGuard::Typeof(TypeofKind::String)
110/// x instanceof MyClass      -> TypeGuard::Instanceof(MyClass_type)
111/// x === null                -> TypeGuard::NullishEquality
112/// x                         -> TypeGuard::Truthy
113/// x.kind === "circle"       -> TypeGuard::Discriminant { property: "kind", value: "circle" }
114/// ```
115#[derive(Clone, Debug, PartialEq)]
116pub enum TypeGuard {
117    /// `typeof x === "typename"`
118    ///
119    /// Narrows a union to only members matching the typeof result.
120    /// For example, narrowing `string | number` with `Typeof(TypeofKind::String)` yields `string`.
121    Typeof(TypeofKind),
122
123    /// `x instanceof Class`
124    ///
125    /// Narrows to the class type or its subtypes.
126    Instanceof(TypeId),
127
128    /// `x === literal` or `x !== literal`
129    ///
130    /// Narrows to exactly that literal type (for equality) or excludes it (for inequality).
131    LiteralEquality(TypeId),
132
133    /// `x == null` or `x != null` (checks both null and undefined)
134    ///
135    /// JavaScript/TypeScript treats `== null` as matching both `null` and `undefined`.
136    NullishEquality,
137
138    /// `x` (truthiness check in a conditional)
139    ///
140    /// Removes falsy types from a union: `null`, `undefined`, `false`, `0`, `""`, `NaN`.
141    Truthy,
142
143    /// `x.prop === literal` or `x.payload.type === "value"` (Discriminated Union narrowing)
144    ///
145    /// Narrows a union of object types based on a discriminant property.
146    ///
147    /// # Examples
148    /// - Top-level: `{ kind: "A" } | { kind: "B" }` with `path: ["kind"]` yields `{ kind: "A" }`
149    /// - Nested: `{ payload: { type: "user" } } | { payload: { type: "product" } }`
150    ///   with `path: ["payload", "type"]` yields `{ payload: { type: "user" } }`
151    Discriminant {
152        /// Property path from base to discriminant (e.g., ["payload", "type"])
153        property_path: Vec<Atom>,
154        /// The literal value to match against
155        value_type: TypeId,
156    },
157
158    /// `prop in x`
159    ///
160    /// Narrows to types that have the specified property.
161    InProperty(Atom),
162
163    /// `x is T` or `asserts x is T` (User-Defined Type Guard)
164    ///
165    /// Narrows a type based on a user-defined type predicate function.
166    ///
167    /// # Examples
168    /// ```typescript
169    /// function isString(x: any): x is string { ... }
170    /// function assertDefined(x: any): asserts x is Date { ... }
171    ///
172    /// if (isString(x)) { x; // string }
173    /// assertDefined(x); x; // Date
174    /// ```
175    ///
176    /// - `type_id: Some(T)`: The type to narrow to (e.g., `string` or `Date`)
177    /// - `type_id: None`: Truthiness assertion (`asserts x`), behaves like `Truthy`
178    /// - `asserts: true`: This is an assertion (throws if false), affects control flow
179    Predicate {
180        type_id: Option<TypeId>,
181        asserts: bool,
182    },
183
184    /// `Array.isArray(x)`
185    ///
186    /// Narrows a type to only array-like types (arrays, tuples, readonly arrays).
187    ///
188    /// # Examples
189    /// ```typescript
190    /// function process(x: string[] | number | { length: number }) {
191    ///   if (Array.isArray(x)) {
192    ///     x; // string[] (not number or the object)
193    ///   }
194    /// }
195    /// ```
196    ///
197    /// This preserves element types - `string[] | number[]` stays as `string[] | number[]`,
198    /// it doesn't collapse to `any[]`.
199    Array,
200
201    /// `array.every(predicate)` where predicate has type predicate
202    ///
203    /// Narrows an array's element type based on a type predicate.
204    ///
205    /// # Examples
206    /// ```typescript
207    /// const arr: (number | string)[] = ['aaa'];
208    /// const isString = (x: unknown): x is string => typeof x === 'string';
209    /// if (arr.every(isString)) {
210    ///   arr; // string[] (element type narrowed from number | string to string)
211    /// }
212    /// ```
213    ///
214    /// This only applies to arrays. For non-array types, the type is unchanged.
215    ArrayElementPredicate {
216        /// The type to narrow array elements to
217        element_type: TypeId,
218    },
219}
220
221#[inline]
222pub(crate) fn union_or_single_preserve(db: &dyn TypeDatabase, types: Vec<TypeId>) -> TypeId {
223    match types.len() {
224        0 => TypeId::NEVER,
225        1 => types[0],
226        _ => db.union_from_sorted_vec(types),
227    }
228}
229
230/// Result of a narrowing operation.
231///
232/// Represents the types in both branches of a condition.
233#[derive(Clone, Debug)]
234pub struct NarrowingResult {
235    /// The type in the "true" branch of the condition
236    pub true_type: TypeId,
237    /// The type in the "false" branch of the condition
238    pub false_type: TypeId,
239}
240
241/// Result of finding discriminant properties in a union.
242#[derive(Clone, Debug)]
243pub struct DiscriminantInfo {
244    /// The name of the discriminant property
245    pub property_name: Atom,
246    /// Map from literal value to the union member type
247    pub variants: Vec<(TypeId, TypeId)>, // (literal_type, member_type)
248}
249
250/// Narrowing context for type guards and control flow analysis.
251/// Shared across multiple narrowing contexts to persist resolution results.
252#[derive(Default, Clone, Debug)]
253pub struct NarrowingCache {
254    /// Cache for type resolution (Lazy/App/Template -> Structural)
255    pub resolve_cache: RefCell<FxHashMap<TypeId, TypeId>>,
256    /// Cache for top-level property type lookups (TypeId, `PropName`) -> `PropType`
257    pub property_cache: RefCell<FxHashMap<(TypeId, Atom), Option<TypeId>>>,
258}
259
260impl NarrowingCache {
261    pub fn new() -> Self {
262        Self::default()
263    }
264}
265
266/// Narrowing context for type guards and control flow analysis.
267pub struct NarrowingContext<'a> {
268    pub(crate) db: &'a dyn QueryDatabase,
269    /// Optional `TypeResolver` for resolving Lazy types (e.g., type aliases).
270    /// When present, this enables proper narrowing of type aliases like `type Shape = Circle | Square`.
271    pub(crate) resolver: Option<&'a dyn TypeResolver>,
272    /// Cache for narrowing operations.
273    /// If provided, uses the shared cache; otherwise uses a local ephemeral cache.
274    pub(crate) cache: std::borrow::Cow<'a, NarrowingCache>,
275}
276
277impl<'a> NarrowingContext<'a> {
278    pub fn new(db: &'a dyn QueryDatabase) -> Self {
279        NarrowingContext {
280            db,
281            resolver: None,
282            cache: std::borrow::Cow::Owned(NarrowingCache::new()),
283        }
284    }
285
286    /// Create a new context with a shared cache.
287    pub fn with_cache(db: &'a dyn QueryDatabase, cache: &'a NarrowingCache) -> Self {
288        NarrowingContext {
289            db,
290            resolver: None,
291            cache: std::borrow::Cow::Borrowed(cache),
292        }
293    }
294
295    /// Set the `TypeResolver` for this context.
296    ///
297    /// This enables proper resolution of Lazy types (type aliases) during narrowing.
298    /// The resolver should be borrowed from the Checker's `TypeEnvironment`.
299    pub fn with_resolver(mut self, resolver: &'a dyn TypeResolver) -> Self {
300        self.resolver = Some(resolver);
301        self
302    }
303
304    /// Resolve a type to its structural representation.
305    ///
306    /// Unwraps:
307    /// - Lazy types (evaluates them using resolver if available, otherwise falls back to db)
308    /// - Application types (evaluates the generic instantiation)
309    ///
310    /// This ensures that type aliases, interfaces, and generics are resolved
311    /// to their actual structural types before performing narrowing operations.
312    pub(crate) fn resolve_type(&self, type_id: TypeId) -> TypeId {
313        if let Some(&cached) = self.cache.resolve_cache.borrow().get(&type_id) {
314            return cached;
315        }
316
317        let result = self.resolve_type_uncached(type_id);
318        self.cache
319            .resolve_cache
320            .borrow_mut()
321            .insert(type_id, result);
322        result
323    }
324
325    fn resolve_type_uncached(&self, mut type_id: TypeId) -> TypeId {
326        // Prevent infinite loops with a fuel counter
327        let mut fuel = 100;
328
329        while fuel > 0 {
330            fuel -= 1;
331
332            // 1. Handle Lazy types (DefId-based, not SymbolRef)
333            // If we have a TypeResolver, try to resolve Lazy types through it first
334            if let Some(def_id) = lazy_def_id(self.db, type_id) {
335                if let Some(resolver) = self.resolver
336                    && let Some(resolved) =
337                        resolver.resolve_lazy(def_id, self.db.as_type_database())
338                {
339                    type_id = resolved;
340                    continue;
341                }
342                // Fallback to database evaluation if no resolver or resolution failed
343                type_id = self.db.evaluate_type(type_id);
344                continue;
345            }
346
347            // 2. Handle Application types (Generics)
348            // CRITICAL: When a resolver is available (from the checker's TypeEnvironment),
349            // use it to resolve the Application's base type and instantiate with args.
350            // Without the resolver, generic type aliases like `Box<number>` can't resolve
351            // their DefId-based base types, causing narrowing to fail on discriminated
352            // unions wrapped in generics.
353            if let Some(TypeData::Application(app_id)) = self.db.lookup(type_id) {
354                if let Some(resolver) = self.resolver {
355                    let app = self.db.type_application(app_id);
356                    // Try to resolve the base type's DefId and instantiate manually
357                    if let Some(def_id) = lazy_def_id(self.db, app.base) {
358                        let resolved_body =
359                            resolver.resolve_lazy(def_id, self.db.as_type_database());
360                        let type_params = resolver.get_lazy_type_params(def_id);
361                        if let (Some(body), Some(params)) = (resolved_body, type_params) {
362                            let instantiated = crate::instantiate::instantiate_generic(
363                                self.db.as_type_database(),
364                                body,
365                                &params,
366                                &app.args,
367                            );
368                            type_id = instantiated;
369                            continue;
370                        }
371                    }
372                }
373                // Fallback: use db.evaluate_type (works when resolver isn't needed)
374                type_id = self.db.evaluate_type(type_id);
375                continue;
376            }
377
378            // 3. Handle TemplateLiteral types that can be fully evaluated to string literals.
379            // Template literal spans may contain Lazy(DefId) types (e.g., `${EnumType.Member}`)
380            // that must be resolved before evaluation. We resolve all lazy spans first,
381            // rebuild the template literal, then let the evaluator handle it.
382            if let Some(TypeData::TemplateLiteral(spans_id)) = self.db.lookup(type_id) {
383                use crate::types::TemplateSpan;
384                let spans = self.db.template_list(spans_id);
385                let mut new_spans = Vec::with_capacity(spans.len());
386                let mut changed = false;
387                for span in spans.iter() {
388                    match span {
389                        TemplateSpan::Type(inner_id) => {
390                            let resolved = self.resolve_type(*inner_id);
391                            if resolved != *inner_id {
392                                changed = true;
393                            }
394                            new_spans.push(TemplateSpan::Type(resolved));
395                        }
396                        other => new_spans.push(other.clone()),
397                    }
398                }
399                let eval_input = if changed {
400                    self.db.template_literal(new_spans)
401                } else {
402                    type_id
403                };
404                let evaluated = self.db.evaluate_type(eval_input);
405                if evaluated != type_id {
406                    type_id = evaluated;
407                    continue;
408                }
409            }
410
411            // It's a structural type (Object, Union, Intersection, Primitive)
412            break;
413        }
414
415        type_id
416    }
417
418    /// Narrow a type based on a typeof check.
419    ///
420    /// Example: `typeof x === "string"` narrows `string | number` to `string`
421    pub fn narrow_by_typeof(&self, source_type: TypeId, typeof_result: &str) -> TypeId {
422        let _span =
423            span!(Level::TRACE, "narrow_by_typeof", source_type = source_type.0, %typeof_result)
424                .entered();
425
426        // CRITICAL FIX: Narrow `any` for typeof checks
427        // TypeScript narrows `any` for typeof/instanceof/Array.isArray/user-defined guards
428        // But NOT for equality/truthiness/in operator
429        if source_type == TypeId::UNKNOWN || source_type == TypeId::ANY {
430            return match typeof_result {
431                "string" => TypeId::STRING,
432                "number" => TypeId::NUMBER,
433                "boolean" => TypeId::BOOLEAN,
434                "bigint" => TypeId::BIGINT,
435                "symbol" => TypeId::SYMBOL,
436                "undefined" => TypeId::UNDEFINED,
437                "object" => self.db.union2(TypeId::OBJECT, TypeId::NULL),
438                "function" => self.function_type(),
439                _ => source_type,
440            };
441        }
442
443        let target_type = match typeof_result {
444            "string" => TypeId::STRING,
445            "number" => TypeId::NUMBER,
446            "boolean" => TypeId::BOOLEAN,
447            "bigint" => TypeId::BIGINT,
448            "symbol" => TypeId::SYMBOL,
449            "undefined" => TypeId::UNDEFINED,
450            "object" => TypeId::OBJECT, // includes null
451            "function" => return self.narrow_to_function(source_type),
452            _ => return source_type,
453        };
454
455        self.narrow_to_type(source_type, target_type)
456    }
457
458    /// Narrow a type based on an instanceof check.
459    ///
460    /// Example: `x instanceof MyClass` narrows `A | B` to include only `A` where `A` is an instance of `MyClass`
461    pub fn narrow_by_instanceof(
462        &self,
463        source_type: TypeId,
464        constructor_type: TypeId,
465        sense: bool,
466    ) -> TypeId {
467        let _span = span!(
468            Level::TRACE,
469            "narrow_by_instanceof",
470            source_type = source_type.0,
471            constructor_type = constructor_type.0,
472            sense
473        )
474        .entered();
475
476        // TODO: Check for static [Symbol.hasInstance] method which overrides standard narrowing
477        // TypeScript allows classes to define custom instanceof behavior via:
478        //   static [Symbol.hasInstance](value: any): boolean
479        // This would require evaluating method calls and type predicates, which is
480        // significantly more complex than the standard construct signature approach.
481
482        // CRITICAL: Resolve Lazy types for both source and constructor
483        // This ensures type aliases are resolved to their actual types
484        let resolved_source = self.resolve_type(source_type);
485        let resolved_constructor = self.resolve_type(constructor_type);
486
487        // Extract the instance type from the constructor
488        use crate::type_queries_extended::InstanceTypeKind;
489        use crate::type_queries_extended::classify_for_instance_type;
490
491        let instance_type = match classify_for_instance_type(self.db, resolved_constructor) {
492            InstanceTypeKind::Callable(shape_id) => {
493                // For callable types with construct signatures, get the return type of the construct signature
494                let shape = self.db.callable_shape(shape_id);
495                // Find a construct signature and get its return type (the instance type)
496                if let Some(construct_sig) = shape.construct_signatures.first() {
497                    construct_sig.return_type
498                } else {
499                    // No construct signature found, can't narrow
500                    trace!("No construct signature found in callable type");
501                    return source_type;
502                }
503            }
504            InstanceTypeKind::Function(shape_id) => {
505                // For function types, check if it's a constructor
506                let shape = self.db.function_shape(shape_id);
507                if shape.is_constructor {
508                    // The return type is the instance type
509                    shape.return_type
510                } else {
511                    trace!("Function is not a constructor");
512                    return source_type;
513                }
514            }
515            InstanceTypeKind::Intersection(members) => {
516                // For intersection types, we need to extract instance types from all members
517                // For now, create an intersection of the instance types
518                let instance_types: Vec<TypeId> = members
519                    .iter()
520                    .map(|&member| self.narrow_by_instanceof(source_type, member, sense))
521                    .collect();
522
523                if sense {
524                    intersection_or_single(self.db, instance_types)
525                } else {
526                    // For negation with intersection, we can't easily exclude
527                    // Fall back to returning the source type unchanged
528                    source_type
529                }
530            }
531            InstanceTypeKind::Union(members) => {
532                // For union types, extract instance types from all members
533                let instance_types: Vec<TypeId> = members
534                    .iter()
535                    .filter_map(|&member| {
536                        self.narrow_by_instanceof(source_type, member, sense)
537                            .non_never()
538                    })
539                    .collect();
540
541                if sense {
542                    union_or_single(self.db, instance_types)
543                } else {
544                    // For negation with union, we can't easily exclude
545                    // Fall back to returning the source type unchanged
546                    source_type
547                }
548            }
549            InstanceTypeKind::Readonly(inner) => {
550                // Readonly wrapper - extract from inner type
551                return self.narrow_by_instanceof(source_type, inner, sense);
552            }
553            InstanceTypeKind::TypeParameter { constraint } => {
554                // Follow type parameter constraint
555                if let Some(constraint) = constraint {
556                    return self.narrow_by_instanceof(source_type, constraint, sense);
557                }
558                trace!("Type parameter has no constraint");
559                return source_type;
560            }
561            InstanceTypeKind::SymbolRef(_) | InstanceTypeKind::NeedsEvaluation => {
562                // Complex cases that need further evaluation
563                // For now, return the source type unchanged
564                trace!("Complex instance type (SymbolRef or NeedsEvaluation), returning unchanged");
565                return source_type;
566            }
567            InstanceTypeKind::NotConstructor => {
568                trace!("Constructor type is not a valid constructor");
569                return source_type;
570            }
571        };
572
573        // Now narrow based on the sense (positive or negative)
574        if sense {
575            // CRITICAL: instanceof DOES narrow any/unknown (unlike equality checks)
576            if resolved_source == TypeId::ANY {
577                // any narrows to the instance type with instanceof
578                trace!("Narrowing any to instance type via instanceof");
579                return instance_type;
580            }
581
582            if resolved_source == TypeId::UNKNOWN {
583                // unknown narrows to the instance type with instanceof
584                trace!("Narrowing unknown to instance type via instanceof");
585                return instance_type;
586            }
587
588            // Handle Union: filter members based on instanceof relationship
589            if let Some(members_id) = union_list_id(self.db, resolved_source) {
590                let members = self.db.type_list(members_id);
591                // PERF: Reuse a single SubtypeChecker across all member checks
592                // instead of allocating 4 hash sets per is_subtype_of call.
593                let mut checker = SubtypeChecker::new(self.db.as_type_database());
594                let mut filtered_members: Vec<TypeId> = Vec::new();
595                for &member in &*members {
596                    // Check if member is assignable to instance type
597                    checker.reset();
598                    if checker.is_subtype_of(member, instance_type) {
599                        trace!(
600                            "Union member {} is assignable to instance type {}, keeping",
601                            member.0, instance_type.0
602                        );
603                        filtered_members.push(member);
604                        continue;
605                    }
606
607                    // Check if instance type is assignable to member (subclass case)
608                    // If we have a Dog and instanceof Animal, Dog is an instance of Animal
609                    checker.reset();
610                    if checker.is_subtype_of(instance_type, member) {
611                        trace!(
612                            "Instance type {} is assignable to union member {} (subclass), narrowing to instance type",
613                            instance_type.0, member.0
614                        );
615                        filtered_members.push(instance_type);
616                        continue;
617                    }
618
619                    // Interface overlap: both are object-like but not assignable
620                    // Use intersection to preserve properties from both
621                    if self.are_object_like(member) && self.are_object_like(instance_type) {
622                        trace!(
623                            "Interface overlap between {} and {}, using intersection",
624                            member.0, instance_type.0
625                        );
626                        filtered_members.push(self.db.intersection2(member, instance_type));
627                        continue;
628                    }
629
630                    trace!("Union member {} excluded by instanceof check", member.0);
631                }
632
633                union_or_single(self.db, filtered_members)
634            } else {
635                // Non-union type: use standard narrowing with intersection fallback
636                let narrowed = self.narrow_to_type(resolved_source, instance_type);
637
638                // If that returns NEVER, try intersection approach for interface vs class cases
639                // In TypeScript, instanceof on an interface narrows to intersection, not NEVER
640                if narrowed == TypeId::NEVER && resolved_source != TypeId::NEVER {
641                    // Check for interface overlap before using intersection
642                    if self.are_object_like(resolved_source) && self.are_object_like(instance_type)
643                    {
644                        trace!("Interface vs class detected, using intersection instead of NEVER");
645                        self.db.intersection2(resolved_source, instance_type)
646                    } else {
647                        narrowed
648                    }
649                } else {
650                    narrowed
651                }
652            }
653        } else {
654            // Negative: !(x instanceof Constructor) - exclude the instance type
655            // For unions, exclude members that are subtypes of the instance type
656            if let Some(members_id) = union_list_id(self.db, resolved_source) {
657                let members = self.db.type_list(members_id);
658                // PERF: Reuse a single SubtypeChecker across all member checks
659                let mut checker = SubtypeChecker::new(self.db.as_type_database());
660                let mut filtered_members: Vec<TypeId> = Vec::new();
661                for &member in &*members {
662                    // Exclude members that are definitely subtypes of the instance type
663                    checker.reset();
664                    if !checker.is_subtype_of(member, instance_type) {
665                        filtered_members.push(member);
666                    }
667                }
668
669                union_or_single(self.db, filtered_members)
670            } else {
671                // Non-union: use standard exclusion
672                self.narrow_excluding_type(resolved_source, instance_type)
673            }
674        }
675    }
676
677    /// Narrow a type to include only members assignable to target.
678    pub fn narrow_to_type(&self, source_type: TypeId, target_type: TypeId) -> TypeId {
679        let _span = span!(
680            Level::TRACE,
681            "narrow_to_type",
682            source_type = source_type.0,
683            target_type = target_type.0
684        )
685        .entered();
686
687        // CRITICAL FIX: Resolve Lazy/Ref types to inspect their structure.
688        // This fixes the "Missing type resolution" bug where type aliases and
689        // generics weren't being narrowed correctly.
690        let resolved_source = self.resolve_type(source_type);
691
692        // Gracefully handle resolution failures: if evaluation fails but the input
693        // wasn't ERROR, we can't narrow structurally. Return original source to
694        // avoid cascading ERRORs through the type system.
695        if resolved_source == TypeId::ERROR && source_type != TypeId::ERROR {
696            trace!("Source type resolution failed, returning original source");
697            return source_type;
698        }
699
700        // Resolve target for consistency
701        let resolved_target = self.resolve_type(target_type);
702        if resolved_target == TypeId::ERROR && target_type != TypeId::ERROR {
703            trace!("Target type resolution failed, returning original source");
704            return source_type;
705        }
706
707        // If source is the target, return it
708        if resolved_source == resolved_target {
709            trace!("Source type equals target type, returning unchanged");
710            return source_type;
711        }
712
713        // Special case: unknown can be narrowed to any type through type guards
714        // This handles cases like: if (typeof x === "string") where x: unknown
715        if resolved_source == TypeId::UNKNOWN {
716            trace!("Narrowing unknown to specific type via type guard");
717            return target_type;
718        }
719
720        // Special case: any can be narrowed to any type through type guards
721        // This handles cases like: if (x === null) where x: any
722        // CRITICAL: Unlike unknown, any MUST be narrowed to match target type
723        if resolved_source == TypeId::ANY {
724            trace!("Narrowing any to specific type via type guard");
725            return target_type;
726        }
727
728        // If source is a union, filter members
729        // Use resolved_source for structural inspection
730        if let Some(members) = union_list_id(self.db, resolved_source) {
731            let members = self.db.type_list(members);
732            trace!(
733                "Narrowing union with {} members to type {}",
734                members.len(),
735                target_type.0
736            );
737            let matching: Vec<TypeId> = members
738                .iter()
739                .filter_map(|&member| {
740                    if let Some(narrowed) = self.narrow_type_param(member, target_type) {
741                        return Some(narrowed);
742                    }
743                    if self.is_assignable_to(member, target_type) {
744                        return Some(member);
745                    }
746                    // CRITICAL FIX: Check if target_type is a subtype of member
747                    // This handles cases like narrowing string | number by "hello"
748                    // where "hello" is a subtype of string, so we should narrow to "hello"
749                    if crate::subtype::is_subtype_of_with_db(self.db, target_type, member) {
750                        return Some(target_type);
751                    }
752                    // CRITICAL FIX: instanceof Array matching
753                    // When narrowing by `instanceof Array`, if the member is array-like and target
754                    // is a Lazy/Application type (which includes Array<T> interface references),
755                    // assume it's the global Array and match the member.
756                    // This handles: `x: Message | Message[]` with `instanceof Array` should keep `Message[]`.
757                    // At runtime, instanceof only checks prototype chain, not generic type arguments.
758                    if self.is_array_like(member) {
759                        use crate::type_queries;
760                        // Check if target is a type reference or generic application (Array<T>)
761                        let is_target_lazy_or_app = type_queries::is_type_reference(self.db, resolved_target)
762                            || type_queries::is_generic_type(self.db, resolved_target);
763
764                        trace!("Member is array-like: member={}, target={}, is_target_lazy_or_app={}",
765                            member.0, resolved_target.0, is_target_lazy_or_app);
766
767                        if is_target_lazy_or_app {
768                            trace!("Array member with lazy/app target (likely Array interface), keeping member");
769                            return Some(member);
770                        }
771                    }
772                    None
773                })
774                .collect();
775
776            if matching.is_empty() {
777                trace!("No matching members found, returning NEVER");
778                return TypeId::NEVER;
779            } else if matching.len() == 1 {
780                trace!("Found single matching member, returning {}", matching[0].0);
781                return matching[0];
782            }
783            trace!(
784                "Found {} matching members, creating new union",
785                matching.len()
786            );
787            return self.db.union(matching);
788        }
789
790        // Check if this is a type parameter that needs narrowing
791        // Use resolved_source to handle type parameters behind aliases
792        if let Some(narrowed) = self.narrow_type_param(resolved_source, target_type) {
793            trace!("Narrowed type parameter to {}", narrowed.0);
794            return narrowed;
795        }
796
797        // Task 13: Handle boolean -> literal narrowing
798        // When narrowing boolean to true or false, return the corresponding literal
799        if resolved_source == TypeId::BOOLEAN {
800            let is_target_true = if let Some(lit) = literal_value(self.db, resolved_target) {
801                matches!(lit, LiteralValue::Boolean(true))
802            } else {
803                resolved_target == TypeId::BOOLEAN_TRUE
804            };
805
806            if is_target_true {
807                trace!("Narrowing boolean to true");
808                return TypeId::BOOLEAN_TRUE;
809            }
810
811            let is_target_false = if let Some(lit) = literal_value(self.db, resolved_target) {
812                matches!(lit, LiteralValue::Boolean(false))
813            } else {
814                resolved_target == TypeId::BOOLEAN_FALSE
815            };
816
817            if is_target_false {
818                trace!("Narrowing boolean to false");
819                return TypeId::BOOLEAN_FALSE;
820            }
821        }
822
823        // Check if source is assignable to target using resolved types for comparison
824        if self.is_assignable_to(resolved_source, resolved_target) {
825            trace!("Source type is assignable to target, returning source");
826            source_type
827        } else if crate::subtype::is_subtype_of_with_db(self.db, resolved_target, resolved_source) {
828            // CRITICAL FIX: Check if target is a subtype of source (reverse narrowing)
829            // This handles cases like narrowing string to "hello" where "hello" is a subtype of string
830            // The inference engine uses this to narrow upper bounds by lower bounds
831            trace!("Target is subtype of source, returning target");
832            target_type
833        } else {
834            trace!("Source type is not assignable to target, returning NEVER");
835            TypeId::NEVER
836        }
837    }
838
839    /// Narrow a type by instanceof check using the instance type.
840    ///
841    /// Unlike `narrow_to_type` which uses structural assignability to filter union members,
842    /// this method uses instanceof-specific semantics:
843    /// - Type parameters with constraints assignable to the target are kept (intersected)
844    /// - When a type parameter absorbs the target, anonymous object types are excluded
845    ///   since they cannot be class instances at runtime
846    ///
847    /// This prevents anonymous object types like `{ x: string }` from surviving instanceof
848    /// narrowing when they happen to be structurally compatible with the class type.
849    pub fn narrow_by_instance_type(&self, source_type: TypeId, instance_type: TypeId) -> TypeId {
850        let resolved_source = self.resolve_type(source_type);
851
852        if resolved_source == TypeId::ERROR && source_type != TypeId::ERROR {
853            return source_type;
854        }
855
856        let resolved_target = self.resolve_type(instance_type);
857        if resolved_target == TypeId::ERROR && instance_type != TypeId::ERROR {
858            return source_type;
859        }
860
861        if resolved_source == resolved_target {
862            return source_type;
863        }
864
865        // any/unknown narrow to instance type with instanceof
866        if resolved_source == TypeId::ANY || resolved_source == TypeId::UNKNOWN {
867            return instance_type;
868        }
869
870        // If source is a union, filter members using instanceof semantics
871        if let Some(members) = union_list_id(self.db, resolved_source) {
872            let members = self.db.type_list(members);
873            trace!(
874                "instanceof: narrowing union with {} members {:?} to instance type {}",
875                members.len(),
876                members.iter().map(|m| m.0).collect::<Vec<_>>(),
877                instance_type.0
878            );
879
880            // First pass: check if any type parameter matches the instance type.
881            let mut type_param_results: Vec<(usize, TypeId)> = Vec::new();
882            for (i, &member) in members.iter().enumerate() {
883                if let Some(narrowed) = self.narrow_type_param(member, instance_type) {
884                    type_param_results.push((i, narrowed));
885                }
886            }
887
888            let matching: Vec<TypeId> = if !type_param_results.is_empty() {
889                // Type parameter(s) matched: keep type params and exclude anonymous
890                // object types that can't be class instances at runtime.
891                let mut result = Vec::new();
892                let tp_indices: Vec<usize> = type_param_results.iter().map(|(i, _)| *i).collect();
893                for &(_, narrowed) in &type_param_results {
894                    result.push(narrowed);
895                }
896                for (i, &member) in members.iter().enumerate() {
897                    if tp_indices.contains(&i) {
898                        continue;
899                    }
900                    if crate::type_queries::is_object_type(self.db, member) {
901                        trace!(
902                            "instanceof: excluding anonymous object {} (type param absorbs)",
903                            member.0
904                        );
905                        continue;
906                    }
907                    if crate::subtype::is_subtype_of_with_db(self.db, member, instance_type) {
908                        result.push(member);
909                    } else if crate::subtype::is_subtype_of_with_db(self.db, instance_type, member)
910                    {
911                        result.push(instance_type);
912                    }
913                }
914                result
915            } else {
916                // No type parameter match: filter by instanceof semantics.
917                // Primitives can never pass instanceof; non-primitives are
918                // checked for assignability with the instance type.
919                members
920                    .iter()
921                    .filter_map(|&member| {
922                        // Primitive types can never pass `instanceof` at runtime.
923                        if self.is_js_primitive(member) {
924                            return None;
925                        }
926                        if let Some(narrowed) = self.narrow_type_param(member, instance_type) {
927                            return Some(narrowed);
928                        }
929                        // Member assignable to instance type → keep member
930                        if self.is_assignable_to(member, instance_type) {
931                            return Some(member);
932                        }
933                        // Instance type assignable to member → narrow to instance
934                        // (e.g., member=Animal, instance=Dog → Dog)
935                        if self.is_assignable_to(instance_type, member) {
936                            return Some(instance_type);
937                        }
938                        // Neither direction holds — create intersection per tsc
939                        // semantics. This handles cases like Date instanceof Object
940                        // where assignability checks may miss the relationship.
941                        // The intersection preserves the member's shape while
942                        // constraining it to the instance type.
943                        Some(self.db.intersection2(member, instance_type))
944                    })
945                    .collect()
946            };
947
948            if matching.is_empty() {
949                return self.narrow_to_type(source_type, instance_type);
950            } else if matching.len() == 1 {
951                return matching[0];
952            }
953            return self.db.union(matching);
954        }
955
956        // Non-union: use instanceof-specific semantics
957        trace!(
958            "instanceof: non-union path for source_type={}",
959            source_type.0
960        );
961
962        // Try type parameter narrowing first (produces T & InstanceType)
963        if let Some(narrowed) = self.narrow_type_param(resolved_source, instance_type) {
964            return narrowed;
965        }
966
967        // For non-primitive, non-type-param source types, instanceof narrowing
968        // should keep them when there's a potential runtime relationship.
969        // This handles cases like `readonly number[]` narrowed by `instanceof Array`:
970        // - readonly number[] is NOT a subtype of Array<T> (missing mutating methods)
971        // - Array<T> is NOT a subtype of readonly number[] (unbound T)
972        // - But at runtime, a readonly array IS an Array instance
973        if !self.is_js_primitive(resolved_source) {
974            if self.is_assignable_to(resolved_source, instance_type) {
975                return source_type;
976            }
977            if self.is_assignable_to(instance_type, resolved_source) {
978                return instance_type;
979            }
980            // Non-primitive types may still be instances at runtime.
981            // Neither direction holds — create intersection per tsc semantics.
982            // This handles cases like `interface I {}` narrowed by `instanceof RegExp`.
983            return self.db.intersection2(source_type, instance_type);
984        }
985        // Primitives can never pass instanceof
986        TypeId::NEVER
987    }
988
989    /// Narrow a type for the false branch of `instanceof`.
990    ///
991    /// Keeps primitive types (which can never pass instanceof) and excludes
992    /// non-primitive members that are subtypes of the instance type.
993    /// For example, `string | number | Date` with `instanceof Object` false
994    /// branch gives `string | number` (Date is excluded as it's an Object instance).
995    pub fn narrow_by_instanceof_false(&self, source_type: TypeId, instance_type: TypeId) -> TypeId {
996        let resolved_source = self.resolve_type(source_type);
997
998        if let Some(members) = union_list_id(self.db, resolved_source) {
999            let members = self.db.type_list(members);
1000            let remaining: Vec<TypeId> = members
1001                .iter()
1002                .filter(|&&member| {
1003                    // Primitives always survive the false branch of instanceof
1004                    if self.is_js_primitive(member) {
1005                        return true;
1006                    }
1007                    // A member only fails to reach the false branch if it is GUARANTEED
1008                    // to pass the true branch. In TypeScript, this means the member
1009                    // is assignable to the instance type.
1010                    // If it is NOT assignable, it MIGHT fail at runtime, so we MUST keep it.
1011                    !self.is_assignable_to(member, instance_type)
1012                })
1013                .copied()
1014                .collect();
1015
1016            if remaining.is_empty() {
1017                return TypeId::NEVER;
1018            } else if remaining.len() == 1 {
1019                return remaining[0];
1020            }
1021            return self.db.union(remaining);
1022        }
1023
1024        // Non-union: if it's guaranteed to be an instance, it will never reach the false branch.
1025        if self.is_assignable_to(resolved_source, instance_type) {
1026            return TypeId::NEVER;
1027        }
1028
1029        // Otherwise, it might reach the false branch, so we keep the original type.
1030        source_type
1031    }
1032
1033    /// Check if a literal type is assignable to a target for narrowing purposes.
1034    ///
1035    /// Handles union decomposition: if the target is a union, checks each member.
1036    /// Falls back to `narrow_to_type` to determine if the literal can narrow to the target.
1037    pub fn literal_assignable_to(&self, literal: TypeId, target: TypeId) -> bool {
1038        if literal == target || target == TypeId::ANY || target == TypeId::UNKNOWN {
1039            return true;
1040        }
1041
1042        if let UnionMembersKind::Union(members) = classify_for_union_members(self.db, target) {
1043            return members
1044                .iter()
1045                .any(|&member| self.literal_assignable_to(literal, member));
1046        }
1047
1048        self.narrow_to_type(literal, target) != TypeId::NEVER
1049    }
1050
1051    /// Narrow a type to exclude members assignable to target.
1052    pub fn narrow_excluding_type(&self, source_type: TypeId, excluded_type: TypeId) -> TypeId {
1053        if let Some(members) = intersection_list_id(self.db, source_type) {
1054            let members = self.db.type_list(members);
1055            let mut narrowed_members = Vec::with_capacity(members.len());
1056            let mut changed = false;
1057            for &member in members.iter() {
1058                let narrowed = self.narrow_excluding_type(member, excluded_type);
1059                if narrowed == TypeId::NEVER {
1060                    return TypeId::NEVER;
1061                }
1062                if narrowed != member {
1063                    changed = true;
1064                }
1065                narrowed_members.push(narrowed);
1066            }
1067            if !changed {
1068                return source_type;
1069            }
1070            return self.db.intersection(narrowed_members);
1071        }
1072
1073        // If source is a union, filter out matching members
1074        if let Some(members) = union_list_id(self.db, source_type) {
1075            let members = self.db.type_list(members);
1076            let remaining: Vec<TypeId> = members
1077                .iter()
1078                .filter_map(|&member| {
1079                    if intersection_list_id(self.db, member).is_some() {
1080                        return self
1081                            .narrow_excluding_type(member, excluded_type)
1082                            .non_never();
1083                    }
1084                    if let Some(narrowed) = self.narrow_type_param_excluding(member, excluded_type)
1085                    {
1086                        return narrowed.non_never();
1087                    }
1088                    if self.is_assignable_to(member, excluded_type) {
1089                        None
1090                    } else {
1091                        Some(member)
1092                    }
1093                })
1094                .collect();
1095
1096            tracing::trace!(
1097                remaining_count = remaining.len(),
1098                remaining = ?remaining.iter().map(|t| t.0).collect::<Vec<_>>(),
1099                "narrow_excluding_type: union filter result"
1100            );
1101            if remaining.is_empty() {
1102                return TypeId::NEVER;
1103            } else if remaining.len() == 1 {
1104                return remaining[0];
1105            }
1106            return self.db.union(remaining);
1107        }
1108
1109        if let Some(narrowed) = self.narrow_type_param_excluding(source_type, excluded_type) {
1110            return narrowed;
1111        }
1112
1113        // Special case: boolean type (treat as true | false union)
1114        // Task 13: Fix Boolean Narrowing Logic
1115        // When excluding true or false from boolean, return the other literal
1116        // When excluding both true and false from boolean, return never
1117        if source_type == TypeId::BOOLEAN
1118            || source_type == TypeId::BOOLEAN_TRUE
1119            || source_type == TypeId::BOOLEAN_FALSE
1120        {
1121            // Check if excluded_type is a boolean literal
1122            let is_excluding_true = if let Some(lit) = literal_value(self.db, excluded_type) {
1123                matches!(lit, LiteralValue::Boolean(true))
1124            } else {
1125                excluded_type == TypeId::BOOLEAN_TRUE
1126            };
1127
1128            let is_excluding_false = if let Some(lit) = literal_value(self.db, excluded_type) {
1129                matches!(lit, LiteralValue::Boolean(false))
1130            } else {
1131                excluded_type == TypeId::BOOLEAN_FALSE
1132            };
1133
1134            // Handle exclusion from boolean, true, or false
1135            if source_type == TypeId::BOOLEAN {
1136                if is_excluding_true {
1137                    // Excluding true from boolean -> return false
1138                    return TypeId::BOOLEAN_FALSE;
1139                } else if is_excluding_false {
1140                    // Excluding false from boolean -> return true
1141                    return TypeId::BOOLEAN_TRUE;
1142                }
1143                // If excluding BOOLEAN, let the final is_assignable_to check handle it below
1144            } else if source_type == TypeId::BOOLEAN_TRUE {
1145                if is_excluding_true {
1146                    // Excluding true from true -> return never
1147                    return TypeId::NEVER;
1148                }
1149                // For other cases (e.g., excluding BOOLEAN from TRUE),
1150                // let the final is_assignable_to check handle it below
1151            } else if source_type == TypeId::BOOLEAN_FALSE && is_excluding_false {
1152                // Excluding false from false -> return never
1153                return TypeId::NEVER;
1154            }
1155            // For other cases, let the final is_assignable_to check handle it below
1156            // CRITICAL: Do NOT return source_type here.
1157            // Fall through to the standard is_assignable_to check below.
1158            // This handles edge cases like narrow_excluding_type(TRUE, BOOLEAN) -> NEVER
1159        }
1160
1161        // If source is assignable to excluded, return never
1162        if self.is_assignable_to(source_type, excluded_type) {
1163            TypeId::NEVER
1164        } else {
1165            source_type
1166        }
1167    }
1168
1169    /// Narrow a type by excluding multiple types at once (batched version).
1170    ///
1171    /// This is an optimized version of `narrow_excluding_type` for cases like
1172    /// switch default clauses where we need to exclude many types at once.
1173    /// It avoids creating intermediate union types and reduces complexity from O(N²) to O(N).
1174    ///
1175    /// # Arguments
1176    /// * `source_type` - The type to narrow (typically a union)
1177    /// * `excluded_types` - Types to exclude from the source
1178    ///
1179    /// # Returns
1180    /// The narrowed type with all excluded types removed
1181    pub fn narrow_excluding_types(&self, source_type: TypeId, excluded_types: &[TypeId]) -> TypeId {
1182        if excluded_types.is_empty() {
1183            return source_type;
1184        }
1185
1186        // For small lists, use sequential narrowing (avoids HashSet overhead)
1187        if excluded_types.len() <= 4 {
1188            let mut result = source_type;
1189            for &excluded in excluded_types {
1190                result = self.narrow_excluding_type(result, excluded);
1191                if result == TypeId::NEVER {
1192                    return TypeId::NEVER;
1193                }
1194            }
1195            return result;
1196        }
1197
1198        // For larger lists, use HashSet for O(1) lookup
1199        let excluded_set: rustc_hash::FxHashSet<TypeId> = excluded_types.iter().copied().collect();
1200
1201        // Handle union source type
1202        if let Some(members) = union_list_id(self.db, source_type) {
1203            let members = self.db.type_list(members);
1204            let remaining: Vec<TypeId> = members
1205                .iter()
1206                .filter_map(|&member| {
1207                    // Fast path: direct identity check against the set
1208                    if excluded_set.contains(&member) {
1209                        return None;
1210                    }
1211
1212                    // Handle intersection members
1213                    if intersection_list_id(self.db, member).is_some() {
1214                        return self
1215                            .narrow_excluding_types(member, excluded_types)
1216                            .non_never();
1217                    }
1218
1219                    // Handle type parameters
1220                    if let Some(narrowed) =
1221                        self.narrow_type_param_excluding_set(member, &excluded_set)
1222                    {
1223                        return narrowed.non_never();
1224                    }
1225
1226                    // Slow path: check assignability for complex cases
1227                    // This handles cases where the member isn't identical to an excluded type
1228                    // but might still be assignable to one (e.g., literal subtypes)
1229                    for &excluded in &excluded_set {
1230                        if self.is_assignable_to(member, excluded) {
1231                            return None;
1232                        }
1233                    }
1234                    Some(member)
1235                })
1236                .collect();
1237
1238            if remaining.is_empty() {
1239                return TypeId::NEVER;
1240            } else if remaining.len() == 1 {
1241                return remaining[0];
1242            }
1243            return self.db.union(remaining);
1244        }
1245
1246        // Handle single type (not a union)
1247        if excluded_set.contains(&source_type) {
1248            return TypeId::NEVER;
1249        }
1250
1251        // Check assignability for single type
1252        for &excluded in &excluded_set {
1253            if self.is_assignable_to(source_type, excluded) {
1254                return TypeId::NEVER;
1255            }
1256        }
1257
1258        source_type
1259    }
1260
1261    /// Helper for `narrow_excluding_types` with type parameters
1262    fn narrow_type_param_excluding_set(
1263        &self,
1264        source: TypeId,
1265        excluded_set: &rustc_hash::FxHashSet<TypeId>,
1266    ) -> Option<TypeId> {
1267        let info = type_param_info(self.db, source)?;
1268
1269        let constraint = info.constraint?;
1270        if constraint == source || constraint == TypeId::UNKNOWN {
1271            return None;
1272        }
1273
1274        // Narrow the constraint by excluding all types in the set
1275        let excluded_vec: Vec<TypeId> = excluded_set.iter().copied().collect();
1276        let narrowed_constraint = self.narrow_excluding_types(constraint, &excluded_vec);
1277
1278        if narrowed_constraint == constraint {
1279            return None;
1280        }
1281        if narrowed_constraint == TypeId::NEVER {
1282            return Some(TypeId::NEVER);
1283        }
1284
1285        Some(self.db.intersection2(source, narrowed_constraint))
1286    }
1287
1288    /// Narrow to function types only.
1289    fn narrow_to_function(&self, source_type: TypeId) -> TypeId {
1290        if let Some(members) = union_list_id(self.db, source_type) {
1291            let members = self.db.type_list(members);
1292            let functions: Vec<TypeId> = members
1293                .iter()
1294                .filter_map(|&member| {
1295                    if let Some(narrowed) = self.narrow_type_param_to_function(member) {
1296                        return narrowed.non_never();
1297                    }
1298                    self.is_function_type(member).then_some(member)
1299                })
1300                .collect();
1301
1302            return union_or_single(self.db, functions);
1303        }
1304
1305        if let Some(narrowed) = self.narrow_type_param_to_function(source_type) {
1306            return narrowed;
1307        }
1308
1309        if self.is_function_type(source_type) {
1310            source_type
1311        } else if source_type == TypeId::OBJECT {
1312            self.function_type()
1313        } else if let Some(shape_id) = object_shape_id(self.db, source_type) {
1314            let shape = self.db.object_shape(shape_id);
1315            if shape.properties.is_empty() {
1316                self.function_type()
1317            } else {
1318                TypeId::NEVER
1319            }
1320        } else if let Some(shape_id) = object_with_index_shape_id(self.db, source_type) {
1321            let shape = self.db.object_shape(shape_id);
1322            if shape.properties.is_empty()
1323                && shape.string_index.is_none()
1324                && shape.number_index.is_none()
1325            {
1326                self.function_type()
1327            } else {
1328                TypeId::NEVER
1329            }
1330        } else if index_access_parts(self.db, source_type).is_some() {
1331            // For indexed access types like T[K], narrow to T[K] & Function
1332            // This handles cases like: typeof obj[key] === 'function'
1333            let function_type = self.function_type();
1334            self.db.intersection2(source_type, function_type)
1335        } else {
1336            TypeId::NEVER
1337        }
1338    }
1339
1340    /// Check if a type is a function type.
1341    /// Uses the visitor pattern from `solver::visitor`.
1342    fn is_function_type(&self, type_id: TypeId) -> bool {
1343        is_function_type_db(self.db, type_id)
1344    }
1345
1346    /// Narrow a type to exclude function-like members (typeof !== "function").
1347    pub fn narrow_excluding_function(&self, source_type: TypeId) -> TypeId {
1348        if let Some(members) = union_list_id(self.db, source_type) {
1349            let members = self.db.type_list(members);
1350            let remaining: Vec<TypeId> = members
1351                .iter()
1352                .filter_map(|&member| {
1353                    if let Some(narrowed) = self.narrow_type_param_excluding_function(member) {
1354                        return narrowed.non_never();
1355                    }
1356                    if self.is_function_type(member) {
1357                        None
1358                    } else {
1359                        Some(member)
1360                    }
1361                })
1362                .collect();
1363
1364            return union_or_single(self.db, remaining);
1365        }
1366
1367        if let Some(narrowed) = self.narrow_type_param_excluding_function(source_type) {
1368            return narrowed;
1369        }
1370
1371        if self.is_function_type(source_type) {
1372            TypeId::NEVER
1373        } else {
1374            source_type
1375        }
1376    }
1377
1378    /// Check if a type has typeof "object".
1379    /// Uses the visitor pattern from `solver::visitor`.
1380    fn is_object_typeof(&self, type_id: TypeId) -> bool {
1381        is_object_like_type_db(self.db, type_id)
1382    }
1383
1384    fn narrow_type_param(&self, source: TypeId, target: TypeId) -> Option<TypeId> {
1385        let info = type_param_info(self.db, source)?;
1386
1387        let constraint = info.constraint.unwrap_or(TypeId::UNKNOWN);
1388        if constraint == source {
1389            return None;
1390        }
1391
1392        let narrowed_constraint = if constraint == TypeId::UNKNOWN {
1393            target
1394        } else {
1395            self.narrow_to_type(constraint, target)
1396        };
1397
1398        if narrowed_constraint == TypeId::NEVER {
1399            return None;
1400        }
1401
1402        Some(self.db.intersection2(source, narrowed_constraint))
1403    }
1404
1405    fn narrow_type_param_to_function(&self, source: TypeId) -> Option<TypeId> {
1406        let info = type_param_info(self.db, source)?;
1407
1408        let constraint = info.constraint.unwrap_or(TypeId::UNKNOWN);
1409        if constraint == source || constraint == TypeId::UNKNOWN {
1410            let function_type = self.function_type();
1411            return Some(self.db.intersection2(source, function_type));
1412        }
1413
1414        let narrowed_constraint = self.narrow_to_function(constraint);
1415        if narrowed_constraint == TypeId::NEVER {
1416            return None;
1417        }
1418
1419        Some(self.db.intersection2(source, narrowed_constraint))
1420    }
1421
1422    fn narrow_type_param_excluding(&self, source: TypeId, excluded: TypeId) -> Option<TypeId> {
1423        let info = type_param_info(self.db, source)?;
1424
1425        let constraint = info.constraint?;
1426        if constraint == source || constraint == TypeId::UNKNOWN {
1427            return None;
1428        }
1429
1430        let narrowed_constraint = self.narrow_excluding_type(constraint, excluded);
1431        if narrowed_constraint == constraint {
1432            return None;
1433        }
1434        if narrowed_constraint == TypeId::NEVER {
1435            return Some(TypeId::NEVER);
1436        }
1437
1438        Some(self.db.intersection2(source, narrowed_constraint))
1439    }
1440
1441    fn narrow_type_param_excluding_function(&self, source: TypeId) -> Option<TypeId> {
1442        let info = type_param_info(self.db, source)?;
1443
1444        let constraint = info.constraint.unwrap_or(TypeId::UNKNOWN);
1445        if constraint == source || constraint == TypeId::UNKNOWN {
1446            return Some(source);
1447        }
1448
1449        let narrowed_constraint = self.narrow_excluding_function(constraint);
1450        if narrowed_constraint == constraint {
1451            return Some(source);
1452        }
1453        if narrowed_constraint == TypeId::NEVER {
1454            return Some(TypeId::NEVER);
1455        }
1456
1457        Some(self.db.intersection2(source, narrowed_constraint))
1458    }
1459
1460    pub(crate) fn function_type(&self) -> TypeId {
1461        let rest_array = self.db.array(TypeId::ANY);
1462        let rest_param = ParamInfo {
1463            name: None,
1464            type_id: rest_array,
1465            optional: false,
1466            rest: true,
1467        };
1468        self.db.function(FunctionShape {
1469            params: vec![rest_param],
1470            this_type: None,
1471            return_type: TypeId::ANY,
1472            type_params: Vec::new(),
1473            type_predicate: None,
1474            is_constructor: false,
1475            is_method: false,
1476        })
1477    }
1478
1479    /// Check if a type is a JS primitive that can never pass `instanceof`.
1480    /// Includes string, number, boolean, bigint, symbol, undefined, null,
1481    /// void, never, and their literal forms.
1482    fn is_js_primitive(&self, type_id: TypeId) -> bool {
1483        matches!(
1484            type_id,
1485            TypeId::STRING
1486                | TypeId::NUMBER
1487                | TypeId::BOOLEAN
1488                | TypeId::BIGINT
1489                | TypeId::SYMBOL
1490                | TypeId::UNDEFINED
1491                | TypeId::NULL
1492                | TypeId::VOID
1493                | TypeId::NEVER
1494                | TypeId::BOOLEAN_TRUE
1495                | TypeId::BOOLEAN_FALSE
1496        ) || matches!(self.db.lookup(type_id), Some(TypeData::Literal(_)))
1497    }
1498
1499    /// Simple assignability check for narrowing purposes.
1500    fn is_assignable_to(&self, source: TypeId, target: TypeId) -> bool {
1501        if source == target {
1502            return true;
1503        }
1504
1505        // never is assignable to everything
1506        if source == TypeId::NEVER {
1507            return true;
1508        }
1509
1510        // everything is assignable to any/unknown
1511        if target.is_any_or_unknown() {
1512            return true;
1513        }
1514
1515        // Literal to base type
1516        if let Some(lit) = literal_value(self.db, source) {
1517            match (lit, target) {
1518                (LiteralValue::String(_), t) if t == TypeId::STRING => return true,
1519                (LiteralValue::Number(_), t) if t == TypeId::NUMBER => return true,
1520                (LiteralValue::Boolean(_), t) if t == TypeId::BOOLEAN => return true,
1521                (LiteralValue::BigInt(_), t) if t == TypeId::BIGINT => return true,
1522                _ => {}
1523            }
1524        }
1525
1526        // object/null for typeof "object"
1527        if target == TypeId::OBJECT {
1528            if source == TypeId::NULL {
1529                return true;
1530            }
1531            if self.is_object_typeof(source) {
1532                return true;
1533            }
1534            return false;
1535        }
1536
1537        if let Some(members) = intersection_list_id(self.db, source) {
1538            let members = self.db.type_list(members);
1539            if members
1540                .iter()
1541                .any(|member| self.is_assignable_to(*member, target))
1542            {
1543                return true;
1544            }
1545        }
1546
1547        if target == TypeId::STRING && template_literal_id(self.db, source).is_some() {
1548            return true;
1549        }
1550
1551        // Check if source is assignable to any member of a union target
1552        if let Some(members) = union_list_id(self.db, target) {
1553            let members = self.db.type_list(members);
1554            if members
1555                .iter()
1556                .any(|&member| self.is_assignable_to(source, member))
1557            {
1558                return true;
1559            }
1560        }
1561
1562        // Fallback: use full structural/nominal subtype check.
1563        // This handles class inheritance (Derived extends Base), interface
1564        // implementations, and other structural relationships that the
1565        // fast-path checks above don't cover.
1566        // CRITICAL: Resolve Lazy(DefId) types before the subtype check.
1567        // Without resolution, two unrelated interfaces (e.g., Cat and Dog)
1568        // remain as opaque Lazy types and the SubtypeChecker can't distinguish them.
1569        let source = self.resolve_type(source);
1570        let target = self.resolve_type(target);
1571        if source == target {
1572            return true;
1573        }
1574        crate::subtype::is_subtype_of_with_db(self.db, source, target)
1575    }
1576
1577    /// Applies a type guard to narrow a type.
1578    ///
1579    /// This is the main entry point for AST-agnostic type narrowing.
1580    /// The Checker extracts a `TypeGuard` from AST nodes, and the Solver
1581    /// applies it to compute the narrowed type.
1582    ///
1583    /// # Arguments
1584    /// * `source_type` - The type to narrow
1585    /// * `guard` - The guard condition (extracted from AST by Checker)
1586    /// * `sense` - If true, narrow for the "true" branch; if false, narrow for the "false" branch
1587    ///
1588    /// # Returns
1589    /// The narrowed type after applying the guard.
1590    ///
1591    /// # Examples
1592    /// ```ignore
1593    /// // typeof x === "string"
1594    /// let guard = TypeGuard::Typeof(TypeofKind::String);
1595    /// let narrowed = narrowing.narrow_type(string_or_number, &guard, true);
1596    /// assert_eq!(narrowed, TypeId::STRING);
1597    ///
1598    /// // x !== null (negated sense)
1599    /// let guard = TypeGuard::NullishEquality;
1600    /// let narrowed = narrowing.narrow_type(string_or_null, &guard, false);
1601    /// // Result should exclude null and undefined
1602    /// ```
1603    pub fn narrow_type(&self, source_type: TypeId, guard: &TypeGuard, sense: bool) -> TypeId {
1604        match guard {
1605            TypeGuard::Typeof(typeof_kind) => {
1606                let type_name = typeof_kind.as_str();
1607                if sense {
1608                    self.narrow_by_typeof(source_type, type_name)
1609                } else {
1610                    // Negation: exclude typeof type
1611                    self.narrow_by_typeof_negation(source_type, type_name)
1612                }
1613            }
1614
1615            TypeGuard::Instanceof(instance_type) => {
1616                if sense {
1617                    // Positive: x instanceof Class
1618                    // Special case: `unknown` instanceof X narrows to X (or object if X unknown)
1619                    // This must be handled here in the solver, not in the checker.
1620                    if source_type == TypeId::UNKNOWN {
1621                        return *instance_type;
1622                    }
1623
1624                    // CRITICAL: The payload is already the Instance Type (extracted by Checker)
1625                    // Use narrow_by_instance_type for instanceof-specific semantics:
1626                    // type parameters with matching constraints are kept, but anonymous
1627                    // object types that happen to be structurally compatible are excluded.
1628                    // Primitive types are filtered out since they can never pass instanceof.
1629                    let narrowed = self.narrow_by_instance_type(source_type, *instance_type);
1630
1631                    if narrowed != TypeId::NEVER || source_type == TypeId::NEVER {
1632                        return narrowed;
1633                    }
1634
1635                    // Fallback 1: If standard narrowing returns NEVER but source wasn't NEVER,
1636                    // it might be an interface vs class check (which is allowed in TS).
1637                    // Use intersection in that case.
1638                    let intersection = self.db.intersection2(source_type, *instance_type);
1639                    if intersection != TypeId::NEVER {
1640                        return intersection;
1641                    }
1642
1643                    // Fallback 2: If even intersection fails, narrow to object-like types.
1644                    // On the true branch of instanceof, we know the value must be some
1645                    // kind of object (primitives can never pass instanceof).
1646                    self.narrow_to_objectish(source_type)
1647                } else {
1648                    // Negative: !(x instanceof Class)
1649                    // Keep primitives (they can never pass instanceof) and exclude
1650                    // non-primitive types assignable to the instance type.
1651                    if *instance_type == TypeId::OBJECT {
1652                        source_type
1653                    } else {
1654                        self.narrow_by_instanceof_false(source_type, *instance_type)
1655                    }
1656                }
1657            }
1658
1659            TypeGuard::LiteralEquality(literal_type) => {
1660                if sense {
1661                    // Equality: narrow to the literal type
1662                    self.narrow_to_type(source_type, *literal_type)
1663                } else {
1664                    // Inequality: exclude the literal type
1665                    self.narrow_excluding_type(source_type, *literal_type)
1666                }
1667            }
1668
1669            TypeGuard::NullishEquality => {
1670                if sense {
1671                    // Equality with null: narrow to null | undefined
1672                    self.db.union(vec![TypeId::NULL, TypeId::UNDEFINED])
1673                } else {
1674                    // Inequality: exclude null and undefined
1675                    let without_null = self.narrow_excluding_type(source_type, TypeId::NULL);
1676                    self.narrow_excluding_type(without_null, TypeId::UNDEFINED)
1677                }
1678            }
1679
1680            TypeGuard::Truthy => {
1681                if sense {
1682                    // Truthy: remove null and undefined (TypeScript doesn't narrow other falsy values)
1683                    self.narrow_by_truthiness(source_type)
1684                } else {
1685                    // Falsy: narrow to the falsy component(s)
1686                    // This handles cases like: if (!x) where x: string → "" in false branch
1687                    self.narrow_to_falsy(source_type)
1688                }
1689            }
1690
1691            TypeGuard::Discriminant {
1692                property_path,
1693                value_type,
1694            } => {
1695                // Use narrow_by_discriminant_for_type which handles type parameters
1696                // by narrowing the constraint and returning T & NarrowedConstraint
1697                self.narrow_by_discriminant_for_type(source_type, property_path, *value_type, sense)
1698            }
1699
1700            TypeGuard::InProperty(property_name) => {
1701                if sense {
1702                    // Positive: "prop" in x - narrow to types that have the property
1703                    self.narrow_by_property_presence(source_type, *property_name, true)
1704                } else {
1705                    // Negative: !("prop" in x) - narrow to types that don't have the property
1706                    self.narrow_by_property_presence(source_type, *property_name, false)
1707                }
1708            }
1709
1710            TypeGuard::Predicate { type_id, asserts } => {
1711                match type_id {
1712                    Some(target_type) => {
1713                        // Type guard with specific type: is T or asserts T
1714                        if sense {
1715                            // True branch: narrow source to the predicate type.
1716                            // Following TSC's narrowType logic:
1717                            // 1. For unions: filter members using narrow_to_type
1718                            // 2. For non-unions:
1719                            //    a. source <: target → return source
1720                            //    b. target <: source → return target
1721                            //    c. otherwise → return source & target
1722                            //
1723                            // Following TSC's narrowType logic which uses
1724                            // isTypeSubtypeOf (not isTypeAssignableTo) to decide
1725                            // whether source is already specific enough.
1726                            //
1727                            // If source is a strict subtype of the target, return
1728                            // source (it's already more specific). If target is a
1729                            // strict subtype of source, return target (narrowing
1730                            // down). Otherwise, return the intersection.
1731                            //
1732                            // narrow_to_type uses assignability internally, which is
1733                            // too loose for type predicates (e.g. {} is assignable to
1734                            // Record<string,unknown> but not a subtype).
1735                            let resolved_source = self.resolve_type(source_type);
1736
1737                            if resolved_source == self.resolve_type(*target_type) {
1738                                source_type
1739                            } else if resolved_source == TypeId::UNKNOWN
1740                                || resolved_source == TypeId::ANY
1741                            {
1742                                *target_type
1743                            } else if union_list_id(self.db, resolved_source).is_some() {
1744                                // For unions: filter members, fall back to
1745                                // intersection if nothing matches.
1746                                let narrowed = self.narrow_to_type(source_type, *target_type);
1747                                if narrowed == TypeId::NEVER && source_type != TypeId::NEVER {
1748                                    self.db.intersection2(source_type, *target_type)
1749                                } else {
1750                                    narrowed
1751                                }
1752                            } else {
1753                                // Non-union source: use narrow_to_type first.
1754                                // If it returns source unchanged (assignable but
1755                                // possibly losing structural info) or NEVER (no
1756                                // overlap), fall back to intersection.
1757                                let narrowed = self.narrow_to_type(source_type, *target_type);
1758                                if narrowed == source_type && narrowed != *target_type {
1759                                    // Source was unchanged — intersect to preserve
1760                                    // target's structural info (index sigs, etc.)
1761                                    self.db.intersection2(source_type, *target_type)
1762                                } else if narrowed == TypeId::NEVER && source_type != TypeId::NEVER
1763                                {
1764                                    self.db.intersection2(source_type, *target_type)
1765                                } else {
1766                                    narrowed
1767                                }
1768                            }
1769                        } else if *asserts {
1770                            // CRITICAL: For assertion functions, the false branch is unreachable
1771                            // (the function throws if the assertion fails), so we don't narrow
1772                            source_type
1773                        } else {
1774                            // False branch for regular type guards: exclude the target type
1775                            self.narrow_excluding_type(source_type, *target_type)
1776                        }
1777                    }
1778                    None => {
1779                        // Truthiness assertion: asserts x
1780                        // Behaves like TypeGuard::Truthy (narrows to truthy in true branch)
1781                        if *asserts {
1782                            self.narrow_by_truthiness(source_type)
1783                        } else {
1784                            source_type
1785                        }
1786                    }
1787                }
1788            }
1789
1790            TypeGuard::Array => {
1791                if sense {
1792                    // Positive: Array.isArray(x) - narrow to array-like types
1793                    self.narrow_to_array(source_type)
1794                } else {
1795                    // Negative: !Array.isArray(x) - exclude array-like types
1796                    self.narrow_excluding_array(source_type)
1797                }
1798            }
1799
1800            TypeGuard::ArrayElementPredicate { element_type } => {
1801                trace!(
1802                    ?element_type,
1803                    ?sense,
1804                    "Applying ArrayElementPredicate guard"
1805                );
1806                if sense {
1807                    // True branch: narrow array element type
1808                    let result = self.narrow_array_element_type(source_type, *element_type);
1809                    trace!(?result, "ArrayElementPredicate narrowing result");
1810                    result
1811                } else {
1812                    // False branch: we don't narrow (arr.every could be false for various reasons)
1813                    trace!("ArrayElementPredicate false branch, no narrowing");
1814                    source_type
1815                }
1816            }
1817        }
1818    }
1819}
1820
1821#[cfg(test)]
1822#[path = "../tests/narrowing_tests.rs"]
1823mod tests;