Skip to main content

submilli_engine/runtime/prelude/string/
install.rs

1//! ABI wiring for the Rust `String` methods: marshals `$string` ↔ [`Str`],
2//! registers each method under its dispatch key, and declares the value symbols
3//! codegen routes through. The string operations themselves live in the parent
4//! module — this file is the wasmtime boundary they're kept clear of.
5
6use crate::runtime::host::{abi_arg, abi_result};
7use wasmtime::{
8    ArrayRef, ArrayRefPre, ArrayType, Caller, FuncType, HeapType, Linker, RefType, Rooted,
9    StructRef, StructRefPre, StructType, Val, ValType,
10};
11
12use super::gc_text::{GcText, GcUnits, Run, allocate, fill_run};
13use super::transforms::{TrimEnds, to_lower_case, to_upper_case, trim_range};
14use super::{
15    RangeError, Str, at_index, cmp, code_point, concat_length, ends_with_window, from_char_code,
16    from_code_point, includes, index_of, is_well_formed, last_index_of, normalize, pad_length,
17    repeat_length, slice_range, starts_with_window, string_index_unit, substr_range,
18    substring_range, to_well_formed, unit_index,
19};
20use crate::runtime::StoreData;
21use crate::runtime::fuel;
22use crate::runtime::host::{
23    fatal_host_error, intrinsic_string_type, range_error, read_code_unit, read_code_units,
24    read_code_units_range, register_host_fn, register_host_fn_async, string_array_type,
25    write_boxed_number_struct, write_submilli_string_struct, write_submilli_string_struct_units,
26};
27use crate::runtime::intrinsic_types::{build_intrinsic_types, intrinsic_types};
28use crate::runtime::prelude::{MODULE_NAME, declare_method};
29use crate::{MangledName, PackageDeclaration, Param, Type};
30
31/// The dispatch key codegen looks up for `String#<method>`: the prelude's
32/// `String` interface mangled name extended by the method. The host fn's linker
33/// field and the value symbol's mangled name must both be exactly this.
34fn method_key(method: &str) -> MangledName {
35    crate::mangle::extend(&crate::mangle::prelude("String"), method)
36}
37
38/// The dispatch key for a `StringConstructor` static (`String.fromCharCode`,
39/// `String.fromCodePoint`) — the `StringConstructor` interface mangled name
40/// extended by the method.
41fn ctor_key(method: &str) -> MangledName {
42    crate::mangle::extend(&crate::mangle::prelude("StringConstructor"), method)
43}
44
45pub fn install(linker: &mut Linker<StoreData>) -> wasmtime::Result<()> {
46    let engine = linker.engine().clone();
47    let abi = StringAbi::recover(&engine)?;
48
49    // Accessors read the few units they need straight from the payload
50    // instead of copying the whole string.
51    reg_unit_to_str(linker, &engine, &abi, "charAt", unit_index)?;
52    reg_string_index(linker, &engine, &abi)?;
53    reg_unit_to_str_or_undefined(linker, &engine, &abi, "at", at_index)?;
54    reg_char_code_at(linker, &engine, &abi)?;
55    reg_code_point_at(linker, &engine, &abi)?;
56
57    reg_range_to_str(linker, &engine, &abi, "slice", slice_range)?;
58    reg_range_to_str(linker, &engine, &abi, "substring", substring_range)?;
59    reg_range_to_str(linker, &engine, &abi, "substr", substr_range)?;
60
61    reg_str_str_num_to_num(linker, &engine, &abi, "indexOf", index_of)?;
62    reg_str_str_num_to_num(linker, &engine, &abi, "lastIndexOf", last_index_of)?;
63    register_string_search_predicate(linker, &engine, &abi, "includes", includes)?;
64    reg_window_predicate(linker, &engine, &abi, "startsWith", starts_with_window)?;
65    reg_window_predicate(linker, &engine, &abi, "endsWith", ends_with_window)?;
66
67    reg_concat(linker, &engine, &abi)?;
68    reg_pad(linker, &engine, &abi, "padStart", PadSide::Start)?;
69    reg_pad(linker, &engine, &abi, "padEnd", PadSide::End)?;
70
71    reg_str_to_bool(linker, &engine, &abi, "isWellFormed", is_well_formed)?;
72    reg_str_to_str(linker, &engine, &abi, "toWellFormed", to_well_formed)?;
73
74    super::transforms::prepare_case_properties()?;
75    // Closures, because a generic `fn(&mut impl Text)` is not a fn pointer for
76    // every `GcText` lifetime.
77    reg_case_map(linker, &engine, &abi, "toUpperCase", |text| {
78        to_upper_case(text)
79    })?;
80    reg_case_map(linker, &engine, &abi, "toLowerCase", |text| {
81        to_lower_case(text)
82    })?;
83    reg_trim(linker, &engine, &abi, "trim", TrimEnds::Both)?;
84    reg_trim(linker, &engine, &abi, "trimStart", TrimEnds::Start)?;
85    reg_trim(linker, &engine, &abi, "trimEnd", TrimEnds::End)?;
86    reg_trim(linker, &engine, &abi, "trimLeft", TrimEnds::Start)?;
87    reg_trim(linker, &engine, &abi, "trimRight", TrimEnds::End)?;
88    reg_str_str_to_str_fallible(linker, &engine, &abi, "normalize", normalize)?;
89
90    reg_repeat(linker, &engine, &abi)?;
91
92    // StringConstructor statics: `Dispatch::Static`, so the receiver is dropped
93    // and the variadic args arrive packed into one `$Array` of boxed numbers.
94    let array_ref = ValType::Ref(RefType::new(
95        false,
96        HeapType::ConcreteStruct(build_intrinsic_types(&engine)?.array),
97    ));
98    register_host_fn(
99        linker,
100        MODULE_NAME,
101        ctor_key("fromCharCode"),
102        FuncType::new(&engine, [array_ref.clone()], [abi.value_type()]),
103        move |caller, params, results| {
104            let codes = read_number_array(caller, abi_arg(params, 0)?, "String.fromCharCode")?;
105            let out = from_char_code(&codes);
106            let st = write_submilli_string_struct_units(caller, out.units())?;
107            *abi_result(results, 0)? = Val::AnyRef(Some(st.to_anyref()));
108            Ok(())
109        },
110    )?;
111    register_host_fn(
112        linker,
113        MODULE_NAME,
114        ctor_key("fromCodePoint"),
115        FuncType::new(&engine, [array_ref], [abi.value_type()]),
116        move |caller, params, results| {
117            let codes = read_number_array(caller, abi_arg(params, 0)?, "String.fromCodePoint")?;
118            let out = from_code_point(&codes).map_err(throw)?;
119            let st = write_submilli_string_struct_units(caller, out.units())?;
120            *abi_result(results, 0)? = Val::AnyRef(Some(st.to_anyref()));
121            Ok(())
122        },
123    )?;
124
125    // Identity/serialization/comparison members (their vtable-slot twins live
126    // in `vtable.rs`; these are the direct-dispatch method keys).
127    register_host_fn(
128        linker,
129        MODULE_NAME,
130        method_key("toString"),
131        FuncType::new(&engine, [abi.value_type()], [abi.value_type()]),
132        |_caller, params, results| {
133            *abi_result(results, 0)? = *abi_arg(params, 0)?;
134            Ok(())
135        },
136    )?;
137    let json_abi = abi.clone();
138    register_host_fn(
139        linker,
140        MODULE_NAME,
141        method_key("toJson"),
142        FuncType::new(&engine, [abi.value_type()], [abi.value_type()]),
143        move |caller, params, results| {
144            let recv = json_abi.read(caller, abi_arg(params, 0)?, "String#toJson")?;
145            *abi_result(results, 0)? =
146                crate::runtime::prelude::vtable::quote_string(caller, recv.value.units())?;
147            Ok(())
148        },
149    )?;
150    let equals_abi = abi.clone();
151    register_host_fn(
152        linker,
153        MODULE_NAME,
154        method_key("equals"),
155        FuncType::new(
156            &engine,
157            [abi.value_type(), abi.value_type()],
158            [ValType::I32],
159        ),
160        move |caller, params, results| {
161            *abi_result(results, 0)? = Val::I32(super::super::vtable::string_equals(
162                caller,
163                abi_arg(params, 0)?,
164                abi_arg(params, 1)?,
165                &equals_abi.string_ty,
166            )? as i32);
167            Ok(())
168        },
169    )?;
170    let lc_abi = abi.clone();
171    register_host_fn(
172        linker,
173        MODULE_NAME,
174        method_key("localeCompare"),
175        FuncType::new(
176            &engine,
177            [abi.value_type(), abi.value_type()],
178            [ValType::F64],
179        ),
180        move |caller, params, results| {
181            let a = lc_abi.read(caller, abi_arg(params, 0)?, "String#localeCompare")?;
182            let b = lc_abi.read(caller, abi_arg(params, 1)?, "String#localeCompare")?;
183            *abi_result(results, 0)? = Val::F64(f64::from(cmp(&a.value, &b.value)).to_bits());
184            Ok(())
185        },
186    )?;
187
188    // `String(value)` — the conversion call-signature (`Dispatch::Static`, so
189    // only the value arrives). A bigint renders its decimal text directly (its
190    // vtable slots are host-owned but the Wasm path special-cased it, and the
191    // dispatch is cheaper); everything else re-enters its `toString` slot.
192    let obj_param = ValType::Ref(RefType::new(
193        true,
194        HeapType::ConcreteStruct(build_intrinsic_types(&engine)?.object),
195    ));
196    register_host_fn_async(
197        linker,
198        MODULE_NAME,
199        crate::mangle::extend(&crate::mangle::prelude("StringConstructor"), "@call"),
200        FuncType::new(&engine, [obj_param], [abi.value_type()]),
201        |caller, params, results| {
202            Box::pin(async move {
203                *abi_result(results, 0)? = string_ctor_call(caller, abi_arg(params, 0)?).await?;
204                Ok(())
205            })
206        },
207    )?;
208
209    // `String#iterator` — the for-of cursor over code points.
210    let obj_ret = ValType::Ref(RefType::new(
211        true,
212        HeapType::ConcreteStruct(build_intrinsic_types(&engine)?.object),
213    ));
214    register_host_fn(
215        linker,
216        MODULE_NAME,
217        method_key("iterator"),
218        FuncType::new(&engine, [abi.value_type()], [obj_ret]),
219        |caller, params, results| {
220            *abi_result(results, 0)? = crate::runtime::prelude::iterator::make_string_iterator(
221                caller,
222                *abi_arg(params, 0)?,
223            )?;
224            Ok(())
225        },
226    )?;
227
228    // Operator primitives — value symbols, not method keys: codegen emits
229    // direct calls to these for string `+`, `===`/`==`, and relational compares.
230    let s = abi.value_type();
231    let concat_abi = abi.clone();
232    register_host_fn(
233        linker,
234        MODULE_NAME,
235        crate::mangle::prelude("string_concat"),
236        FuncType::new(&engine, [s.clone(), s.clone()], [s.clone()]),
237        move |caller, params, results| {
238            *abi_result(results, 0)? = concat_abi.concat(
239                caller,
240                abi_arg(params, 0)?,
241                abi_arg(params, 1)?,
242                "string_concat",
243            )?;
244            Ok(())
245        },
246    )?;
247    let eq_abi = abi.clone();
248    register_host_fn(
249        linker,
250        MODULE_NAME,
251        crate::mangle::prelude("string_eq"),
252        FuncType::new(&engine, [s.clone(), s.clone()], [ValType::I32]),
253        move |caller, params, results| {
254            let equal = super::super::vtable::string_equals(
255                caller,
256                abi_arg(params, 0)?,
257                abi_arg(params, 1)?,
258                &eq_abi.string_ty,
259            )?;
260            *abi_result(results, 0)? = Val::I32(equal as i32);
261            Ok(())
262        },
263    )?;
264    let cmp_abi = abi.clone();
265    register_host_fn(
266        linker,
267        MODULE_NAME,
268        crate::mangle::prelude("string_cmp"),
269        FuncType::new(&engine, [s.clone(), s], [ValType::I32]),
270        move |caller, params, results| {
271            let a = cmp_abi.read(caller, abi_arg(params, 0)?, "string_cmp")?;
272            let b = cmp_abi.read(caller, abi_arg(params, 1)?, "string_cmp")?;
273            *abi_result(results, 0)? = Val::I32(cmp(&a.value, &b.value));
274            Ok(())
275        },
276    )?;
277    Ok(())
278}
279
280pub fn declare(defs: &mut PackageDeclaration) {
281    let m = |defs: &mut PackageDeclaration, name: &str, params: Vec<Param>, ret: Type| {
282        declare_method(defs, name, method_key(name), params, ret);
283    };
284    let s = || Param::new("s", Type::String);
285
286    m(
287        defs,
288        "charAt",
289        vec![s(), Param::new("index", Type::Number)],
290        Type::String,
291    );
292    m(
293        defs,
294        "at",
295        vec![s(), Param::new("index", Type::Number)],
296        Type::Union(vec![Type::String, Type::Undefined]),
297    );
298    m(
299        defs,
300        "charCodeAt",
301        vec![s(), Param::new("index", Type::Number)],
302        Type::Number,
303    );
304    m(
305        defs,
306        "codePointAt",
307        vec![s(), Param::new("index", Type::Number)],
308        Type::Union(vec![Type::Number, Type::Undefined]),
309    );
310    m(
311        defs,
312        "slice",
313        vec![
314            s(),
315            Param::new("start", Type::Number),
316            Param::new("end", Type::Number),
317        ],
318        Type::String,
319    );
320    m(
321        defs,
322        "substring",
323        vec![
324            s(),
325            Param::new("start", Type::Number),
326            Param::new("end", Type::Number),
327        ],
328        Type::String,
329    );
330    m(
331        defs,
332        "substr",
333        vec![
334            s(),
335            Param::new("start", Type::Number),
336            Param::new("length", Type::Number),
337        ],
338        Type::String,
339    );
340    m(
341        defs,
342        "indexOf",
343        vec![
344            s(),
345            Param::new("search", Type::String),
346            Param::new("fromIndex", Type::Number),
347        ],
348        Type::Number,
349    );
350    m(
351        defs,
352        "lastIndexOf",
353        vec![
354            s(),
355            Param::new("search", Type::String),
356            Param::new("fromIndex", Type::Number),
357        ],
358        Type::Number,
359    );
360    m(
361        defs,
362        "includes",
363        vec![
364            s(),
365            Param::new("search", Type::Unknown),
366            Param::new("fromIndex", Type::Unknown),
367        ],
368        Type::Boolean,
369    );
370    m(
371        defs,
372        "startsWith",
373        vec![
374            s(),
375            Param::new("search", Type::Unknown),
376            Param::new("position", Type::Unknown),
377        ],
378        Type::Boolean,
379    );
380    m(
381        defs,
382        "endsWith",
383        vec![
384            s(),
385            Param::new("search", Type::Unknown),
386            Param::new("endPosition", Type::Unknown),
387        ],
388        Type::Boolean,
389    );
390    m(
391        defs,
392        "concat",
393        vec![
394            s(),
395            Param::new("strings", Type::Array(Box::new(Type::String))),
396        ],
397        Type::String,
398    );
399    m(
400        defs,
401        "padStart",
402        vec![
403            s(),
404            Param::new("targetLength", Type::Number),
405            Param::new("padString", Type::String),
406        ],
407        Type::String,
408    );
409    m(
410        defs,
411        "padEnd",
412        vec![
413            s(),
414            Param::new("targetLength", Type::Number),
415            Param::new("padString", Type::String),
416        ],
417        Type::String,
418    );
419    m(defs, "isWellFormed", vec![s()], Type::Boolean);
420    m(defs, "toWellFormed", vec![s()], Type::String);
421    m(defs, "toUpperCase", vec![s()], Type::String);
422    m(defs, "toLowerCase", vec![s()], Type::String);
423    m(defs, "trim", vec![s()], Type::String);
424    m(defs, "trimStart", vec![s()], Type::String);
425    m(defs, "trimEnd", vec![s()], Type::String);
426    m(defs, "trimLeft", vec![s()], Type::String);
427    m(defs, "trimRight", vec![s()], Type::String);
428    m(
429        defs,
430        "normalize",
431        vec![s(), Param::new("form", Type::String)],
432        Type::String,
433    );
434    m(
435        defs,
436        "repeat",
437        vec![s(), Param::new("count", Type::Number)],
438        Type::String,
439    );
440
441    declare_method(
442        defs,
443        "fromCharCode",
444        ctor_key("fromCharCode"),
445        vec![Param::rest("codes", Type::Array(Box::new(Type::Number)))],
446        Type::String,
447    );
448    declare_method(
449        defs,
450        "fromCodePoint",
451        ctor_key("fromCodePoint"),
452        vec![Param::rest(
453            "codePoints",
454            Type::Array(Box::new(Type::Number)),
455        )],
456        Type::String,
457    );
458
459    m(
460        defs,
461        "iterator",
462        vec![s()],
463        Type::prelude_interface("Iterator".to_string(), vec![Type::String]),
464    );
465    m(defs, "toString", vec![s()], Type::String);
466    m(defs, "toJson", vec![s()], Type::String);
467    m(
468        defs,
469        "equals",
470        vec![s(), Param::new("other", Type::String)],
471        Type::Boolean,
472    );
473    m(
474        defs,
475        "localeCompare",
476        vec![s(), Param::new("other", Type::String)],
477        Type::Number,
478    );
479    declare_method(
480        defs,
481        "@call",
482        crate::mangle::extend(&crate::mangle::prelude("StringConstructor"), "@call"),
483        vec![Param::new("value", Type::Unknown)],
484        Type::String,
485    );
486
487    // Operator primitives (value symbols; `string_cmp`'s boolean return is an
488    // i32 sign, matching the prelude declaration it overrides).
489    let two_strings = || vec![Param::anon(Type::String), Param::anon(Type::String)];
490    declare_method(
491        defs,
492        "string_concat",
493        crate::mangle::prelude("string_concat"),
494        two_strings(),
495        Type::String,
496    );
497    declare_method(
498        defs,
499        "string_eq",
500        crate::mangle::prelude("string_eq"),
501        two_strings(),
502        Type::Boolean,
503    );
504    declare_method(
505        defs,
506        "string_cmp",
507        crate::mangle::prelude("string_cmp"),
508        two_strings(),
509        Type::Boolean,
510    );
511    declare_method(
512        defs,
513        "string_index",
514        crate::mangle::prelude("string_index"),
515        vec![Param::anon(Type::String), Param::anon(Type::Number)],
516        Type::String,
517    );
518}
519
520/// `(string, f64) -> string`: `charAt`. `pick` names the unit for the index,
521/// if any; the result is that unit or `""`.
522fn reg_unit_to_str(
523    linker: &mut Linker<StoreData>,
524    engine: &wasmtime::Engine,
525    abi: &StringAbi,
526    name: &'static str,
527    pick: fn(usize, f64) -> Option<usize>,
528) -> wasmtime::Result<()> {
529    let s = abi.value_type();
530    let abi = abi.clone();
531    register_host_fn(
532        linker,
533        MODULE_NAME,
534        method_key(name),
535        FuncType::new(engine, [s.clone(), ValType::F64], [s]),
536        move |caller, params, results| {
537            let recv = abi.payload(caller, abi_arg(params, 0)?, name)?;
538            let arg = number(abi_arg(params, 1)?, name)?;
539            let unit = pick(recv.len, arg)
540                .map(|i| read_code_unit(&mut *caller, recv.array, i))
541                .transpose()?;
542            let out = Str::from_units(unit.map_or_else(Vec::new, |unit| vec![unit]));
543            *abi_result(results, 0)? = abi.write(caller, recv.vtable, &out)?;
544            Ok(())
545        },
546    )
547}
548
549/// `(string, f64) -> string`: `s[index]`. Out of range throws the `RangeError`
550/// an array read throws, rather than JavaScript's `undefined`, which the
551/// `string` it is typed as can't hold.
552fn reg_string_index(
553    linker: &mut Linker<StoreData>,
554    engine: &wasmtime::Engine,
555    abi: &StringAbi,
556) -> wasmtime::Result<()> {
557    let s = abi.value_type();
558    let abi = abi.clone();
559    register_host_fn(
560        linker,
561        MODULE_NAME,
562        crate::mangle::prelude("string_index"),
563        FuncType::new(engine, [s.clone(), ValType::F64], [s]),
564        move |caller, params, results| {
565            let recv = abi.payload(caller, abi_arg(params, 0)?, "string_index")?;
566            let arg = number(abi_arg(params, 1)?, "string_index")?;
567            let Some(index) = string_index_unit(recv.len, arg) else {
568                return Err(range_error(crate::codegen::bounds::INDEX_OOB_MESSAGE));
569            };
570            let unit = read_code_unit(&mut *caller, recv.array, index)?;
571            *abi_result(results, 0)? =
572                abi.write(caller, recv.vtable, &Str::from_units(vec![unit]))?;
573            Ok(())
574        },
575    )
576}
577
578/// `(string, f64) -> string | undefined`: `at`, whose out-of-range answer is `undefined`.
579/// The result slot is the union lowering `(ref null $Object)`; a `$string` is an
580/// `$Object` subtype, so the hit case needs no extra boxing.
581fn reg_unit_to_str_or_undefined(
582    linker: &mut Linker<StoreData>,
583    engine: &wasmtime::Engine,
584    abi: &StringAbi,
585    name: &'static str,
586    pick: fn(usize, f64) -> Option<usize>,
587) -> wasmtime::Result<()> {
588    let s = abi.value_type();
589    let object = ValType::Ref(RefType::new(
590        true,
591        HeapType::ConcreteStruct(build_intrinsic_types(engine)?.object),
592    ));
593    let abi = abi.clone();
594    register_host_fn(
595        linker,
596        MODULE_NAME,
597        method_key(name),
598        FuncType::new(engine, [s, ValType::F64], [object]),
599        move |caller, params, results| {
600            let recv = abi.payload(caller, abi_arg(params, 0)?, name)?;
601            let arg = number(abi_arg(params, 1)?, name)?;
602            *abi_result(results, 0)? = match pick(recv.len, arg) {
603                Some(i) => {
604                    let unit = read_code_unit(&mut *caller, recv.array, i)?;
605                    abi.write(caller, recv.vtable, &Str::from_units(vec![unit]))?
606                }
607                None => crate::runtime::prelude::undefined::value(caller)?,
608            };
609            Ok(())
610        },
611    )
612}
613
614/// `charCodeAt`: the code unit at the index, or `NaN` out of range.
615fn reg_char_code_at(
616    linker: &mut Linker<StoreData>,
617    engine: &wasmtime::Engine,
618    abi: &StringAbi,
619) -> wasmtime::Result<()> {
620    const NAME: &str = "charCodeAt";
621    let s = abi.value_type();
622    let abi = abi.clone();
623    register_host_fn(
624        linker,
625        MODULE_NAME,
626        method_key(NAME),
627        FuncType::new(engine, [s, ValType::F64], [ValType::F64]),
628        move |caller, params, results| {
629            let recv = abi.payload(caller, abi_arg(params, 0)?, NAME)?;
630            let arg = number(abi_arg(params, 1)?, NAME)?;
631            let value = match unit_index(recv.len, arg) {
632                Some(i) => f64::from(read_code_unit(&mut *caller, recv.array, i)?),
633                None => f64::NAN,
634            };
635            *abi_result(results, 0)? = Val::F64(value.to_bits());
636            Ok(())
637        },
638    )
639}
640
641/// `codePointAt`: the code point starting at the index, joining a surrogate
642/// pair, or `undefined` out of range.
643fn reg_code_point_at(
644    linker: &mut Linker<StoreData>,
645    engine: &wasmtime::Engine,
646    abi: &StringAbi,
647) -> wasmtime::Result<()> {
648    const NAME: &str = "codePointAt";
649    let s = abi.value_type();
650    let object = ValType::Ref(RefType::new(
651        true,
652        HeapType::ConcreteStruct(build_intrinsic_types(engine)?.object),
653    ));
654    let abi = abi.clone();
655    register_host_fn(
656        linker,
657        MODULE_NAME,
658        method_key(NAME),
659        FuncType::new(engine, [s, ValType::F64], [object]),
660        move |caller, params, results| {
661            let recv = abi.payload(caller, abi_arg(params, 0)?, NAME)?;
662            let arg = number(abi_arg(params, 1)?, NAME)?;
663            let Some(i) = unit_index(recv.len, arg) else {
664                *abi_result(results, 0)? = crate::runtime::prelude::undefined::value(caller)?;
665                return Ok(());
666            };
667            let unit = read_code_unit(&mut *caller, recv.array, i)?;
668            let next = if i + 1 < recv.len {
669                Some(read_code_unit(&mut *caller, recv.array, i + 1)?)
670            } else {
671                None
672            };
673            let boxed = write_boxed_number_struct(caller, code_point(unit, next))?;
674            *abi_result(results, 0)? = Val::AnyRef(Some(boxed.to_anyref()));
675            Ok(())
676        },
677    )
678}
679
680/// `repeat`: the receiver `count` times, built in place (see [`StringAbi::build`]).
681fn reg_repeat(
682    linker: &mut Linker<StoreData>,
683    engine: &wasmtime::Engine,
684    abi: &StringAbi,
685) -> wasmtime::Result<()> {
686    let s = abi.value_type();
687    let abi = abi.clone();
688    register_host_fn(
689        linker,
690        MODULE_NAME,
691        method_key("repeat"),
692        FuncType::new(engine, [s.clone(), ValType::F64], [s]),
693        move |caller, params, results| {
694            let recv = abi.payload(caller, abi_arg(params, 0)?, "repeat")?;
695            let count = number(abi_arg(params, 1)?, "repeat")?;
696            fuel::charge(&mut *caller, fuel::COPY, recv.len as u64)?;
697            let total = repeat_length(recv.len, count).map_err(throw)?;
698            fuel::charge(&mut *caller, fuel::COPY, total as u64)?;
699            let runs = [recv.cycle(total)];
700            *abi_result(results, 0)? = abi.build(caller, recv.vtable, &runs)?;
701            Ok(())
702        },
703    )
704}
705
706/// `(string, f64, f64) -> string`: `slice`, `substring`. `range` names the
707/// units kept; only those are copied.
708fn reg_range_to_str(
709    linker: &mut Linker<StoreData>,
710    engine: &wasmtime::Engine,
711    abi: &StringAbi,
712    name: &'static str,
713    range: fn(usize, f64, f64) -> std::ops::Range<usize>,
714) -> wasmtime::Result<()> {
715    let s = abi.value_type();
716    let abi = abi.clone();
717    register_host_fn(
718        linker,
719        MODULE_NAME,
720        method_key(name),
721        FuncType::new(engine, [s.clone(), ValType::F64, ValType::F64], [s]),
722        move |caller, params, results| {
723            let recv = abi.payload(caller, abi_arg(params, 0)?, name)?;
724            let a = number(abi_arg(params, 1)?, name)?;
725            let b = number(abi_arg(params, 2)?, name)?;
726            let kept = range(recv.len, a, b);
727            let units = read_code_units_range(&mut *caller, recv.array, kept.start, kept.len())?;
728            *abi_result(results, 0)? = abi.write(caller, recv.vtable, &Str::from_units(units))?;
729            Ok(())
730        },
731    )
732}
733
734/// `(string, string, f64) -> f64`: `indexOf`, `lastIndexOf`.
735fn reg_str_str_num_to_num(
736    linker: &mut Linker<StoreData>,
737    engine: &wasmtime::Engine,
738    abi: &StringAbi,
739    name: &'static str,
740    op: fn(&Str, &Str, f64) -> f64,
741) -> wasmtime::Result<()> {
742    let s = abi.value_type();
743    let abi = abi.clone();
744    register_host_fn(
745        linker,
746        MODULE_NAME,
747        method_key(name),
748        FuncType::new(engine, [s.clone(), s, ValType::F64], [ValType::F64]),
749        move |caller, params, results| {
750            let recv = abi.read(caller, abi_arg(params, 0)?, name)?;
751            let search = abi.read(caller, abi_arg(params, 1)?, name)?;
752            let from = number(abi_arg(params, 2)?, name)?;
753            fuel::charge(
754                &mut *caller,
755                fuel::SCAN,
756                (recv.value.len() + search.value.len()) as u64,
757            )?;
758            *abi_result(results, 0)? = Val::F64(op(&recv.value, &search.value, from).to_bits());
759            Ok(())
760        },
761    )
762}
763
764/// Search and position stay boxed so RegExp rejection and coercions run in order.
765fn register_string_search_predicate(
766    linker: &mut Linker<StoreData>,
767    engine: &wasmtime::Engine,
768    abi: &StringAbi,
769    name: &'static str,
770    op: fn(&Str, &Str, f64) -> bool,
771) -> wasmtime::Result<()> {
772    let intr = build_intrinsic_types(engine)?;
773    let value = ValType::Ref(RefType::new(true, HeapType::ConcreteStruct(intr.object)));
774    let abi = abi.clone();
775    register_host_fn_async(
776        linker,
777        MODULE_NAME,
778        method_key(name),
779        FuncType::new(
780            engine,
781            [abi.value_type(), value.clone(), value],
782            [ValType::I32],
783        ),
784        move |caller, params, results| {
785            let abi = abi.clone();
786            Box::pin(async move {
787                let recv = abi.read(caller, abi_arg(params, 0)?, name)?;
788                let search =
789                    super::super::value::search_string(caller, abi_arg(params, 1)?).await?;
790                let from = super::super::value::to_number(caller, abi_arg(params, 2)?).await?;
791                fuel::charge(
792                    &mut *caller,
793                    fuel::SCAN,
794                    (recv.value.len() + search.len()) as u64,
795                )?;
796                *abi_result(results, 0)? =
797                    Val::I32(op(&recv.value, &Str::from_units(search), from) as i32);
798                Ok(())
799            })
800        },
801    )
802}
803
804/// `startsWith`, `endsWith`: compare the needle with the one window of the
805/// receiver it could occupy, copying nothing else. Search and position stay
806/// boxed so RegExp rejection and coercions run in order.
807fn reg_window_predicate(
808    linker: &mut Linker<StoreData>,
809    engine: &wasmtime::Engine,
810    abi: &StringAbi,
811    name: &'static str,
812    window: fn(usize, usize, f64) -> Option<std::ops::Range<usize>>,
813) -> wasmtime::Result<()> {
814    let intr = build_intrinsic_types(engine)?;
815    let value = ValType::Ref(RefType::new(true, HeapType::ConcreteStruct(intr.object)));
816    let abi = abi.clone();
817    register_host_fn_async(
818        linker,
819        MODULE_NAME,
820        method_key(name),
821        FuncType::new(
822            engine,
823            [abi.value_type(), value.clone(), value],
824            [ValType::I32],
825        ),
826        move |caller, params, results| {
827            let abi = abi.clone();
828            Box::pin(async move {
829                let recv = abi.payload(caller, abi_arg(params, 0)?, name)?;
830                let needle =
831                    super::super::value::search_string(caller, abi_arg(params, 1)?).await?;
832                let position = super::super::value::to_number(caller, abi_arg(params, 2)?).await?;
833                let found = match window(recv.len, needle.len(), position) {
834                    Some(range) => {
835                        read_code_units_range(caller, recv.array, range.start, range.len())?
836                            == needle
837                    }
838                    None => false,
839                };
840                *abi_result(results, 0)? = Val::I32(found as i32);
841                Ok(())
842            })
843        },
844    )
845}
846
847/// `String#concat(...strings)`; the `+` operator registers the two-string
848/// operation as `string_concat`.
849fn reg_concat(
850    linker: &mut Linker<StoreData>,
851    engine: &wasmtime::Engine,
852    abi: &StringAbi,
853) -> wasmtime::Result<()> {
854    let s = abi.value_type();
855    let array = ValType::Ref(RefType::new(
856        false,
857        HeapType::ConcreteStruct(crate::runtime::host::intrinsic_array_type(engine)?),
858    ));
859    let abi = abi.clone();
860    register_host_fn(
861        linker,
862        MODULE_NAME,
863        method_key("concat"),
864        FuncType::new(engine, [s.clone(), array.clone()], [s]),
865        move |caller, params, results| {
866            *abi_result(results, 0)? =
867                abi.concat_all(caller, abi_arg(params, 0)?, abi_arg(params, 1)?)?;
868            Ok(())
869        },
870    )
871}
872
873/// Which side of the receiver a pad fills.
874#[derive(Clone, Copy)]
875enum PadSide {
876    Start,
877    End,
878}
879
880/// `padStart`, `padEnd`: repetitions of the pad, cut to fit, beside the receiver.
881fn reg_pad(
882    linker: &mut Linker<StoreData>,
883    engine: &wasmtime::Engine,
884    abi: &StringAbi,
885    name: &'static str,
886    side: PadSide,
887) -> wasmtime::Result<()> {
888    let s = abi.value_type();
889    let abi = abi.clone();
890    register_host_fn(
891        linker,
892        MODULE_NAME,
893        method_key(name),
894        FuncType::new(engine, [s.clone(), ValType::F64, s.clone()], [s]),
895        move |caller, params, results| {
896            let recv = abi.payload(caller, abi_arg(params, 0)?, name)?;
897            let target = number(abi_arg(params, 1)?, name)?;
898            let pad = abi.payload(caller, abi_arg(params, 2)?, name)?;
899            fuel::charge(&mut *caller, fuel::COPY, recv.len as u64 + pad.len as u64)?;
900            let total = pad_length(recv.len, target, pad.len).map_err(throw)?;
901            fuel::charge(&mut *caller, fuel::COPY, total as u64)?;
902            // pad_length never shortens the receiver.
903            let fill_len = total
904                .checked_sub(recv.len)
905                .ok_or_else(|| fatal_host_error("padded string shorter than its receiver"))?;
906            let fill = pad.cycle(fill_len);
907            let runs = match side {
908                PadSide::Start => [fill, recv.whole()],
909                PadSide::End => [recv.whole(), fill],
910            };
911            *abi_result(results, 0)? = abi.build(caller, recv.vtable, &runs)?;
912            Ok(())
913        },
914    )
915}
916
917/// `(string, string) -> string` for a builder that can reject its input:
918/// `normalize` (invalid form throws).
919fn reg_str_str_to_str_fallible(
920    linker: &mut Linker<StoreData>,
921    engine: &wasmtime::Engine,
922    abi: &StringAbi,
923    name: &'static str,
924    op: fn(&Str, &Str) -> super::Result<Str>,
925) -> wasmtime::Result<()> {
926    let s = abi.value_type();
927    let abi = abi.clone();
928    register_host_fn(
929        linker,
930        MODULE_NAME,
931        method_key(name),
932        FuncType::new(engine, [s.clone(), s.clone()], [s]),
933        move |caller, params, results| {
934            let recv = abi.read(caller, abi_arg(params, 0)?, name)?;
935            let arg = abi.read(caller, abi_arg(params, 1)?, name)?;
936            let out = op(&recv.value, &arg.value).map_err(throw)?;
937            *abi_result(results, 0)? = abi.write(caller, recv.vtable, &out)?;
938            Ok(())
939        },
940    )
941}
942
943/// `(string) -> boolean`: `isWellFormed`.
944fn reg_str_to_bool(
945    linker: &mut Linker<StoreData>,
946    engine: &wasmtime::Engine,
947    abi: &StringAbi,
948    name: &'static str,
949    op: fn(&Str) -> bool,
950) -> wasmtime::Result<()> {
951    let s = abi.value_type();
952    let abi = abi.clone();
953    register_host_fn(
954        linker,
955        MODULE_NAME,
956        method_key(name),
957        FuncType::new(engine, [s], [ValType::I32]),
958        move |caller, params, results| {
959            let recv = abi.read(caller, abi_arg(params, 0)?, name)?;
960            *abi_result(results, 0)? = Val::I32(op(&recv.value) as i32);
961            Ok(())
962        },
963    )
964}
965
966/// `(string) -> string`: `toWellFormed`.
967fn reg_str_to_str(
968    linker: &mut Linker<StoreData>,
969    engine: &wasmtime::Engine,
970    abi: &StringAbi,
971    name: &'static str,
972    op: fn(&Str) -> Str,
973) -> wasmtime::Result<()> {
974    let s = abi.value_type();
975    let abi = abi.clone();
976    register_host_fn(
977        linker,
978        MODULE_NAME,
979        method_key(name),
980        FuncType::new(engine, [s.clone()], [s]),
981        move |caller, params, results| {
982            let recv = abi.read(caller, abi_arg(params, 0)?, name)?;
983            // toWellFormed scans the input and its mutable copy.
984            fuel::charge(
985                &mut *caller,
986                fuel::SCAN,
987                2 * recv.value.units().len() as u64,
988            )?;
989            *abi_result(results, 0)? = abi.write(caller, recv.vtable, &op(&recv.value))?;
990            Ok(())
991        },
992    )
993}
994
995/// `toUpperCase`, `toLowerCase`: read the receiver and write the result in
996/// place (see [`GcText`]).
997fn reg_case_map(
998    linker: &mut Linker<StoreData>,
999    engine: &wasmtime::Engine,
1000    abi: &StringAbi,
1001    name: &'static str,
1002    map: fn(&mut GcText<'_, '_>) -> wasmtime::Result<()>,
1003) -> wasmtime::Result<()> {
1004    let s = abi.value_type();
1005    let abi = abi.clone();
1006    register_host_fn(
1007        linker,
1008        MODULE_NAME,
1009        method_key(name),
1010        FuncType::new(engine, [s.clone()], [s]),
1011        move |caller, params, results| {
1012            let recv = abi.payload(caller, abi_arg(params, 0)?, name)?;
1013            // Read, map, and write a result of the receiver's length; a result
1014            // of another length pays the difference in `GcText::finish`.
1015            let len = recv.len as u64;
1016            fuel::charge(&mut *caller, fuel::COPY, len)?;
1017            fuel::charge(&mut *caller, fuel::SCAN, len)?;
1018            fuel::charge(&mut *caller, fuel::COPY, len)?;
1019            let guess = allocate(caller, &abi.payload_ty, recv.len)?;
1020            let input = GcUnits::new(caller, recv.array, recv.len);
1021            let mut text = GcText::new(input, abi.payload_ty.clone(), guess, recv.len);
1022            map(&mut text)?;
1023            let payload = text.finish()?;
1024            *abi_result(results, 0)? = abi.wrap(caller, recv.vtable, payload)?;
1025            Ok(())
1026        },
1027    )
1028}
1029
1030/// `trim`, `trimStart`, `trimEnd`: scan the receiver's ends in place and copy
1031/// the kept range.
1032fn reg_trim(
1033    linker: &mut Linker<StoreData>,
1034    engine: &wasmtime::Engine,
1035    abi: &StringAbi,
1036    name: &'static str,
1037    ends: TrimEnds,
1038) -> wasmtime::Result<()> {
1039    let s = abi.value_type();
1040    let abi = abi.clone();
1041    register_host_fn(
1042        linker,
1043        MODULE_NAME,
1044        method_key(name),
1045        FuncType::new(engine, [s.clone()], [s]),
1046        move |caller, params, results| {
1047            let recv = abi.payload(caller, abi_arg(params, 0)?, name)?;
1048            // The SUB-1292 price; the scan reads only the trimmed ends.
1049            fuel::charge(&mut *caller, fuel::COPY, recv.len as u64)?;
1050            fuel::charge(&mut *caller, fuel::SCAN, recv.len as u64)?;
1051            let kept = trim_range(&mut GcUnits::new(caller, recv.array, recv.len), ends)?;
1052            fuel::charge(&mut *caller, fuel::COPY, kept.len() as u64)?;
1053            *abi_result(results, 0)? = abi.build(caller, recv.vtable, &[recv.range_run(kept)])?;
1054            Ok(())
1055        },
1056    )
1057}
1058
1059/// The canonical `$string` ABI types, recovered once at install and reused to
1060/// build every result. `$string` is `(struct (field vtable) (field $rawString))`.
1061#[derive(Clone)]
1062struct StringAbi {
1063    string_ty: StructType,
1064    payload_ty: ArrayType,
1065}
1066
1067/// A `$string` receiver split into its vtable and code units. The result is
1068/// built with the *same* vtable: it's the one shared `$string` vtable singleton,
1069/// so reusing the receiver's avoids a prelude-instance lookup.
1070struct Receiver {
1071    vtable: Val,
1072    value: Str,
1073}
1074
1075/// A `$string` kept where it is: its payload array and length. Accessors read
1076/// a few units or a range from it; builders copy from it as the source of a run.
1077struct Payload {
1078    vtable: Val,
1079    array: Rooted<ArrayRef>,
1080    len: usize,
1081}
1082
1083impl Payload {
1084    /// The units in `range`, copied once.
1085    fn range_run(&self, range: std::ops::Range<usize>) -> Run {
1086        Run::copy(self.array, range)
1087    }
1088
1089    /// The whole string, copied once.
1090    fn whole(&self) -> Run {
1091        Run::copy(self.array, 0..self.len)
1092    }
1093
1094    /// `len` units cycling through this string from its start.
1095    fn cycle(&self, len: usize) -> Run {
1096        Run {
1097            source: self.array,
1098            start: 0,
1099            period: self.len,
1100            len,
1101        }
1102    }
1103
1104    fn read_all(&self, caller: &mut Caller<'_, StoreData>, name: &str) -> wasmtime::Result<Str> {
1105        Ok(Str::from_units(read_code_units(
1106            &mut *caller,
1107            self.array,
1108            name,
1109        )?))
1110    }
1111}
1112
1113impl StringAbi {
1114    fn recover(engine: &wasmtime::Engine) -> wasmtime::Result<Self> {
1115        Ok(Self {
1116            string_ty: intrinsic_string_type(engine)?,
1117            payload_ty: string_array_type(engine),
1118        })
1119    }
1120
1121    fn value_type(&self) -> ValType {
1122        ValType::Ref(RefType::new(
1123            false,
1124            HeapType::ConcreteStruct(self.string_ty.clone()),
1125        ))
1126    }
1127
1128    fn read(
1129        &self,
1130        caller: &mut Caller<'_, StoreData>,
1131        val: &Val,
1132        name: &str,
1133    ) -> wasmtime::Result<Receiver> {
1134        let payload = self.payload(caller, val, name)?;
1135        Ok(Receiver {
1136            vtable: payload.vtable,
1137            value: payload.read_all(caller, name)?,
1138        })
1139    }
1140
1141    fn payload(
1142        &self,
1143        caller: &mut Caller<'_, StoreData>,
1144        val: &Val,
1145        name: &str,
1146    ) -> wasmtime::Result<Payload> {
1147        let Val::AnyRef(Some(any)) = val else {
1148            return Err(wasmtime::Error::msg(format!(
1149                "{name} expects a string, got {val:?}"
1150            )));
1151        };
1152        let st = any
1153            .as_struct(&mut *caller)?
1154            .ok_or_else(|| wasmtime::Error::msg(format!("{name}: expected a $string struct")))?;
1155        let vtable = st.field(&mut *caller, 0)?;
1156        let array = match st.field(&mut *caller, 1)? {
1157            Val::AnyRef(Some(arr)) => arr.unwrap_array(&mut *caller)?,
1158            other => {
1159                return Err(wasmtime::Error::msg(format!(
1160                    "{name}: malformed $string payload {other:?}"
1161                )));
1162            }
1163        };
1164        let len = usize::try_from(array.len(&mut *caller)?)
1165            .map_err(crate::runtime::host::fatal_host_error)?;
1166        Ok(Payload { vtable, array, len })
1167    }
1168
1169    fn write(
1170        &self,
1171        caller: &mut Caller<'_, StoreData>,
1172        vtable: Val,
1173        s: &Str,
1174    ) -> wasmtime::Result<Val> {
1175        fuel::charge(&mut *caller, fuel::COPY, s.units().len() as u64)?;
1176        self.write_units(caller, vtable, s)
1177    }
1178
1179    fn write_units(
1180        &self,
1181        caller: &mut Caller<'_, StoreData>,
1182        vtable: Val,
1183        s: &Str,
1184    ) -> wasmtime::Result<Val> {
1185        let pre = ArrayRefPre::new(&mut *caller, self.payload_ty.clone());
1186        let payload = ArrayRef::new_from_i16_slice(&mut *caller, &pre, s.units())?;
1187        self.wrap(caller, vtable, payload)
1188    }
1189
1190    /// `a` followed by `b`, built in place.
1191    fn concat(
1192        &self,
1193        caller: &mut Caller<'_, StoreData>,
1194        a: &Val,
1195        b: &Val,
1196        name: &str,
1197    ) -> wasmtime::Result<Val> {
1198        let a = self.payload(caller, a, name)?;
1199        let b = self.payload(caller, b, name)?;
1200        fuel::charge(&mut *caller, fuel::COPY, a.len as u64 + b.len as u64)?;
1201        let total = concat_length(a.len, b.len)?;
1202        fuel::charge(&mut *caller, fuel::COPY, total as u64)?;
1203        self.build(caller, a.vtable, &[a.whole(), b.whole()])
1204    }
1205
1206    /// `String#concat`: the receiver followed by each string in `others`, an
1207    /// `$Array` of the rest arguments.
1208    fn concat_all(
1209        &self,
1210        caller: &mut Caller<'_, StoreData>,
1211        receiver: &Val,
1212        others: &Val,
1213    ) -> wasmtime::Result<Val> {
1214        let receiver = self.payload(caller, receiver, "concat")?;
1215        let others =
1216            crate::runtime::array_storage::ArrayStorage::read(caller, others)?.snapshot(caller)?;
1217        let mut runs = Vec::with_capacity(others.len() + 1);
1218        let mut total = receiver.len;
1219        runs.push(receiver.whole());
1220        for other in &others {
1221            let other = self.payload(caller, other, "concat")?;
1222            total = concat_length(total, other.len)?;
1223            runs.push(other.whole());
1224        }
1225        fuel::charge(&mut *caller, fuel::COPY, total as u64)?;
1226        self.build(caller, receiver.vtable, &runs)
1227    }
1228
1229    /// Builds a string from `runs` directly in a new GC array (see [`gc_text`]).
1230    fn build(
1231        &self,
1232        caller: &mut Caller<'_, StoreData>,
1233        vtable: Val,
1234        runs: &[Run],
1235    ) -> wasmtime::Result<Val> {
1236        let total = runs
1237            .iter()
1238            .try_fold(0usize, |total, run| total.checked_add(run.len))
1239            .ok_or_else(|| fatal_host_error("string length overflow"))?;
1240        let payload = allocate(caller, &self.payload_ty, total)?;
1241        let mut at = 0;
1242        for run in runs {
1243            fill_run(&mut *caller, payload, at, run)?;
1244            at += run.len;
1245        }
1246        self.wrap(caller, vtable, payload)
1247    }
1248
1249    fn wrap(
1250        &self,
1251        caller: &mut Caller<'_, StoreData>,
1252        vtable: Val,
1253        payload: Rooted<ArrayRef>,
1254    ) -> wasmtime::Result<Val> {
1255        let pre = StructRefPre::new(&mut *caller, self.string_ty.clone());
1256        let st = StructRef::new(
1257            &mut *caller,
1258            &pre,
1259            &[vtable, Val::AnyRef(Some(payload.to_anyref())), Val::I64(0)],
1260        )?;
1261        Ok(Val::AnyRef(Some(st.to_anyref())))
1262    }
1263}
1264
1265/// Read a `$Array` of boxed numbers (the packed rest args of a `fromCharCode` /
1266/// `fromCodePoint` call) into their `f64` payloads.
1267/// `String(value)`: null renders `"null"`, as ECMAScript's ToString does; a
1268/// bigint renders its decimal text; anything else re-enters its vtable
1269/// `toString` slot.
1270pub(crate) async fn string_ctor_call(
1271    caller: &mut Caller<'_, StoreData>,
1272    value: &Val,
1273) -> wasmtime::Result<Val> {
1274    let Val::AnyRef(Some(any)) = value else {
1275        let st = write_submilli_string_struct(caller, "null")?;
1276        return Ok(Val::AnyRef(Some(st.to_anyref())));
1277    };
1278    let intr = intrinsic_types(&mut *caller)?;
1279    if let Some(st) = any.as_struct(&mut *caller)?
1280        && StructType::eq(&st.ty(&*caller)?, &intr.bigint)
1281    {
1282        let (sign, limbs) = crate::runtime::prelude::bigint::ops::read_bigint_struct(
1283            caller,
1284            value,
1285            "String(bigint)",
1286        )?;
1287        let value = crate::runtime::prelude::bigint::ops::limbs_to_bigint(sign, &limbs)?;
1288        let text = crate::runtime::prelude::bigint::ops::format_bigint(caller, &value, 10)?;
1289        let st = write_submilli_string_struct(caller, &text)?;
1290        return Ok(Val::AnyRef(Some(st.to_anyref())));
1291    }
1292    crate::runtime::prelude::vtable::dispatch_vtable_slot(caller, value, 0, &[]).await
1293}
1294
1295fn read_number_array(
1296    caller: &mut Caller<'_, StoreData>,
1297    val: &Val,
1298    name: &str,
1299) -> wasmtime::Result<Vec<f64>> {
1300    let storage = crate::runtime::array_storage::ArrayStorage::read(caller, val)?;
1301    let backing = storage.backing;
1302    let len = storage.len;
1303    let mut nums = Vec::new();
1304    nums.try_reserve_exact(len as usize)
1305        .map_err(crate::runtime::host::fatal_host_error)?;
1306    for i in 0..len {
1307        let boxed = match backing.get(&mut *caller, i)? {
1308            Val::AnyRef(Some(b)) => b
1309                .as_struct(&mut *caller)?
1310                .ok_or_else(|| wasmtime::Error::msg(format!("{name}: arg {i} not a struct")))?,
1311            other => {
1312                return Err(wasmtime::Error::msg(format!(
1313                    "{name}: arg {i} is {other:?}, not a boxed number"
1314                )));
1315            }
1316        };
1317        match boxed.field(&mut *caller, 1)? {
1318            Val::F64(bits) => nums.push(f64::from_bits(bits)),
1319            other => {
1320                return Err(wasmtime::Error::msg(format!(
1321                    "{name}: arg {i} payload {other:?} is not f64"
1322                )));
1323            }
1324        }
1325    }
1326    Ok(nums)
1327}
1328
1329fn number(val: &Val, name: &str) -> wasmtime::Result<f64> {
1330    match val {
1331        Val::F64(bits) => Ok(f64::from_bits(*bits)),
1332        other => Err(wasmtime::Error::msg(format!(
1333            "{name} expects f64, got {other:?}"
1334        ))),
1335    }
1336}
1337
1338fn throw(e: RangeError) -> wasmtime::Error {
1339    range_error(e.message())
1340}
1341
1342/// The type/interface surface this module implements — its slice of the
1343/// prelude declaration (see `declaration::prelude_package_declaration`).
1344#[allow(clippy::too_many_lines)]
1345pub(crate) fn declare_types(defs: &mut crate::PackageDeclaration) {
1346    use crate::runtime::prelude::declaration::doc;
1347    use crate::{
1348        Dispatch, MethodSig, Param, PropertySig, Span, Type, TypeKind, TypeSymbol, ValueKind,
1349        ValueSymbol,
1350    };
1351    use std::collections::BTreeMap;
1352    defs.values.insert(
1353        "string_concat".to_string(),
1354        ValueSymbol {
1355            name: "string_concat".to_string(),
1356            mangled_name: crate::mangle::prelude("string_concat"),
1357            declaration_span: Span::at(crate::FileId::PRELUDE),
1358            kind: ValueKind::Function {
1359                generics: Vec::new(),
1360                params: vec![Param::anon(Type::String), Param::anon(Type::String)],
1361                ret: Type::String,
1362                type_predicate: None,
1363                doc: doc("/** Internal: concatenate two strings. */"),
1364            },
1365        },
1366    );
1367    defs.values.insert(
1368        "string_eq".to_string(),
1369        ValueSymbol {
1370            name: "string_eq".to_string(),
1371            mangled_name: crate::mangle::prelude("string_eq"),
1372            declaration_span: Span::at(crate::FileId::PRELUDE),
1373            kind: ValueKind::Function {
1374                generics: Vec::new(),
1375                params: vec![Param::anon(Type::String), Param::anon(Type::String)],
1376                ret: Type::Boolean,
1377                type_predicate: None,
1378                doc: doc("/** Internal: structural string equality. */"),
1379            },
1380        },
1381    );
1382    defs.values.insert(
1383        "string_length".to_string(),
1384        ValueSymbol {
1385            name: "string_length".to_string(),
1386            mangled_name: crate::mangle::prelude("string_length"),
1387            declaration_span: Span::at(crate::FileId::PRELUDE),
1388            kind: ValueKind::Function {
1389                generics: Vec::new(),
1390                params: vec![Param::anon(Type::String)],
1391                ret: Type::Number,
1392                type_predicate: None,
1393                doc: doc("/** Internal: UTF-16 code-unit length. */"),
1394            },
1395        },
1396    );
1397    defs.values.insert(
1398        "string_index".to_string(),
1399        ValueSymbol {
1400            name: "string_index".to_string(),
1401            mangled_name: crate::mangle::prelude("string_index"),
1402            declaration_span: Span::at(crate::FileId::PRELUDE),
1403            kind: ValueKind::Function {
1404                generics: Vec::new(),
1405                params: vec![Param::anon(Type::String), Param::anon(Type::Number)],
1406                ret: Type::String,
1407                type_predicate: None,
1408                doc: doc("/** Internal: `s[index]`, throwing `RangeError` out of range. */"),
1409            },
1410        },
1411    );
1412    // `ret: Boolean` only to get an i32 result; the value is a comparison sign,
1413    // never read as a boolean and never user-callable.
1414    defs.values.insert(
1415        "string_cmp".to_string(),
1416        ValueSymbol {
1417            name: "string_cmp".to_string(),
1418            mangled_name: crate::mangle::prelude("string_cmp"),
1419            declaration_span: Span::at(crate::FileId::PRELUDE),
1420            kind: ValueKind::Function {
1421                generics: Vec::new(),
1422                params: vec![Param::anon(Type::String), Param::anon(Type::String)],
1423                ret: Type::Boolean,
1424                type_predicate: None,
1425                doc: doc("/** Internal: lexicographic compare; i32 sign result. */"),
1426            },
1427        },
1428    );
1429
1430    for name in [
1431        "string_true",
1432        "string_false",
1433        "string_comma",
1434        "string_object_object",
1435        "string_object_function",
1436        "string_null",
1437    ] {
1438        defs.values.insert(
1439            name.to_string(),
1440            ValueSymbol {
1441                name: name.to_string(),
1442                mangled_name: crate::mangle::prelude(name),
1443                declaration_span: Span::at(crate::FileId::PRELUDE),
1444                kind: ValueKind::Const {
1445                    ty: Type::String,
1446                    doc: None,
1447                },
1448            },
1449        );
1450    }
1451    defs.types.insert(
1452        "String".to_string(),
1453        TypeSymbol {
1454            name: "String".to_string(),
1455            mangled_name: crate::mangle::prelude("String"),
1456            declaration_span: Span::at(crate::FileId::PRELUDE),
1457            kind: TypeKind::Interface { index: None,
1458                generics: Vec::new(),
1459                methods: BTreeMap::from([
1460                    (
1461                        "toString".to_string(),
1462                        MethodSig {
1463                            optional: false,
1464                            generics: Vec::new(),
1465                            params: Vec::new(),
1466                            ret: Type::String,
1467                            predicate: None,
1468                            doc: doc("/** Returns this string unchanged (identity). */"),
1469                        },
1470                    ),
1471                    (
1472                        "toJson".to_string(),
1473                        MethodSig {
1474                            optional: false,
1475                            generics: Vec::new(),
1476                            params: Vec::new(),
1477                            ret: Type::String,
1478                            predicate: None,
1479                            doc: doc(
1480                                "/** Returns this string wrapped in `\"…\"` with JSON escapes applied (`\\\"`, `\\\\`, `\\b`, `\\f`, `\\n`, `\\r`, `\\t`, and `\\u00XX` for control codepoints). */",
1481                            ),
1482                        },
1483                    ),
1484                    (
1485                        "concat".to_string(),
1486                        MethodSig {
1487                            optional: false,
1488                            generics: Vec::new(),
1489                            params: vec![Param::rest(
1490                                "strings",
1491                                Type::Array(Box::new(Type::String)),
1492                            )],
1493                            ret: Type::String,
1494                            predicate: None,
1495                            doc: doc(
1496                                "/**\n * Returns a new string with each of `strings` appended, in order.\n * The receiver is not modified.\n * @param strings Zero or more strings to append.\n */",
1497                            ),
1498                        },
1499                    ),
1500                    (
1501                        "equals".to_string(),
1502                        MethodSig {
1503                            optional: false,
1504                            generics: Vec::new(),
1505                            params: vec![Param::new("other", Type::String)],
1506                            ret: Type::Boolean,
1507                            predicate: None,
1508                            doc: doc(
1509                                "/**\n * Returns `true` when both strings have identical code units.\n * @param other String to compare against.\n */",
1510                            ),
1511                        },
1512                    ),
1513                    (
1514                        "localeCompare".to_string(),
1515                        MethodSig {
1516                            optional: false,
1517                            generics: Vec::new(),
1518                            params: vec![Param::new("other", Type::String)],
1519                            ret: Type::Number,
1520                            predicate: None,
1521                            doc: doc(
1522                                "/**\n * Compares this string with `other` in UTF-16 code-unit order.\n * @param other The string to compare against.\n * @returns A negative number if this sorts before `other`, `0` if equal, a positive number if after. No locale awareness — pure code-unit order.\n */",
1523                            ),
1524                        },
1525                    ),
1526                    (
1527                        "iterator".to_string(),
1528                        MethodSig {
1529                            optional: false,
1530                            generics: Vec::new(),
1531                            params: Vec::new(),
1532                            ret: Type::prelude_interface("Iterator".to_string(), vec![Type::String]),
1533                            predicate: None,
1534                            doc: doc(
1535                                "/** Returns an `Iterator<string>` of CODE POINTS — surrogate pairs arrive as one two-unit string. Drives `for-of` over a string. */",
1536                            ),
1537                        },
1538                    ),
1539                    (
1540                        "isWellFormed".to_string(),
1541                        MethodSig {
1542                            optional: false,
1543                            generics: Vec::new(),
1544                            params: Vec::new(),
1545                            ret: Type::Boolean,
1546                            predicate: None,
1547                            doc: doc(
1548                                "/** Returns `true` when the string contains no lone surrogates (it round-trips losslessly through UTF-8). */",
1549                            ),
1550                        },
1551                    ),
1552                    (
1553                        "toWellFormed".to_string(),
1554                        MethodSig {
1555                            optional: false,
1556                            generics: Vec::new(),
1557                            params: Vec::new(),
1558                            ret: Type::String,
1559                            predicate: None,
1560                            doc: doc(
1561                                "/** Returns a copy with every lone surrogate replaced by U+FFFD (`\u{fffd}`); well-formed strings come back unchanged. */",
1562                            ),
1563                        },
1564                    ),
1565                    (
1566                        "charAt".to_string(),
1567                        MethodSig {
1568                            optional: false,
1569                            generics: Vec::new(),
1570                            params: vec![Param::new("index", Type::Number)],
1571                            ret: Type::String,
1572                            predicate: None,
1573                            doc: doc(
1574                                "/**\n * Returns the UTF-16 code unit at `index` as a single-character string.\n * Returns `\"\"` if `index` is negative or beyond the string length.\n * @param index Zero-based index of the code unit.\n */",
1575                            ),
1576                        },
1577                    ),
1578                    (
1579                        "at".to_string(),
1580                        MethodSig {
1581                            optional: false,
1582                            generics: Vec::new(),
1583                            params: vec![Param::new("index", Type::Number)],
1584                            ret: Type::Union(vec![Type::String, Type::Undefined]),
1585                            predicate: None,
1586                            doc: doc(
1587                                "/**\n * Returns the code unit at `index` as a single-character string. Negative `index` counts from the end.\n * Returns `undefined` on out-of-range. Use `charAt` for the `\"\"`-on-miss form.\n * @param index Zero-based index; negatives count from the end.\n */",
1588                            ),
1589                        },
1590                    ),
1591                    (
1592                        "charCodeAt".to_string(),
1593                        MethodSig {
1594                            optional: false,
1595                            generics: Vec::new(),
1596                            params: vec![Param::new("index", Type::Number)],
1597                            ret: Type::Number,
1598                            predicate: None,
1599                            doc: doc(
1600                                "/**\n * Returns the UTF-16 code unit at `index` as an integer (0..65535), or `NaN` if out of range.\n * @param index Zero-based index of the code unit.\n */",
1601                            ),
1602                        },
1603                    ),
1604                    (
1605                        "codePointAt".to_string(),
1606                        MethodSig {
1607                            optional: false,
1608                            generics: Vec::new(),
1609                            params: vec![Param::new("index", Type::Number)],
1610                            ret: Type::Union(vec![Type::Number, Type::Undefined]),
1611                            predicate: None,
1612                            doc: doc(
1613                                "/**\n * Returns the Unicode code point starting at `index`, decoding surrogate pairs into values up to 0x10FFFF.\n * Returns `undefined` if `index` is out of range.\n * @param index Zero-based index of the first code unit of the code point.\n */",
1614                            ),
1615                        },
1616                    ),
1617                    (
1618                        "slice".to_string(),
1619                        MethodSig {
1620                            optional: false,
1621                            generics: Vec::new(),
1622                            params: vec![
1623                                Param::with_default(
1624                                    "start",
1625                                    Type::Number,
1626                                    crate::DefaultValue::Number(0.0),
1627                                ),
1628                                // `end` omitted means "to the end".
1629                                Param::with_default(
1630                                    "end",
1631                                    Type::Number,
1632                                    crate::DefaultValue::Number(f64::INFINITY),
1633                                ),
1634                            ],
1635                            ret: Type::String,
1636                            predicate: None,
1637                            doc: doc(
1638                                "/**\n * Returns a new string containing the code units from `start` (inclusive) to `end` (exclusive).\n * Negative values count from the end. The receiver is not modified.\n * @param start First index to copy. Defaults to `0`.\n * @param end Index one past the last to copy. Defaults to the string length.\n */",
1639                            ),
1640                        },
1641                    ),
1642                    (
1643                        "substr".to_string(),
1644                        MethodSig {
1645                            optional: false,
1646                            generics: Vec::new(),
1647                            params: vec![
1648                                Param::new("start", Type::Number),
1649                                Param::with_default(
1650                                    "length",
1651                                    Type::Number,
1652                                    crate::DefaultValue::Number(f64::INFINITY),
1653                                ),
1654                            ],
1655                            ret: Type::String,
1656                            predicate: None,
1657                            doc: doc(
1658                                "/**\n * Returns `length` code units starting at `start`; a negative `start` counts from the end. Deprecated in JavaScript in favour of `slice`.\n * @param start First index to copy.\n * @param length How many code units to copy. Defaults to the rest of the string.\n */",
1659                            ),
1660                        },
1661                    ),
1662                    (
1663                        "substring".to_string(),
1664                        MethodSig {
1665                            optional: false,
1666                            generics: Vec::new(),
1667                            params: vec![
1668                                Param::with_default(
1669                                    "start",
1670                                    Type::Number,
1671                                    crate::DefaultValue::Number(0.0),
1672                                ),
1673                                Param::with_default(
1674                                    "end",
1675                                    Type::Number,
1676                                    crate::DefaultValue::Number(f64::INFINITY),
1677                                ),
1678                            ],
1679                            ret: Type::String,
1680                            predicate: None,
1681                            doc: doc(
1682                                "/**\n * Like `slice`, but negative values clamp to `0`, and `start` and `end` are swapped when `start > end`.\n * @param start First index to copy. Defaults to `0`.\n * @param end Index one past the last to copy. Defaults to the string length.\n */",
1683                            ),
1684                        },
1685                    ),
1686                    (
1687                        "indexOf".to_string(),
1688                        MethodSig {
1689                            optional: false,
1690                            generics: Vec::new(),
1691                            params: vec![
1692                                Param::new("search", Type::String),
1693                                Param::with_default(
1694                                    "fromIndex",
1695                                    Type::Number,
1696                                    crate::DefaultValue::Number(0.0),
1697                                ),
1698                            ],
1699                            ret: Type::Number,
1700                            predicate: None,
1701                            doc: doc(
1702                                "/**\n * Returns the index of the first occurrence of `search` at or after `fromIndex`, or `-1` if none.\n * An empty `search` returns `min(fromIndex, length)`.\n * @param search Substring to search for.\n * @param fromIndex Index to begin searching from.\n */",
1703                            ),
1704                        },
1705                    ),
1706                    (
1707                        "lastIndexOf".to_string(),
1708                        MethodSig {
1709                            optional: false,
1710                            generics: Vec::new(),
1711                            params: vec![
1712                                Param::new("search", Type::String),
1713                                Param::with_default(
1714                                    "fromIndex",
1715                                    Type::Number,
1716                                    crate::DefaultValue::Number(f64::INFINITY),
1717                                ),
1718                            ],
1719                            ret: Type::Number,
1720                            predicate: None,
1721                            doc: doc(
1722                                "/**\n * Returns the index of the last occurrence of `search` at or before `fromIndex`, or `-1` if none.\n * @param search Substring to search for.\n * @param fromIndex Highest valid start position to consider. Defaults to the string length.\n */",
1723                            ),
1724                        },
1725                    ),
1726                    (
1727                        "includes".to_string(),
1728                        MethodSig {
1729                            optional: false,
1730                            generics: Vec::new(),
1731                            params: vec![
1732                                Param::new("search", Type::String),
1733                                Param::with_default(
1734                                    "fromIndex",
1735                                    Type::Number,
1736                                    crate::DefaultValue::Number(0.0),
1737                                ),
1738                            ],
1739                            ret: Type::Boolean,
1740                            predicate: None,
1741                            doc: doc(
1742                                "/**\n * Returns `true` if `search` occurs at or after `fromIndex`.\n * @param search Substring to search for.\n * @param fromIndex Index to begin searching from.\n */",
1743                            ),
1744                        },
1745                    ),
1746                    (
1747                        "startsWith".to_string(),
1748                        MethodSig {
1749                            optional: false,
1750                            generics: Vec::new(),
1751                            params: vec![
1752                                Param::new("search", Type::String),
1753                                Param::with_default(
1754                                    "position",
1755                                    Type::Number,
1756                                    crate::DefaultValue::Number(0.0),
1757                                ),
1758                            ],
1759                            ret: Type::Boolean,
1760                            predicate: None,
1761                            doc: doc(
1762                                "/**\n * Returns `true` if the substring starting at `position` begins with `search`.\n * @param search Prefix to test for.\n * @param position Index at which to start the comparison.\n */",
1763                            ),
1764                        },
1765                    ),
1766                    (
1767                        "endsWith".to_string(),
1768                        MethodSig {
1769                            optional: false,
1770                            generics: Vec::new(),
1771                            params: vec![
1772                                Param::new("search", Type::String),
1773                                Param::with_default(
1774                                    "endPosition",
1775                                    Type::Number,
1776                                    crate::DefaultValue::Number(f64::INFINITY),
1777                                ),
1778                            ],
1779                            ret: Type::Boolean,
1780                            predicate: None,
1781                            doc: doc(
1782                                "/**\n * Returns `true` if the substring ending at `endPosition` ends with `search`.\n * @param search Suffix to test for.\n * @param endPosition Index marking the end (exclusive) of the substring considered.\n */",
1783                            ),
1784                        },
1785                    ),
1786                    (
1787                        "repeat".to_string(),
1788                        MethodSig {
1789                            optional: false,
1790                            generics: Vec::new(),
1791                            params: vec![Param::new("count", Type::Number)],
1792                            ret: Type::String,
1793                            predicate: None,
1794                            doc: doc(
1795                                "/**\n * Returns a new string containing `count` copies of this string concatenated.\n * Zero `count` (or an empty receiver) returns `\"\"`; a negative `count` throws a `RangeError`.\n * @param count Number of repetitions.\n */",
1796                            ),
1797                        },
1798                    ),
1799                    (
1800                        "padStart".to_string(),
1801                        MethodSig {
1802                            optional: false,
1803                            generics: Vec::new(),
1804                            params: vec![
1805                                Param::new("targetLength", Type::Number),
1806                                Param::with_default(
1807                                    "padString",
1808                                    Type::String,
1809                                    crate::DefaultValue::String(" ".to_string()),
1810                                ),
1811                            ],
1812                            ret: Type::String,
1813                            predicate: None,
1814                            doc: doc(
1815                                "/**\n * Pads this string with `padString` on the left until the result reaches `targetLength` code units. Returns this string unchanged when no padding is needed or when `padString` is empty.\n * @param targetLength Length of the padded result.\n * @param padString Filler repeated to reach the target.\n */",
1816                            ),
1817                        },
1818                    ),
1819                    (
1820                        "padEnd".to_string(),
1821                        MethodSig {
1822                            optional: false,
1823                            generics: Vec::new(),
1824                            params: vec![
1825                                Param::new("targetLength", Type::Number),
1826                                Param::with_default(
1827                                    "padString",
1828                                    Type::String,
1829                                    crate::DefaultValue::String(" ".to_string()),
1830                                ),
1831                            ],
1832                            ret: Type::String,
1833                            predicate: None,
1834                            doc: doc(
1835                                "/**\n * Pads this string with `padString` on the right until the result reaches `targetLength` code units. Returns this string unchanged when no padding is needed or when `padString` is empty.\n * @param targetLength Length of the padded result.\n * @param padString Filler repeated to reach the target.\n */",
1836                            ),
1837                        },
1838                    ),
1839                    (
1840                        "toUpperCase".to_string(),
1841                        MethodSig {
1842                            optional: false,
1843                            generics: Vec::new(),
1844                            params: Vec::new(),
1845                            ret: Type::String,
1846                            predicate: None,
1847                            doc: doc(
1848                                "/**\n * Returns a new string with every character converted to its Unicode uppercase form.\n */",
1849                            ),
1850                        },
1851                    ),
1852                    (
1853                        "toLowerCase".to_string(),
1854                        MethodSig {
1855                            optional: false,
1856                            generics: Vec::new(),
1857                            params: Vec::new(),
1858                            ret: Type::String,
1859                            predicate: None,
1860                            doc: doc(
1861                                "/**\n * Returns a new string with every character converted to its Unicode lowercase form.\n */",
1862                            ),
1863                        },
1864                    ),
1865                    (
1866                        "trim".to_string(),
1867                        MethodSig {
1868                            optional: false,
1869                            generics: Vec::new(),
1870                            params: Vec::new(),
1871                            ret: Type::String,
1872                            predicate: None,
1873                            doc: doc(
1874                                "/**\n * Returns a new string with Unicode whitespace removed from both ends.\n */",
1875                            ),
1876                        },
1877                    ),
1878                    (
1879                        "trimStart".to_string(),
1880                        MethodSig {
1881                            optional: false,
1882                            generics: Vec::new(),
1883                            params: Vec::new(),
1884                            ret: Type::String,
1885                            predicate: None,
1886                            doc: doc(
1887                                "/**\n * Returns a new string with Unicode whitespace removed from the start.\n */",
1888                            ),
1889                        },
1890                    ),
1891                    (
1892                        "trimLeft".to_string(),
1893                        MethodSig {
1894                            optional: false,
1895                            generics: Vec::new(),
1896                            params: Vec::new(),
1897                            ret: Type::String,
1898                            predicate: None,
1899                            doc: doc(
1900                                "/**\n * The older name of `trimStart`: returns a new string with Unicode whitespace removed from the start.\n */",
1901                            ),
1902                        },
1903                    ),
1904                    (
1905                        "trimRight".to_string(),
1906                        MethodSig {
1907                            optional: false,
1908                            generics: Vec::new(),
1909                            params: Vec::new(),
1910                            ret: Type::String,
1911                            predicate: None,
1912                            doc: doc(
1913                                "/**\n * The older name of `trimEnd`: returns a new string with Unicode whitespace removed from the end.\n */",
1914                            ),
1915                        },
1916                    ),
1917                    (
1918                        "trimEnd".to_string(),
1919                        MethodSig {
1920                            optional: false,
1921                            generics: Vec::new(),
1922                            params: Vec::new(),
1923                            ret: Type::String,
1924                            predicate: None,
1925                            doc: doc(
1926                                "/**\n * Returns a new string with Unicode whitespace removed from the end.\n */",
1927                            ),
1928                        },
1929                    ),
1930                    (
1931                        "normalize".to_string(),
1932                        MethodSig {
1933                            optional: false,
1934                            generics: Vec::new(),
1935                            params: vec![Param::with_default(
1936                                "form",
1937                                Type::String,
1938                                crate::DefaultValue::String("NFC".to_string()),
1939                            )],
1940                            ret: Type::String,
1941                            predicate: None,
1942                            doc: doc(
1943                                "/**\n * Returns a new string in the specified Unicode normalization form.\n * @param form One of `\"NFC\"`, `\"NFD\"`, `\"NFKC\"`, `\"NFKD\"`; any other value throws `Error`. Defaults to `\"NFC\"`.\n */",
1944                            ),
1945                        },
1946                    ),
1947                    // regex-arm String overloads. `match`
1948                    // and `search` re-use the `RegExp.exec` host fn;
1949                    // `matchAll` / `split` / `replace` / `replaceAll`
1950                    // are tracked separately.
1951                    (
1952                        "match".to_string(),
1953                        MethodSig {
1954                            optional: false,
1955                            generics: Vec::new(),
1956                            params: vec![Param::new(
1957                                "re",
1958                                Type::Union(vec![
1959                                    Type::String,
1960                                    Type::prelude_interface("RegExp".to_string(), Vec::new()),
1961                                ]),
1962                            )],
1963                            ret: Type::Union(vec![
1964                                Type::prelude_interface("RegExpMatch".to_string(), Vec::new()),
1965                                Type::Null,
1966                            ]),
1967                            predicate: None,
1968                            doc: doc(
1969                                "/**\n * Find the next match of `re` in this string. Equivalent to `re.exec(this)` (v1; the `g`-flag array shape is a follow-up).\n * @param re The pattern to search for; a string is compiled as a pattern, as `new RegExp(re)` does.\n */",
1970                            ),
1971                        },
1972                    ),
1973                    (
1974                        "search".to_string(),
1975                        MethodSig {
1976                            optional: false,
1977                            generics: Vec::new(),
1978                            params: vec![Param::new(
1979                                "re",
1980                                Type::Union(vec![
1981                                    Type::String,
1982                                    Type::prelude_interface("RegExp".to_string(), Vec::new()),
1983                                ]),
1984                            )],
1985                            ret: Type::Number,
1986                            predicate: None,
1987                            doc: doc(
1988                                "/**\n * Index of the first match of `re` in this string, or `-1` if no match.\n * @param re The pattern to search for; a string is compiled as a pattern, as `new RegExp(re)` does.\n */",
1989                            ),
1990                        },
1991                    ),
1992                    (
1993                        "replace".to_string(),
1994                        MethodSig {
1995                            optional: false,
1996                            generics: Vec::new(),
1997                            params: vec![
1998                                Param::new(
1999                                    "search",
2000                                    Type::Union(vec![
2001                                        Type::String,
2002                                        Type::prelude_interface("RegExp".to_string(), Vec::new()),
2003                                    ]),
2004                                ),
2005                                Param::new(
2006                                    "replacement",
2007                                    crate::runtime::prelude::regex::replacement_type(),
2008                                ),
2009                            ],
2010                            ret: Type::String,
2011                            predicate: None,
2012                            doc: doc(
2013                                "/**\n * Replace the first match of `search` with `replacement`. For a `RegExp` `search`, the replacement may reference numbered captures (`$1`, `$2`) and named captures (`$<name>`); for both `string` and `RegExp` arms, the special tokens `$$` (literal `$`), `$&` (matched substring), `` $` `` (substring before the match), and `$'` (substring after the match) are honoured.\n * @param search Substring or pattern to match.\n * @param replacement The replacement string, or a function called for each match with the matched text, each capture (`undefined` if it didn't participate), the match's offset, the whole string and, when the pattern names groups, an object of them, whose result replaces the match.\n */",
2014                            ),
2015                        },
2016                    ),
2017                    (
2018                        "replaceAll".to_string(),
2019                        MethodSig {
2020                            optional: false,
2021                            generics: Vec::new(),
2022                            params: vec![
2023                                Param::new(
2024                                    "search",
2025                                    Type::Union(vec![
2026                                        Type::String,
2027                                        Type::prelude_interface("RegExp".to_string(), Vec::new()),
2028                                    ]),
2029                                ),
2030                                Param::new(
2031                                    "replacement",
2032                                    crate::runtime::prelude::regex::replacement_type(),
2033                                ),
2034                            ],
2035                            ret: Type::String,
2036                            predicate: None,
2037                            doc: doc(
2038                                "/**\n * Replace every non-overlapping match of `search` with `replacement`. Replacement-token semantics match `replace`.\n * @param search Substring or pattern to match.\n * @param replacement The replacement string.\n */",
2039                            ),
2040                        },
2041                    ),
2042                    (
2043                        "split".to_string(),
2044                        MethodSig {
2045                            optional: false,
2046                            generics: Vec::new(),
2047                            params: vec![
2048                                Param::new(
2049                                    "separator",
2050                                    Type::Union(vec![
2051                                        Type::String,
2052                                        Type::prelude_interface("RegExp".to_string(), Vec::new()),
2053                                    ]),
2054                                ),
2055                                Param::with_default(
2056                                    "limit",
2057                                    Type::Number,
2058                                    crate::DefaultValue::Number(f64::INFINITY),
2059                                ),
2060                            ],
2061                            ret: Type::Array(Box::new(Type::String)),
2062                            predicate: None,
2063                            doc: doc(
2064                                "/**\n * Split this string into pieces by matches of `separator`. For an empty string `separator`, returns each code unit as a separate part. For a `RegExp` `separator`, each match's captured groups are inserted after the part before it, as in JavaScript (`undefined` for a group that didn't participate).\n * @param separator Substring or pattern to split on.\n * @param limit Optional cap on the number of returned parts (default: no limit).\n */",
2065                            ),
2066                        },
2067                    ),
2068                    (
2069                        "matchAll".to_string(),
2070                        MethodSig {
2071                            optional: false,
2072                            generics: Vec::new(),
2073                            params: vec![Param::new(
2074                                "re",
2075                                Type::prelude_interface("RegExp".to_string(), Vec::new()),
2076                            )],
2077                            ret: Type::Array(Box::new(Type::prelude_interface("RegExpMatch".to_string(), Vec::new()))),
2078                            predicate: None,
2079                            doc: doc(
2080                                "/**\n * Find every non-overlapping match of `re` in this string. Zero-length matches advance by one to avoid infinite loops, per ECMA-262.\n * @param re The pattern to match.\n */",
2081                            ),
2082                        },
2083                    ),
2084                ]),
2085                properties: BTreeMap::from([(
2086                    "length".to_string(),
2087                    PropertySig {
2088                        ty: Type::Number,
2089                        readonly: true,
2090                        intrinsic: true,
2091                        optional: false,
2092                        doc: doc("/** The number of UTF-16 code units in the string. */"),
2093                    },
2094                )]),
2095                dispatch: Dispatch::Direct,
2096                doc: doc("/** The UTF-16 string type. */"),
2097            },
2098        },
2099    );
2100    defs.types.insert(
2101        "StringConstructor".to_string(),
2102        TypeSymbol {
2103            name: "StringConstructor".to_string(),
2104            mangled_name: crate::mangle::prelude("StringConstructor"),
2105            declaration_span: Span::at(crate::FileId::PRELUDE),
2106            kind: TypeKind::Interface { index: None,
2107                generics: Vec::new(),
2108                methods: BTreeMap::from([
2109                    (
2110                        "@call".to_string(),
2111                        MethodSig {
2112                            optional: false,
2113                            generics: Vec::new(),
2114                            params: vec![Param::new("value", Type::Unknown)],
2115                            ret: Type::String,
2116                            predicate: None,
2117                            doc: doc(
2118                                "/**\n * Convert any value to its string representation: `\"null\"` and `\"undefined\"` for those values, otherwise the value's `toString()` method.\n */",
2119                            ),
2120                        },
2121                    ),
2122                    (
2123                        "fromCharCode".to_string(),
2124                        MethodSig {
2125                            optional: false,
2126                            generics: Vec::new(),
2127                            params: vec![Param::rest("codes", Type::Array(Box::new(Type::Number)))],
2128                            ret: Type::String,
2129                            predicate: None,
2130                            doc: doc(
2131                                "/**\n * Builds a string from UTF-16 code units — one unit per argument.\n * Each value is truncated and reduced modulo 65536, as JavaScript does, so `-1` is U+FFFF.\n * @param codes Zero or more UTF-16 code units (0–65535).\n */",
2132                            ),
2133                        },
2134                    ),
2135                    (
2136                        "fromCodePoint".to_string(),
2137                        MethodSig {
2138                            optional: false,
2139                            generics: Vec::new(),
2140                            params: vec![Param::rest(
2141                                "codePoints",
2142                                Type::Array(Box::new(Type::Number)),
2143                            )],
2144                            ret: Type::String,
2145                            predicate: None,
2146                            doc: doc(
2147                                "/**\n * Builds a string from Unicode code points; astral code points (> U+FFFF) encode as surrogate pairs.\n * Throws a `RangeError` on a negative, non-integer, or > U+10FFFF input.\n * @param codePoints Zero or more Unicode code points (0–0x10FFFF).\n */",
2148                            ),
2149                        },
2150                    ),
2151                ]),
2152                properties: BTreeMap::new(),
2153                dispatch: Dispatch::Static,
2154                doc: doc(
2155                    "/** Constructor object for `string`. Accessed via the global `String` binding. */",
2156                ),
2157            },
2158        },
2159    );
2160    defs.values.insert(
2161        "String".to_string(),
2162        ValueSymbol {
2163            name: "String".to_string(),
2164            mangled_name: crate::mangle::prelude("String"),
2165            declaration_span: Span::at(crate::FileId::PRELUDE),
2166            kind: ValueKind::Const {
2167                ty: Type::prelude_interface("StringConstructor".to_string(), Vec::new()),
2168                doc: doc(
2169                    "/** The `String` constructor — call `String(x)` to convert any value to a string via its `toString()` method. */",
2170                ),
2171            },
2172        },
2173    );
2174}