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