Skip to main content

submilli_engine/runtime/prelude/map/
install.rs

1//! ABI wiring for the Rust `Map` / `MapConstructor` methods: registers each
2//! method under its dispatch key and declares the value symbols codegen routes
3//! through. The operations live in the parent module.
4//!
5//! `Map#size` is intentionally **not** ported — its Wasm getter is a pure
6//! `struct.get` of the backing's size field and works unchanged on host-built
7//! `$MapBacking` instances, so it stays on the Wasm path.
8
9use crate::runtime::host::{abi_arg, abi_result};
10use wasmtime::{FuncType, HeapType, Linker, RefType, ValType};
11
12use crate::runtime::StoreData;
13use crate::runtime::host::{register_host_fn, register_host_fn_async};
14use crate::runtime::intrinsic_types::build_intrinsic_types;
15use crate::runtime::prelude::{MODULE_NAME, closure, declare_method};
16use crate::{MangledName, PackageDeclaration, Param, Type};
17
18/// The dispatch key codegen looks up for `Map#<method>`.
19fn method_key(method: &str) -> MangledName {
20    crate::mangle::extend(&crate::mangle::prelude("Map"), method)
21}
22
23/// The dispatch key for a `MapConstructor` static (`new`).
24fn ctor_key(method: &str) -> MangledName {
25    crate::mangle::extend(&crate::mangle::prelude("MapConstructor"), method)
26}
27
28pub fn install(linker: &mut Linker<StoreData>) -> wasmtime::Result<()> {
29    let engine = linker.engine().clone();
30    let intr = build_intrinsic_types(&engine)?;
31    let obj = ValType::Ref(RefType::new(
32        true,
33        HeapType::ConcreteStruct(intr.object.clone()),
34    ));
35    let boolean = ValType::I32;
36    let ft = |params: Vec<ValType>, results: Vec<ValType>| FuncType::new(&engine, params, results);
37
38    register_host_fn_async(
39        linker,
40        MODULE_NAME,
41        method_key("get"),
42        ft(vec![obj.clone(), obj.clone()], vec![obj.clone()]),
43        true,
44        |caller, params, results| {
45            Box::pin(async move {
46                *abi_result(results, 0)? =
47                    super::get(caller, abi_arg(params, 0)?, abi_arg(params, 1)?).await?;
48                Ok(())
49            })
50        },
51    )?;
52    register_host_fn_async(
53        linker,
54        MODULE_NAME,
55        method_key("set"),
56        ft(
57            vec![obj.clone(), obj.clone(), obj.clone()],
58            vec![obj.clone()],
59        ),
60        true,
61        |caller, params, results| {
62            Box::pin(async move {
63                *abi_result(results, 0)? = super::set(
64                    caller,
65                    abi_arg(params, 0)?,
66                    abi_arg(params, 1)?,
67                    abi_arg(params, 2)?,
68                )
69                .await?;
70                Ok(())
71            })
72        },
73    )?;
74    register_host_fn_async(
75        linker,
76        MODULE_NAME,
77        method_key("has"),
78        ft(vec![obj.clone(), obj.clone()], vec![boolean.clone()]),
79        true,
80        |caller, params, results| {
81            Box::pin(async move {
82                let r = super::has(caller, abi_arg(params, 0)?, abi_arg(params, 1)?).await?;
83                *abi_result(results, 0)? = wasmtime::Val::I32(i32::from(r));
84                Ok(())
85            })
86        },
87    )?;
88    register_host_fn_async(
89        linker,
90        MODULE_NAME,
91        method_key("delete"),
92        ft(vec![obj.clone(), obj.clone()], vec![boolean.clone()]),
93        true,
94        |caller, params, results| {
95            Box::pin(async move {
96                let r = super::delete(caller, abi_arg(params, 0)?, abi_arg(params, 1)?).await?;
97                *abi_result(results, 0)? = wasmtime::Val::I32(i32::from(r));
98                Ok(())
99            })
100        },
101    )?;
102    register_host_fn(
103        linker,
104        MODULE_NAME,
105        method_key("clear"),
106        ft(vec![obj.clone()], vec![]),
107        true,
108        |caller, params, _results| {
109            super::clear(caller, abi_arg(params, 0)?)?;
110            Ok(())
111        },
112    )?;
113    register_host_fn(
114        linker,
115        MODULE_NAME,
116        method_key("size"),
117        ft(vec![obj.clone()], vec![ValType::F64]),
118        true,
119        |caller, params, results| {
120            *abi_result(results, 0)? = super::size(caller, abi_arg(params, 0)?)?;
121            Ok(())
122        },
123    )?;
124    register_host_fn_async(
125        linker,
126        MODULE_NAME,
127        method_key("forEach"),
128        // The callback crosses erased, as `Array`'s do.
129        ft(vec![obj.clone(), obj.clone()], vec![]),
130        true,
131        |caller, params, _results| {
132            Box::pin(async move {
133                let f =
134                    closure::read_callback(caller, abi_arg(params, 1)?, "Map#forEach callback")?;
135                super::for_each(caller, abi_arg(params, 0)?, &f).await
136            })
137        },
138    )?;
139    let iter_ret = obj.clone();
140    register_host_fn(
141        linker,
142        MODULE_NAME,
143        method_key("keys"),
144        ft(vec![obj.clone()], vec![iter_ret.clone()]),
145        true,
146        |caller, params, results| {
147            *abi_result(results, 0)? = super::keys(caller, abi_arg(params, 0)?)?;
148            Ok(())
149        },
150    )?;
151    register_host_fn(
152        linker,
153        MODULE_NAME,
154        method_key("values"),
155        ft(vec![obj.clone()], vec![iter_ret.clone()]),
156        true,
157        |caller, params, results| {
158            *abi_result(results, 0)? = super::values(caller, abi_arg(params, 0)?)?;
159            Ok(())
160        },
161    )?;
162    for name in ["entries", "iterator"] {
163        register_host_fn(
164            linker,
165            MODULE_NAME,
166            method_key(name),
167            ft(vec![obj.clone()], vec![iter_ret.clone()]),
168            true,
169            |caller, params, results| {
170                *abi_result(results, 0)? = super::entries(caller, abi_arg(params, 0)?)?;
171                Ok(())
172            },
173        )?;
174    }
175    // `MapConstructor#new(init?)` — Static dispatch (the constructor receiver is
176    // dropped at the call site), so the host fn sees only the `entries` arg.
177    register_host_fn_async(
178        linker,
179        MODULE_NAME,
180        ctor_key("new"),
181        ft(vec![obj.clone()], vec![obj.clone()]),
182        true,
183        |caller, params, results| {
184            Box::pin(async move {
185                *abi_result(results, 0)? = super::construct(caller, abi_arg(params, 0)?).await?;
186                Ok(())
187            })
188        },
189    )?;
190    Ok(())
191}
192
193pub fn declare(defs: &mut PackageDeclaration) {
194    let k = || Type::TypeVar("K".to_string());
195    let v = || Type::TypeVar("V".to_string());
196    let map_ty = || Type::prelude_interface("Map".to_string(), vec![k(), v()]);
197    let entry = || Type::Tuple(vec![k(), v()].into());
198    let iter = |t: Type| Type::prelude_interface("Iterator".to_string(), vec![t]);
199    let map = || Param::new("map", map_ty());
200    let key = || Param::new("key", k());
201    let m = |defs: &mut PackageDeclaration, name: &str, params: Vec<Param>, ret: Type| {
202        declare_method(defs, name, method_key(name), params, ret);
203    };
204
205    m(defs, "size", vec![map()], Type::Number);
206    m(
207        defs,
208        "get",
209        vec![map(), key()],
210        Type::Union(vec![v(), Type::Undefined]),
211    );
212    m(
213        defs,
214        "set",
215        vec![map(), key(), Param::new("value", v())],
216        map_ty(),
217    );
218    m(defs, "has", vec![map(), key()], Type::Boolean);
219    m(defs, "delete", vec![map(), key()], Type::Boolean);
220    m(defs, "clear", vec![map()], Type::Void);
221
222    // The callback's slot is erased (see `install`); its type for checking calls
223    // is in `declare_types`.
224    let callback = Type::Unknown;
225    m(
226        defs,
227        "forEach",
228        vec![map(), Param::new("callback", callback)],
229        Type::Void,
230    );
231    m(defs, "keys", vec![map()], iter(k()));
232    m(defs, "values", vec![map()], iter(v()));
233    m(defs, "entries", vec![map()], iter(entry()));
234    m(defs, "iterator", vec![map()], iter(entry()));
235
236    // Static: no receiver param — the constructor receiver is dropped at the call
237    // site, so the host fn sees only `entries`.
238    declare_method(
239        defs,
240        "new",
241        ctor_key("new"),
242        vec![Param::new(
243            "entries",
244            Type::union(vec![
245                Type::Readonly(Box::new(Type::Array(Box::new(entry())))),
246                Type::prelude_interface("Iterable".to_string(), vec![entry()]),
247                Type::prelude_interface("Iterator".to_string(), vec![entry()]),
248                Type::Null,
249                Type::Undefined,
250            ]),
251        )],
252        map_ty(),
253    );
254}
255
256/// The type/interface surface this module implements — its slice of the
257/// prelude declaration (see `declaration::prelude_package_declaration`).
258#[allow(clippy::too_many_lines)]
259pub(crate) fn declare_types(defs: &mut crate::PackageDeclaration) {
260    use crate::runtime::prelude::declaration::doc;
261    use crate::{
262        Dispatch, MethodSig, Param, PropertySig, Span, Type, TypeKind, TypeSymbol, ValueKind,
263        ValueSymbol,
264    };
265    use std::collections::BTreeMap;
266    defs.types.insert(
267        "Map".to_string(),
268        TypeSymbol {
269            name: "Map".to_string(),
270            mangled_name: crate::mangle::prelude("Map"),
271            declaration_span: Span::at(crate::FileId::PRELUDE),
272            kind: TypeKind::Interface { index: None,
273                generics: vec!["K".to_string(), "V".to_string()],
274                methods: BTreeMap::from([
275                    (
276                        "get".to_string(),
277                        MethodSig {
278                            optional: false,
279                            generics: Vec::new(),
280                            params: vec![Param::new("key", Type::TypeVar("K".to_string()))],
281                            ret: Type::Union(vec![
282                                Type::TypeVar("V".to_string()),
283                                Type::Undefined,
284                            ]),
285                            predicate: None,
286                            doc: doc(
287                                "/**\n * Returns the value associated with `key`, or `undefined` if the key is not present.\n * @param key The key to look up.\n */",
288                            ),
289                        },
290                    ),
291                    (
292                        "set".to_string(),
293                        MethodSig {
294                            optional: false,
295                            generics: Vec::new(),
296                            params: vec![
297                                Param::new("key", Type::TypeVar("K".to_string())),
298                                Param::new("value", Type::TypeVar("V".to_string())),
299                            ],
300                            ret: Type::prelude_interface("Map".to_string(), vec![
301                                    Type::TypeVar("K".to_string()),
302                                    Type::TypeVar("V".to_string()),
303                                ]),
304                            predicate: None,
305                            doc: doc(
306                                "/**\n * Associates `value` with `key`, overwriting any prior value.\n * @returns The same map (for chaining).\n */",
307                            ),
308                        },
309                    ),
310                    (
311                        "has".to_string(),
312                        MethodSig {
313                            optional: false,
314                            generics: Vec::new(),
315                            params: vec![Param::new("key", Type::TypeVar("K".to_string()))],
316                            ret: Type::Boolean,
317                            predicate: None,
318                            doc: doc(
319                                "/**\n * Returns `true` when `key` is present.\n * @param key The key to test for.\n */",
320                            ),
321                        },
322                    ),
323                    (
324                        "delete".to_string(),
325                        MethodSig {
326                            optional: false,
327                            generics: Vec::new(),
328                            params: vec![Param::new("key", Type::TypeVar("K".to_string()))],
329                            ret: Type::Boolean,
330                            predicate: None,
331                            doc: doc(
332                                "/**\n * Removes `key` from the map. Returns `true` when something was actually removed.\n * @param key The key to remove.\n */",
333                            ),
334                        },
335                    ),
336                    (
337                        "keys".to_string(),
338                        MethodSig {
339                            optional: false,
340                            generics: Vec::new(),
341                            params: Vec::new(),
342                            ret: Type::prelude_interface("Iterator".to_string(), vec![Type::TypeVar("K".to_string())]),
343                            predicate: None,
344                            doc: doc(
345                                "/** Returns a lazy `Iterator<K>` over the keys in insertion order. It walks the map live: keys added later are visited, keys deleted first are skipped. */",
346                            ),
347                        },
348                    ),
349                    (
350                        "values".to_string(),
351                        MethodSig {
352                            optional: false,
353                            generics: Vec::new(),
354                            params: Vec::new(),
355                            ret: Type::prelude_interface("Iterator".to_string(), vec![Type::TypeVar("V".to_string())]),
356                            predicate: None,
357                            doc: doc(
358                                "/** Returns a lazy `Iterator<V>` over the values in insertion order. It walks the map live: entries added later are visited, entries deleted first are skipped. */",
359                            ),
360                        },
361                    ),
362                    (
363                        "entries".to_string(),
364                        MethodSig {
365                            optional: false,
366                            generics: Vec::new(),
367                            params: Vec::new(),
368                            ret: Type::prelude_interface("Iterator".to_string(), vec![Type::Tuple(vec![
369                                Type::TypeVar("K".to_string()),
370                                Type::TypeVar("V".to_string()),
371                            ].into())]),
372                            predicate: None,
373                            doc: doc(
374                                "/** Returns a lazy `Iterator<[K, V]>` over the entries — the same cursor `for-of` uses. */",
375                            ),
376                        },
377                    ),
378                    (
379                        "forEach".to_string(),
380                        MethodSig {
381                            optional: false,
382                            generics: Vec::new(),
383                            params: vec![Param::new(
384                                "callback",
385                                Type::Function {
386                                    optional: 0,
387                                    params: vec![
388                                        Type::TypeVar("V".to_string()),
389                                        Type::TypeVar("K".to_string()),
390                                        Type::prelude_interface(
391                                            "Map".to_string(),
392                                            vec![
393                                                Type::TypeVar("K".to_string()),
394                                                Type::TypeVar("V".to_string()),
395                                            ],
396                                        ),
397                                    ],
398                                    ret: Box::new(Type::Void),
399                                    predicate: None,
400                                    has_rest: false,
401                                },
402                            )],
403                            ret: Type::Void,
404                            predicate: None,
405                            doc: doc(
406                                "/**\n * Calls `callback(value, key, map)` once for each entry in insertion order, including entries the callback adds.\n * @param callback Function called once per entry — value first, key second, matching JS.\n */",
407                            ),
408                        },
409                    ),
410                    (
411                        // A live cursor over `[K, V]` pairs; this is what
412                        // makes `Map<K, V>` structurally satisfy
413                        // `Iterable<[K, V]>`.
414                        "iterator".to_string(),
415                        MethodSig {
416                            optional: false,
417                            generics: Vec::new(),
418                            params: Vec::new(),
419                            ret: Type::prelude_interface("Iterator".to_string(), vec![Type::Tuple(vec![
420                                    Type::TypeVar("K".to_string()),
421                                    Type::TypeVar("V".to_string()),
422                                ].into())]),
423                            predicate: None,
424                            doc: doc(
425                                "/** Returns a fresh `Iterator<[K, V]>` over the entries in insertion order. It walks the map live: entries added later are visited, entries deleted first are skipped. */",
426                            ),
427                        },
428                    ),
429                    (
430                        "clear".to_string(),
431                        MethodSig {
432                            optional: false,
433                            generics: Vec::new(),
434                            params: Vec::new(),
435                            ret: Type::Void,
436                            predicate: None,
437                            doc: doc("/** Removes every entry. */"),
438                        },
439                    ),
440                ]),
441                properties: BTreeMap::from([(
442                    "size".to_string(),
443                    PropertySig {
444                        ty: Type::Number,
445                        readonly: true,
446                        intrinsic: false,
447                        optional: false,
448                        doc: doc("/** The number of entries currently in the map. */"),
449                    },
450                )]),
451                dispatch: Dispatch::Direct,
452                doc: doc(
453                    "/** A hash-backed key-value collection. Keys are compared by structural equality through each key's `equals` method; lookup buckets via `hash`. Iteration follows insertion order. */",
454                ),
455            },
456        },
457    );
458
459    defs.types.insert(
460        "MapConstructor".to_string(),
461        TypeSymbol {
462            name: "MapConstructor".to_string(),
463            mangled_name: crate::mangle::prelude("MapConstructor"),
464            declaration_span: Span::at(crate::FileId::PRELUDE),
465            kind: TypeKind::Interface { index: None,
466                generics: Vec::new(),
467                methods: BTreeMap::from([(
468                    "new".to_string(),
469                    MethodSig {
470                        optional: false,
471                        generics: vec!["K".to_string(), "V".to_string()],
472                        params: vec![Param::with_default(
473                            "entries",
474                            Type::union(vec![
475                                Type::Readonly(Box::new(Type::Array(Box::new(Type::Tuple(vec![
476                                    Type::TypeVar("K".to_string()),
477                                    Type::TypeVar("V".to_string()),
478                                ].into()))))),
479                                Type::prelude_interface(
480                                    "Iterable".to_string(),
481                                    vec![Type::Tuple(vec![
482                                        Type::TypeVar("K".to_string()),
483                                        Type::TypeVar("V".to_string()),
484                                    ].into())],
485                                ),
486                                Type::prelude_interface(
487                                    "Iterator".to_string(),
488                                    vec![Type::Tuple(vec![
489                                        Type::TypeVar("K".to_string()),
490                                        Type::TypeVar("V".to_string()),
491                                    ].into())],
492                                ),
493                                Type::Null,
494                                Type::Undefined,
495                            ]),
496                            crate::DefaultValue::Undefined,
497                        )],
498                        ret: Type::prelude_interface("Map".to_string(), vec![
499                                Type::TypeVar("K".to_string()),
500                                Type::TypeVar("V".to_string()),
501                            ]),
502                        predicate: None,
503                        doc: doc(
504                            "/** Construct a `Map<K, V>`, optionally from an iterable of `[K, V]` entries: `new Map([[\"a\", 1]])`. */",
505                        ),
506                    },
507                )]),
508                properties: BTreeMap::new(),
509                dispatch: Dispatch::Static,
510                doc: doc(
511                    "/** Constructor object for `Map`. Accessed via the global `Map` binding — call `new Map<K, V>()`. */",
512                ),
513            },
514        },
515    );
516    defs.values.insert(
517        "Map".to_string(),
518        ValueSymbol {
519            name: "Map".to_string(),
520            mangled_name: crate::mangle::prelude("Map"),
521            declaration_span: Span::at(crate::FileId::PRELUDE),
522            kind: ValueKind::Const {
523                ty: Type::prelude_interface("MapConstructor".to_string(), Vec::new()),
524                doc: doc("/** The `Map` constructor — call `new Map<K, V>()`. */"),
525            },
526        },
527    );
528}