Skip to main content

tsz_solver/objects/
apparent.rs

1use crate::TypeDatabase;
2use crate::types::{
3    IndexSignature, IntrinsicKind, ObjectFlags, ObjectShape, PropertyInfo, TypeId, Visibility,
4};
5
6pub enum ApparentMemberKind {
7    Value(TypeId),
8    Method(TypeId),
9}
10
11pub struct ApparentMember {
12    pub name: &'static str,
13    pub kind: ApparentMemberKind,
14}
15
16const STRING_METHODS_RETURN_STRING: &[&str] = &[
17    "anchor",
18    "at",
19    "big",
20    "blink",
21    "bold",
22    "charAt",
23    "concat",
24    "fixed",
25    "fontcolor",
26    "fontsize",
27    "italics",
28    "link",
29    "normalize",
30    "padEnd",
31    "padStart",
32    "repeat",
33    "replace",
34    "replaceAll",
35    "slice",
36    "small",
37    "strike",
38    "sub",
39    "substr",
40    "substring",
41    "sup",
42    "toLocaleLowerCase",
43    "toLocaleUpperCase",
44    "toLowerCase",
45    "toString",
46    "toUpperCase",
47    "trim",
48    "trimEnd",
49    "trimLeft",
50    "trimRight",
51    "trimStart",
52    "toWellFormed",
53    "valueOf",
54];
55const STRING_METHODS_RETURN_NUMBER: &[&str] = &[
56    "charCodeAt",
57    "codePointAt",
58    "indexOf",
59    "lastIndexOf",
60    "localeCompare",
61    "search",
62];
63const STRING_METHODS_RETURN_BOOLEAN: &[&str] =
64    &["endsWith", "includes", "isWellFormed", "startsWith"];
65const STRING_METHODS_RETURN_ANY: &[&str] = &["match", "matchAll"];
66const STRING_METHODS_RETURN_STRING_ARRAY: &[&str] = &["split"];
67
68const NUMBER_METHODS_RETURN_STRING: &[&str] = &[
69    "toExponential",
70    "toFixed",
71    "toLocaleString",
72    "toPrecision",
73    "toString",
74];
75
76const BOOLEAN_METHODS_RETURN_STRING: &[&str] = &["toLocaleString", "toString"];
77
78const BIGINT_METHODS_RETURN_STRING: &[&str] = &["toLocaleString", "toString"];
79
80const OBJECT_METHODS_RETURN_BOOLEAN: &[&str] =
81    &["hasOwnProperty", "isPrototypeOf", "propertyIsEnumerable"];
82const OBJECT_METHODS_RETURN_STRING: &[&str] = &["toString"];
83const OBJECT_METHODS_RETURN_ANY: &[&str] = &["valueOf"];
84
85fn is_member(name: &str, list: &[&str]) -> bool {
86    list.contains(&name)
87}
88
89fn object_member_kind(name: &str, include_to_locale: bool) -> Option<ApparentMemberKind> {
90    if name == "constructor" {
91        return Some(ApparentMemberKind::Value(TypeId::FUNCTION));
92    }
93    if name == "toString" {
94        return Some(ApparentMemberKind::Method(TypeId::STRING));
95    }
96    if name == "valueOf" {
97        return Some(ApparentMemberKind::Method(TypeId::ANY));
98    }
99    if is_member(name, OBJECT_METHODS_RETURN_BOOLEAN) {
100        return Some(ApparentMemberKind::Method(TypeId::BOOLEAN));
101    }
102    if is_member(name, OBJECT_METHODS_RETURN_STRING) {
103        return Some(ApparentMemberKind::Method(TypeId::STRING));
104    }
105    if is_member(name, OBJECT_METHODS_RETURN_ANY) {
106        return Some(ApparentMemberKind::Method(TypeId::ANY));
107    }
108    if include_to_locale && name == "toLocaleString" {
109        return Some(ApparentMemberKind::Method(TypeId::STRING));
110    }
111    None
112}
113
114fn push_object_members(members: &mut Vec<ApparentMember>, include_to_locale: bool) {
115    members.push(ApparentMember {
116        name: "constructor",
117        kind: ApparentMemberKind::Value(TypeId::ANY),
118    });
119    members.push(ApparentMember {
120        name: "toString",
121        kind: ApparentMemberKind::Method(TypeId::STRING),
122    });
123    members.push(ApparentMember {
124        name: "valueOf",
125        kind: ApparentMemberKind::Method(TypeId::ANY),
126    });
127    for &name in OBJECT_METHODS_RETURN_BOOLEAN {
128        members.push(ApparentMember {
129            name,
130            kind: ApparentMemberKind::Method(TypeId::BOOLEAN),
131        });
132    }
133    for &name in OBJECT_METHODS_RETURN_STRING {
134        members.push(ApparentMember {
135            name,
136            kind: ApparentMemberKind::Method(TypeId::STRING),
137        });
138    }
139    for &name in OBJECT_METHODS_RETURN_ANY {
140        members.push(ApparentMember {
141            name,
142            kind: ApparentMemberKind::Method(TypeId::ANY),
143        });
144    }
145    if include_to_locale {
146        members.push(ApparentMember {
147            name: "toLocaleString",
148            kind: ApparentMemberKind::Method(TypeId::STRING),
149        });
150    }
151}
152
153pub fn apparent_object_member_kind(name: &str) -> Option<ApparentMemberKind> {
154    object_member_kind(name, true)
155}
156
157pub fn apparent_primitive_member_kind(
158    interner: &dyn TypeDatabase,
159    kind: IntrinsicKind,
160    name: &str,
161) -> Option<ApparentMemberKind> {
162    match kind {
163        IntrinsicKind::String => {
164            if name == "length" {
165                return Some(ApparentMemberKind::Value(TypeId::NUMBER));
166            }
167            if is_member(name, STRING_METHODS_RETURN_STRING) {
168                return Some(ApparentMemberKind::Method(TypeId::STRING));
169            }
170            if is_member(name, STRING_METHODS_RETURN_NUMBER) {
171                return Some(ApparentMemberKind::Method(TypeId::NUMBER));
172            }
173            if is_member(name, STRING_METHODS_RETURN_BOOLEAN) {
174                return Some(ApparentMemberKind::Method(TypeId::BOOLEAN));
175            }
176            if is_member(name, STRING_METHODS_RETURN_ANY) {
177                return Some(ApparentMemberKind::Method(TypeId::ANY));
178            }
179            if is_member(name, STRING_METHODS_RETURN_STRING_ARRAY) {
180                let string_array = interner.array(TypeId::STRING);
181                return Some(ApparentMemberKind::Method(string_array));
182            }
183            object_member_kind(name, true)
184        }
185        IntrinsicKind::Number => {
186            if is_member(name, NUMBER_METHODS_RETURN_STRING) {
187                return Some(ApparentMemberKind::Method(TypeId::STRING));
188            }
189            if name == "valueOf" {
190                return Some(ApparentMemberKind::Method(TypeId::NUMBER));
191            }
192            object_member_kind(name, false)
193        }
194        IntrinsicKind::Boolean => {
195            if is_member(name, BOOLEAN_METHODS_RETURN_STRING) {
196                return Some(ApparentMemberKind::Method(TypeId::STRING));
197            }
198            if name == "valueOf" {
199                return Some(ApparentMemberKind::Method(TypeId::BOOLEAN));
200            }
201            object_member_kind(name, false)
202        }
203        IntrinsicKind::Bigint => {
204            if is_member(name, BIGINT_METHODS_RETURN_STRING) {
205                return Some(ApparentMemberKind::Method(TypeId::STRING));
206            }
207            if name == "valueOf" {
208                return Some(ApparentMemberKind::Method(TypeId::BIGINT));
209            }
210            object_member_kind(name, false)
211        }
212        IntrinsicKind::Symbol => {
213            if name == "description" {
214                let description = interner.union2(TypeId::STRING, TypeId::UNDEFINED);
215                return Some(ApparentMemberKind::Value(description));
216            }
217            if name == "toString" {
218                return Some(ApparentMemberKind::Method(TypeId::STRING));
219            }
220            if name == "valueOf" {
221                return Some(ApparentMemberKind::Method(TypeId::SYMBOL));
222            }
223            object_member_kind(name, true)
224        }
225        IntrinsicKind::Object => object_member_kind(name, true),
226        _ => None,
227    }
228}
229
230pub fn apparent_primitive_members(
231    interner: &dyn TypeDatabase,
232    kind: IntrinsicKind,
233) -> Vec<ApparentMember> {
234    let mut members = Vec::new();
235
236    match kind {
237        IntrinsicKind::String => {
238            members.push(ApparentMember {
239                name: "length",
240                kind: ApparentMemberKind::Value(TypeId::NUMBER),
241            });
242            for &name in STRING_METHODS_RETURN_STRING {
243                members.push(ApparentMember {
244                    name,
245                    kind: ApparentMemberKind::Method(TypeId::STRING),
246                });
247            }
248            for &name in STRING_METHODS_RETURN_NUMBER {
249                members.push(ApparentMember {
250                    name,
251                    kind: ApparentMemberKind::Method(TypeId::NUMBER),
252                });
253            }
254            for &name in STRING_METHODS_RETURN_BOOLEAN {
255                members.push(ApparentMember {
256                    name,
257                    kind: ApparentMemberKind::Method(TypeId::BOOLEAN),
258                });
259            }
260            for &name in STRING_METHODS_RETURN_ANY {
261                members.push(ApparentMember {
262                    name,
263                    kind: ApparentMemberKind::Method(TypeId::ANY),
264                });
265            }
266            let string_array = interner.array(TypeId::STRING);
267            for &name in STRING_METHODS_RETURN_STRING_ARRAY {
268                members.push(ApparentMember {
269                    name,
270                    kind: ApparentMemberKind::Method(string_array),
271                });
272            }
273            push_object_members(&mut members, true);
274        }
275        IntrinsicKind::Number => {
276            for &name in NUMBER_METHODS_RETURN_STRING {
277                members.push(ApparentMember {
278                    name,
279                    kind: ApparentMemberKind::Method(TypeId::STRING),
280                });
281            }
282            members.push(ApparentMember {
283                name: "valueOf",
284                kind: ApparentMemberKind::Method(TypeId::NUMBER),
285            });
286            push_object_members(&mut members, false);
287        }
288        IntrinsicKind::Boolean => {
289            for &name in BOOLEAN_METHODS_RETURN_STRING {
290                members.push(ApparentMember {
291                    name,
292                    kind: ApparentMemberKind::Method(TypeId::STRING),
293                });
294            }
295            members.push(ApparentMember {
296                name: "valueOf",
297                kind: ApparentMemberKind::Method(TypeId::BOOLEAN),
298            });
299            push_object_members(&mut members, false);
300        }
301        IntrinsicKind::Bigint => {
302            for &name in BIGINT_METHODS_RETURN_STRING {
303                members.push(ApparentMember {
304                    name,
305                    kind: ApparentMemberKind::Method(TypeId::STRING),
306                });
307            }
308            members.push(ApparentMember {
309                name: "valueOf",
310                kind: ApparentMemberKind::Method(TypeId::BIGINT),
311            });
312            push_object_members(&mut members, false);
313        }
314        IntrinsicKind::Symbol => {
315            let description = interner.union2(TypeId::STRING, TypeId::UNDEFINED);
316            members.push(ApparentMember {
317                name: "description",
318                kind: ApparentMemberKind::Value(description),
319            });
320            members.push(ApparentMember {
321                name: "toString",
322                kind: ApparentMemberKind::Method(TypeId::STRING),
323            });
324            members.push(ApparentMember {
325                name: "valueOf",
326                kind: ApparentMemberKind::Method(TypeId::SYMBOL),
327            });
328            push_object_members(&mut members, true);
329        }
330        IntrinsicKind::Object => {
331            push_object_members(&mut members, true);
332        }
333        _ => {}
334    }
335
336    members
337}
338
339/// Build an `ObjectShape` for a primitive type's apparent members.
340///
341/// This is the shared implementation used by both the evaluator and the subtype
342/// checker.  The `make_method_type` callback controls how method signatures are
343/// created — the evaluator passes `make_apparent_method_type` (which includes a
344/// `...any[]` rest param for full evaluation semantics), while the subtype
345/// checker may use a simpler shape.
346pub fn apparent_primitive_shape(
347    db: &dyn TypeDatabase,
348    kind: IntrinsicKind,
349    make_method_type: impl Fn(&dyn TypeDatabase, TypeId) -> TypeId,
350) -> ObjectShape {
351    let members = apparent_primitive_members(db, kind);
352    let mut properties = Vec::with_capacity(members.len());
353
354    for member in members {
355        let name = db.intern_string(member.name);
356        match member.kind {
357            ApparentMemberKind::Value(type_id) => properties.push(PropertyInfo {
358                name,
359                type_id,
360                write_type: type_id,
361                optional: false,
362                readonly: false,
363                is_method: false,
364                visibility: Visibility::Public,
365                parent_id: None,
366            }),
367            ApparentMemberKind::Method(return_type) => {
368                let method_ty = make_method_type(db, return_type);
369                properties.push(PropertyInfo {
370                    name,
371                    type_id: method_ty,
372                    write_type: method_ty,
373                    optional: false,
374                    readonly: false,
375                    is_method: true,
376                    visibility: Visibility::Public,
377                    parent_id: None,
378                });
379            }
380        }
381    }
382    properties.sort_by_key(|a| a.name);
383
384    let number_index = (kind == IntrinsicKind::String).then_some(IndexSignature {
385        key_type: TypeId::NUMBER,
386        value_type: TypeId::STRING,
387        readonly: false,
388    });
389
390    ObjectShape {
391        flags: ObjectFlags::empty(),
392        properties,
393        string_index: None,
394        number_index,
395        symbol: None,
396    }
397}