1use crate::relations::subtype::TypeResolver;
7#[cfg(test)]
8use crate::types::*;
9use crate::types::{
10 IndexSignature, IntrinsicKind, ObjectShape, PropertyInfo, TypeData, TypeId, TypeListId,
11 Visibility,
12};
13use rustc_hash::{FxHashMap, FxHashSet};
14use tsz_common::interner::Atom;
15
16use crate::caches::db::TypeDatabase;
18
19const fn merge_visibility(a: Visibility, b: Visibility) -> Visibility {
23 match (a, b) {
24 (Visibility::Private, _) | (_, Visibility::Private) => Visibility::Private,
25 (Visibility::Protected, _) | (_, Visibility::Protected) => Visibility::Protected,
26 (Visibility::Public, Visibility::Public) => Visibility::Public,
27 }
28}
29
30#[derive(Debug, Clone, PartialEq)]
32pub enum PropertyCollectionResult {
33 Any,
35 NonObject,
37 Properties {
39 properties: Vec<PropertyInfo>,
40 string_index: Option<IndexSignature>,
41 number_index: Option<IndexSignature>,
42 },
43}
44
45pub fn collect_properties<R>(
69 type_id: TypeId,
70 interner: &dyn TypeDatabase,
71 resolver: &R,
72) -> PropertyCollectionResult
73where
74 R: TypeResolver,
75{
76 let mut collector = PropertyCollector {
77 interner,
78 resolver,
79 properties: Vec::new(),
80 prop_index: FxHashMap::default(),
81 string_index: None,
82 number_index: None,
83 seen: FxHashSet::default(),
84 found_any: false,
85 };
86 collector.collect(type_id);
87
88 if collector.found_any {
90 return PropertyCollectionResult::Any;
91 }
92
93 if collector.properties.is_empty()
95 && collector.string_index.is_none()
96 && collector.number_index.is_none()
97 {
98 return PropertyCollectionResult::NonObject;
99 }
100
101 collector.properties.sort_by_key(|p| p.name.0);
103
104 PropertyCollectionResult::Properties {
105 properties: collector.properties,
106 string_index: collector.string_index,
107 number_index: collector.number_index,
108 }
109}
110
111fn resolve_type<R>(type_id: TypeId, interner: &dyn TypeDatabase, resolver: &R) -> TypeId
113where
114 R: TypeResolver,
115{
116 use crate::visitor::{lazy_def_id, ref_symbol};
117
118 if let Some(def_id) = lazy_def_id(interner, type_id) {
120 return resolver.resolve_lazy(def_id, interner).unwrap_or(type_id);
121 }
122
123 if let Some(symbol) = ref_symbol(interner, type_id) {
125 resolver
126 .resolve_symbol_ref(symbol, interner)
127 .unwrap_or(type_id)
128 } else {
129 type_id
130 }
131}
132
133struct PropertyCollector<'a, R> {
138 interner: &'a dyn TypeDatabase,
139 resolver: &'a R,
140 properties: Vec<PropertyInfo>,
141 prop_index: FxHashMap<Atom, usize>,
143 string_index: Option<IndexSignature>,
144 number_index: Option<IndexSignature>,
145 seen: FxHashSet<TypeId>,
147 found_any: bool,
149}
150
151impl<'a, R: TypeResolver> PropertyCollector<'a, R> {
152 fn collect(&mut self, type_id: TypeId) {
153 if !self.seen.insert(type_id) {
155 return;
156 }
157
158 let resolved = resolve_type(type_id, self.interner, self.resolver);
160
161 match self.interner.lookup(resolved) {
163 Some(TypeData::Intersection(members_id)) => {
164 for &member in self.interner.type_list(members_id).iter() {
166 self.collect(member);
167 }
168 }
169 Some(TypeData::Object(shape_id) | TypeData::ObjectWithIndex(shape_id)) => {
170 let shape = self.interner.object_shape(shape_id);
171 self.merge_shape(&shape);
172 }
173 Some(TypeData::Intrinsic(IntrinsicKind::Any)) => {
175 self.found_any = true;
176 }
177 Some(TypeData::TypeParameter(info)) => {
179 if let Some(constraint) = info.constraint {
180 self.collect(constraint);
181 }
182 }
183 Some(TypeData::Union(members_id)) => {
185 self.collect_union_common(members_id);
186 }
187 _ => {
190 }
192 }
193 }
194
195 fn collect_union_common(&mut self, members_id: TypeListId) {
199 let member_list = self.interner.type_list(members_id);
200 if member_list.is_empty() {
201 return;
202 }
203
204 let mut member_props: Vec<PropertyCollectionResult> = Vec::new();
206 for &member in member_list.iter() {
207 let result = collect_properties(member, self.interner, self.resolver);
208 member_props.push(result);
209 }
210
211 if member_props
213 .iter()
214 .any(|r| matches!(r, PropertyCollectionResult::Any))
215 {
216 self.found_any = true;
217 return;
218 }
219
220 let first = match &member_props[0] {
223 PropertyCollectionResult::Properties { properties, .. } => properties,
224 _ => return, };
226
227 for prop in first {
229 let mut present_in_all = true;
230 let mut type_ids = vec![prop.type_id];
231 let mut all_optional = prop.optional;
232 let mut any_readonly = prop.readonly;
233
234 for member_result in member_props.iter().skip(1) {
235 match member_result {
236 PropertyCollectionResult::Properties { properties, .. } => {
237 if let Some(other_prop) = PropertyInfo::find_in_slice(properties, prop.name)
238 {
239 type_ids.push(other_prop.type_id);
240 all_optional = all_optional && other_prop.optional;
241 any_readonly = any_readonly || other_prop.readonly;
242 } else {
243 present_in_all = false;
244 break;
245 }
246 }
247 _ => {
248 present_in_all = false;
249 break;
250 }
251 }
252 }
253
254 if present_in_all {
255 let union_type = if type_ids.len() == 1 {
257 type_ids[0]
258 } else {
259 self.interner.union(type_ids)
260 };
261
262 if let Some(&idx) = self.prop_index.get(&prop.name) {
264 let existing = &mut self.properties[idx];
265 existing.type_id = self
266 .interner
267 .intersect_types_raw2(existing.type_id, union_type);
268 existing.optional = existing.optional && all_optional;
269 existing.readonly = existing.readonly || any_readonly;
270 } else {
271 let new_idx = self.properties.len();
272 self.prop_index.insert(prop.name, new_idx);
273 self.properties.push(PropertyInfo {
274 name: prop.name,
275 type_id: union_type,
276 write_type: union_type,
277 optional: all_optional,
278 readonly: any_readonly,
279 visibility: prop.visibility,
280 is_method: prop.is_method,
281 parent_id: prop.parent_id,
282 });
283 }
284 }
285 }
286 }
287
288 fn merge_shape(&mut self, shape: &ObjectShape) {
289 for prop in &shape.properties {
291 if let Some(&idx) = self.prop_index.get(&prop.name) {
292 let existing = &mut self.properties[idx];
293 existing.type_id = self
295 .interner
296 .intersect_types_raw2(existing.type_id, prop.type_id);
297 existing.write_type = self
298 .interner
299 .intersect_types_raw2(existing.write_type, prop.write_type);
300 existing.optional = existing.optional && prop.optional;
302 existing.readonly = existing.readonly || prop.readonly;
304 existing.visibility = merge_visibility(existing.visibility, prop.visibility);
306 existing.is_method = existing.is_method && prop.is_method;
308 } else {
309 let new_idx = self.properties.len();
310 self.prop_index.insert(prop.name, new_idx);
311 self.properties.push(prop.clone());
312 }
313 }
314
315 if let Some(ref idx) = shape.string_index {
317 if let Some(existing) = &mut self.string_index {
318 existing.value_type = self
320 .interner
321 .intersect_types_raw2(existing.value_type, idx.value_type);
322 existing.readonly = existing.readonly || idx.readonly;
324 } else {
325 self.string_index = Some(idx.clone());
326 }
327 }
328
329 if let Some(ref idx) = shape.number_index {
331 if let Some(existing) = &mut self.number_index {
332 existing.value_type = self
334 .interner
335 .intersect_types_raw2(existing.value_type, idx.value_type);
336 existing.readonly = existing.readonly || idx.readonly;
338 } else {
339 self.number_index = Some(idx.clone());
340 }
341 }
342 }
343}
344
345#[cfg(test)]
346#[path = "../../tests/objects_tests.rs"]
347mod tests;