Skip to main content

tsz_solver/type_queries/
classifiers.rs

1//! Additional type query classifiers.
2//!
3//! Contains classification enums and functions for specific checker scenarios:
4//! - Excess property checking
5//! - Constructor access levels
6//! - Assignability evaluation
7//! - Binding element type extraction
8//! - Type identity/accessor helpers
9//! - Symbol resolution traversal
10//! - Interface merge type classification
11//! - Augmentation target classification
12
13use crate::{TypeData, TypeDatabase, TypeId};
14
15// =============================================================================
16// Excess Properties Classification
17// =============================================================================
18
19/// Classification for checking excess properties.
20#[derive(Debug, Clone)]
21pub enum ExcessPropertiesKind {
22    /// Object type (without index signature) - check for excess
23    Object(crate::types::ObjectShapeId),
24    /// Object with index signature - accepts any property
25    ObjectWithIndex(crate::types::ObjectShapeId),
26    /// Union - check all members
27    Union(Vec<TypeId>),
28    /// Intersection - merge known members from all object constituents
29    Intersection(Vec<TypeId>),
30    /// Not an object type
31    NotObject,
32}
33
34/// Classify a type for excess property checking.
35pub fn classify_for_excess_properties(
36    db: &dyn TypeDatabase,
37    type_id: TypeId,
38) -> ExcessPropertiesKind {
39    let Some(key) = db.lookup(type_id) else {
40        return ExcessPropertiesKind::NotObject;
41    };
42
43    match key {
44        TypeData::Object(shape_id) => ExcessPropertiesKind::Object(shape_id),
45        TypeData::ObjectWithIndex(shape_id) => ExcessPropertiesKind::ObjectWithIndex(shape_id),
46        TypeData::Union(list_id) => {
47            let members = db.type_list(list_id);
48            ExcessPropertiesKind::Union(members.to_vec())
49        }
50        TypeData::Intersection(list_id) => {
51            let members = db.type_list(list_id);
52            ExcessPropertiesKind::Intersection(members.to_vec())
53        }
54        _ => ExcessPropertiesKind::NotObject,
55    }
56}
57
58// =============================================================================
59// Constructor Access Level Classification
60// =============================================================================
61
62/// Classification for checking constructor access level.
63#[derive(Debug, Clone)]
64pub enum ConstructorAccessKind {
65    /// Ref or `TypeQuery` - resolve symbol
66    SymbolRef(crate::types::SymbolRef),
67    /// Application - check base
68    Application(crate::types::TypeApplicationId),
69    /// Not applicable
70    Other,
71}
72
73/// Classify a type for constructor access level checking.
74pub fn classify_for_constructor_access(
75    db: &dyn TypeDatabase,
76    type_id: TypeId,
77) -> ConstructorAccessKind {
78    let Some(key) = db.lookup(type_id) else {
79        return ConstructorAccessKind::Other;
80    };
81
82    match key {
83        TypeData::TypeQuery(sym_ref) => ConstructorAccessKind::SymbolRef(sym_ref),
84        TypeData::Application(app_id) => ConstructorAccessKind::Application(app_id),
85        _ => ConstructorAccessKind::Other,
86    }
87}
88
89// =============================================================================
90// Assignability Evaluation Classification
91// =============================================================================
92
93/// Classification for types that need evaluation before assignability.
94#[derive(Debug, Clone)]
95pub enum AssignabilityEvalKind {
96    /// Application - evaluate with resolution
97    Application,
98    /// Index/KeyOf/Mapped/Conditional - evaluate with env
99    NeedsEnvEval,
100    /// Already resolved
101    Resolved,
102}
103
104/// Classify a type for assignability evaluation.
105pub fn classify_for_assignability_eval(
106    db: &dyn TypeDatabase,
107    type_id: TypeId,
108) -> AssignabilityEvalKind {
109    let Some(key) = db.lookup(type_id) else {
110        return AssignabilityEvalKind::Resolved;
111    };
112
113    match key {
114        TypeData::Application(_) | TypeData::Lazy(_) => AssignabilityEvalKind::Application,
115        TypeData::IndexAccess(_, _)
116        | TypeData::KeyOf(_)
117        | TypeData::Mapped(_)
118        | TypeData::Conditional(_) => AssignabilityEvalKind::NeedsEnvEval,
119        _ => AssignabilityEvalKind::Resolved,
120    }
121}
122
123// =============================================================================
124// Binding Element Type Classification
125// =============================================================================
126
127/// Classification for binding element (destructuring) type extraction.
128#[derive(Debug, Clone)]
129pub enum BindingElementTypeKind {
130    /// Array type - use element type
131    Array(TypeId),
132    /// Tuple type - use element by index
133    Tuple(crate::types::TupleListId),
134    /// Object type - use property type
135    Object(crate::types::ObjectShapeId),
136    /// Not applicable
137    Other,
138}
139
140/// Classify a type for binding element type extraction.
141pub fn classify_for_binding_element(
142    db: &dyn TypeDatabase,
143    type_id: TypeId,
144) -> BindingElementTypeKind {
145    let Some(key) = db.lookup(type_id) else {
146        return BindingElementTypeKind::Other;
147    };
148
149    match key {
150        TypeData::Array(elem) => BindingElementTypeKind::Array(elem),
151        TypeData::Tuple(list_id) => BindingElementTypeKind::Tuple(list_id),
152        TypeData::Object(shape_id) => BindingElementTypeKind::Object(shape_id),
153        _ => BindingElementTypeKind::Other,
154    }
155}
156
157// =============================================================================
158// Additional Accessor Helpers
159// =============================================================================
160
161/// Get the `DefId` from a Lazy type.
162pub fn get_lazy_def_id(db: &dyn TypeDatabase, type_id: TypeId) -> Option<crate::def::DefId> {
163    match db.lookup(type_id) {
164        Some(TypeData::Lazy(def_id)) => Some(def_id),
165        _ => None,
166    }
167}
168
169/// Get the `DefId` from a Lazy type.
170/// Returns (Option<SymbolRef>, Option<DefId>) - `DefId` will be Some for Lazy types.
171pub fn get_type_identity(
172    db: &dyn TypeDatabase,
173    type_id: TypeId,
174) -> (Option<crate::types::SymbolRef>, Option<crate::def::DefId>) {
175    match db.lookup(type_id) {
176        Some(TypeData::Lazy(def_id)) => (None, Some(def_id)),
177        _ => (None, None),
178    }
179}
180
181/// Get the enum components (`DefId` and member type) if the type is an Enum type.
182///
183/// Returns `Some((def_id, member_type))` where:
184/// - `def_id` is the unique identity of the enum for nominal checking
185/// - `member_type` is the structural union of member types (e.g., 0 | 1)
186pub fn get_enum_components(
187    db: &dyn TypeDatabase,
188    type_id: TypeId,
189) -> Option<(crate::def::DefId, TypeId)> {
190    match db.lookup(type_id) {
191        Some(TypeData::Enum(def_id, member_type)) => Some((def_id, member_type)),
192        _ => None,
193    }
194}
195
196/// Get the mapped type ID if the type is a Mapped type.
197pub fn get_mapped_type_id(
198    db: &dyn TypeDatabase,
199    type_id: TypeId,
200) -> Option<crate::types::MappedTypeId> {
201    match db.lookup(type_id) {
202        Some(TypeData::Mapped(mapped_id)) => Some(mapped_id),
203        _ => None,
204    }
205}
206
207/// Get the conditional type ID if the type is a Conditional type.
208pub fn get_conditional_type_id(
209    db: &dyn TypeDatabase,
210    type_id: TypeId,
211) -> Option<crate::types::ConditionalTypeId> {
212    match db.lookup(type_id) {
213        Some(TypeData::Conditional(cond_id)) => Some(cond_id),
214        _ => None,
215    }
216}
217
218/// Get the keyof inner type if the type is a `KeyOf` type.
219pub fn get_keyof_type(db: &dyn TypeDatabase, type_id: TypeId) -> Option<TypeId> {
220    match db.lookup(type_id) {
221        Some(TypeData::KeyOf(inner)) => Some(inner),
222        _ => None,
223    }
224}
225
226// =============================================================================
227// Symbol Resolution Traversal Classification
228// =============================================================================
229
230/// Classification for traversing types to resolve symbols.
231/// Used by `ensure_application_symbols_resolved_inner`.
232#[derive(Debug, Clone)]
233pub enum SymbolResolutionTraversalKind {
234    /// Application type - resolve base symbol and recurse
235    Application {
236        app_id: crate::types::TypeApplicationId,
237        base: TypeId,
238        args: Vec<TypeId>,
239    },
240    /// Lazy(DefId) type - resolve via `DefId`
241    Lazy(crate::def::DefId),
242    /// Type parameter - recurse into constraint/default
243    TypeParameter {
244        constraint: Option<TypeId>,
245        default: Option<TypeId>,
246    },
247    /// Union or Intersection - recurse into members
248    Members(Vec<TypeId>),
249    /// Function type - recurse into signature components
250    Function(crate::types::FunctionShapeId),
251    /// Callable type - recurse into signatures
252    Callable(crate::types::CallableShapeId),
253    /// Object type - recurse into properties and index signatures
254    Object(crate::types::ObjectShapeId),
255    /// Array type - recurse into element
256    Array(TypeId),
257    /// Tuple type - recurse into elements
258    Tuple(crate::types::TupleListId),
259    /// Conditional type - recurse into all branches
260    Conditional(crate::types::ConditionalTypeId),
261    /// Mapped type - recurse into constraint, template, `name_type`
262    Mapped(crate::types::MappedTypeId),
263    /// Readonly wrapper - recurse into inner
264    Readonly(TypeId),
265    /// Index access - recurse into both types
266    IndexAccess { object: TypeId, index: TypeId },
267    /// `KeyOf` - recurse into inner
268    KeyOf(TypeId),
269    /// Terminal type - no further traversal needed
270    Terminal,
271}
272
273/// Classify a type for symbol resolution traversal.
274pub fn classify_for_symbol_resolution_traversal(
275    db: &dyn TypeDatabase,
276    type_id: TypeId,
277) -> SymbolResolutionTraversalKind {
278    let Some(key) = db.lookup(type_id) else {
279        return SymbolResolutionTraversalKind::Terminal;
280    };
281
282    match key {
283        TypeData::Application(app_id) => {
284            let app = db.type_application(app_id);
285            SymbolResolutionTraversalKind::Application {
286                app_id,
287                base: app.base,
288                args: app.args.clone(),
289            }
290        }
291        TypeData::Lazy(def_id) => SymbolResolutionTraversalKind::Lazy(def_id),
292        TypeData::TypeParameter(param) | TypeData::Infer(param) => {
293            SymbolResolutionTraversalKind::TypeParameter {
294                constraint: param.constraint,
295                default: param.default,
296            }
297        }
298        TypeData::Union(members_id) | TypeData::Intersection(members_id) => {
299            let members = db.type_list(members_id);
300            SymbolResolutionTraversalKind::Members(members.to_vec())
301        }
302        TypeData::Function(shape_id) => SymbolResolutionTraversalKind::Function(shape_id),
303        TypeData::Callable(shape_id) => SymbolResolutionTraversalKind::Callable(shape_id),
304        TypeData::Object(shape_id) | TypeData::ObjectWithIndex(shape_id) => {
305            SymbolResolutionTraversalKind::Object(shape_id)
306        }
307        TypeData::Array(elem) => SymbolResolutionTraversalKind::Array(elem),
308        TypeData::Tuple(elems_id) => SymbolResolutionTraversalKind::Tuple(elems_id),
309        TypeData::Conditional(cond_id) => SymbolResolutionTraversalKind::Conditional(cond_id),
310        TypeData::Mapped(mapped_id) => SymbolResolutionTraversalKind::Mapped(mapped_id),
311        TypeData::ReadonlyType(inner) => SymbolResolutionTraversalKind::Readonly(inner),
312        TypeData::IndexAccess(obj, idx) => SymbolResolutionTraversalKind::IndexAccess {
313            object: obj,
314            index: idx,
315        },
316        TypeData::KeyOf(inner) => SymbolResolutionTraversalKind::KeyOf(inner),
317        _ => SymbolResolutionTraversalKind::Terminal,
318    }
319}
320
321// =============================================================================
322// Interface Merge Type Classification
323// =============================================================================
324
325/// Classification for types when merging interfaces.
326///
327/// This enum provides a structured way to handle interface type merging,
328/// abstracting away the internal `TypeData` representation. Used for merging
329/// derived and base interface types.
330#[derive(Debug, Clone)]
331pub enum InterfaceMergeKind {
332    /// Callable type with call/construct signatures and properties
333    Callable(crate::types::CallableShapeId),
334    /// Object type with properties only
335    Object(crate::types::ObjectShapeId),
336    /// Object type with properties and index signatures
337    ObjectWithIndex(crate::types::ObjectShapeId),
338    /// Intersection type - create intersection with base
339    Intersection,
340    /// Other type kinds - return derived unchanged
341    Other,
342}
343
344/// Classify a type for interface merging operations.
345///
346/// This function examines a type and returns information about how to handle it
347/// when merging interface types. Used by `merge_interface_types`.
348///
349/// # Example
350///
351/// ```ignore
352/// use crate::type_queries::{classify_for_interface_merge, InterfaceMergeKind};
353///
354/// match classify_for_interface_merge(&db, type_id) {
355///     InterfaceMergeKind::Callable(shape_id) => {
356///         let shape = db.callable_shape(shape_id);
357///         // Merge signatures and properties
358///     }
359///     InterfaceMergeKind::Object(shape_id) => {
360///         let shape = db.object_shape(shape_id);
361///         // Merge properties only
362///     }
363///     InterfaceMergeKind::ObjectWithIndex(shape_id) => {
364///         let shape = db.object_shape(shape_id);
365///         // Merge properties and index signatures
366///     }
367///     InterfaceMergeKind::Intersection => {
368///         // Create intersection with base type
369///     }
370///     InterfaceMergeKind::Other => {
371///         // Return derived unchanged
372///     }
373/// }
374/// ```
375pub fn classify_for_interface_merge(db: &dyn TypeDatabase, type_id: TypeId) -> InterfaceMergeKind {
376    let Some(key) = db.lookup(type_id) else {
377        return InterfaceMergeKind::Other;
378    };
379
380    match key {
381        TypeData::Callable(shape_id) => InterfaceMergeKind::Callable(shape_id),
382        TypeData::Object(shape_id) => InterfaceMergeKind::Object(shape_id),
383        TypeData::ObjectWithIndex(shape_id) => InterfaceMergeKind::ObjectWithIndex(shape_id),
384        TypeData::Intersection(_) => InterfaceMergeKind::Intersection,
385        // All other types cannot be structurally merged for interfaces
386        TypeData::BoundParameter(_)
387        | TypeData::Intrinsic(_)
388        | TypeData::Literal(_)
389        | TypeData::Union(_)
390        | TypeData::Array(_)
391        | TypeData::Tuple(_)
392        | TypeData::Function(_)
393        | TypeData::TypeParameter(_)
394        | TypeData::Infer(_)
395        | TypeData::Lazy(_)
396        | TypeData::Recursive(_)
397        | TypeData::Application(_)
398        | TypeData::Conditional(_)
399        | TypeData::Mapped(_)
400        | TypeData::IndexAccess(_, _)
401        | TypeData::KeyOf(_)
402        | TypeData::TemplateLiteral(_)
403        | TypeData::UniqueSymbol(_)
404        | TypeData::ThisType
405        | TypeData::TypeQuery(_)
406        | TypeData::ReadonlyType(_)
407        | TypeData::NoInfer(_)
408        | TypeData::StringIntrinsic { .. }
409        | TypeData::ModuleNamespace(_)
410        | TypeData::Error
411        | TypeData::Enum(_, _) => InterfaceMergeKind::Other,
412    }
413}
414
415// =============================================================================
416// Augmentation Target Classification
417// =============================================================================
418
419/// Classification for augmentation operations on types.
420///
421/// Similar to `InterfaceMergeKind` but specifically for module augmentation
422/// where we merge additional properties into an existing type.
423#[derive(Debug, Clone)]
424pub enum AugmentationTargetKind {
425    /// Object type - merge properties directly
426    Object(crate::types::ObjectShapeId),
427    /// Object with index signatures - preserve index signatures when merging
428    ObjectWithIndex(crate::types::ObjectShapeId),
429    /// Callable type - merge properties while preserving signatures
430    Callable(crate::types::CallableShapeId),
431    /// Other type - create new object with augmentation members
432    Other,
433}
434
435/// Classify a type for augmentation operations.
436///
437/// This function examines a type and returns information about how to handle it
438/// when applying module augmentations. Used by `apply_module_augmentations`.
439pub fn classify_for_augmentation(db: &dyn TypeDatabase, type_id: TypeId) -> AugmentationTargetKind {
440    let Some(key) = db.lookup(type_id) else {
441        return AugmentationTargetKind::Other;
442    };
443
444    match key {
445        TypeData::Object(shape_id) => AugmentationTargetKind::Object(shape_id),
446        TypeData::ObjectWithIndex(shape_id) => AugmentationTargetKind::ObjectWithIndex(shape_id),
447        TypeData::Callable(shape_id) => AugmentationTargetKind::Callable(shape_id),
448        // All other types are treated as Other for augmentation
449        _ => AugmentationTargetKind::Other,
450    }
451}