Skip to main content

tsz_solver/evaluate_rules/
keyof.rs

1//! keyof operator evaluation.
2//!
3//! Handles TypeScript's keyof operator: `keyof T`
4
5use crate::TypeDatabase;
6use crate::subtype::TypeResolver;
7use crate::types::{IntrinsicKind, LiteralValue, TupleElement, TypeData, TypeId, TypeListId};
8use rustc_hash::FxHashSet;
9use tsz_common::interner::Atom;
10
11use super::super::evaluate::{
12    ARRAY_METHODS_RETURN_ANY, ARRAY_METHODS_RETURN_BOOLEAN, ARRAY_METHODS_RETURN_NUMBER,
13    ARRAY_METHODS_RETURN_STRING, ARRAY_METHODS_RETURN_VOID, TypeEvaluator,
14};
15
16/// Tracks the types of keys found during keyof evaluation.
17pub(crate) struct KeyofKeySet {
18    pub has_string: bool,
19    pub has_number: bool,
20    pub has_symbol: bool,
21    pub string_literals: FxHashSet<Atom>,
22}
23
24impl KeyofKeySet {
25    pub fn new() -> Self {
26        Self {
27            has_string: false,
28            has_number: false,
29            has_symbol: false,
30            string_literals: FxHashSet::default(),
31        }
32    }
33
34    pub fn insert_type(&mut self, interner: &dyn TypeDatabase, type_id: TypeId) -> bool {
35        let Some(key) = interner.lookup(type_id) else {
36            return false;
37        };
38
39        match key {
40            TypeData::Union(members) => {
41                let members = interner.type_list(members);
42                members
43                    .iter()
44                    .all(|&member| self.insert_type(interner, member))
45            }
46            TypeData::Intrinsic(kind) => match kind {
47                IntrinsicKind::String => {
48                    self.has_string = true;
49                    true
50                }
51                IntrinsicKind::Number => {
52                    self.has_number = true;
53                    true
54                }
55                IntrinsicKind::Symbol => {
56                    self.has_symbol = true;
57                    true
58                }
59                IntrinsicKind::Never => true,
60                _ => false,
61            },
62            TypeData::Literal(LiteralValue::String(atom)) => {
63                self.string_literals.insert(atom);
64                true
65            }
66            _ => false,
67        }
68    }
69}
70
71impl<'a, R: TypeResolver> TypeEvaluator<'a, R> {
72    /// Helper to recursively evaluate keyof while respecting depth limits.
73    /// Creates a `KeyOf` type and evaluates it through the main `evaluate()` method.
74    fn recurse_keyof(&mut self, operand: TypeId) -> TypeId {
75        let keyof = self.interner().keyof(operand);
76        self.evaluate(keyof)
77    }
78
79    /// Evaluate keyof T - extract the keys of an object type
80    pub fn evaluate_keyof(&mut self, operand: TypeId) -> TypeId {
81        // CRITICAL: Handle TemplateLiteral BEFORE Union to avoid incorrect intersection.
82        // Template literals that expand to unions should return apparent keys of string,
83        // not the intersection of individual literal keys.
84        if let Some(TypeData::TemplateLiteral(_)) = self.interner().lookup(operand) {
85            return self.apparent_primitive_keyof(IntrinsicKind::String);
86        }
87
88        // CRITICAL: Handle Union types BEFORE general evaluation to avoid union simplification.
89        // keyof (A | B) = keyof A & keyof B (distributive contravariance)
90        // If we call evaluate(operand) first, unions get simplified and we lose members.
91        // See test_keyof_union_string_index_and_literal_narrows
92        if let Some(TypeData::Union(members)) = self.interner().lookup(operand) {
93            let member_list = self.interner().type_list(members);
94
95            // Recursively compute keyof for each member (this resolves Lazy/Ref/etc.)
96            let mut key_types: Vec<TypeId> = Vec::with_capacity(member_list.len());
97            for &member in member_list.iter() {
98                key_types.push(self.recurse_keyof(member));
99            }
100
101            // keyof (A | B) = keyof A & keyof B - compute intersection of all key sets
102            // Prefer explicit key-set intersection to avoid opaque literal intersections
103            if let Some(intersection) = self.intersect_keyof_sets(&key_types) {
104                intersection
105            } else {
106                // Fallback: use general intersection
107                self.interner().intersection(key_types)
108            }
109        } else {
110            // For non-union types, evaluate normally
111            let evaluated_operand = self.evaluate(operand);
112
113            let key = match self.interner().lookup(evaluated_operand) {
114                Some(k) => k,
115                None => return TypeId::NEVER,
116            };
117
118            match key {
119                TypeData::ReadonlyType(inner) => self.recurse_keyof(inner),
120                TypeData::TypeQuery(sym) => {
121                    // Resolve typeof query before computing keyof
122                    let resolved = self.resolver().resolve_symbol_ref(sym, self.interner());
123                    if let Some(resolved) = resolved {
124                        self.recurse_keyof(resolved)
125                    } else {
126                        TypeId::ERROR
127                    }
128                }
129                TypeData::TypeParameter(param) | TypeData::Infer(param) => {
130                    if let Some(constraint) = param.constraint {
131                        if constraint == evaluated_operand {
132                            self.interner().keyof(operand)
133                        } else {
134                            self.recurse_keyof(constraint)
135                        }
136                    } else {
137                        self.interner().keyof(operand)
138                    }
139                }
140                TypeData::Object(shape_id) => {
141                    let shape = self.interner().object_shape(shape_id);
142                    if shape.properties.is_empty() {
143                        return TypeId::NEVER;
144                    }
145                    let key_types: Vec<TypeId> = shape
146                        .properties
147                        .iter()
148                        .map(|p| self.interner().literal_string_atom(p.name))
149                        .collect();
150                    self.interner().union(key_types)
151                }
152                TypeData::ObjectWithIndex(shape_id) => {
153                    let shape = self.interner().object_shape(shape_id);
154                    let mut key_types: Vec<TypeId> = shape
155                        .properties
156                        .iter()
157                        .map(|p| self.interner().literal_string_atom(p.name))
158                        .collect();
159
160                    if shape.string_index.is_some() {
161                        key_types.push(TypeId::STRING);
162                        key_types.push(TypeId::NUMBER);
163                    } else if shape.number_index.is_some() {
164                        key_types.push(TypeId::NUMBER);
165                    }
166
167                    if key_types.is_empty() {
168                        TypeId::NEVER
169                    } else {
170                        self.interner().union(key_types)
171                    }
172                }
173                TypeData::Array(_) => self.interner().union(self.array_keyof_keys()),
174                TypeData::Tuple(elements) => {
175                    let elements = self.interner().tuple_list(elements);
176                    let mut key_types: Vec<TypeId> = Vec::new();
177                    self.append_tuple_indices(&elements, 0, &mut key_types);
178                    let mut array_keys = self.array_keyof_keys();
179                    key_types.append(&mut array_keys);
180                    if key_types.is_empty() {
181                        return TypeId::NEVER;
182                    }
183                    self.interner().union(key_types)
184                }
185                TypeData::Intrinsic(kind) => match kind {
186                    IntrinsicKind::Any => {
187                        // keyof any = string | number | symbol
188                        self.interner()
189                            .union3(TypeId::STRING, TypeId::NUMBER, TypeId::SYMBOL)
190                    }
191                    IntrinsicKind::Unknown => {
192                        // keyof unknown = never
193                        TypeId::NEVER
194                    }
195                    IntrinsicKind::Never
196                    | IntrinsicKind::Void
197                    | IntrinsicKind::Null
198                    | IntrinsicKind::Undefined
199                    | IntrinsicKind::Object
200                    | IntrinsicKind::Function => TypeId::NEVER,
201                    IntrinsicKind::String
202                    | IntrinsicKind::Number
203                    | IntrinsicKind::Boolean
204                    | IntrinsicKind::Bigint
205                    | IntrinsicKind::Symbol => self.apparent_primitive_keyof(kind),
206                },
207                TypeData::Literal(literal) => {
208                    if let Some(kind) = self.apparent_literal_kind(&literal) {
209                        self.apparent_primitive_keyof(kind)
210                    } else {
211                        self.interner().keyof(operand)
212                    }
213                }
214                TypeData::TemplateLiteral(_) => {
215                    self.apparent_primitive_keyof(IntrinsicKind::String)
216                }
217                // NOTE: Union is handled at the top of this function to avoid union simplification
218                TypeData::Intersection(members) => {
219                    // keyof (A & B) = keyof A | keyof B (covariance)
220                    self.keyof_intersection(members, operand)
221                }
222                // CRITICAL: Handle Lazy (type aliases) by attempting resolution via resolver
223                TypeData::Lazy(def_id) => {
224                    match self.resolver().resolve_lazy(def_id, self.interner()) {
225                        Some(resolved) => {
226                            // Recursively compute keyof of the resolved type
227                            self.recurse_keyof(resolved)
228                        }
229                        None => {
230                            // Keep as deferred KeyOf if resolution fails
231                            self.interner().keyof(operand)
232                        }
233                    }
234                }
235                // CRITICAL: Handle Application (generic types) by evaluating them first
236                TypeData::Application(_app_id) => {
237                    // Evaluate the application to get the instantiated type
238                    let evaluated = self.evaluate(evaluated_operand);
239                    // Then compute keyof of the evaluated result
240                    self.recurse_keyof(evaluated)
241                }
242                // For other types (type parameters, etc.), keep as KeyOf (deferred)
243                _ => self.interner().keyof(operand),
244            }
245        }
246    }
247
248    /// Compute keyof for an intersection type: keyof (A & B) = keyof A | keyof B
249    pub(crate) fn keyof_intersection(&mut self, members: TypeListId, _operand: TypeId) -> TypeId {
250        let members = self.interner().type_list(members);
251        // Use recurse_keyof to respect depth limits
252        // Use loop instead of closure to allow mutable self access
253        let mut key_sets: Vec<TypeId> = Vec::with_capacity(members.len());
254        for &m in members.iter() {
255            key_sets.push(self.recurse_keyof(m));
256        }
257        self.interner().union(key_sets)
258    }
259
260    /// Get the keyof keys for an array type (includes all array methods and number index).
261    pub(crate) fn array_keyof_keys(&self) -> Vec<TypeId> {
262        let mut keys = Vec::new();
263        keys.push(TypeId::NUMBER);
264        keys.push(self.interner().literal_string("length"));
265        for &name in ARRAY_METHODS_RETURN_ANY {
266            keys.push(self.interner().literal_string(name));
267        }
268        for &name in ARRAY_METHODS_RETURN_BOOLEAN {
269            keys.push(self.interner().literal_string(name));
270        }
271        for &name in ARRAY_METHODS_RETURN_NUMBER {
272            keys.push(self.interner().literal_string(name));
273        }
274        for &name in ARRAY_METHODS_RETURN_VOID {
275            keys.push(self.interner().literal_string(name));
276        }
277        for &name in ARRAY_METHODS_RETURN_STRING {
278            keys.push(self.interner().literal_string(name));
279        }
280        keys
281    }
282
283    /// Append tuple indices as string literal keys to the output vector.
284    /// Returns the next index to use, or None if a rest element prevents fixed indexing.
285    pub(crate) fn append_tuple_indices(
286        &self,
287        elements: &[TupleElement],
288        base: usize,
289        out: &mut Vec<TypeId>,
290    ) -> Option<usize> {
291        let mut index = base;
292
293        for element in elements {
294            if element.rest {
295                match self.interner().lookup(element.type_id) {
296                    Some(TypeData::Tuple(rest_elements)) => {
297                        let rest_elements = self.interner().tuple_list(rest_elements);
298                        match self.append_tuple_indices(&rest_elements, index, out) {
299                            Some(next) => {
300                                index = next;
301                                continue;
302                            }
303                            None => return None,
304                        }
305                    }
306                    _ => return None,
307                }
308            }
309            out.push(self.interner().literal_string(&index.to_string()));
310            index += 1;
311        }
312
313        Some(index)
314    }
315
316    /// Compute the intersection of multiple keyof key sets.
317    /// Returns None if the intersection cannot be computed (e.g., non-literal keys).
318    pub(crate) fn intersect_keyof_sets(&self, key_sets: &[TypeId]) -> Option<TypeId> {
319        let mut parsed_sets = Vec::with_capacity(key_sets.len());
320        for &key_set in key_sets {
321            let mut parsed = KeyofKeySet::new();
322            if !parsed.insert_type(self.interner(), key_set) {
323                return None;
324            }
325            parsed_sets.push(parsed);
326        }
327
328        let mut all_string = true;
329        let mut string_possible = true;
330        let mut common_literals: Option<FxHashSet<Atom>> = None;
331        let mut all_number = true;
332        let mut all_symbol = true;
333
334        for set in &parsed_sets {
335            if set.has_string {
336                // string index signatures don't restrict literal key overlap
337            } else {
338                all_string = false;
339                if set.string_literals.is_empty() {
340                    string_possible = false;
341                } else {
342                    common_literals = Some(match common_literals {
343                        Some(mut existing) => {
344                            existing.retain(|atom| set.string_literals.contains(atom));
345                            existing
346                        }
347                        None => set.string_literals.clone(),
348                    });
349                }
350            }
351
352            if !set.has_number {
353                all_number = false;
354            }
355            if !set.has_symbol {
356                all_symbol = false;
357            }
358        }
359
360        let mut result_keys = Vec::new();
361        if string_possible {
362            if all_string {
363                result_keys.push(TypeId::STRING);
364            } else if let Some(common) = common_literals {
365                for atom in common {
366                    result_keys.push(self.interner().literal_string_atom(atom));
367                }
368            }
369        }
370        if all_number {
371            result_keys.push(TypeId::NUMBER);
372        }
373        if all_symbol {
374            result_keys.push(TypeId::SYMBOL);
375        }
376
377        if result_keys.is_empty() {
378            Some(TypeId::NEVER)
379        } else if result_keys.len() == 1 {
380            Some(result_keys[0])
381        } else {
382            Some(self.interner().union(result_keys))
383        }
384    }
385}