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.
170pub fn get_def_id(db: &dyn TypeDatabase, type_id: TypeId) -> Option<crate::def::DefId> {
171    match db.lookup(type_id) {
172        Some(TypeData::Lazy(def_id)) => Some(def_id),
173        _ => None,
174    }
175}
176
177/// Get the `DefId` from a Lazy type.
178/// Returns (Option<SymbolRef>, Option<DefId>) - `DefId` will be Some for Lazy types.
179pub fn get_type_identity(
180    db: &dyn TypeDatabase,
181    type_id: TypeId,
182) -> (Option<crate::types::SymbolRef>, Option<crate::def::DefId>) {
183    match db.lookup(type_id) {
184        Some(TypeData::Lazy(def_id)) => (None, Some(def_id)),
185        _ => (None, None),
186    }
187}
188
189/// Get the enum components (`DefId` and member type) if the type is an Enum type.
190///
191/// Returns `Some((def_id, member_type))` where:
192/// - `def_id` is the unique identity of the enum for nominal checking
193/// - `member_type` is the structural union of member types (e.g., 0 | 1)
194pub fn get_enum_components(
195    db: &dyn TypeDatabase,
196    type_id: TypeId,
197) -> Option<(crate::def::DefId, TypeId)> {
198    match db.lookup(type_id) {
199        Some(TypeData::Enum(def_id, member_type)) => Some((def_id, member_type)),
200        _ => None,
201    }
202}
203
204/// Get the mapped type ID if the type is a Mapped type.
205pub fn get_mapped_type_id(
206    db: &dyn TypeDatabase,
207    type_id: TypeId,
208) -> Option<crate::types::MappedTypeId> {
209    match db.lookup(type_id) {
210        Some(TypeData::Mapped(mapped_id)) => Some(mapped_id),
211        _ => None,
212    }
213}
214
215/// Get the conditional type ID if the type is a Conditional type.
216pub fn get_conditional_type_id(
217    db: &dyn TypeDatabase,
218    type_id: TypeId,
219) -> Option<crate::types::ConditionalTypeId> {
220    match db.lookup(type_id) {
221        Some(TypeData::Conditional(cond_id)) => Some(cond_id),
222        _ => None,
223    }
224}
225
226/// Get the keyof inner type if the type is a `KeyOf` type.
227pub fn get_keyof_inner(db: &dyn TypeDatabase, type_id: TypeId) -> Option<TypeId> {
228    match db.lookup(type_id) {
229        Some(TypeData::KeyOf(inner)) => Some(inner),
230        _ => None,
231    }
232}
233
234// =============================================================================
235// Symbol Resolution Traversal Classification
236// =============================================================================
237
238/// Classification for traversing types to resolve symbols.
239/// Used by `ensure_application_symbols_resolved_inner`.
240#[derive(Debug, Clone)]
241pub enum SymbolResolutionTraversalKind {
242    /// Application type - resolve base symbol and recurse
243    Application {
244        app_id: crate::types::TypeApplicationId,
245        base: TypeId,
246        args: Vec<TypeId>,
247    },
248    /// Lazy(DefId) type - resolve via `DefId`
249    Lazy(crate::def::DefId),
250    /// Type parameter - recurse into constraint/default
251    TypeParameter {
252        constraint: Option<TypeId>,
253        default: Option<TypeId>,
254    },
255    /// Union or Intersection - recurse into members
256    Members(Vec<TypeId>),
257    /// Function type - recurse into signature components
258    Function(crate::types::FunctionShapeId),
259    /// Callable type - recurse into signatures
260    Callable(crate::types::CallableShapeId),
261    /// Object type - recurse into properties and index signatures
262    Object(crate::types::ObjectShapeId),
263    /// Array type - recurse into element
264    Array(TypeId),
265    /// Tuple type - recurse into elements
266    Tuple(crate::types::TupleListId),
267    /// Conditional type - recurse into all branches
268    Conditional(crate::types::ConditionalTypeId),
269    /// Mapped type - recurse into constraint, template, `name_type`
270    Mapped(crate::types::MappedTypeId),
271    /// Readonly wrapper - recurse into inner
272    Readonly(TypeId),
273    /// Index access - recurse into both types
274    IndexAccess { object: TypeId, index: TypeId },
275    /// `KeyOf` - recurse into inner
276    KeyOf(TypeId),
277    /// Terminal type - no further traversal needed
278    Terminal,
279}
280
281/// Classify a type for symbol resolution traversal.
282pub fn classify_for_symbol_resolution_traversal(
283    db: &dyn TypeDatabase,
284    type_id: TypeId,
285) -> SymbolResolutionTraversalKind {
286    let Some(key) = db.lookup(type_id) else {
287        return SymbolResolutionTraversalKind::Terminal;
288    };
289
290    match key {
291        TypeData::Application(app_id) => {
292            let app = db.type_application(app_id);
293            SymbolResolutionTraversalKind::Application {
294                app_id,
295                base: app.base,
296                args: app.args.clone(),
297            }
298        }
299        TypeData::Lazy(def_id) => SymbolResolutionTraversalKind::Lazy(def_id),
300        TypeData::TypeParameter(param) | TypeData::Infer(param) => {
301            SymbolResolutionTraversalKind::TypeParameter {
302                constraint: param.constraint,
303                default: param.default,
304            }
305        }
306        TypeData::Union(members_id) | TypeData::Intersection(members_id) => {
307            let members = db.type_list(members_id);
308            SymbolResolutionTraversalKind::Members(members.to_vec())
309        }
310        TypeData::Function(shape_id) => SymbolResolutionTraversalKind::Function(shape_id),
311        TypeData::Callable(shape_id) => SymbolResolutionTraversalKind::Callable(shape_id),
312        TypeData::Object(shape_id) | TypeData::ObjectWithIndex(shape_id) => {
313            SymbolResolutionTraversalKind::Object(shape_id)
314        }
315        TypeData::Array(elem) => SymbolResolutionTraversalKind::Array(elem),
316        TypeData::Tuple(elems_id) => SymbolResolutionTraversalKind::Tuple(elems_id),
317        TypeData::Conditional(cond_id) => SymbolResolutionTraversalKind::Conditional(cond_id),
318        TypeData::Mapped(mapped_id) => SymbolResolutionTraversalKind::Mapped(mapped_id),
319        TypeData::ReadonlyType(inner) => SymbolResolutionTraversalKind::Readonly(inner),
320        TypeData::IndexAccess(obj, idx) => SymbolResolutionTraversalKind::IndexAccess {
321            object: obj,
322            index: idx,
323        },
324        TypeData::KeyOf(inner) => SymbolResolutionTraversalKind::KeyOf(inner),
325        _ => SymbolResolutionTraversalKind::Terminal,
326    }
327}
328
329// =============================================================================
330// Interface Merge Type Classification
331// =============================================================================
332
333/// Classification for types when merging interfaces.
334///
335/// This enum provides a structured way to handle interface type merging,
336/// abstracting away the internal `TypeData` representation. Used for merging
337/// derived and base interface types.
338#[derive(Debug, Clone)]
339pub enum InterfaceMergeKind {
340    /// Callable type with call/construct signatures and properties
341    Callable(crate::types::CallableShapeId),
342    /// Object type with properties only
343    Object(crate::types::ObjectShapeId),
344    /// Object type with properties and index signatures
345    ObjectWithIndex(crate::types::ObjectShapeId),
346    /// Intersection type - create intersection with base
347    Intersection,
348    /// Other type kinds - return derived unchanged
349    Other,
350}
351
352/// Classify a type for interface merging operations.
353///
354/// This function examines a type and returns information about how to handle it
355/// when merging interface types. Used by `merge_interface_types`.
356///
357/// # Example
358///
359/// ```ignore
360/// use crate::type_queries::{classify_for_interface_merge, InterfaceMergeKind};
361///
362/// match classify_for_interface_merge(&db, type_id) {
363///     InterfaceMergeKind::Callable(shape_id) => {
364///         let shape = db.callable_shape(shape_id);
365///         // Merge signatures and properties
366///     }
367///     InterfaceMergeKind::Object(shape_id) => {
368///         let shape = db.object_shape(shape_id);
369///         // Merge properties only
370///     }
371///     InterfaceMergeKind::ObjectWithIndex(shape_id) => {
372///         let shape = db.object_shape(shape_id);
373///         // Merge properties and index signatures
374///     }
375///     InterfaceMergeKind::Intersection => {
376///         // Create intersection with base type
377///     }
378///     InterfaceMergeKind::Other => {
379///         // Return derived unchanged
380///     }
381/// }
382/// ```
383pub fn classify_for_interface_merge(db: &dyn TypeDatabase, type_id: TypeId) -> InterfaceMergeKind {
384    let Some(key) = db.lookup(type_id) else {
385        return InterfaceMergeKind::Other;
386    };
387
388    match key {
389        TypeData::Callable(shape_id) => InterfaceMergeKind::Callable(shape_id),
390        TypeData::Object(shape_id) => InterfaceMergeKind::Object(shape_id),
391        TypeData::ObjectWithIndex(shape_id) => InterfaceMergeKind::ObjectWithIndex(shape_id),
392        TypeData::Intersection(_) => InterfaceMergeKind::Intersection,
393        // All other types cannot be structurally merged for interfaces
394        TypeData::BoundParameter(_)
395        | TypeData::Intrinsic(_)
396        | TypeData::Literal(_)
397        | TypeData::Union(_)
398        | TypeData::Array(_)
399        | TypeData::Tuple(_)
400        | TypeData::Function(_)
401        | TypeData::TypeParameter(_)
402        | TypeData::Infer(_)
403        | TypeData::Lazy(_)
404        | TypeData::Recursive(_)
405        | TypeData::Application(_)
406        | TypeData::Conditional(_)
407        | TypeData::Mapped(_)
408        | TypeData::IndexAccess(_, _)
409        | TypeData::KeyOf(_)
410        | TypeData::TemplateLiteral(_)
411        | TypeData::UniqueSymbol(_)
412        | TypeData::ThisType
413        | TypeData::TypeQuery(_)
414        | TypeData::ReadonlyType(_)
415        | TypeData::NoInfer(_)
416        | TypeData::StringIntrinsic { .. }
417        | TypeData::ModuleNamespace(_)
418        | TypeData::Error
419        | TypeData::Enum(_, _) => InterfaceMergeKind::Other,
420    }
421}
422
423// =============================================================================
424// Augmentation Target Classification
425// =============================================================================
426
427/// Classification for augmentation operations on types.
428///
429/// Similar to `InterfaceMergeKind` but specifically for module augmentation
430/// where we merge additional properties into an existing type.
431#[derive(Debug, Clone)]
432pub enum AugmentationTargetKind {
433    /// Object type - merge properties directly
434    Object(crate::types::ObjectShapeId),
435    /// Object with index signatures - preserve index signatures when merging
436    ObjectWithIndex(crate::types::ObjectShapeId),
437    /// Callable type - merge properties while preserving signatures
438    Callable(crate::types::CallableShapeId),
439    /// Other type - create new object with augmentation members
440    Other,
441}
442
443/// Classify a type for augmentation operations.
444///
445/// This function examines a type and returns information about how to handle it
446/// when applying module augmentations. Used by `apply_module_augmentations`.
447pub fn classify_for_augmentation(db: &dyn TypeDatabase, type_id: TypeId) -> AugmentationTargetKind {
448    let Some(key) = db.lookup(type_id) else {
449        return AugmentationTargetKind::Other;
450    };
451
452    match key {
453        TypeData::Object(shape_id) => AugmentationTargetKind::Object(shape_id),
454        TypeData::ObjectWithIndex(shape_id) => AugmentationTargetKind::ObjectWithIndex(shape_id),
455        TypeData::Callable(shape_id) => AugmentationTargetKind::Callable(shape_id),
456        // All other types are treated as Other for augmentation
457        _ => AugmentationTargetKind::Other,
458    }
459}