Skip to main content

tsz_solver/type_queries/
mod.rs

1//! Type Query Functions
2//!
3//! This module provides high-level query functions for inspecting type characteristics.
4//! These functions abstract away the internal `TypeData` representation and provide
5//! a stable API for the checker to query type properties.
6//!
7//! # Design Principles
8//!
9//! - **Abstraction**: Checker code should use these functions instead of matching on `TypeData`
10//! - **TypeDatabase-based**: All queries work through the `TypeDatabase` trait
11//! - **Comprehensive**: Covers all common type checking scenarios
12//! - **Efficient**: Simple lookups with minimal overhead
13//!
14//! # Usage
15//!
16//! ```rust,ignore
17//! use crate::type_queries::*;
18//!
19//! // Check if a type is callable
20//! if is_callable_type(&db, type_id) {
21//!     // Handle callable type
22//! }
23//!
24//! // Check if a type is a tuple
25//! if is_tuple_type(&db, type_id) {
26//!     // Handle tuple type
27//! }
28//! ```
29
30pub mod classifiers;
31pub mod data;
32pub mod extended;
33pub mod extended_constructors;
34pub mod flow;
35pub mod iterable;
36pub mod traversal;
37
38use crate::{QueryDatabase, TypeData, TypeDatabase, TypeId};
39
40// Re-export shared predicates from visitor_predicates to avoid duplication.
41// These are the canonical implementations; type_queries re-exports them so
42// callers can use a single `type_queries::*` import.
43pub use crate::visitors::visitor_predicates::{
44    is_array_type, is_conditional_type, is_empty_object_type, is_function_type,
45    is_index_access_type, is_intersection_type, is_literal_type, is_mapped_type,
46    is_object_like_type, is_primitive_type, is_template_literal_type, is_tuple_type, is_union_type,
47};
48
49// Re-export sub-module items so callers can use `type_queries::*`
50pub use classifiers::{
51    AssignabilityEvalKind, AugmentationTargetKind, BindingElementTypeKind, ConstructorAccessKind,
52    ExcessPropertiesKind, InterfaceMergeKind, SymbolResolutionTraversalKind,
53    classify_for_assignability_eval, classify_for_augmentation, classify_for_binding_element,
54    classify_for_constructor_access, classify_for_excess_properties, classify_for_interface_merge,
55    classify_for_symbol_resolution_traversal, get_conditional_type_id, get_enum_components,
56    get_keyof_type, get_lazy_def_id, get_mapped_type_id, get_type_identity,
57};
58// `get_def_id` is an alias for `get_lazy_def_id` (identical semantics).
59pub use classifiers::get_lazy_def_id as get_def_id;
60pub use extended::get_application_info;
61pub use extended::{
62    ArrayLikeKind, CallSignaturesKind, ContextualLiteralAllowKind, ElementIndexableKind,
63    IndexKeyKind, KeyOfTypeKind, LazyTypeKind, LiteralKeyKind, LiteralTypeKind,
64    MappedConstraintKind, NamespaceMemberKind, PrivateBrandKind, PromiseTypeKind,
65    PropertyAccessResolutionKind, StringLiteralKeyKind, TypeArgumentExtractionKind,
66    TypeParameterKind, TypeQueryKind, TypeResolutionKind, classify_array_like,
67    classify_element_indexable, classify_for_call_signatures, classify_for_contextual_literal,
68    classify_for_lazy_resolution, classify_for_private_brand,
69    classify_for_property_access_resolution, classify_for_string_literal_keys,
70    classify_for_type_argument_extraction, classify_for_type_resolution, classify_index_key,
71    classify_literal_key, classify_literal_type, classify_mapped_constraint,
72    classify_namespace_member, classify_promise_type, classify_type_parameter, classify_type_query,
73    create_boolean_literal_type, create_number_literal_type, create_string_literal_type,
74    get_application_base, get_boolean_literal_value, get_callable_type_param_count,
75    get_invalid_index_type_member, get_literal_property_name, get_number_literal_value,
76    get_string_literal_atom, get_string_literal_value, get_tuple_list_id, get_type_param_default,
77    get_widened_literal_type, is_boolean_literal, is_direct_type_parameter, is_invalid_index_type,
78    is_number_literal, is_object_with_index_type, is_string_literal, unwrap_readonly_for_lookup,
79    widen_literal_to_primitive,
80};
81pub use extended_constructors::{
82    AbstractClassCheckKind, AbstractConstructorAnchor, AbstractConstructorKind,
83    BaseInstanceMergeKind, ClassDeclTypeKind, ConstructSignatureKind, ConstructorCheckKind,
84    ConstructorReturnMergeKind, InstanceTypeKind, NewExpressionTypeKind,
85    classify_for_abstract_check, classify_for_abstract_constructor,
86    classify_for_base_instance_merge, classify_for_class_decl, classify_for_construct_signature,
87    classify_for_constructor_check, classify_for_constructor_return_merge,
88    classify_for_instance_type, classify_for_new_expression, resolve_abstract_constructor_anchor,
89};
90
91pub use data::*;
92pub use flow::*;
93pub use iterable::*;
94pub use traversal::*;
95
96pub fn get_allowed_keys(db: &dyn TypeDatabase, type_id: TypeId) -> rustc_hash::FxHashSet<String> {
97    let atoms = collect_property_name_atoms_for_diagnostics(db, type_id, 10);
98    atoms.into_iter().map(|a| db.resolve_atom(a)).collect()
99}
100
101// =============================================================================
102// Core Type Queries
103// =============================================================================
104
105/// Check if a type is a callable type (function or callable with signatures).
106///
107/// Returns true for `TypeData::Callable` and `TypeData::Function` types.
108pub fn is_callable_type(db: &dyn TypeDatabase, type_id: TypeId) -> bool {
109    matches!(
110        db.lookup(type_id),
111        Some(TypeData::Callable(_) | TypeData::Function(_))
112    )
113}
114
115/// Check if a type is invokable (has call signatures, not just construct signatures).
116///
117/// This is more specific than `is_callable_type` - it ensures the type can be called
118/// as a function (not just constructed with `new`).
119///
120/// # Arguments
121///
122/// * `db` - The type database/interner
123/// * `type_id` - The type to check
124///
125/// # Returns
126///
127/// * `true` - If the type has call signatures
128/// * `false` - Otherwise
129///
130/// # Examples
131///
132/// ```ignore
133/// // Functions are invokable
134/// assert!(is_invokable_type(&db, function_type));
135///
136/// // Callables with call signatures are invokable
137/// assert!(is_invokable_type(&db, callable_with_call_sigs));
138///
139/// // Callables with ONLY construct signatures are NOT invokable
140/// assert!(!is_invokable_type(&db, class_constructor_only));
141/// ```
142pub fn is_invokable_type(db: &dyn TypeDatabase, type_id: TypeId) -> bool {
143    match db.lookup(type_id) {
144        Some(TypeData::Function(_)) => true,
145        Some(TypeData::Callable(shape_id)) => {
146            let shape = db.callable_shape(shape_id);
147            // Must have at least one call signature (not just construct signatures)
148            !shape.call_signatures.is_empty()
149        }
150        // Intersections might contain a callable
151        Some(TypeData::Intersection(list_id)) => {
152            let members = db.type_list(list_id);
153            members.iter().any(|&m| is_invokable_type(db, m))
154        }
155        _ => false,
156    }
157}
158
159/// Check if a type is an object type (with or without index signatures).
160///
161/// Returns true for `TypeData::Object` and `TypeData::ObjectWithIndex`.
162pub fn is_object_type(db: &dyn TypeDatabase, type_id: TypeId) -> bool {
163    matches!(
164        db.lookup(type_id),
165        Some(TypeData::Object(_) | TypeData::ObjectWithIndex(_))
166    )
167}
168
169/// Check if a type is a generic type application (Base<Args>).
170///
171/// Returns true for `TypeData::Application`.
172pub fn is_generic_type(db: &dyn TypeDatabase, type_id: TypeId) -> bool {
173    matches!(db.lookup(type_id), Some(TypeData::Application(_)))
174}
175
176/// Check if a type is a named type reference.
177///
178/// Returns true for `TypeData::Lazy(DefId)` (interfaces, classes, type aliases).
179pub fn is_type_reference(db: &dyn TypeDatabase, type_id: TypeId) -> bool {
180    matches!(
181        db.lookup(type_id),
182        Some(TypeData::Lazy(_) | TypeData::Recursive(_) | TypeData::BoundParameter(_))
183    )
184}
185
186/// Check if a type is a type parameter, bound parameter, or infer type.
187///
188/// Check if a type is a type parameter, or a union/intersection containing one.
189/// This acts as a more specific check than `contains_type_parameters_db` and is useful
190/// for preventing indexed writes to pure type variables (TS2862) while allowing
191/// writes to instantiated generics like mapped types.
192pub fn is_uninstantiated_type_parameter(db: &dyn TypeDatabase, type_id: TypeId) -> bool {
193    match db.lookup(type_id) {
194        Some(
195            TypeData::TypeParameter(_)
196            | TypeData::BoundParameter(_)
197            | TypeData::Infer(_)
198            | TypeData::Lazy(_),
199        ) => true,
200        Some(TypeData::Intersection(list_id) | TypeData::Union(list_id)) => {
201            let elements = db.type_list(list_id);
202            elements
203                .iter()
204                .any(|&e| is_uninstantiated_type_parameter(db, e))
205        }
206        _ => false,
207    }
208}
209
210/// Returns true for `TypeData::TypeParameter`, `TypeData::BoundParameter`,
211/// and `TypeData::Infer`. `BoundParameter` is included because it represents
212/// a type parameter that has been bound to a specific index in a generic
213/// signature — it should still be treated as "unresolved" for purposes like
214/// excess property checking and constraint validation.
215///
216/// Use this instead of `visitor_predicates::is_type_parameter` when you need
217/// to treat bound (de Bruijn indexed) parameters as type-parameter-like.
218pub fn is_type_parameter_like(db: &dyn TypeDatabase, type_id: TypeId) -> bool {
219    matches!(
220        db.lookup(type_id),
221        Some(TypeData::TypeParameter(_) | TypeData::BoundParameter(_) | TypeData::Infer(_))
222    )
223}
224
225/// Check if a type is a keyof type.
226///
227/// Returns true for `TypeData::KeyOf`.
228pub fn is_keyof_type(db: &dyn TypeDatabase, type_id: TypeId) -> bool {
229    matches!(db.lookup(type_id), Some(TypeData::KeyOf(_)))
230}
231
232/// Check if a type is a type query (typeof expr).
233///
234/// Returns true for `TypeData::TypeQuery`.
235pub fn is_type_query(db: &dyn TypeDatabase, type_id: TypeId) -> bool {
236    matches!(db.lookup(type_id), Some(TypeData::TypeQuery(_)))
237}
238
239/// Check if a type is a readonly type modifier.
240///
241/// Returns true for `TypeData::ReadonlyType`.
242pub fn is_readonly_type(db: &dyn TypeDatabase, type_id: TypeId) -> bool {
243    matches!(db.lookup(type_id), Some(TypeData::ReadonlyType(_)))
244}
245
246/// Check if a type is a unique symbol type.
247///
248/// Returns true for `TypeData::UniqueSymbol`.
249pub fn is_unique_symbol_type(db: &dyn TypeDatabase, type_id: TypeId) -> bool {
250    matches!(db.lookup(type_id), Some(TypeData::UniqueSymbol(_)))
251}
252
253/// Check if a type is usable as a property name (TS1166/TS1165/TS1169).
254///
255/// Returns true for string literals, number literals, and unique symbol types.
256/// This corresponds to TypeScript's `isTypeUsableAsPropertyName` check.
257pub fn is_type_usable_as_property_name(db: &dyn TypeDatabase, type_id: TypeId) -> bool {
258    if type_id == TypeId::ANY {
259        return true;
260    }
261    matches!(
262        db.lookup(type_id),
263        Some(
264            TypeData::Literal(crate::LiteralValue::String(_))
265                | TypeData::Literal(crate::LiteralValue::Number(_))
266                | TypeData::UniqueSymbol(_)
267        )
268    )
269}
270
271/// Check if a type is the this type.
272///
273/// Returns true for `TypeData::ThisType`.
274pub fn is_this_type(db: &dyn TypeDatabase, type_id: TypeId) -> bool {
275    matches!(db.lookup(type_id), Some(TypeData::ThisType))
276}
277
278/// Check if a type is an error type.
279///
280/// Returns true for `TypeData::Error` or `TypeId::ERROR`.
281pub fn is_error_type(db: &dyn TypeDatabase, type_id: TypeId) -> bool {
282    type_id == TypeId::ERROR || matches!(db.lookup(type_id), Some(TypeData::Error))
283}
284
285/// Check if a type needs evaluation before interface merging.
286///
287/// Returns true for Application and Lazy types, which are meta-types that
288/// may resolve to Object/Callable types when evaluated. Used before
289/// `classify_for_interface_merge` to ensure that type-alias-based heritage
290/// (e.g., `interface X extends TypeAlias<T>`) is properly resolved.
291pub fn needs_evaluation_for_merge(db: &dyn TypeDatabase, type_id: TypeId) -> bool {
292    matches!(
293        db.lookup(type_id),
294        Some(TypeData::Application(_) | TypeData::Lazy(_))
295    )
296}
297
298/// Get the return type of a function type.
299///
300/// Returns `TypeId::ERROR` if the type is not a Function.
301pub fn get_function_return_type(db: &dyn TypeDatabase, type_id: TypeId) -> TypeId {
302    match db.lookup(type_id) {
303        Some(TypeData::Function(shape_id)) => db.function_shape(shape_id).return_type,
304        _ => TypeId::ERROR,
305    }
306}
307
308/// Get the parameter types of a function type.
309///
310/// Returns an empty vector if the type is not a Function.
311pub fn get_function_parameter_types(db: &dyn TypeDatabase, type_id: TypeId) -> Vec<TypeId> {
312    match db.lookup(type_id) {
313        Some(TypeData::Function(shape_id)) => db
314            .function_shape(shape_id)
315            .params
316            .iter()
317            .map(|p| p.type_id)
318            .collect(),
319        _ => Vec::new(),
320    }
321}
322
323/// Check if a type is an intrinsic type (any, unknown, never, void, etc.).
324///
325/// Returns true for `TypeData::Intrinsic`.
326pub fn is_intrinsic_type(db: &dyn TypeDatabase, type_id: TypeId) -> bool {
327    matches!(db.lookup(type_id), Some(TypeData::Intrinsic(_)))
328}
329
330// =============================================================================
331// Intrinsic Type Queries
332// =============================================================================
333//
334// These functions provide TypeData-free checking for intrinsic types.
335// Checker code should use these instead of matching on TypeData::Intrinsic.
336//
337// ## Important Usage Notes
338//
339// These are TYPE IDENTITY checks, NOT compatibility checks:
340//
341// - Identity: `is_string_type(TypeId::STRING)` -> TRUE
342// - Identity: `is_string_type(literal "hello")` -> FALSE (literal, not intrinsic)
343// - Identity: `is_string_type(string & {tag: 1})` -> FALSE (intersection, not intrinsic)
344//
345// For assignability/compatibility checks, use Solver subtyping:
346// - `solver.is_subtype_of(literal, TypeId::STRING)` -> TRUE
347// - `solver.is_subtype_of(branded, TypeId::STRING)` -> TRUE (if assignable)
348//
349// ### When to use these helpers
350// - Checking if a type annotation is explicitly the intrinsic keyword
351// - Validating type constructor arguments
352// - Distinguishing `void` from `undefined` in return types
353//
354// ### When NOT to use these helpers
355// - Assignment/compatibility checks -> Use `is_subtype_of` instead
356// - Type narrowing -> Use Solver's narrowing analysis
357// - Checking if a value IS a string (not literal) -> Use `is_subtype_of`
358//
359// ## Implementation Notes
360// - Shallow queries: do NOT resolve Lazy/Ref (caller's responsibility)
361// - Defensive pattern: check both TypeId constants AND TypeData::Intrinsic
362// - Fast-path O(1) using TypeId integer comparison
363
364use crate::types::IntrinsicKind;
365
366/// Check if a type is the `any` type.
367///
368/// Generate an intrinsic type checker function that checks both the well-known
369/// `TypeId` constant and the `TypeData::Intrinsic` variant.
370macro_rules! define_intrinsic_check {
371    ($fn_name:ident, $type_id:ident, $kind:ident) => {
372        pub fn $fn_name(db: &dyn TypeDatabase, type_id: TypeId) -> bool {
373            type_id == TypeId::$type_id
374                || matches!(
375                    db.lookup(type_id),
376                    Some(TypeData::Intrinsic(IntrinsicKind::$kind))
377                )
378        }
379    };
380}
381
382define_intrinsic_check!(is_any_type, ANY, Any);
383define_intrinsic_check!(is_unknown_type, UNKNOWN, Unknown);
384define_intrinsic_check!(is_never_type, NEVER, Never);
385define_intrinsic_check!(is_void_type, VOID, Void);
386define_intrinsic_check!(is_undefined_type, UNDEFINED, Undefined);
387define_intrinsic_check!(is_null_type, NULL, Null);
388define_intrinsic_check!(is_string_type, STRING, String);
389define_intrinsic_check!(is_number_type, NUMBER, Number);
390define_intrinsic_check!(is_bigint_type, BIGINT, Bigint);
391define_intrinsic_check!(is_boolean_type, BOOLEAN, Boolean);
392define_intrinsic_check!(is_symbol_type, SYMBOL, Symbol);
393
394// =============================================================================
395// Composite Type Queries
396// =============================================================================
397
398/// Check if a type is valid for object spreading (`{...x}`).
399///
400/// Returns `true` for types that can be spread into an object literal:
401/// - `any`, `never`, `error` (always spreadable)
402/// - Object types, arrays, tuples, functions, callables, mapped types
403/// - `object` intrinsic (non-primitive)
404/// - Type parameters (spreadable by default; constraint checked separately)
405/// - Unions: all members must be spreadable
406/// - Intersections: all members must be spreadable
407///
408/// Returns `false` for primitive types (`number`, `string`, `boolean`, etc.),
409/// literals, `null`, `undefined`, `void`, and `unknown`.
410pub fn is_valid_spread_type(db: &dyn TypeDatabase, type_id: TypeId) -> bool {
411    is_valid_spread_type_impl(db, type_id, 0)
412}
413
414fn is_valid_spread_type_impl(db: &dyn TypeDatabase, type_id: TypeId, depth: u32) -> bool {
415    if depth > 20 {
416        return true;
417    }
418    match type_id {
419        TypeId::ANY | TypeId::NEVER | TypeId::ERROR => return true,
420        _ => {}
421    }
422    match db.lookup(type_id) {
423        // Primitives and literals are not spreadable
424        Some(
425            TypeData::Intrinsic(
426                IntrinsicKind::String
427                | IntrinsicKind::Number
428                | IntrinsicKind::Boolean
429                | IntrinsicKind::Bigint
430                | IntrinsicKind::Symbol
431                | IntrinsicKind::Void
432                | IntrinsicKind::Null
433                | IntrinsicKind::Undefined
434                | IntrinsicKind::Unknown,
435            )
436            | TypeData::Literal(_),
437        ) => false,
438        // Union: all members must be spreadable
439        Some(TypeData::Union(members)) => {
440            let members = db.type_list(members);
441            members
442                .iter()
443                .all(|&m| is_valid_spread_type_impl(db, m, depth + 1))
444        }
445        // Intersection: all members must be spreadable
446        Some(TypeData::Intersection(members)) => {
447            let members = db.type_list(members);
448            members
449                .iter()
450                .all(|&m| is_valid_spread_type_impl(db, m, depth + 1))
451        }
452        Some(TypeData::ReadonlyType(inner)) => is_valid_spread_type_impl(db, inner, depth + 1),
453        // Everything else is spreadable: object types, arrays, tuples, functions,
454        // callables, mapped types, type parameters, lazy refs, applications, etc.
455        _ => true,
456    }
457}
458
459// =============================================================================
460// Constructor Type Collection Helpers
461// =============================================================================
462
463/// Result of classifying a type for constructor collection.
464///
465/// This enum tells the caller what kind of type this is and how to proceed
466/// when collecting constructor types from a composite type structure.
467#[derive(Debug, Clone, PartialEq, Eq)]
468pub enum ConstructorTypeKind {
469    /// This is a Callable type - always a constructor type
470    Callable,
471    /// This is a Function type - check `is_constructor` flag on the shape
472    Function(crate::types::FunctionShapeId),
473    /// Recurse into these member types (Union, Intersection)
474    Members(Vec<TypeId>),
475    /// Recurse into the inner type (`ReadonlyType`)
476    Inner(TypeId),
477    /// Recurse into the constraint (`TypeParameter`, Infer)
478    Constraint(Option<TypeId>),
479    /// This type needs full type evaluation (Conditional, Mapped, `IndexAccess`, `KeyOf`)
480    NeedsTypeEvaluation,
481    /// This is a generic application that needs instantiation
482    NeedsApplicationEvaluation,
483    /// This is a `TypeQuery` - resolve the symbol reference to get its type
484    TypeQuery(crate::types::SymbolRef),
485    /// This type cannot be a constructor (primitives, literals, etc.)
486    NotConstructor,
487}
488
489/// Classify a type for constructor type collection.
490///
491/// This function examines a `TypeData` and returns information about how to handle it
492/// when collecting constructor types. The caller is responsible for:
493/// - Checking the `is_constructor` flag for Function types
494/// - Evaluating types when `NeedsTypeEvaluation` or `NeedsApplicationEvaluation` is returned
495/// - Resolving symbol references for `TypeQuery`
496/// - Recursing into members/inner types
497///
498/// # Example
499///
500/// ```rust,ignore
501/// use crate::type_queries::{classify_constructor_type, ConstructorTypeKind};
502///
503/// match classify_constructor_type(db, type_id) {
504///     ConstructorTypeKind::Callable => {
505///         // This is a constructor type
506///         ctor_types.push(type_id);
507///     }
508///     ConstructorTypeKind::Function(shape_id) => {
509///         let shape = db.function_shape(shape_id);
510///         if shape.is_constructor {
511///             ctor_types.push(type_id);
512///         }
513///     }
514///     ConstructorTypeKind::Members(members) => {
515///         for member in members {
516///             // Recurse
517///         }
518///     }
519///     ConstructorTypeKind::NeedsTypeEvaluation => {
520///         // Use evaluate_type_with_env
521///     }
522///     ConstructorTypeKind::NeedsApplicationEvaluation => {
523///         // Use evaluate_application_type
524///     }
525///     // ... handle other cases
526/// }
527/// ```
528pub fn classify_constructor_type(db: &dyn TypeDatabase, type_id: TypeId) -> ConstructorTypeKind {
529    let Some(key) = db.lookup(type_id) else {
530        return ConstructorTypeKind::NotConstructor;
531    };
532
533    match key {
534        TypeData::Callable(_) => ConstructorTypeKind::Callable,
535        TypeData::Function(shape_id) => ConstructorTypeKind::Function(shape_id),
536        TypeData::Intersection(members_id) | TypeData::Union(members_id) => {
537            let members = db.type_list(members_id);
538            ConstructorTypeKind::Members(members.to_vec())
539        }
540        TypeData::ReadonlyType(inner) | TypeData::NoInfer(inner) => {
541            ConstructorTypeKind::Inner(inner)
542        }
543        TypeData::TypeParameter(info) | TypeData::Infer(info) => {
544            ConstructorTypeKind::Constraint(info.constraint)
545        }
546        TypeData::Conditional(_)
547        | TypeData::Mapped(_)
548        | TypeData::IndexAccess(_, _)
549        | TypeData::KeyOf(_) => ConstructorTypeKind::NeedsTypeEvaluation,
550        TypeData::Application(_) => ConstructorTypeKind::NeedsApplicationEvaluation,
551        TypeData::TypeQuery(sym_ref) => ConstructorTypeKind::TypeQuery(sym_ref),
552        // All other types cannot be constructors
553        TypeData::Enum(_, _)
554        | TypeData::BoundParameter(_)
555        | TypeData::Intrinsic(_)
556        | TypeData::Literal(_)
557        | TypeData::Object(_)
558        | TypeData::ObjectWithIndex(_)
559        | TypeData::Array(_)
560        | TypeData::Tuple(_)
561        | TypeData::Lazy(_)
562        | TypeData::Recursive(_)
563        | TypeData::TemplateLiteral(_)
564        | TypeData::UniqueSymbol(_)
565        | TypeData::ThisType
566        | TypeData::StringIntrinsic { .. }
567        | TypeData::ModuleNamespace(_)
568        | TypeData::Error => ConstructorTypeKind::NotConstructor,
569    }
570}
571
572// =============================================================================
573// Static Property Collection Helpers
574// =============================================================================
575
576/// Result of extracting static properties from a type.
577///
578/// This enum allows the caller to handle recursion and type evaluation
579/// while keeping the `TypeData` matching logic in the solver layer.
580#[derive(Debug, Clone)]
581pub enum StaticPropertySource {
582    /// Direct properties from Callable, Object, or `ObjectWithIndex` types.
583    Properties(Vec<crate::PropertyInfo>),
584    /// Member types that should be recursively processed (Union/Intersection).
585    RecurseMembers(Vec<TypeId>),
586    /// Single type to recurse into (`TypeParameter` constraint, `ReadonlyType` inner).
587    RecurseSingle(TypeId),
588    /// Type that needs evaluation before property extraction (Conditional, Mapped, etc.).
589    NeedsEvaluation,
590    /// Type that needs application evaluation (Application type).
591    NeedsApplicationEvaluation,
592    /// No properties available (primitives, error types, etc.).
593    None,
594}
595
596/// Extract static property information from a type.
597///
598/// This function handles the `TypeData` matching for property collection,
599/// returning a `StaticPropertySource` that tells the caller how to proceed.
600/// The caller is responsible for:
601/// - Handling recursion for `RecurseMembers` and `RecurseSingle` cases
602/// - Evaluating types for `NeedsEvaluation` and `NeedsApplicationEvaluation` cases
603/// - Tracking visited types to prevent infinite loops
604///
605/// # Example
606///
607/// ```ignore
608/// match get_static_property_source(&db, type_id) {
609///     StaticPropertySource::Properties(props) => {
610///         for prop in props {
611///             properties.entry(prop.name).or_insert(prop);
612///         }
613///     }
614///     StaticPropertySource::RecurseMembers(members) => {
615///         for member in members {
616///             // Recursively collect from member
617///         }
618///     }
619///     // ... handle other cases
620/// }
621/// ```
622pub fn get_static_property_source(db: &dyn TypeDatabase, type_id: TypeId) -> StaticPropertySource {
623    let Some(key) = db.lookup(type_id) else {
624        return StaticPropertySource::None;
625    };
626
627    match key {
628        TypeData::Callable(shape_id) => {
629            let shape = db.callable_shape(shape_id);
630            StaticPropertySource::Properties(shape.properties.to_vec())
631        }
632        TypeData::Object(shape_id) | TypeData::ObjectWithIndex(shape_id) => {
633            let shape = db.object_shape(shape_id);
634            StaticPropertySource::Properties(shape.properties.to_vec())
635        }
636        TypeData::Intersection(members_id) | TypeData::Union(members_id) => {
637            let members = db.type_list(members_id);
638            StaticPropertySource::RecurseMembers(members.to_vec())
639        }
640        TypeData::TypeParameter(info) | TypeData::Infer(info) => {
641            if let Some(constraint) = info.constraint {
642                StaticPropertySource::RecurseSingle(constraint)
643            } else {
644                StaticPropertySource::None
645            }
646        }
647        TypeData::ReadonlyType(inner) => StaticPropertySource::RecurseSingle(inner),
648        TypeData::Conditional(_)
649        | TypeData::Mapped(_)
650        | TypeData::IndexAccess(_, _)
651        | TypeData::KeyOf(_) => StaticPropertySource::NeedsEvaluation,
652        TypeData::Application(_) => StaticPropertySource::NeedsApplicationEvaluation,
653        _ => StaticPropertySource::None,
654    }
655}
656
657// =============================================================================
658// Construct Signature Queries
659// =============================================================================
660
661/// Check if a Callable type has construct signatures.
662///
663/// Returns true only for Callable types that have non-empty `construct_signatures`.
664/// This is a direct check and does not resolve through Ref or `TypeQuery` types.
665pub fn has_construct_signatures(db: &dyn TypeDatabase, type_id: TypeId) -> bool {
666    match db.lookup(type_id) {
667        Some(TypeData::Callable(shape_id)) => {
668            let shape = db.callable_shape(shape_id);
669            !shape.construct_signatures.is_empty()
670        }
671        _ => false,
672    }
673}
674
675/// Get the symbol reference from a `TypeQuery` type.
676///
677/// Returns None if the type is not a `TypeQuery`.
678pub fn get_symbol_ref_from_type(
679    db: &dyn TypeDatabase,
680    type_id: TypeId,
681) -> Option<crate::types::SymbolRef> {
682    match db.lookup(type_id) {
683        Some(TypeData::TypeQuery(sym_ref)) => Some(sym_ref),
684        _ => None,
685    }
686}
687
688/// Kind of constructable type for `get_construct_type_from_type`.
689///
690/// This enum represents the different ways a type can be constructable,
691/// allowing the caller to handle each case appropriately without matching
692/// directly on `TypeData`.
693#[derive(Debug, Clone)]
694pub enum ConstructableTypeKind {
695    /// Callable type with construct signatures - return transformed callable
696    CallableWithConstruct,
697    /// Callable type without construct signatures - check for prototype property
698    CallableMaybePrototype,
699    /// Function type - always constructable
700    Function,
701    /// Reference to a symbol - need to check symbol flags
702    SymbolRef(crate::types::SymbolRef),
703    /// `TypeQuery` (typeof expr) - need to check symbol flags
704    TypeQueryRef(crate::types::SymbolRef),
705    /// Type parameter with a constraint to check recursively
706    TypeParameterWithConstraint(TypeId),
707    /// Type parameter without constraint - not constructable
708    TypeParameterNoConstraint,
709    /// Intersection type - all members must be constructable
710    Intersection(Vec<TypeId>),
711    /// Application (generic instantiation) - return as-is
712    Application,
713    /// Object type - return as-is (may have construct signatures)
714    Object,
715    /// Not constructable
716    NotConstructable,
717}
718
719/// Classify a type for constructability checking.
720///
721/// This function examines a type and returns information about how to handle it
722/// when determining if it can be used with `new`. This is specifically for
723/// the `get_construct_type_from_type` use case.
724///
725/// The caller is responsible for:
726/// - Checking symbol flags for SymbolRef/TypeQueryRef cases
727/// - Checking prototype property for `CallableMaybePrototype`
728/// - Recursing into constraint for `TypeParameterWithConstraint`
729/// - Checking all members for Intersection
730pub fn classify_for_constructability(
731    db: &dyn TypeDatabase,
732    type_id: TypeId,
733) -> ConstructableTypeKind {
734    let Some(key) = db.lookup(type_id) else {
735        return ConstructableTypeKind::NotConstructable;
736    };
737
738    match key {
739        TypeData::Callable(shape_id) => {
740            let shape = db.callable_shape(shape_id);
741            if shape.construct_signatures.is_empty() {
742                ConstructableTypeKind::CallableMaybePrototype
743            } else {
744                ConstructableTypeKind::CallableWithConstruct
745            }
746        }
747        TypeData::Function(_) => ConstructableTypeKind::Function,
748        TypeData::TypeQuery(sym_ref) => ConstructableTypeKind::TypeQueryRef(sym_ref),
749        TypeData::TypeParameter(info) | TypeData::Infer(info) => {
750            if let Some(constraint) = info.constraint {
751                ConstructableTypeKind::TypeParameterWithConstraint(constraint)
752            } else {
753                ConstructableTypeKind::TypeParameterNoConstraint
754            }
755        }
756        TypeData::Intersection(members_id) => {
757            let members = db.type_list(members_id);
758            ConstructableTypeKind::Intersection(members.to_vec())
759        }
760        TypeData::Application(_) => ConstructableTypeKind::Application,
761        TypeData::Object(_) | TypeData::ObjectWithIndex(_) => ConstructableTypeKind::Object,
762        _ => ConstructableTypeKind::NotConstructable,
763    }
764}
765
766/// Create a callable type with construct signatures converted to call signatures.
767///
768/// This is used when resolving `new` expressions where we need to treat
769/// construct signatures as call signatures for type checking purposes.
770/// Returns None if the type doesn't have construct signatures.
771pub fn construct_to_call_callable(db: &dyn TypeDatabase, type_id: TypeId) -> Option<TypeId> {
772    match db.lookup(type_id) {
773        Some(TypeData::Callable(shape_id)) => {
774            let shape = db.callable_shape(shape_id);
775            if shape.construct_signatures.is_empty() {
776                None
777            } else {
778                Some(db.callable(crate::types::CallableShape {
779                    call_signatures: shape.construct_signatures.clone(),
780                    construct_signatures: Vec::new(),
781                    properties: Vec::new(),
782                    string_index: None,
783                    number_index: None,
784                    symbol: None,
785                }))
786            }
787        }
788        _ => None,
789    }
790}
791
792// =============================================================================
793// Constraint Type Classification Helpers
794// =============================================================================
795
796/// Classification for constraint types.
797#[derive(Debug, Clone)]
798pub enum ConstraintTypeKind {
799    /// Type parameter or infer with constraint
800    TypeParameter {
801        constraint: Option<TypeId>,
802        default: Option<TypeId>,
803    },
804    /// Union - get constraint from each member
805    Union(Vec<TypeId>),
806    /// Intersection - get constraint from each member
807    Intersection(Vec<TypeId>),
808    /// Symbol reference - resolve first
809    SymbolRef(crate::types::SymbolRef),
810    /// Application - evaluate first
811    Application { app_id: u32 },
812    /// Mapped type - evaluate constraint
813    Mapped { mapped_id: u32 },
814    /// `KeyOf` - special handling
815    KeyOf(TypeId),
816    /// Literal or resolved constraint
817    Resolved(TypeId),
818    /// No constraint
819    NoConstraint,
820}
821
822/// Classify a type for constraint extraction.
823pub fn classify_for_constraint(db: &dyn TypeDatabase, type_id: TypeId) -> ConstraintTypeKind {
824    let Some(key) = db.lookup(type_id) else {
825        return ConstraintTypeKind::NoConstraint;
826    };
827    match key {
828        TypeData::TypeParameter(info) | TypeData::Infer(info) => {
829            ConstraintTypeKind::TypeParameter {
830                constraint: info.constraint,
831                default: info.default,
832            }
833        }
834        TypeData::Union(list_id) => {
835            let members = db.type_list(list_id);
836            ConstraintTypeKind::Union(members.to_vec())
837        }
838        TypeData::Intersection(list_id) => {
839            let members = db.type_list(list_id);
840            ConstraintTypeKind::Intersection(members.to_vec())
841        }
842        TypeData::Application(app_id) => ConstraintTypeKind::Application { app_id: app_id.0 },
843        TypeData::Mapped(mapped_id) => ConstraintTypeKind::Mapped {
844            mapped_id: mapped_id.0,
845        },
846        TypeData::KeyOf(operand) => ConstraintTypeKind::KeyOf(operand),
847        TypeData::Literal(_) => ConstraintTypeKind::Resolved(type_id),
848        TypeData::BoundParameter(_)
849        | TypeData::Intrinsic(_)
850        | TypeData::Object(_)
851        | TypeData::ObjectWithIndex(_)
852        | TypeData::Array(_)
853        | TypeData::Tuple(_)
854        | TypeData::Function(_)
855        | TypeData::Callable(_)
856        | TypeData::Conditional(_)
857        | TypeData::IndexAccess(_, _)
858        | TypeData::TemplateLiteral(_)
859        | TypeData::UniqueSymbol(_)
860        | TypeData::ThisType
861        | TypeData::ReadonlyType(_)
862        | TypeData::NoInfer(_)
863        | TypeData::TypeQuery(_)
864        | TypeData::StringIntrinsic { .. }
865        | TypeData::ModuleNamespace(_)
866        | TypeData::Enum(_, _)
867        | TypeData::Lazy(_)
868        | TypeData::Recursive(_)
869        | TypeData::Error => ConstraintTypeKind::NoConstraint,
870    }
871}
872
873// =============================================================================
874// Signature Classification
875// =============================================================================
876
877/// Classification for types when extracting call/construct signatures.
878#[derive(Debug, Clone)]
879pub enum SignatureTypeKind {
880    /// Callable type with `shape_id` - has `call_signatures` and `construct_signatures`
881    Callable(crate::types::CallableShapeId),
882    /// Function type with `shape_id` - has single signature
883    Function(crate::types::FunctionShapeId),
884    /// Union type - get signatures from each member
885    Union(Vec<TypeId>),
886    /// Intersection type - get signatures from each member
887    Intersection(Vec<TypeId>),
888    /// Readonly wrapper - unwrap and get signatures from inner type
889    ReadonlyType(TypeId),
890    /// Type parameter with optional constraint - may need to check constraint
891    TypeParameter { constraint: Option<TypeId> },
892    /// Types that need evaluation before signature extraction (Conditional, Mapped, `IndexAccess`, `KeyOf`)
893    NeedsEvaluation(TypeId),
894    /// Types without signatures (Intrinsic, Literal, Object without callable, etc.)
895    NoSignatures,
896}
897
898/// Classify a type for signature extraction.
899pub fn classify_for_signatures(db: &dyn TypeDatabase, type_id: TypeId) -> SignatureTypeKind {
900    // Handle special TypeIds first
901    if type_id == TypeId::ERROR || type_id == TypeId::NEVER {
902        return SignatureTypeKind::NoSignatures;
903    }
904    if type_id == TypeId::ANY {
905        // any is callable but has no concrete signatures
906        return SignatureTypeKind::NoSignatures;
907    }
908
909    let Some(key) = db.lookup(type_id) else {
910        return SignatureTypeKind::NoSignatures;
911    };
912
913    match key {
914        // Callable types - have call_signatures and construct_signatures
915        TypeData::Callable(shape_id) => SignatureTypeKind::Callable(shape_id),
916
917        // Function types - have a single signature
918        TypeData::Function(shape_id) => SignatureTypeKind::Function(shape_id),
919
920        // Union type - get signatures from each member
921        TypeData::Union(members_id) => {
922            let members = db.type_list(members_id);
923            SignatureTypeKind::Union(members.to_vec())
924        }
925
926        // Intersection type - get signatures from each member
927        TypeData::Intersection(members_id) => {
928            let members = db.type_list(members_id);
929            SignatureTypeKind::Intersection(members.to_vec())
930        }
931
932        // Readonly wrapper - unwrap and recurse
933        TypeData::ReadonlyType(inner) | TypeData::NoInfer(inner) => {
934            SignatureTypeKind::ReadonlyType(inner)
935        }
936
937        // Type parameter - may have constraint with signatures
938        TypeData::TypeParameter(info) | TypeData::Infer(info) => SignatureTypeKind::TypeParameter {
939            constraint: info.constraint,
940        },
941
942        // Complex types that need evaluation before signature extraction
943        TypeData::Conditional(_)
944        | TypeData::Mapped(_)
945        | TypeData::IndexAccess(_, _)
946        | TypeData::KeyOf(_) => SignatureTypeKind::NeedsEvaluation(type_id),
947
948        // All other types don't have callable signatures
949        TypeData::BoundParameter(_)
950        | TypeData::Intrinsic(_)
951        | TypeData::Literal(_)
952        | TypeData::Object(_)
953        | TypeData::ObjectWithIndex(_)
954        | TypeData::Array(_)
955        | TypeData::Tuple(_)
956        | TypeData::Lazy(_)
957        | TypeData::Recursive(_)
958        | TypeData::Application(_)
959        | TypeData::TemplateLiteral(_)
960        | TypeData::UniqueSymbol(_)
961        | TypeData::ThisType
962        | TypeData::TypeQuery(_)
963        | TypeData::StringIntrinsic { .. }
964        | TypeData::ModuleNamespace(_)
965        | TypeData::Enum(_, _)
966        | TypeData::Error => SignatureTypeKind::NoSignatures,
967    }
968}
969
970// =============================================================================
971// Property Lookup Type Classification
972// =============================================================================
973
974/// Classification for types when looking up properties.
975///
976/// This enum provides a structured way to handle property lookups on different
977/// type kinds, abstracting away the internal `TypeData` representation.
978///
979/// # Design Principles
980///
981/// - **No Symbol Resolution**: Keeps solver layer pure
982/// - **No Type Evaluation**: Returns classification for caller to handle
983/// - **Complete Coverage**: Handles all common property access patterns
984#[derive(Debug, Clone)]
985pub enum PropertyLookupKind {
986    /// Object type with `shape_id` - has properties
987    Object(crate::types::ObjectShapeId),
988    /// Object with index signature - has properties and index signatures
989    ObjectWithIndex(crate::types::ObjectShapeId),
990    /// Union type - lookup on each member
991    Union(Vec<TypeId>),
992    /// Intersection type - lookup on each member
993    Intersection(Vec<TypeId>),
994    /// Array type - element type for numeric access
995    Array(TypeId),
996    /// Tuple type - element types
997    Tuple(Vec<crate::types::TupleElement>),
998    /// Type that doesn't have direct properties (Intrinsic, Literal, etc.)
999    NoProperties,
1000}
1001
1002/// Classify a type for property lookup operations.
1003///
1004/// This function examines a type and returns information about how to handle it
1005/// when looking up properties. This is used for:
1006/// - Merging base type properties
1007/// - Checking excess properties in object literals
1008/// - Getting binding element types from destructuring patterns
1009///
1010/// The caller is responsible for:
1011/// - Recursing into Union/Intersection members
1012/// - Handling Array/Tuple element access appropriately
1013/// - Accessing the object shape using the returned `shape_id`
1014///
1015/// # Example
1016///
1017/// ```ignore
1018/// use crate::type_queries::{classify_for_property_lookup, PropertyLookupKind};
1019///
1020/// match classify_for_property_lookup(&db, type_id) {
1021///     PropertyLookupKind::Object(shape_id) | PropertyLookupKind::ObjectWithIndex(shape_id) => {
1022///         let shape = db.object_shape(shape_id);
1023///         for prop in shape.properties.iter() {
1024///             // Process property
1025///         }
1026///     }
1027///     PropertyLookupKind::Union(members) | PropertyLookupKind::Intersection(members) => {
1028///         for member in members {
1029///             // Recurse
1030///         }
1031///     }
1032///     PropertyLookupKind::Array(elem_type) => {
1033///         // Use element type for numeric index access
1034///     }
1035///     PropertyLookupKind::Tuple(elements) => {
1036///         // Use specific element type by index
1037///     }
1038///     PropertyLookupKind::NoProperties => {
1039///         // Handle types without properties
1040///     }
1041/// }
1042/// ```
1043pub fn classify_for_property_lookup(db: &dyn TypeDatabase, type_id: TypeId) -> PropertyLookupKind {
1044    let Some(key) = db.lookup(type_id) else {
1045        return PropertyLookupKind::NoProperties;
1046    };
1047
1048    match key {
1049        TypeData::Object(shape_id) => PropertyLookupKind::Object(shape_id),
1050        TypeData::ObjectWithIndex(shape_id) => PropertyLookupKind::ObjectWithIndex(shape_id),
1051        TypeData::Union(list_id) => {
1052            let members = db.type_list(list_id);
1053            PropertyLookupKind::Union(members.to_vec())
1054        }
1055        TypeData::Intersection(list_id) => {
1056            let members = db.type_list(list_id);
1057            PropertyLookupKind::Intersection(members.to_vec())
1058        }
1059        TypeData::Array(elem_type) => PropertyLookupKind::Array(elem_type),
1060        TypeData::Tuple(tuple_id) => {
1061            let elements = db.tuple_list(tuple_id);
1062            PropertyLookupKind::Tuple(elements.to_vec())
1063        }
1064        // All other types don't have direct properties for this use case
1065        TypeData::BoundParameter(_)
1066        | TypeData::Intrinsic(_)
1067        | TypeData::Literal(_)
1068        | TypeData::Function(_)
1069        | TypeData::Callable(_)
1070        | TypeData::TypeParameter(_)
1071        | TypeData::Infer(_)
1072        | TypeData::Lazy(_)
1073        | TypeData::Recursive(_)
1074        | TypeData::Application(_)
1075        | TypeData::Conditional(_)
1076        | TypeData::Mapped(_)
1077        | TypeData::IndexAccess(_, _)
1078        | TypeData::KeyOf(_)
1079        | TypeData::TemplateLiteral(_)
1080        | TypeData::UniqueSymbol(_)
1081        | TypeData::ThisType
1082        | TypeData::TypeQuery(_)
1083        | TypeData::ReadonlyType(_)
1084        | TypeData::NoInfer(_)
1085        | TypeData::StringIntrinsic { .. }
1086        | TypeData::ModuleNamespace(_)
1087        | TypeData::Enum(_, _)
1088        | TypeData::Error => PropertyLookupKind::NoProperties,
1089    }
1090}
1091
1092// =============================================================================
1093// EvaluationNeeded - Classification for types that need evaluation
1094// =============================================================================
1095
1096/// Classification for types that need evaluation before use.
1097#[derive(Debug, Clone)]
1098pub enum EvaluationNeeded {
1099    /// Already resolved, no evaluation needed
1100    Resolved(TypeId),
1101    /// Symbol reference - resolve symbol first
1102    SymbolRef(crate::types::SymbolRef),
1103    /// Type query (typeof) - evaluate first
1104    TypeQuery(crate::types::SymbolRef),
1105    /// Generic application - instantiate first
1106    Application {
1107        app_id: crate::types::TypeApplicationId,
1108    },
1109    /// Index access T[K] - evaluate with environment
1110    IndexAccess { object: TypeId, index: TypeId },
1111    /// `KeyOf` type - evaluate
1112    KeyOf(TypeId),
1113    /// Mapped type - evaluate
1114    Mapped {
1115        mapped_id: crate::types::MappedTypeId,
1116    },
1117    /// Conditional type - evaluate
1118    Conditional {
1119        cond_id: crate::types::ConditionalTypeId,
1120    },
1121    /// Callable type (for contextual typing checks)
1122    Callable(crate::types::CallableShapeId),
1123    /// Function type
1124    Function(crate::types::FunctionShapeId),
1125    /// Union - may need per-member evaluation
1126    Union(Vec<TypeId>),
1127    /// Intersection - may need per-member evaluation
1128    Intersection(Vec<TypeId>),
1129    /// Type parameter with constraint
1130    TypeParameter { constraint: Option<TypeId> },
1131    /// Readonly wrapper - unwrap
1132    Readonly(TypeId),
1133}
1134
1135/// Classify a type for what kind of evaluation it needs.
1136pub fn classify_for_evaluation(db: &dyn TypeDatabase, type_id: TypeId) -> EvaluationNeeded {
1137    let Some(key) = db.lookup(type_id) else {
1138        return EvaluationNeeded::Resolved(type_id);
1139    };
1140
1141    match key {
1142        TypeData::TypeQuery(sym_ref) => EvaluationNeeded::TypeQuery(sym_ref),
1143        TypeData::Application(app_id) => EvaluationNeeded::Application { app_id },
1144        TypeData::IndexAccess(object, index) => EvaluationNeeded::IndexAccess { object, index },
1145        TypeData::KeyOf(inner) => EvaluationNeeded::KeyOf(inner),
1146        TypeData::Mapped(mapped_id) => EvaluationNeeded::Mapped { mapped_id },
1147        TypeData::Conditional(cond_id) => EvaluationNeeded::Conditional { cond_id },
1148        TypeData::Callable(shape_id) => EvaluationNeeded::Callable(shape_id),
1149        TypeData::Function(shape_id) => EvaluationNeeded::Function(shape_id),
1150        TypeData::Union(list_id) => {
1151            let members = db.type_list(list_id);
1152            EvaluationNeeded::Union(members.to_vec())
1153        }
1154        TypeData::Intersection(list_id) => {
1155            let members = db.type_list(list_id);
1156            EvaluationNeeded::Intersection(members.to_vec())
1157        }
1158        TypeData::TypeParameter(info) | TypeData::Infer(info) => EvaluationNeeded::TypeParameter {
1159            constraint: info.constraint,
1160        },
1161        TypeData::ReadonlyType(inner) | TypeData::NoInfer(inner) => {
1162            EvaluationNeeded::Readonly(inner)
1163        }
1164        // Already resolved types (Lazy needs special handling when DefId lookup is implemented)
1165        TypeData::BoundParameter(_)
1166        | TypeData::Intrinsic(_)
1167        | TypeData::Literal(_)
1168        | TypeData::Object(_)
1169        | TypeData::ObjectWithIndex(_)
1170        | TypeData::Array(_)
1171        | TypeData::Tuple(_)
1172        | TypeData::Lazy(_)
1173        | TypeData::Recursive(_)
1174        | TypeData::TemplateLiteral(_)
1175        | TypeData::UniqueSymbol(_)
1176        | TypeData::ThisType
1177        | TypeData::StringIntrinsic { .. }
1178        | TypeData::ModuleNamespace(_)
1179        | TypeData::Enum(_, _)
1180        | TypeData::Error => EvaluationNeeded::Resolved(type_id),
1181    }
1182}
1183
1184/// Evaluate contextual wrapper structure while delegating leaf evaluation.
1185///
1186/// Solver owns traversal over semantic type shape; caller provides the concrete
1187/// leaf evaluator (for example checker's judge-based environment evaluation).
1188pub fn evaluate_contextual_structure_with(
1189    db: &dyn QueryDatabase,
1190    type_id: TypeId,
1191    evaluate_leaf: &mut dyn FnMut(TypeId) -> TypeId,
1192) -> TypeId {
1193    fn visit(
1194        db: &dyn QueryDatabase,
1195        type_id: TypeId,
1196        evaluate_leaf: &mut dyn FnMut(TypeId) -> TypeId,
1197    ) -> TypeId {
1198        match classify_for_evaluation(db, type_id) {
1199            EvaluationNeeded::Union(members) => {
1200                let mut changed = false;
1201                let evaluated: Vec<TypeId> = members
1202                    .iter()
1203                    .map(|&member| {
1204                        let ev = visit(db, member, evaluate_leaf);
1205                        if ev != member {
1206                            changed = true;
1207                        }
1208                        ev
1209                    })
1210                    .collect();
1211                if changed {
1212                    db.factory().union(evaluated)
1213                } else {
1214                    type_id
1215                }
1216            }
1217            EvaluationNeeded::Intersection(members) => {
1218                let mut changed = false;
1219                let evaluated: Vec<TypeId> = members
1220                    .iter()
1221                    .map(|&member| {
1222                        let ev = visit(db, member, evaluate_leaf);
1223                        if ev != member {
1224                            changed = true;
1225                        }
1226                        ev
1227                    })
1228                    .collect();
1229                if changed {
1230                    db.factory().intersection(evaluated)
1231                } else {
1232                    type_id
1233                }
1234            }
1235            EvaluationNeeded::Application { .. }
1236            | EvaluationNeeded::Mapped { .. }
1237            | EvaluationNeeded::Conditional { .. } => {
1238                let evaluated = evaluate_leaf(type_id);
1239                if evaluated != type_id {
1240                    evaluated
1241                } else {
1242                    type_id
1243                }
1244            }
1245            _ if get_lazy_def_id(db, type_id).is_some() => {
1246                let evaluated = evaluate_leaf(type_id);
1247                if evaluated != type_id {
1248                    evaluated
1249                } else {
1250                    type_id
1251                }
1252            }
1253            _ => type_id,
1254        }
1255    }
1256
1257    visit(db, type_id, evaluate_leaf)
1258}