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