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}