Skip to main content

smelt_stdlib/
recognition.rs

1//! Exact source-call recognition metadata shared by frontends.
2
3use crate::RuleId;
4
5/// Exact syntactic call name that can be recognized without semantic inspection.
6#[derive(Clone, Copy, Debug, Eq, PartialEq)]
7#[non_exhaustive]
8pub struct CallRecognition {
9    /// Static namespace receiver, or `None` for a global free function.
10    pub receiver: Option<&'static str>,
11    /// Function or static method name.
12    pub member: &'static str,
13    /// Shared rule selected by this exact source spelling.
14    pub rule: RuleId,
15}
16
17/// TypeScript receiver category used when recognition depends on lowered type shape.
18#[derive(Clone, Copy, Debug, Eq, PartialEq)]
19#[non_exhaustive]
20pub enum TypeScriptReceiverKind {
21    /// A source `Map<K, V>` value, represented internally as a dictionary.
22    Map,
23    /// A source `Set<T>` value.
24    Set,
25}
26
27/// Receiver-method call shape recognized after a frontend knows the receiver type.
28#[derive(Clone, Copy, Debug, Eq, PartialEq)]
29#[non_exhaustive]
30pub struct MethodRecognition {
31    /// Receiver category selected by frontend type lowering.
32    pub receiver: TypeScriptReceiverKind,
33    /// Source method name.
34    pub member: &'static str,
35    /// Shared rule selected by this receiver/member pair.
36    pub rule: RuleId,
37}
38
39/// Exact TypeScript call spellings that are safe to recognize before type lowering.
40///
41/// Receiver methods are deliberately absent because names such as `map`, `test`,
42/// and `toString` require receiver types or source shapes to select a lowering.
43pub const TYPESCRIPT_CALLS: &[CallRecognition] = &[
44    free("fetch", RuleId::TsFetch),
45    free("structuredClone", RuleId::TsStructuredClone),
46    free("String", RuleId::TsPrimitiveCast),
47    free("Number", RuleId::TsPrimitiveCast),
48    free("Boolean", RuleId::TsPrimitiveCast),
49    free("BigInt", RuleId::TsPrimitiveCast),
50    free("parseFloat", RuleId::TsPrimitiveCast),
51    free("parseInt", RuleId::TsPrimitiveCast),
52    free("isNaN", RuleId::TsNumberPredicate),
53    free("Symbol", RuleId::TsSymbol),
54    static_call("JSON", "stringify", RuleId::TsJsonStringify),
55    static_call("JSON", "parse", RuleId::TsJsonParse),
56    static_call("Date", "now", RuleId::TsDateNow),
57    static_call("Math", "random", RuleId::TsMathRandom),
58    static_call("Math", "abs", RuleId::TsMathNumeric),
59    static_call("Math", "floor", RuleId::TsMathNumeric),
60    static_call("Math", "ceil", RuleId::TsMathNumeric),
61    static_call("Math", "round", RuleId::TsMathNumeric),
62    static_call("Math", "trunc", RuleId::TsMathNumeric),
63    static_call("Math", "max", RuleId::TsMathNumeric),
64    static_call("Math", "min", RuleId::TsMathNumeric),
65    static_call("Math", "hypot", RuleId::TsMathNumeric),
66    static_call("Math", "sqrt", RuleId::TsMathNumeric),
67    static_call("Math", "cbrt", RuleId::TsMathNumeric),
68    static_call("Math", "sign", RuleId::TsMathNumeric),
69    static_call("Math", "sin", RuleId::TsMathNumeric),
70    static_call("Math", "cos", RuleId::TsMathNumeric),
71    static_call("Math", "tan", RuleId::TsMathNumeric),
72    static_call("Math", "asin", RuleId::TsMathNumeric),
73    static_call("Math", "acos", RuleId::TsMathNumeric),
74    static_call("Math", "atan", RuleId::TsMathNumeric),
75    static_call("Math", "log", RuleId::TsMathNumeric),
76    static_call("Math", "log10", RuleId::TsMathNumeric),
77    static_call("Math", "log2", RuleId::TsMathNumeric),
78    static_call("Math", "exp", RuleId::TsMathNumeric),
79    static_call("Math", "pow", RuleId::TsMathNumeric),
80    static_call("Math", "atan2", RuleId::TsMathNumeric),
81    static_call("Number", "isFinite", RuleId::TsNumberPredicate),
82    static_call("Number", "isInteger", RuleId::TsNumberPredicate),
83    static_call("Number", "isNaN", RuleId::TsNumberPredicate),
84    static_call("Number", "parseFloat", RuleId::TsNumberParseFloat),
85    static_call("Number", "parseInt", RuleId::TsNumberParseInt),
86    static_call("Promise", "resolve", RuleId::TsPromiseStatic),
87    static_call("Promise", "all", RuleId::TsPromiseStatic),
88    static_call("Promise", "race", RuleId::TsPromiseStatic),
89    static_call("Promise", "allSettled", RuleId::TsPromiseStatic),
90    static_call("Symbol", "for", RuleId::TsSymbol),
91    static_call("Object", "is", RuleId::TsObjectStatic),
92    static_call("Object", "fromEntries", RuleId::TsObjectStatic),
93    static_call("Object", "create", RuleId::TsObjectStatic),
94    static_call("Object", "getPrototypeOf", RuleId::TsObjectStatic),
95    static_call("Array", "isArray", RuleId::TsArrayStatic),
96    static_call("Array", "from", RuleId::TsArrayStatic),
97    static_call("ArrayBuffer", "isView", RuleId::TsArrayStatic),
98    static_call("Buffer", "from", RuleId::TsBufferStatic),
99    static_call("Buffer", "alloc", RuleId::TsBufferStatic),
100    static_call("Buffer", "concat", RuleId::TsBufferStatic),
101    static_call("Buffer", "isBuffer", RuleId::TsBufferStatic),
102];
103
104/// TypeScript receiver-method spellings keyed by known receiver type.
105pub const TYPESCRIPT_METHODS: &[MethodRecognition] = &[
106    method(TypeScriptReceiverKind::Map, "has", RuleId::TsMapHas),
107    method(TypeScriptReceiverKind::Map, "get", RuleId::TsMapGet),
108    method(TypeScriptReceiverKind::Map, "set", RuleId::TsMapMutation),
109    method(TypeScriptReceiverKind::Map, "delete", RuleId::TsMapMutation),
110    method(TypeScriptReceiverKind::Map, "clear", RuleId::TsMapMutation),
111    method(TypeScriptReceiverKind::Map, "keys", RuleId::TsMapProjection),
112    method(
113        TypeScriptReceiverKind::Map,
114        "values",
115        RuleId::TsMapProjection,
116    ),
117    method(
118        TypeScriptReceiverKind::Map,
119        "entries",
120        RuleId::TsMapProjection,
121    ),
122    method(TypeScriptReceiverKind::Set, "has", RuleId::TsSetHas),
123    method(TypeScriptReceiverKind::Set, "add", RuleId::TsSetMutation),
124    method(TypeScriptReceiverKind::Set, "delete", RuleId::TsSetMutation),
125    method(TypeScriptReceiverKind::Set, "clear", RuleId::TsSetMutation),
126    method(TypeScriptReceiverKind::Set, "keys", RuleId::TsSetProjection),
127    method(
128        TypeScriptReceiverKind::Set,
129        "values",
130        RuleId::TsSetProjection,
131    ),
132    method(
133        TypeScriptReceiverKind::Set,
134        "entries",
135        RuleId::TsSetProjection,
136    ),
137];
138
139/// Return the shared rule for an exact TypeScript global or static namespace call.
140#[must_use]
141pub fn typescript_call_rule(receiver: Option<&str>, member: &str) -> Option<RuleId> {
142    TYPESCRIPT_CALLS
143        .iter()
144        .find(|entry| entry.receiver == receiver && entry.member == member)
145        .map(|entry| entry.rule)
146}
147
148/// Return the shared rule for a TypeScript receiver-method call.
149#[must_use]
150pub fn typescript_method_rule(receiver: TypeScriptReceiverKind, member: &str) -> Option<RuleId> {
151    TYPESCRIPT_METHODS
152        .iter()
153        .find(|entry| entry.receiver == receiver && entry.member == member)
154        .map(|entry| entry.rule)
155}
156
157/// Build free-function recognition metadata.
158const fn free(member: &'static str, rule: RuleId) -> CallRecognition {
159    CallRecognition {
160        receiver: None,
161        member,
162        rule,
163    }
164}
165
166/// Build static namespace-call recognition metadata.
167const fn static_call(
168    receiver: &'static str,
169    member: &'static str,
170    rule: RuleId,
171) -> CallRecognition {
172    CallRecognition {
173        receiver: Some(receiver),
174        member,
175        rule,
176    }
177}
178
179/// Build receiver-method recognition metadata.
180const fn method(
181    receiver: TypeScriptReceiverKind,
182    member: &'static str,
183    rule: RuleId,
184) -> MethodRecognition {
185    MethodRecognition {
186        receiver,
187        member,
188        rule,
189    }
190}
191
192#[cfg(test)]
193mod tests {
194    use super::*;
195
196    /// Exact supported spellings map to their intended shared rule.
197    #[test]
198    fn recognizes_exact_typescript_calls() {
199        let cases = [
200            ((None, "structuredClone"), RuleId::TsStructuredClone),
201            ((Some("JSON"), "parse"), RuleId::TsJsonParse),
202            ((Some("Math"), "floor"), RuleId::TsMathNumeric),
203            ((Some("Number"), "isNaN"), RuleId::TsNumberPredicate),
204            ((Some("Promise"), "allSettled"), RuleId::TsPromiseStatic),
205            ((Some("Object"), "fromEntries"), RuleId::TsObjectStatic),
206            ((Some("Object"), "create"), RuleId::TsObjectStatic),
207            ((Some("Array"), "isArray"), RuleId::TsArrayStatic),
208        ];
209
210        for ((receiver, member), expected) in cases {
211            assert_eq!(typescript_call_rule(receiver, member), Some(expected));
212        }
213    }
214
215    /// Similar unsupported spellings do not accidentally select a rule.
216    #[test]
217    fn rejects_similar_unsupported_typescript_calls() {
218        let cases = [
219            (None, "fetcher"),
220            (Some("json"), "parse"),
221            (Some("Math"), "randomBytes"),
222            (Some("Number"), "parseDouble"),
223            (Some("Promise"), "any"),
224            (Some("Object"), "entries"),
225            (Some("Array"), "of"),
226            (Some("value"), "map"),
227        ];
228
229        for (receiver, member) in cases {
230            assert_eq!(typescript_call_rule(receiver, member), None);
231        }
232    }
233
234    /// Receiver-method rules are selected by frontend-provided receiver kind.
235    #[test]
236    fn recognizes_typescript_receiver_methods() {
237        let cases = [
238            (TypeScriptReceiverKind::Map, "has", Some(RuleId::TsMapHas)),
239            (TypeScriptReceiverKind::Map, "get", Some(RuleId::TsMapGet)),
240            (
241                TypeScriptReceiverKind::Map,
242                "set",
243                Some(RuleId::TsMapMutation),
244            ),
245            (
246                TypeScriptReceiverKind::Map,
247                "entries",
248                Some(RuleId::TsMapProjection),
249            ),
250            (TypeScriptReceiverKind::Set, "has", Some(RuleId::TsSetHas)),
251            (
252                TypeScriptReceiverKind::Set,
253                "add",
254                Some(RuleId::TsSetMutation),
255            ),
256            (
257                TypeScriptReceiverKind::Set,
258                "values",
259                Some(RuleId::TsSetProjection),
260            ),
261            (TypeScriptReceiverKind::Set, "get", None),
262            (TypeScriptReceiverKind::Map, "add", None),
263        ];
264
265        for (receiver, member, expected) in cases {
266            assert_eq!(typescript_method_rule(receiver, member), expected);
267        }
268    }
269}