1use crate::TypeDatabase;
10use crate::type_queries::data::{get_callable_shape, get_object_shape};
11use crate::types::{IntrinsicKind, TemplateSpan, TypeData, TypeId};
12use tsz_common::interner::Atom;
13
14#[derive(Debug, Clone)]
20pub enum PropertyAccessClassification {
21 Direct(TypeId),
23 SymbolRef(crate::types::SymbolRef),
25 TypeQuery(crate::types::SymbolRef),
27 Application {
29 app_id: crate::types::TypeApplicationId,
30 },
31 Union(Vec<TypeId>),
33 Intersection(Vec<TypeId>),
35 IndexAccess { object: TypeId, index: TypeId },
37 Readonly(TypeId),
39 Callable(TypeId),
41 TypeParameter { constraint: Option<TypeId> },
43 NeedsEvaluation(TypeId),
45 Resolved(TypeId),
47}
48
49pub fn classify_for_property_access(
51 db: &dyn TypeDatabase,
52 type_id: TypeId,
53) -> PropertyAccessClassification {
54 let Some(key) = db.lookup(type_id) else {
55 return PropertyAccessClassification::Resolved(type_id);
56 };
57
58 match key {
59 TypeData::Object(_) | TypeData::ObjectWithIndex(_) => {
60 PropertyAccessClassification::Direct(type_id)
61 }
62 TypeData::TypeQuery(sym_ref) => PropertyAccessClassification::TypeQuery(sym_ref),
63 TypeData::Application(app_id) => PropertyAccessClassification::Application { app_id },
64 TypeData::Union(list_id) => {
65 let members = db.type_list(list_id);
66 PropertyAccessClassification::Union(members.to_vec())
67 }
68 TypeData::Intersection(list_id) => {
69 let members = db.type_list(list_id);
70 PropertyAccessClassification::Intersection(members.to_vec())
71 }
72 TypeData::IndexAccess(object, index) => {
73 PropertyAccessClassification::IndexAccess { object, index }
74 }
75 TypeData::ReadonlyType(inner) | TypeData::NoInfer(inner) => PropertyAccessClassification::Readonly(inner),
76 TypeData::Function(_) | TypeData::Callable(_) => {
77 PropertyAccessClassification::Callable(type_id)
78 }
79 TypeData::TypeParameter(info) | TypeData::Infer(info) => {
80 PropertyAccessClassification::TypeParameter {
81 constraint: info.constraint,
82 }
83 }
84 TypeData::Conditional(_) | TypeData::Mapped(_) | TypeData::KeyOf(_) => {
85 PropertyAccessClassification::NeedsEvaluation(type_id)
86 }
87 TypeData::BoundParameter(_)
89 | TypeData::Intrinsic(_)
91 | TypeData::Literal(_)
92 | TypeData::Array(_)
93 | TypeData::Tuple(_)
94 | TypeData::Lazy(_)
95 | TypeData::Recursive(_)
96 | TypeData::TemplateLiteral(_)
97 | TypeData::UniqueSymbol(_)
98 | TypeData::ThisType
99 | TypeData::StringIntrinsic { .. }
100 | TypeData::ModuleNamespace(_)
101 | TypeData::Error
102 | TypeData::Enum(_, _) => PropertyAccessClassification::Resolved(type_id),
103 }
104}
105
106#[derive(Debug, Clone)]
116pub enum TypeTraversalKind {
117 Application {
119 app_id: crate::types::TypeApplicationId,
120 base: TypeId,
121 args: Vec<TypeId>,
122 },
123 SymbolRef(crate::types::SymbolRef),
125 Lazy(crate::def::DefId),
127 TypeQuery(crate::types::SymbolRef),
129 TypeParameter {
131 constraint: Option<TypeId>,
132 default: Option<TypeId>,
133 },
134 Members(Vec<TypeId>),
136 Function(crate::types::FunctionShapeId),
138 Callable(crate::types::CallableShapeId),
140 Object(crate::types::ObjectShapeId),
142 Array(TypeId),
144 Tuple(crate::types::TupleListId),
146 Conditional(crate::types::ConditionalTypeId),
148 Mapped(crate::types::MappedTypeId),
150 Readonly(TypeId),
152 IndexAccess { object: TypeId, index: TypeId },
154 KeyOf(TypeId),
156 TemplateLiteral(Vec<TypeId>),
158 StringIntrinsic(TypeId),
160 Terminal,
162}
163
164pub fn classify_for_traversal(db: &dyn TypeDatabase, type_id: TypeId) -> TypeTraversalKind {
169 let Some(key) = db.lookup(type_id) else {
170 return TypeTraversalKind::Terminal;
171 };
172
173 match key {
174 TypeData::Application(app_id) => {
175 let app = db.type_application(app_id);
176 TypeTraversalKind::Application {
177 app_id,
178 base: app.base,
179 args: app.args.clone(),
180 }
181 }
182 TypeData::TypeParameter(info) | TypeData::Infer(info) => TypeTraversalKind::TypeParameter {
183 constraint: info.constraint,
184 default: info.default,
185 },
186 TypeData::Union(list_id) | TypeData::Intersection(list_id) => {
187 let members = db.type_list(list_id);
188 TypeTraversalKind::Members(members.to_vec())
189 }
190 TypeData::Function(shape_id) => TypeTraversalKind::Function(shape_id),
191 TypeData::Callable(shape_id) => TypeTraversalKind::Callable(shape_id),
192 TypeData::Object(shape_id) | TypeData::ObjectWithIndex(shape_id) => {
193 TypeTraversalKind::Object(shape_id)
194 }
195 TypeData::Array(elem) => TypeTraversalKind::Array(elem),
196 TypeData::Tuple(list_id) => TypeTraversalKind::Tuple(list_id),
197 TypeData::Conditional(cond_id) => TypeTraversalKind::Conditional(cond_id),
198 TypeData::Mapped(mapped_id) => TypeTraversalKind::Mapped(mapped_id),
199 TypeData::ReadonlyType(inner) | TypeData::NoInfer(inner) => {
200 TypeTraversalKind::Readonly(inner)
201 }
202 TypeData::IndexAccess(object, index) => TypeTraversalKind::IndexAccess { object, index },
203 TypeData::KeyOf(inner) => TypeTraversalKind::KeyOf(inner),
204 TypeData::TemplateLiteral(list_id) => {
206 let spans = db.template_list(list_id);
207 let types: Vec<TypeId> = spans
208 .iter()
209 .filter_map(|span| match span {
210 TemplateSpan::Type(id) => Some(*id),
211 _ => None,
212 })
213 .collect();
214 if types.is_empty() {
215 TypeTraversalKind::Terminal
216 } else {
217 TypeTraversalKind::TemplateLiteral(types)
218 }
219 }
220 TypeData::StringIntrinsic { type_arg, .. } => TypeTraversalKind::StringIntrinsic(type_arg),
222 TypeData::Lazy(def_id) => TypeTraversalKind::Lazy(def_id),
224 TypeData::TypeQuery(symbol_ref) => TypeTraversalKind::TypeQuery(symbol_ref),
226 TypeData::BoundParameter(_)
228 | TypeData::Intrinsic(_)
229 | TypeData::Literal(_)
230 | TypeData::Recursive(_)
231 | TypeData::UniqueSymbol(_)
232 | TypeData::ThisType
233 | TypeData::ModuleNamespace(_)
234 | TypeData::Error
235 | TypeData::Enum(_, _) => TypeTraversalKind::Terminal,
236 }
237}
238
239pub fn get_lazy_if_def(db: &dyn TypeDatabase, type_id: TypeId) -> Option<crate::def::DefId> {
243 match db.lookup(type_id) {
244 Some(TypeData::Lazy(def_id)) => Some(def_id),
245 _ => None,
246 }
247}
248
249#[derive(Debug, Clone)]
254pub enum PropertyTraversalKind {
255 Object(std::sync::Arc<crate::types::ObjectShape>),
256 Callable(std::sync::Arc<crate::types::CallableShape>),
257 Members(Vec<TypeId>),
258 Other,
259}
260
261pub fn classify_property_traversal(
263 db: &dyn TypeDatabase,
264 type_id: TypeId,
265) -> PropertyTraversalKind {
266 match classify_for_traversal(db, type_id) {
267 TypeTraversalKind::Object(_) => get_object_shape(db, type_id)
268 .map_or(PropertyTraversalKind::Other, PropertyTraversalKind::Object),
269 TypeTraversalKind::Callable(_) => get_callable_shape(db, type_id).map_or(
270 PropertyTraversalKind::Other,
271 PropertyTraversalKind::Callable,
272 ),
273 TypeTraversalKind::Members(members) => PropertyTraversalKind::Members(members),
274 _ => PropertyTraversalKind::Other,
275 }
276}
277
278pub fn collect_property_name_atoms_for_diagnostics(
282 db: &dyn TypeDatabase,
283 type_id: TypeId,
284 max_depth: usize,
285) -> Vec<Atom> {
286 fn collect_inner(
287 db: &dyn TypeDatabase,
288 type_id: TypeId,
289 out: &mut Vec<Atom>,
290 depth: usize,
291 max_depth: usize,
292 ) {
293 if depth > max_depth {
294 return;
295 }
296 match classify_property_traversal(db, type_id) {
297 PropertyTraversalKind::Object(shape) => {
298 for prop in &shape.properties {
299 out.push(prop.name);
300 }
301 }
302 PropertyTraversalKind::Callable(shape) => {
303 for prop in &shape.properties {
304 out.push(prop.name);
305 }
306 }
307 PropertyTraversalKind::Members(members) => {
308 for member in members {
309 collect_inner(db, member, out, depth + 1, max_depth);
310 }
311 }
312 PropertyTraversalKind::Other => {}
313 }
314 }
315
316 let mut atoms = Vec::new();
317 collect_inner(db, type_id, &mut atoms, 0, max_depth);
318 atoms.sort_unstable();
319 atoms.dedup();
320 atoms
321}
322
323pub fn collect_accessible_property_names_for_suggestion(
328 db: &dyn TypeDatabase,
329 type_id: TypeId,
330 max_depth: usize,
331) -> Vec<Atom> {
332 if let Some(TypeData::Union(list_id)) = db.lookup(type_id) {
333 let members = db.type_list(list_id).to_vec();
334 if members.is_empty() {
335 return vec![];
336 }
337 let mut common = collect_property_name_atoms_for_diagnostics(db, members[0], max_depth);
338 common.sort_unstable();
339 common.dedup();
340 for &member in &members[1..] {
341 let mut member_props =
342 collect_property_name_atoms_for_diagnostics(db, member, max_depth);
343 member_props.sort_unstable();
344 member_props.dedup();
345 common.retain(|a| member_props.binary_search(a).is_ok());
346 if common.is_empty() {
347 return vec![];
348 }
349 }
350 return common;
351 }
352 collect_property_name_atoms_for_diagnostics(db, type_id, max_depth)
353}
354
355pub fn is_only_null_or_undefined(db: &dyn TypeDatabase, type_id: TypeId) -> bool {
357 if type_id == TypeId::NULL || type_id == TypeId::UNDEFINED {
358 return true;
359 }
360 match db.lookup(type_id) {
361 Some(TypeData::Intrinsic(IntrinsicKind::Null | IntrinsicKind::Undefined)) => true,
362 Some(TypeData::Union(list_id)) => {
363 let members = db.type_list(list_id);
364 members.iter().all(|&m| is_only_null_or_undefined(db, m))
365 }
366 _ => false,
367 }
368}