Skip to main content

tsz_solver/visitors/
visitor_extract.rs

1//! Type data extraction helpers.
2//!
3//! Contains convenience functions for extracting specific data from `TypeData` variants
4//! using the visitor pattern. Each function takes a `TypeDatabase` and `TypeId` and returns
5//! the relevant data if the type matches the expected variant.
6
7use crate::def::DefId;
8use crate::types::{
9    CallableShapeId, ConditionalTypeId, FunctionShapeId, IntrinsicKind, LiteralValue, MappedTypeId,
10    ObjectShapeId, OrderedFloat, StringIntrinsicKind, TemplateLiteralId, TemplateSpan, TupleListId,
11    TypeApplicationId, TypeListId, TypeParamInfo,
12};
13use crate::visitor::TypeVisitor;
14use crate::{SymbolRef, TypeData, TypeDatabase, TypeId};
15use rustc_hash::FxHashSet;
16use tsz_common::interner::Atom;
17
18struct TypeDataDataVisitor<F, T>
19where
20    F: Fn(&TypeData) -> Option<T>,
21{
22    extractor: F,
23}
24
25impl<F, T> TypeDataDataVisitor<F, T>
26where
27    F: Fn(&TypeData) -> Option<T>,
28{
29    const fn new(extractor: F) -> Self {
30        Self { extractor }
31    }
32}
33
34impl<F, T> TypeVisitor for TypeDataDataVisitor<F, T>
35where
36    F: Fn(&TypeData) -> Option<T>,
37{
38    type Output = Option<T>;
39
40    fn visit_type_key(&mut self, _types: &dyn TypeDatabase, type_key: &TypeData) -> Self::Output {
41        (self.extractor)(type_key)
42    }
43
44    fn visit_intrinsic(&mut self, _kind: IntrinsicKind) -> Self::Output {
45        Self::default_output()
46    }
47
48    fn visit_literal(&mut self, _value: &LiteralValue) -> Self::Output {
49        Self::default_output()
50    }
51
52    fn default_output() -> Self::Output {
53        None
54    }
55}
56
57fn extract_type_data<T, F>(types: &dyn TypeDatabase, type_id: TypeId, extractor: F) -> Option<T>
58where
59    F: Fn(&TypeData) -> Option<T>,
60{
61    let mut visitor = TypeDataDataVisitor::new(extractor);
62    visitor.visit_type(types, type_id)
63}
64
65/// Extract the union list id if this is a union type.
66pub fn union_list_id(types: &dyn TypeDatabase, type_id: TypeId) -> Option<TypeListId> {
67    extract_type_data(types, type_id, |key| match key {
68        TypeData::Union(list_id) => Some(*list_id),
69        _ => None,
70    })
71}
72
73/// Extract the intersection list id if this is an intersection type.
74pub fn intersection_list_id(types: &dyn TypeDatabase, type_id: TypeId) -> Option<TypeListId> {
75    extract_type_data(types, type_id, |key| match key {
76        TypeData::Intersection(list_id) => Some(*list_id),
77        _ => None,
78    })
79}
80
81/// Extract the object shape id if this is an object type.
82pub fn object_shape_id(types: &dyn TypeDatabase, type_id: TypeId) -> Option<ObjectShapeId> {
83    extract_type_data(types, type_id, |key| match key {
84        TypeData::Object(shape_id) => Some(*shape_id),
85        _ => None,
86    })
87}
88
89/// Extract the object-with-index shape id if this is an indexed object type.
90pub fn object_with_index_shape_id(
91    types: &dyn TypeDatabase,
92    type_id: TypeId,
93) -> Option<ObjectShapeId> {
94    extract_type_data(types, type_id, |key| match key {
95        TypeData::ObjectWithIndex(shape_id) => Some(*shape_id),
96        _ => None,
97    })
98}
99
100/// Extract the array element type if this is an array type.
101pub fn array_element_type(types: &dyn TypeDatabase, type_id: TypeId) -> Option<TypeId> {
102    extract_type_data(types, type_id, |key| match key {
103        TypeData::Array(element) => Some(*element),
104        _ => None,
105    })
106}
107
108/// Extract the tuple list id if this is a tuple type.
109pub fn tuple_list_id(types: &dyn TypeDatabase, type_id: TypeId) -> Option<TupleListId> {
110    extract_type_data(types, type_id, |key| match key {
111        TypeData::Tuple(list_id) => Some(*list_id),
112        _ => None,
113    })
114}
115
116/// Extract the intrinsic kind if this is an intrinsic type.
117pub fn intrinsic_kind(types: &dyn TypeDatabase, type_id: TypeId) -> Option<IntrinsicKind> {
118    extract_type_data(types, type_id, |key| match key {
119        TypeData::Intrinsic(kind) => Some(*kind),
120        _ => None,
121    })
122}
123
124/// Extract the literal value if this is a literal type.
125pub fn literal_value(types: &dyn TypeDatabase, type_id: TypeId) -> Option<LiteralValue> {
126    extract_type_data(types, type_id, |key| match key {
127        TypeData::Literal(value) => Some(value.clone()),
128        _ => None,
129    })
130}
131
132/// Extract the string literal atom if this is a string literal type.
133pub fn literal_string(types: &dyn TypeDatabase, type_id: TypeId) -> Option<Atom> {
134    match literal_value(types, type_id) {
135        Some(LiteralValue::String(atom)) => Some(atom),
136        _ => None,
137    }
138}
139
140/// Extract the numeric literal if this is a number literal type.
141pub fn literal_number(types: &dyn TypeDatabase, type_id: TypeId) -> Option<OrderedFloat> {
142    match literal_value(types, type_id) {
143        Some(LiteralValue::Number(value)) => Some(value),
144        _ => None,
145    }
146}
147
148/// Extract the template literal list id if this is a template literal type.
149pub fn template_literal_id(types: &dyn TypeDatabase, type_id: TypeId) -> Option<TemplateLiteralId> {
150    extract_type_data(types, type_id, |key| match key {
151        TypeData::TemplateLiteral(list_id) => Some(*list_id),
152        _ => None,
153    })
154}
155
156/// Extract the type parameter info if this is a type parameter or infer type.
157pub fn type_param_info(types: &dyn TypeDatabase, type_id: TypeId) -> Option<TypeParamInfo> {
158    extract_type_data(types, type_id, |key| match key {
159        TypeData::TypeParameter(info) | TypeData::Infer(info) => Some(info.clone()),
160        _ => None,
161    })
162}
163
164/// Extract the type reference symbol if this is a Ref type.
165pub fn ref_symbol(types: &dyn TypeDatabase, type_id: TypeId) -> Option<SymbolRef> {
166    extract_type_data(types, type_id, |key| match key {
167        TypeData::Lazy(_def_id) => {
168            // TypeData::Ref has been migrated to TypeData::Lazy(DefId)
169            // We can no longer extract SymbolRef from it
170            // Return None or handle as needed based on migration strategy
171            None
172        }
173        _ => None,
174    })
175}
176
177/// Extract the lazy `DefId` if this is a Lazy type.
178pub fn lazy_def_id(types: &dyn TypeDatabase, type_id: TypeId) -> Option<DefId> {
179    extract_type_data(types, type_id, |key| match key {
180        TypeData::Lazy(def_id) => Some(*def_id),
181        _ => None,
182    })
183}
184
185/// Extract the De Bruijn index if this is a bound type parameter.
186pub fn bound_parameter_index(types: &dyn TypeDatabase, type_id: TypeId) -> Option<u32> {
187    extract_type_data(types, type_id, |key| match key {
188        TypeData::BoundParameter(index) => Some(*index),
189        _ => None,
190    })
191}
192
193/// Extract the De Bruijn index if this is a recursive type reference.
194pub fn recursive_index(types: &dyn TypeDatabase, type_id: TypeId) -> Option<u32> {
195    extract_type_data(types, type_id, |key| match key {
196        TypeData::Recursive(index) => Some(*index),
197        _ => None,
198    })
199}
200
201/// Check if this is an Enum type.
202pub fn is_enum_type(types: &dyn TypeDatabase, type_id: TypeId) -> bool {
203    matches!(types.lookup(type_id), Some(TypeData::Enum(_, _)))
204}
205
206/// Extract the enum components (`DefId` and member type) if this is an Enum type.
207///
208/// Returns `Some((def_id, member_type))` where:
209/// - `def_id` is the unique identity of the enum for nominal checking
210/// - `member_type` is the structural union of member types (e.g., 0 | 1)
211pub fn enum_components(types: &dyn TypeDatabase, type_id: TypeId) -> Option<(DefId, TypeId)> {
212    extract_type_data(types, type_id, |key| match key {
213        TypeData::Enum(def_id, member_type) => Some((*def_id, *member_type)),
214        _ => None,
215    })
216}
217
218/// Extract the application id if this is a generic application type.
219pub fn application_id(types: &dyn TypeDatabase, type_id: TypeId) -> Option<TypeApplicationId> {
220    extract_type_data(types, type_id, |key| match key {
221        TypeData::Application(app_id) => Some(*app_id),
222        _ => None,
223    })
224}
225
226/// Extract the mapped type id if this is a mapped type.
227pub fn mapped_type_id(types: &dyn TypeDatabase, type_id: TypeId) -> Option<MappedTypeId> {
228    extract_type_data(types, type_id, |key| match key {
229        TypeData::Mapped(mapped_id) => Some(*mapped_id),
230        _ => None,
231    })
232}
233
234/// Extract the conditional type id if this is a conditional type.
235pub fn conditional_type_id(types: &dyn TypeDatabase, type_id: TypeId) -> Option<ConditionalTypeId> {
236    extract_type_data(types, type_id, |key| match key {
237        TypeData::Conditional(cond_id) => Some(*cond_id),
238        _ => None,
239    })
240}
241
242/// Extract index access components if this is an index access type.
243pub fn index_access_parts(types: &dyn TypeDatabase, type_id: TypeId) -> Option<(TypeId, TypeId)> {
244    extract_type_data(types, type_id, |key| match key {
245        TypeData::IndexAccess(object_type, index_type) => Some((*object_type, *index_type)),
246        _ => None,
247    })
248}
249
250/// Extract the type query symbol if this is a `TypeQuery`.
251pub fn type_query_symbol(types: &dyn TypeDatabase, type_id: TypeId) -> Option<SymbolRef> {
252    extract_type_data(types, type_id, |key| match key {
253        TypeData::TypeQuery(sym_ref) => Some(*sym_ref),
254        _ => None,
255    })
256}
257
258/// Extract the inner type if this is a keyof type.
259pub fn keyof_inner_type(types: &dyn TypeDatabase, type_id: TypeId) -> Option<TypeId> {
260    extract_type_data(types, type_id, |key| match key {
261        TypeData::KeyOf(inner) => Some(*inner),
262        _ => None,
263    })
264}
265
266/// Extract the inner type if this is a readonly type.
267pub fn readonly_inner_type(types: &dyn TypeDatabase, type_id: TypeId) -> Option<TypeId> {
268    extract_type_data(types, type_id, |key| match key {
269        TypeData::ReadonlyType(inner) => Some(*inner),
270        _ => None,
271    })
272}
273
274/// Extract the inner type if this is a `NoInfer` type.
275pub fn no_infer_inner_type(types: &dyn TypeDatabase, type_id: TypeId) -> Option<TypeId> {
276    extract_type_data(types, type_id, |key| match key {
277        TypeData::NoInfer(inner) => Some(*inner),
278        _ => None,
279    })
280}
281
282/// Extract string intrinsic components if this is a string intrinsic type.
283pub fn string_intrinsic_components(
284    types: &dyn TypeDatabase,
285    type_id: TypeId,
286) -> Option<(StringIntrinsicKind, TypeId)> {
287    extract_type_data(types, type_id, |key| match key {
288        TypeData::StringIntrinsic { kind, type_arg } => Some((*kind, *type_arg)),
289        _ => None,
290    })
291}
292
293/// Extract the unique symbol ref if this is a unique symbol type.
294pub fn unique_symbol_ref(types: &dyn TypeDatabase, type_id: TypeId) -> Option<SymbolRef> {
295    extract_type_data(types, type_id, |key| match key {
296        TypeData::UniqueSymbol(sym_ref) => Some(*sym_ref),
297        _ => None,
298    })
299}
300
301/// Extract the module namespace symbol ref if this is a module namespace type.
302pub fn module_namespace_symbol_ref(types: &dyn TypeDatabase, type_id: TypeId) -> Option<SymbolRef> {
303    extract_type_data(types, type_id, |key| match key {
304        TypeData::ModuleNamespace(sym_ref) => Some(*sym_ref),
305        _ => None,
306    })
307}
308
309/// Check if a type is the special `this` type.
310pub fn is_this_type(types: &dyn TypeDatabase, type_id: TypeId) -> bool {
311    extract_type_data(types, type_id, |key| match key {
312        TypeData::ThisType => Some(true),
313        _ => None,
314    })
315    .unwrap_or(false)
316}
317
318/// Check whether this is an explicit error type.
319pub fn is_error_type(types: &dyn TypeDatabase, type_id: TypeId) -> bool {
320    extract_type_data(types, type_id, |key| match key {
321        TypeData::Error => Some(true),
322        _ => None,
323    })
324    .unwrap_or(false)
325}
326
327/// Extract the function shape id if this is a function type.
328pub fn function_shape_id(types: &dyn TypeDatabase, type_id: TypeId) -> Option<FunctionShapeId> {
329    extract_type_data(types, type_id, |key| match key {
330        TypeData::Function(shape_id) => Some(*shape_id),
331        _ => None,
332    })
333}
334
335/// Extract the callable shape id if this is a callable type.
336pub fn callable_shape_id(types: &dyn TypeDatabase, type_id: TypeId) -> Option<CallableShapeId> {
337    extract_type_data(types, type_id, |key| match key {
338        TypeData::Callable(shape_id) => Some(*shape_id),
339        _ => None,
340    })
341}
342
343/// Recursively walk the type graph and collect all `Infer` type bindings.
344///
345/// Returns a list of `(name, type_id)` pairs — one for each `TypeData::Infer`
346/// encountered during deep traversal. This handles cycle detection via a visited
347/// set and walks into all composite type structures (unions, objects, functions,
348/// conditionals, mapped types, etc.).
349///
350/// This is the solver-owned utility for type-graph traversal that was previously
351/// duplicated in the lowering crate. Per architecture rules, type-graph walking
352/// belongs in the solver.
353pub fn collect_infer_bindings(types: &dyn TypeDatabase, type_id: TypeId) -> Vec<(Atom, TypeId)> {
354    let mut result = Vec::new();
355    let mut visited = FxHashSet::default();
356    collect_infer_bindings_inner(types, type_id, &mut result, &mut visited);
357    result
358}
359
360fn collect_infer_bindings_inner(
361    types: &dyn TypeDatabase,
362    type_id: TypeId,
363    result: &mut Vec<(Atom, TypeId)>,
364    visited: &mut FxHashSet<TypeId>,
365) {
366    if !visited.insert(type_id) {
367        return;
368    }
369
370    let key = match types.lookup(type_id) {
371        Some(key) => key,
372        None => return,
373    };
374
375    match key {
376        TypeData::Infer(info) => {
377            result.push((info.name, type_id));
378            if let Some(constraint) = info.constraint {
379                collect_infer_bindings_inner(types, constraint, result, visited);
380            }
381            if let Some(default) = info.default {
382                collect_infer_bindings_inner(types, default, result, visited);
383            }
384        }
385        TypeData::Array(elem) => {
386            collect_infer_bindings_inner(types, elem, result, visited);
387        }
388        TypeData::Tuple(elements) => {
389            let elements = types.tuple_list(elements);
390            for element in elements.iter() {
391                collect_infer_bindings_inner(types, element.type_id, result, visited);
392            }
393        }
394        TypeData::Union(members) | TypeData::Intersection(members) => {
395            let members = types.type_list(members);
396            for member in members.iter() {
397                collect_infer_bindings_inner(types, *member, result, visited);
398            }
399        }
400        TypeData::Object(shape_id) => {
401            let shape = types.object_shape(shape_id);
402            for prop in &shape.properties {
403                collect_infer_bindings_inner(types, prop.type_id, result, visited);
404            }
405        }
406        TypeData::ObjectWithIndex(shape_id) => {
407            let shape = types.object_shape(shape_id);
408            for prop in &shape.properties {
409                collect_infer_bindings_inner(types, prop.type_id, result, visited);
410            }
411            if let Some(index) = &shape.string_index {
412                collect_infer_bindings_inner(types, index.key_type, result, visited);
413                collect_infer_bindings_inner(types, index.value_type, result, visited);
414            }
415            if let Some(index) = &shape.number_index {
416                collect_infer_bindings_inner(types, index.key_type, result, visited);
417                collect_infer_bindings_inner(types, index.value_type, result, visited);
418            }
419        }
420        TypeData::Function(shape_id) => {
421            let shape = types.function_shape(shape_id);
422            for param in &shape.params {
423                collect_infer_bindings_inner(types, param.type_id, result, visited);
424            }
425            collect_infer_bindings_inner(types, shape.return_type, result, visited);
426            for param in &shape.type_params {
427                if let Some(constraint) = param.constraint {
428                    collect_infer_bindings_inner(types, constraint, result, visited);
429                }
430                if let Some(default) = param.default {
431                    collect_infer_bindings_inner(types, default, result, visited);
432                }
433            }
434        }
435        TypeData::Callable(shape_id) => {
436            let shape = types.callable_shape(shape_id);
437            for sig in &shape.call_signatures {
438                collect_infer_sig(types, sig, result, visited);
439            }
440            for sig in &shape.construct_signatures {
441                collect_infer_sig(types, sig, result, visited);
442            }
443            for prop in &shape.properties {
444                collect_infer_bindings_inner(types, prop.type_id, result, visited);
445            }
446        }
447        TypeData::TypeParameter(info) => {
448            if let Some(constraint) = info.constraint {
449                collect_infer_bindings_inner(types, constraint, result, visited);
450            }
451            if let Some(default) = info.default {
452                collect_infer_bindings_inner(types, default, result, visited);
453            }
454        }
455        TypeData::Application(app_id) => {
456            let app = types.type_application(app_id);
457            collect_infer_bindings_inner(types, app.base, result, visited);
458            for &arg in &app.args {
459                collect_infer_bindings_inner(types, arg, result, visited);
460            }
461        }
462        TypeData::Conditional(cond_id) => {
463            let cond = types.conditional_type(cond_id);
464            collect_infer_bindings_inner(types, cond.check_type, result, visited);
465            collect_infer_bindings_inner(types, cond.extends_type, result, visited);
466            collect_infer_bindings_inner(types, cond.true_type, result, visited);
467            collect_infer_bindings_inner(types, cond.false_type, result, visited);
468        }
469        TypeData::Mapped(mapped_id) => {
470            let mapped = types.mapped_type(mapped_id);
471            if let Some(constraint) = mapped.type_param.constraint {
472                collect_infer_bindings_inner(types, constraint, result, visited);
473            }
474            if let Some(default) = mapped.type_param.default {
475                collect_infer_bindings_inner(types, default, result, visited);
476            }
477            collect_infer_bindings_inner(types, mapped.constraint, result, visited);
478            if let Some(name_type) = mapped.name_type {
479                collect_infer_bindings_inner(types, name_type, result, visited);
480            }
481            collect_infer_bindings_inner(types, mapped.template, result, visited);
482        }
483        TypeData::IndexAccess(obj, idx) => {
484            collect_infer_bindings_inner(types, obj, result, visited);
485            collect_infer_bindings_inner(types, idx, result, visited);
486        }
487        TypeData::KeyOf(inner) | TypeData::ReadonlyType(inner) | TypeData::NoInfer(inner) => {
488            collect_infer_bindings_inner(types, inner, result, visited);
489        }
490        TypeData::TemplateLiteral(spans) => {
491            let spans = types.template_list(spans);
492            for span in spans.iter() {
493                if let TemplateSpan::Type(inner) = span {
494                    collect_infer_bindings_inner(types, *inner, result, visited);
495                }
496            }
497        }
498        TypeData::StringIntrinsic { type_arg, .. } => {
499            collect_infer_bindings_inner(types, type_arg, result, visited);
500        }
501        TypeData::Enum(_def_id, member_type) => {
502            collect_infer_bindings_inner(types, member_type, result, visited);
503        }
504        TypeData::Intrinsic(_)
505        | TypeData::Literal(_)
506        | TypeData::Lazy(_)
507        | TypeData::Recursive(_)
508        | TypeData::BoundParameter(_)
509        | TypeData::TypeQuery(_)
510        | TypeData::UniqueSymbol(_)
511        | TypeData::ThisType
512        | TypeData::ModuleNamespace(_)
513        | TypeData::Error => {}
514    }
515}
516
517/// Helper to collect infer bindings from a call signature's params, return type,
518/// and type params.
519fn collect_infer_sig(
520    types: &dyn TypeDatabase,
521    sig: &crate::types::CallSignature,
522    result: &mut Vec<(Atom, TypeId)>,
523    visited: &mut FxHashSet<TypeId>,
524) {
525    for param in &sig.params {
526        collect_infer_bindings_inner(types, param.type_id, result, visited);
527    }
528    collect_infer_bindings_inner(types, sig.return_type, result, visited);
529    for param in &sig.type_params {
530        if let Some(constraint) = param.constraint {
531            collect_infer_bindings_inner(types, constraint, result, visited);
532        }
533        if let Some(default) = param.default {
534            collect_infer_bindings_inner(types, default, result, visited);
535        }
536    }
537}