Skip to main content

tsz_solver/relations/
judge.rs

1//! The Judge: Query-based type algebra engine.
2//!
3//! This module implements the "Judge" layer - pure set-theoretic computations
4//! for TypeScript's type system. The Judge knows nothing about TypeScript-specific
5//! quirks; those are handled by the "Lawyer" layer (compat.rs).
6//!
7//! ## Architecture
8//!
9//! ```text
10//!     Checker (AST traversal, diagnostics)
11//!         |
12//!         v
13//!     Lawyer (TypeScript compatibility rules)
14//!         |
15//!         v
16//!     Judge (Pure type algebra - THIS MODULE)
17//!         |
18//!         v
19//!     TypeInterner (Type storage, deduplication)
20//! ```
21//!
22//! ## Key Design Principles
23//!
24//! 1. **Pure Queries**: All Judge methods are pure functions that can be memoized.
25//!    Side effects (like diagnostics) are handled by the Lawyer's "explain slow" pattern.
26//!
27//! 2. **Coinductive Cycle Handling**: Recursive types use greatest fixed point semantics.
28//!    When a cycle is detected, we assume `true` for subtype checks.
29//!
30//! 3. **Classifier APIs**: Instead of exposing type internals, the Judge provides
31//!    high-level classifiers (e.g., `classify_iterable`, `classify_callable`).
32//!
33//! 4. **Configuration as Input**: Compiler options like `strictNullChecks` are
34//!    explicit parameters, enabling proper caching and invalidation.
35//!
36//! ## Usage
37//!
38//! ```ignore
39//! let judge = Judge::new(&interner);
40//!
41//! // Pure subtype check (cached)
42//! let is_subtype = judge.is_subtype(source, target);
43//!
44//! // Evaluate meta-types (cached)
45//! let evaluated = judge.evaluate(conditional_type_id);
46//!
47//! // High-level classifiers
48//! let iterable_kind = judge.classify_iterable(type_id);
49//! ```
50
51use crate::TypeDatabase;
52use crate::evaluation::evaluate::TypeEvaluator;
53use crate::objects::index_signatures::IndexKind;
54use crate::relations::subtype::{SubtypeChecker, TypeEnvironment};
55use crate::types::{
56    CallSignature, IntrinsicKind, LiteralValue, ParamInfo, TypeData, TypeId, TypeParamInfo,
57};
58#[cfg(test)]
59use crate::types::{FunctionShape, PropertyInfo, Visibility};
60use rustc_hash::FxHashMap;
61use std::cell::RefCell;
62use std::sync::Arc;
63use tsz_common::interner::Atom;
64
65// =============================================================================
66// Classification Enums
67// =============================================================================
68
69/// Classification of how a type can be iterated.
70///
71/// Used by the Checker to determine valid for-of targets, spread behavior, etc.
72#[derive(Clone, Debug, PartialEq, Eq)]
73pub enum IterableKind {
74    /// Array<T> - provides element type
75    Array(TypeId),
76    /// Tuple [T, U, V] - provides element union or individual types
77    Tuple(Vec<TypeId>),
78    /// string - iterates chars
79    String,
80    /// Has [Symbol.iterator] method returning an iterator
81    SyncIterator {
82        /// The iterator type returned by [Symbol.iterator]
83        iterator_type: TypeId,
84        /// The yielded element type (from Iterator.next().value)
85        element_type: TypeId,
86    },
87    /// Has [Symbol.asyncIterator] method returning an async iterator
88    AsyncIterator {
89        /// The async iterator type
90        iterator_type: TypeId,
91        /// The yielded element type
92        element_type: TypeId,
93    },
94    /// Not iterable
95    NotIterable,
96}
97
98/// Classification of how a type can be called.
99#[derive(Clone, Debug, PartialEq, Eq)]
100pub enum CallableKind {
101    /// Regular function with a single call signature
102    Function {
103        params: Vec<ParamInfo>,
104        return_type: TypeId,
105        type_params: Vec<TypeParamInfo>,
106    },
107    /// Constructor (new-able)
108    Constructor {
109        params: Vec<ParamInfo>,
110        return_type: TypeId,
111        type_params: Vec<TypeParamInfo>,
112    },
113    /// Overloaded function with multiple call signatures
114    Overloaded {
115        call_signatures: Vec<CallSignature>,
116        construct_signatures: Vec<CallSignature>,
117    },
118    /// Not callable
119    NotCallable,
120}
121
122bitflags::bitflags! {
123    /// Flags indicating primitive-like behavior.
124    #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
125    pub struct PrimitiveFlags: u32 {
126        const STRING_LIKE = 1 << 0;
127        const NUMBER_LIKE = 1 << 1;
128        const BOOLEAN_LIKE = 1 << 2;
129        const BIGINT_LIKE = 1 << 3;
130        const SYMBOL_LIKE = 1 << 4;
131        const VOID_LIKE = 1 << 5;
132        const NULLABLE = 1 << 6;
133        const UNDEFINED = 1 << 7;
134        const NULL = 1 << 8;
135        const NEVER = 1 << 9;
136        const UNKNOWN = 1 << 10;
137        const ANY = 1 << 11;
138    }
139}
140
141/// Classification of a type's truthiness behavior.
142#[derive(Clone, Copy, Debug, PartialEq, Eq)]
143pub enum TruthinessKind {
144    /// Always truthy (non-empty object, non-zero number, etc.)
145    AlwaysTruthy,
146    /// Always falsy (0, "", null, undefined, false)
147    AlwaysFalsy,
148    /// Could be either (union of truthy and falsy)
149    Sometimes,
150    /// Unknown truthiness (any, unknown)
151    Unknown,
152}
153
154/// Property access result from the Judge.
155#[derive(Clone, Debug, PartialEq, Eq)]
156pub enum PropertyResult {
157    /// Property found with this type
158    Found {
159        type_id: TypeId,
160        optional: bool,
161        readonly: bool,
162    },
163    /// Property not found
164    NotFound,
165    /// Type has index signature that could provide the property
166    IndexSignature { value_type: TypeId, readonly: bool },
167    /// Type is any - property access always succeeds with any
168    IsAny,
169    /// Type is unknown - property access requires narrowing
170    IsUnknown,
171    /// Type is error - propagate error silently
172    IsError,
173}
174
175// =============================================================================
176// Judge Configuration
177// =============================================================================
178
179/// Configuration options for the Judge.
180///
181/// These correspond to TypeScript compiler options that affect type checking.
182/// Making them explicit enables proper caching and invalidation.
183#[derive(Clone, Debug, PartialEq, Eq, Hash)]
184pub struct JudgeConfig {
185    /// strictNullChecks - whether null/undefined are separate types
186    pub strict_null_checks: bool,
187    /// strictFunctionTypes - contravariant function parameters
188    pub strict_function_types: bool,
189    /// exactOptionalPropertyTypes - optional properties don't include undefined implicitly
190    pub exact_optional_property_types: bool,
191    /// noUncheckedIndexedAccess - index signatures include undefined
192    pub no_unchecked_indexed_access: bool,
193    /// `sound_mode` - prioritizes soundness (e.g. any doesn't silence structural errors)
194    pub sound_mode: bool,
195}
196
197impl Default for JudgeConfig {
198    fn default() -> Self {
199        Self {
200            strict_null_checks: true,
201            strict_function_types: true,
202            exact_optional_property_types: false,
203            no_unchecked_indexed_access: false,
204            sound_mode: false,
205        }
206    }
207}
208
209// =============================================================================
210// Judge Trait
211// =============================================================================
212
213/// The Judge trait: pure type algebra queries.
214///
215/// This trait defines the query interface for type checking operations.
216/// Implementations can provide different caching strategies (e.g., Salsa).
217///
218/// ## Coinductive Semantics
219///
220/// Subtype checks use coinductive semantics for recursive types:
221/// - When a cycle is detected, assume `true` (greatest fixed point)
222/// - This correctly handles types like `type List<T> = { head: T; tail: List<T> }`
223///
224/// ## Memoization
225///
226/// All methods are designed to be memoizable:
227/// - No side effects (diagnostics handled separately)
228/// - Deterministic results for same inputs
229/// - Configuration is explicit (not implicit state)
230pub trait Judge {
231    // =========================================================================
232    // Core Type Relations
233    // =========================================================================
234
235    /// Check if `source` is a subtype of `target`.
236    ///
237    /// Uses coinductive semantics: cycles assume `true`.
238    ///
239    /// # Example
240    /// ```ignore
241    /// // number <: number | string
242    /// assert!(judge.is_subtype(TypeId::NUMBER, union_type));
243    /// ```
244    fn is_subtype(&self, source: TypeId, target: TypeId) -> bool;
245
246    /// Check if two types are identical (stricter than subtyping).
247    ///
248    /// Identity requires both `A <: B` and `B <: A`.
249    fn are_identical(&self, a: TypeId, b: TypeId) -> bool {
250        a == b || (self.is_subtype(a, b) && self.is_subtype(b, a))
251    }
252
253    // =========================================================================
254    // Type Evaluation
255    // =========================================================================
256
257    /// Evaluate a type, resolving meta-types (conditional, mapped, keyof, etc.).
258    ///
259    /// Returns the evaluated type (may be the same if no evaluation needed).
260    ///
261    /// # Cycle Recovery
262    /// Returns the input type on cycle (identity recovery).
263    fn evaluate(&self, type_id: TypeId) -> TypeId;
264
265    /// Instantiate a generic type with type arguments.
266    ///
267    /// # Example
268    /// ```ignore
269    /// // Array<number> from Array<T> with T=number
270    /// let array_number = judge.instantiate(array_generic, &[TypeId::NUMBER]);
271    /// ```
272    fn instantiate(&self, generic: TypeId, args: &[TypeId]) -> TypeId;
273
274    // =========================================================================
275    // Type Classifiers
276    // =========================================================================
277
278    /// Classify how a type can be iterated.
279    ///
280    /// Used for:
281    /// - for-of loop targets
282    /// - Spread operators
283    /// - `Array.from()` arguments
284    fn classify_iterable(&self, type_id: TypeId) -> IterableKind;
285
286    /// Classify how a type can be called.
287    ///
288    /// Used for:
289    /// - Call expressions
290    /// - new expressions
291    /// - Overload resolution
292    fn classify_callable(&self, type_id: TypeId) -> CallableKind;
293
294    /// Get primitive-like behavior flags for a type.
295    ///
296    /// Used for:
297    /// - Binary operator resolution
298    /// - Type coercion rules
299    fn classify_primitive(&self, type_id: TypeId) -> PrimitiveFlags;
300
301    /// Classify a type's truthiness behavior.
302    ///
303    /// Used for:
304    /// - Control flow narrowing
305    /// - Conditional expressions
306    fn classify_truthiness(&self, type_id: TypeId) -> TruthinessKind;
307
308    // =========================================================================
309    // Property Access
310    // =========================================================================
311
312    /// Get the apparent type (unwrap type params, resolve constraints).
313    fn apparent_type(&self, type_id: TypeId) -> TypeId;
314
315    /// Get a specific property's type from a type.
316    ///
317    /// Returns `PropertyResult` which distinguishes between:
318    /// - Property found
319    /// - Property not found
320    /// - Index signature match
321    /// - Special types (any, unknown, error)
322    fn get_property(&self, type_id: TypeId, name: Atom) -> PropertyResult;
323
324    /// Get all members of a type as (name, type) pairs.
325    fn get_members(&self, type_id: TypeId) -> Arc<Vec<(Atom, TypeId)>>;
326
327    /// Get call signatures of a type.
328    fn get_call_signatures(&self, type_id: TypeId) -> Arc<Vec<CallSignature>>;
329
330    /// Get construct signatures of a type.
331    fn get_construct_signatures(&self, type_id: TypeId) -> Arc<Vec<CallSignature>>;
332
333    /// Get the result of indexing: T[K]
334    fn get_index_type(&self, object: TypeId, key: TypeId) -> TypeId;
335
336    /// Get index signature type (string or number indexer).
337    fn get_index_signature(&self, type_id: TypeId, kind: IndexKind) -> Option<TypeId>;
338
339    /// Get keyof: keyof T
340    fn get_keyof(&self, type_id: TypeId) -> TypeId;
341
342    // =========================================================================
343    // Configuration
344    // =========================================================================
345
346    /// Get the current configuration.
347    fn config(&self) -> &JudgeConfig;
348}
349
350// =============================================================================
351// Default Judge Implementation
352// =============================================================================
353
354/// Default implementation of the Judge trait.
355///
356/// Uses basic caching with `FxHashMap`. For production use with incremental
357/// compilation, consider a Salsa-based implementation.
358pub struct DefaultJudge<'a> {
359    db: &'a dyn TypeDatabase,
360    config: JudgeConfig,
361    /// Type environment for resolving references
362    env: &'a TypeEnvironment,
363    /// Cache for subtype results
364    subtype_cache: RefCell<FxHashMap<(TypeId, TypeId), bool>>,
365    /// Cache for evaluated types
366    eval_cache: RefCell<FxHashMap<TypeId, TypeId>>,
367}
368
369impl<'a> DefaultJudge<'a> {
370    /// Create a new Judge with the given database and configuration.
371    pub fn new(db: &'a dyn TypeDatabase, env: &'a TypeEnvironment, config: JudgeConfig) -> Self {
372        DefaultJudge {
373            db,
374            config,
375            env,
376            subtype_cache: RefCell::new(FxHashMap::default()),
377            eval_cache: RefCell::new(FxHashMap::default()),
378        }
379    }
380
381    /// Create a Judge with default configuration.
382    pub fn with_defaults(db: &'a dyn TypeDatabase, env: &'a TypeEnvironment) -> Self {
383        Self::new(db, env, JudgeConfig::default())
384    }
385
386    /// Clear all caches.
387    pub fn clear_caches(&self) {
388        self.subtype_cache.borrow_mut().clear();
389        self.eval_cache.borrow_mut().clear();
390    }
391
392    /// Get the underlying database.
393    pub fn db(&self) -> &'a dyn TypeDatabase {
394        self.db
395    }
396}
397
398impl<'a> Judge for DefaultJudge<'a> {
399    fn is_subtype(&self, source: TypeId, target: TypeId) -> bool {
400        // Fast path: identity
401        if source == target {
402            return true;
403        }
404
405        // Check cache
406        let key = (source, target);
407        if let Some(&cached) = self.subtype_cache.borrow().get(&key) {
408            return cached;
409        }
410
411        // Create a SubtypeChecker and perform the check
412        let mut checker = SubtypeChecker::with_resolver(self.db, self.env);
413        checker.strict_function_types = self.config.strict_function_types;
414        checker.strict_null_checks = self.config.strict_null_checks;
415        checker.exact_optional_property_types = self.config.exact_optional_property_types;
416        checker.no_unchecked_indexed_access = self.config.no_unchecked_indexed_access;
417
418        let result = checker.is_subtype_of(source, target);
419
420        // Cache the result
421        self.subtype_cache.borrow_mut().insert(key, result);
422
423        result
424    }
425
426    fn evaluate(&self, type_id: TypeId) -> TypeId {
427        // Fast path: intrinsics don't need evaluation
428        if type_id.is_intrinsic() {
429            return type_id;
430        }
431
432        // Check cache
433        if let Some(&cached) = self.eval_cache.borrow().get(&type_id) {
434            return cached;
435        }
436
437        // Create evaluator and evaluate
438        let mut evaluator = TypeEvaluator::with_resolver(self.db, self.env);
439        let result = evaluator.evaluate(type_id);
440
441        // Cache the result
442        self.eval_cache.borrow_mut().insert(type_id, result);
443
444        result
445    }
446
447    fn instantiate(&self, generic: TypeId, args: &[TypeId]) -> TypeId {
448        use crate::instantiation::instantiate::instantiate_generic;
449
450        // Get type params from the generic type
451        let key = match self.db.lookup(generic) {
452            Some(k) => k,
453            None => return TypeId::ERROR,
454        };
455
456        // Try to get type params from Lazy - use DefId directly
457        if let TypeData::Lazy(def_id) = &key
458            && let Some(params) = self.env.get_def_params(*def_id)
459            && let Some(resolved) = self.env.get_def(*def_id)
460        {
461            return instantiate_generic(self.db, resolved, params, args);
462        }
463
464        // Fallback: can't instantiate
465        generic
466    }
467
468    fn classify_iterable(&self, type_id: TypeId) -> IterableKind {
469        let evaluated = self.evaluate(type_id);
470
471        // Check for special types
472        if evaluated == TypeId::ANY || evaluated == TypeId::ERROR {
473            return IterableKind::NotIterable;
474        }
475        if evaluated == TypeId::STRING {
476            return IterableKind::String;
477        }
478
479        let key = match self.db.lookup(evaluated) {
480            Some(k) => k,
481            None => return IterableKind::NotIterable,
482        };
483
484        match key {
485            TypeData::Array(elem) => IterableKind::Array(elem),
486            TypeData::Tuple(list_id) => {
487                let elements = self.db.tuple_list(list_id);
488                let types: Vec<TypeId> = elements.iter().map(|e| e.type_id).collect();
489                IterableKind::Tuple(types)
490            }
491            TypeData::Literal(LiteralValue::String(_)) => IterableKind::String,
492            TypeData::Object(shape_id) | TypeData::ObjectWithIndex(shape_id) => {
493                let has_usable_iterator_signature = |method_type: TypeId| {
494                    if method_type == TypeId::ANY
495                        || method_type == TypeId::UNKNOWN
496                        || method_type == TypeId::ERROR
497                    {
498                        return true;
499                    }
500                    self.get_call_signatures(method_type)
501                        .iter()
502                        .any(|sig| sig.params.iter().all(|param| param.optional || param.rest))
503                };
504
505                // Check for Symbol.iterator
506                let shape = self.db.object_shape(shape_id);
507                let iterator_name = self.db.intern_string("[Symbol.iterator]");
508                for prop in &shape.properties {
509                    if prop.name == iterator_name && has_usable_iterator_signature(prop.type_id) {
510                        // Found iterator - extract element type
511                        return IterableKind::SyncIterator {
512                            iterator_type: prop.type_id,
513                            element_type: self.extract_iterator_element_type(prop.type_id),
514                        };
515                    }
516                }
517                // Check for Symbol.asyncIterator
518                let async_iterator_name = self.db.intern_string("[Symbol.asyncIterator]");
519                for prop in &shape.properties {
520                    if prop.name == async_iterator_name
521                        && has_usable_iterator_signature(prop.type_id)
522                    {
523                        return IterableKind::AsyncIterator {
524                            iterator_type: prop.type_id,
525                            element_type: self.extract_iterator_element_type(prop.type_id),
526                        };
527                    }
528                }
529                IterableKind::NotIterable
530            }
531            TypeData::Union(members_id) => {
532                // All members must be iterable with compatible element types
533                let members = self.db.type_list(members_id);
534                let mut element_types = Vec::new();
535                for &member in members.iter() {
536                    match self.classify_iterable(member) {
537                        IterableKind::Array(elem) => element_types.push(elem),
538                        IterableKind::Tuple(elems) => element_types.extend(elems),
539                        IterableKind::String => element_types.push(TypeId::STRING),
540                        IterableKind::SyncIterator { element_type, .. }
541                        | IterableKind::AsyncIterator { element_type, .. } => {
542                            element_types.push(element_type);
543                        }
544                        IterableKind::NotIterable => return IterableKind::NotIterable,
545                    }
546                }
547                if element_types.is_empty() {
548                    IterableKind::NotIterable
549                } else {
550                    let union = self.db.union(element_types);
551                    IterableKind::Array(union)
552                }
553            }
554            _ => IterableKind::NotIterable,
555        }
556    }
557
558    fn classify_callable(&self, type_id: TypeId) -> CallableKind {
559        let evaluated = self.evaluate(type_id);
560
561        if evaluated == TypeId::ANY {
562            return CallableKind::NotCallable;
563        }
564
565        let key = match self.db.lookup(evaluated) {
566            Some(k) => k,
567            None => return CallableKind::NotCallable,
568        };
569
570        match key {
571            TypeData::Function(fn_id) => {
572                let shape = self.db.function_shape(fn_id);
573                if shape.is_constructor {
574                    CallableKind::Constructor {
575                        params: shape.params.clone(),
576                        return_type: shape.return_type,
577                        type_params: shape.type_params.clone(),
578                    }
579                } else {
580                    CallableKind::Function {
581                        params: shape.params.clone(),
582                        return_type: shape.return_type,
583                        type_params: shape.type_params.clone(),
584                    }
585                }
586            }
587            TypeData::Callable(callable_id) => {
588                let shape = self.db.callable_shape(callable_id);
589                CallableKind::Overloaded {
590                    call_signatures: shape.call_signatures.clone(),
591                    construct_signatures: shape.construct_signatures.clone(),
592                }
593            }
594            _ => CallableKind::NotCallable,
595        }
596    }
597
598    fn classify_primitive(&self, type_id: TypeId) -> PrimitiveFlags {
599        let mut flags = PrimitiveFlags::empty();
600
601        // Handle intrinsics directly
602        match type_id {
603            TypeId::ANY => return PrimitiveFlags::ANY,
604            TypeId::UNKNOWN => return PrimitiveFlags::UNKNOWN,
605            TypeId::NEVER => return PrimitiveFlags::NEVER,
606            TypeId::VOID => return PrimitiveFlags::VOID_LIKE,
607            TypeId::UNDEFINED => {
608                return PrimitiveFlags::UNDEFINED | PrimitiveFlags::NULLABLE;
609            }
610            TypeId::NULL => return PrimitiveFlags::NULL | PrimitiveFlags::NULLABLE,
611            TypeId::BOOLEAN | TypeId::BOOLEAN_TRUE | TypeId::BOOLEAN_FALSE => {
612                return PrimitiveFlags::BOOLEAN_LIKE;
613            }
614            TypeId::NUMBER => return PrimitiveFlags::NUMBER_LIKE,
615            TypeId::STRING => return PrimitiveFlags::STRING_LIKE,
616            TypeId::BIGINT => return PrimitiveFlags::BIGINT_LIKE,
617            TypeId::SYMBOL => return PrimitiveFlags::SYMBOL_LIKE,
618            _ => {}
619        }
620
621        let key = match self.db.lookup(type_id) {
622            Some(k) => k,
623            None => return flags,
624        };
625
626        match key {
627            TypeData::Literal(LiteralValue::String(_)) | TypeData::TemplateLiteral(_) => {
628                flags |= PrimitiveFlags::STRING_LIKE
629            }
630            TypeData::Literal(LiteralValue::Number(_)) => flags |= PrimitiveFlags::NUMBER_LIKE,
631            TypeData::Literal(LiteralValue::Boolean(_)) => flags |= PrimitiveFlags::BOOLEAN_LIKE,
632            TypeData::Literal(LiteralValue::BigInt(_)) => flags |= PrimitiveFlags::BIGINT_LIKE,
633            TypeData::Union(members_id) => {
634                let members = self.db.type_list(members_id);
635                for &member in members.iter() {
636                    flags |= self.classify_primitive(member);
637                }
638            }
639            _ => {}
640        }
641
642        flags
643    }
644
645    fn classify_truthiness(&self, type_id: TypeId) -> TruthinessKind {
646        // Handle intrinsics
647        match type_id {
648            TypeId::ANY | TypeId::UNKNOWN => return TruthinessKind::Unknown,
649            TypeId::NEVER
650            | TypeId::VOID
651            | TypeId::UNDEFINED
652            | TypeId::NULL
653            | TypeId::BOOLEAN_FALSE => return TruthinessKind::AlwaysFalsy,
654            TypeId::BOOLEAN => return TruthinessKind::Sometimes,
655            TypeId::BOOLEAN_TRUE => return TruthinessKind::AlwaysTruthy,
656            _ => {}
657        }
658
659        let key = match self.db.lookup(type_id) {
660            Some(k) => k,
661            None => return TruthinessKind::Unknown,
662        };
663
664        match key {
665            TypeData::Literal(LiteralValue::String(s)) => {
666                let s_str = self.db.resolve_atom(s);
667                if s_str.is_empty() {
668                    TruthinessKind::AlwaysFalsy
669                } else {
670                    TruthinessKind::AlwaysTruthy
671                }
672            }
673            TypeData::Literal(LiteralValue::Number(n)) => {
674                if n.0 == 0.0 || n.0.is_nan() {
675                    TruthinessKind::AlwaysFalsy
676                } else {
677                    TruthinessKind::AlwaysTruthy
678                }
679            }
680            TypeData::Literal(LiteralValue::Boolean(b)) => {
681                if b {
682                    TruthinessKind::AlwaysTruthy
683                } else {
684                    TruthinessKind::AlwaysFalsy
685                }
686            }
687            TypeData::Literal(LiteralValue::BigInt(s)) => {
688                let s_str = self.db.resolve_atom(s);
689                if s_str == "0" || s_str == "0n" {
690                    TruthinessKind::AlwaysFalsy
691                } else {
692                    TruthinessKind::AlwaysTruthy
693                }
694            }
695            TypeData::Object(_)
696            | TypeData::ObjectWithIndex(_)
697            | TypeData::Array(_)
698            | TypeData::Tuple(_)
699            | TypeData::Function(_)
700            | TypeData::Callable(_) => TruthinessKind::AlwaysTruthy,
701            TypeData::Union(members_id) => {
702                let members = self.db.type_list(members_id);
703                let mut has_truthy = false;
704                let mut has_falsy = false;
705                for &member in members.iter() {
706                    match self.classify_truthiness(member) {
707                        TruthinessKind::AlwaysTruthy => has_truthy = true,
708                        TruthinessKind::AlwaysFalsy => has_falsy = true,
709                        TruthinessKind::Sometimes | TruthinessKind::Unknown => {
710                            has_truthy = true;
711                            has_falsy = true;
712                        }
713                    }
714                }
715                match (has_truthy, has_falsy) {
716                    (true, true) => TruthinessKind::Sometimes,
717                    (true, false) => TruthinessKind::AlwaysTruthy,
718                    (false, true) => TruthinessKind::AlwaysFalsy,
719                    (false, false) => TruthinessKind::Unknown,
720                }
721            }
722            TypeData::Intrinsic(
723                IntrinsicKind::String | IntrinsicKind::Number | IntrinsicKind::Bigint,
724            ) => {
725                // Could be empty string, 0, or 0n
726                TruthinessKind::Sometimes
727            }
728            _ => TruthinessKind::Unknown,
729        }
730    }
731
732    fn apparent_type(&self, type_id: TypeId) -> TypeId {
733        let key = match self.db.lookup(type_id) {
734            Some(k) => k,
735            None => return type_id,
736        };
737
738        match key {
739            TypeData::TypeParameter(ref info) => info.constraint.unwrap_or(type_id),
740            TypeData::Lazy(def_id) => self.env.get_def(def_id).unwrap_or(type_id),
741            _ => type_id,
742        }
743    }
744
745    fn get_property(&self, type_id: TypeId, name: Atom) -> PropertyResult {
746        // Handle special types
747        match type_id {
748            TypeId::ANY => return PropertyResult::IsAny,
749            TypeId::UNKNOWN => return PropertyResult::IsUnknown,
750            TypeId::ERROR => return PropertyResult::IsError,
751            _ => {}
752        }
753
754        let evaluated = self.evaluate(type_id);
755        let key = match self.db.lookup(evaluated) {
756            Some(k) => k,
757            None => return PropertyResult::NotFound,
758        };
759
760        match key {
761            TypeData::Object(shape_id) | TypeData::ObjectWithIndex(shape_id) => {
762                let shape = self.db.object_shape(shape_id);
763
764                // Check named properties first
765                for prop in &shape.properties {
766                    if prop.name == name {
767                        return PropertyResult::Found {
768                            type_id: prop.type_id,
769                            optional: prop.optional,
770                            readonly: prop.readonly,
771                        };
772                    }
773                }
774
775                // Check index signatures
776                if let Some(ref string_idx) = shape.string_index {
777                    return PropertyResult::IndexSignature {
778                        value_type: string_idx.value_type,
779                        readonly: string_idx.readonly,
780                    };
781                }
782
783                PropertyResult::NotFound
784            }
785            TypeData::Array(_elem) => {
786                let name_str = self.db.resolve_atom(name);
787                if name_str == "length" {
788                    PropertyResult::Found {
789                        type_id: TypeId::NUMBER,
790                        optional: false,
791                        readonly: false,
792                    }
793                } else {
794                    // Could check for array methods here
795                    PropertyResult::NotFound
796                }
797            }
798            TypeData::Tuple(list_id) => {
799                let name_str = self.db.resolve_atom(name);
800                if name_str == "length" {
801                    let elements = self.db.tuple_list(list_id);
802                    let len_type = self.db.literal_number(elements.len() as f64);
803                    PropertyResult::Found {
804                        type_id: len_type,
805                        optional: false,
806                        readonly: true,
807                    }
808                } else if let Ok(idx) = name_str.parse::<usize>() {
809                    let elements = self.db.tuple_list(list_id);
810                    if let Some(elem) = elements.get(idx) {
811                        PropertyResult::Found {
812                            type_id: elem.type_id,
813                            optional: elem.optional,
814                            readonly: false,
815                        }
816                    } else {
817                        PropertyResult::NotFound
818                    }
819                } else {
820                    PropertyResult::NotFound
821                }
822            }
823            TypeData::Union(members_id) => {
824                let members = self.db.type_list(members_id);
825                let mut result_types = Vec::new();
826                let mut all_optional = true;
827                let mut any_readonly = false;
828
829                for &member in members.iter() {
830                    match self.get_property(member, name) {
831                        PropertyResult::Found {
832                            type_id,
833                            optional,
834                            readonly,
835                        } => {
836                            result_types.push(type_id);
837                            if !optional {
838                                all_optional = false;
839                            }
840                            if readonly {
841                                any_readonly = true;
842                            }
843                        }
844                        PropertyResult::IndexSignature {
845                            value_type,
846                            readonly,
847                        } => {
848                            result_types.push(value_type);
849                            if readonly {
850                                any_readonly = true;
851                            }
852                        }
853                        PropertyResult::IsAny => return PropertyResult::IsAny,
854                        PropertyResult::IsUnknown => return PropertyResult::IsUnknown,
855                        PropertyResult::IsError => return PropertyResult::IsError,
856                        PropertyResult::NotFound => {
857                            // Property missing from at least one union member
858                            return PropertyResult::NotFound;
859                        }
860                    }
861                }
862
863                if result_types.is_empty() {
864                    PropertyResult::NotFound
865                } else {
866                    PropertyResult::Found {
867                        type_id: self.db.union(result_types),
868                        optional: all_optional,
869                        readonly: any_readonly,
870                    }
871                }
872            }
873            TypeData::Intersection(members_id) => {
874                let members = self.db.type_list(members_id);
875                let mut found_types = Vec::new();
876                let mut optional = true;
877                let mut readonly = false;
878
879                for &member in members.iter() {
880                    if let PropertyResult::Found {
881                        type_id,
882                        optional: opt,
883                        readonly: ro,
884                    } = self.get_property(member, name)
885                    {
886                        found_types.push(type_id);
887                        if !opt {
888                            optional = false;
889                        }
890                        if ro {
891                            readonly = true;
892                        }
893                    }
894                }
895
896                if found_types.is_empty() {
897                    PropertyResult::NotFound
898                } else {
899                    PropertyResult::Found {
900                        type_id: self.db.intersection(found_types),
901                        optional,
902                        readonly,
903                    }
904                }
905            }
906            _ => PropertyResult::NotFound,
907        }
908    }
909
910    fn get_members(&self, type_id: TypeId) -> Arc<Vec<(Atom, TypeId)>> {
911        let evaluated = self.evaluate(type_id);
912        let key = match self.db.lookup(evaluated) {
913            Some(k) => k,
914            None => return Arc::new(Vec::new()),
915        };
916
917        match key {
918            TypeData::Object(shape_id) | TypeData::ObjectWithIndex(shape_id) => {
919                let shape = self.db.object_shape(shape_id);
920                Arc::new(
921                    shape
922                        .properties
923                        .iter()
924                        .map(|p| (p.name, p.type_id))
925                        .collect(),
926                )
927            }
928            TypeData::Callable(callable_id) => {
929                let shape = self.db.callable_shape(callable_id);
930                Arc::new(
931                    shape
932                        .properties
933                        .iter()
934                        .map(|p| (p.name, p.type_id))
935                        .collect(),
936                )
937            }
938            _ => Arc::new(Vec::new()),
939        }
940    }
941
942    fn get_call_signatures(&self, type_id: TypeId) -> Arc<Vec<CallSignature>> {
943        let evaluated = self.evaluate(type_id);
944        let key = match self.db.lookup(evaluated) {
945            Some(k) => k,
946            None => return Arc::new(Vec::new()),
947        };
948
949        match key {
950            TypeData::Function(fn_id) => {
951                let shape = self.db.function_shape(fn_id);
952                if shape.is_constructor {
953                    return Arc::new(Vec::new());
954                }
955                Arc::new(vec![CallSignature {
956                    type_params: shape.type_params.clone(),
957                    params: shape.params.clone(),
958                    this_type: shape.this_type,
959                    return_type: shape.return_type,
960                    type_predicate: shape.type_predicate.clone(),
961                    is_method: shape.is_method,
962                }])
963            }
964            TypeData::Callable(callable_id) => {
965                let shape = self.db.callable_shape(callable_id);
966                Arc::new(shape.call_signatures.clone())
967            }
968            _ => Arc::new(Vec::new()),
969        }
970    }
971
972    fn get_construct_signatures(&self, type_id: TypeId) -> Arc<Vec<CallSignature>> {
973        let evaluated = self.evaluate(type_id);
974        let key = match self.db.lookup(evaluated) {
975            Some(k) => k,
976            None => return Arc::new(Vec::new()),
977        };
978
979        match key {
980            TypeData::Function(fn_id) => {
981                let shape = self.db.function_shape(fn_id);
982                if !shape.is_constructor {
983                    return Arc::new(Vec::new());
984                }
985                Arc::new(vec![CallSignature {
986                    type_params: shape.type_params.clone(),
987                    params: shape.params.clone(),
988                    this_type: shape.this_type,
989                    return_type: shape.return_type,
990                    type_predicate: shape.type_predicate.clone(),
991                    is_method: false,
992                }])
993            }
994            TypeData::Callable(callable_id) => {
995                let shape = self.db.callable_shape(callable_id);
996                Arc::new(shape.construct_signatures.clone())
997            }
998            _ => Arc::new(Vec::new()),
999        }
1000    }
1001
1002    fn get_index_type(&self, object: TypeId, key: TypeId) -> TypeId {
1003        crate::evaluation::evaluate::evaluate_index_access_with_options(
1004            self.db,
1005            object,
1006            key,
1007            self.config.no_unchecked_indexed_access,
1008        )
1009    }
1010
1011    fn get_index_signature(&self, type_id: TypeId, kind: IndexKind) -> Option<TypeId> {
1012        let evaluated = self.evaluate(type_id);
1013        let key = self.db.lookup(evaluated)?;
1014
1015        match key {
1016            TypeData::ObjectWithIndex(shape_id) => {
1017                let shape = self.db.object_shape(shape_id);
1018                match kind {
1019                    IndexKind::String => shape.string_index.as_ref().map(|s| s.value_type),
1020                    IndexKind::Number => shape.number_index.as_ref().map(|s| s.value_type),
1021                }
1022            }
1023            TypeData::Array(elem) => (kind == IndexKind::Number).then_some(elem),
1024            TypeData::Tuple(list_id) => (kind == IndexKind::Number).then(|| {
1025                let elements = self.db.tuple_list(list_id);
1026                let types: Vec<TypeId> = elements.iter().map(|e| e.type_id).collect();
1027                self.db.union(types)
1028            }),
1029            _ => None,
1030        }
1031    }
1032
1033    fn get_keyof(&self, type_id: TypeId) -> TypeId {
1034        crate::evaluation::evaluate::evaluate_keyof(self.db, type_id)
1035    }
1036
1037    fn config(&self) -> &JudgeConfig {
1038        &self.config
1039    }
1040}
1041
1042impl<'a> DefaultJudge<'a> {
1043    /// Extract the element type from an iterator type.
1044    fn extract_iterator_element_type(&self, iterator_type: TypeId) -> TypeId {
1045        // Look for .next() method returning { value: T, done: boolean }
1046        let next_name = self.db.intern_string("next");
1047        if let PropertyResult::Found { type_id, .. } = self.get_property(iterator_type, next_name) {
1048            // Check if it's a function
1049            if let Some(TypeData::Function(fn_id)) = self.db.lookup(type_id) {
1050                let shape = self.db.function_shape(fn_id);
1051                // Look for value property in return type
1052                let value_name = self.db.intern_string("value");
1053                if let PropertyResult::Found {
1054                    type_id: value_type,
1055                    ..
1056                } = self.get_property(shape.return_type, value_name)
1057                {
1058                    return value_type;
1059                }
1060            }
1061        }
1062        TypeId::UNKNOWN
1063    }
1064}
1065
1066// =============================================================================
1067// Tests
1068// =============================================================================
1069
1070#[cfg(test)]
1071#[path = "../../tests/judge_tests.rs"]
1072mod tests;