1use crate::runtime::host::{abi_arg, abi_result};
10mod dynamic;
11mod index;
12mod static_value;
13
14use wasmtime::{
15 ArrayRef, ArrayRefPre, Caller, FuncType, HeapType, Linker, RefType, Rooted, StructRef,
16 StructRefPre, StructType, Val, ValType,
17};
18
19use crate::MangledName;
20use crate::runtime::StoreData;
21use crate::runtime::host::{
22 host_object_vtable, intrinsic_array_type, intrinsic_string_type, register_host_fn,
23 register_host_fn_async, write_submilli_array_struct,
24};
25use crate::runtime::intrinsic_types::{build_intrinsic_types, intrinsic_types};
26use crate::runtime::prelude::collection::{FIELD_NAME, is_a};
27use crate::runtime::prelude::iterator::as_struct;
28use crate::runtime::prelude::vtable::{dispatch_vtable_slot, read_string_units};
29use crate::runtime::prelude::{MODULE_NAME, declare_method};
30use crate::{PackageDeclaration, Param, Type};
31
32fn ctor_key(method: &str) -> MangledName {
33 crate::mangle::extend(&crate::mangle::prelude("ObjectConstructor"), method)
34}
35
36#[derive(Clone, Copy)]
37enum Enumerate {
38 Keys,
39 Values,
40 Entries,
41}
42
43impl Enumerate {
44 fn method(self) -> &'static str {
45 match self {
46 Enumerate::Keys => "keys",
47 Enumerate::Values => "values",
48 Enumerate::Entries => "entries",
49 }
50 }
51}
52
53pub(super) fn find_data_slot(
54 caller: &mut Caller<'_, StoreData>,
55 object: &Val,
56 key: &[u16],
57) -> wasmtime::Result<Option<u32>> {
58 let object = as_struct(caller, object, "object equality receiver")?;
59 index::lookup(caller, &object, key, false, false)
60}
61
62pub(super) fn find_field_slot(
63 caller: &mut Caller<'_, StoreData>,
64 object: &Rooted<StructRef>,
65 key: &[u16],
66 accessor: bool,
67) -> wasmtime::Result<Option<u32>> {
68 index::lookup(caller, object, key, accessor, false)
69}
70
71pub(crate) fn data_field_count(
72 caller: &mut Caller<'_, StoreData>,
73 object: &Val,
74) -> wasmtime::Result<usize> {
75 let Some((names, values)) = shape_arrays(caller, object)? else {
76 return Ok(0);
77 };
78 let count = field_count(caller, object)?;
79 let mut present = 0;
80 for slot in 0..count {
81 let name = names.get(&mut *caller, slot)?;
82 let value = values.get(&mut *caller, slot)?;
83 if field_is_present(caller, &name, &value)? && !is_accessor_slot(caller, &name)? {
84 present += 1;
85 }
86 }
87 Ok(present)
88}
89
90pub(crate) fn field_count(
91 caller: &mut Caller<'_, StoreData>,
92 object: &Val,
93) -> wasmtime::Result<u32> {
94 let object = as_struct(caller, object, "object field count")?;
95 index::len(caller, &object)
96}
97
98fn shape_arrays(
101 caller: &mut Caller<'_, StoreData>,
102 obj: &Val,
103) -> wasmtime::Result<Option<(Rooted<ArrayRef>, Rooted<ArrayRef>)>> {
104 let Val::AnyRef(Some(any)) = obj else {
105 return Ok(None);
106 };
107 let Some(st) = any.as_struct(&mut *caller)? else {
108 return Ok(None);
109 };
110 let shape = intrinsic_types(&mut *caller)?.object_shape.clone();
111 if !st.matches_ty(&*caller, &shape)? {
112 return Ok(None);
113 }
114 let names = field_array(caller, &st, 1)?;
115 let values = field_array(caller, &st, 2)?;
116 Ok(Some((names, values)))
117}
118
119pub(super) fn field_array(
120 caller: &mut Caller<'_, StoreData>,
121 st: &Rooted<StructRef>,
122 idx: usize,
123) -> wasmtime::Result<Rooted<ArrayRef>> {
124 match st.field(&mut *caller, idx)? {
125 Val::AnyRef(Some(a)) => a.unwrap_array(&mut *caller),
126 other => Err(wasmtime::Error::msg(format!(
127 "$ObjectShape field {idx} is not an array: {other:?}"
128 ))),
129 }
130}
131
132pub(crate) fn field_is_present(
134 caller: &mut Caller<'_, StoreData>,
135 name: &Val,
136 value: &Val,
137) -> wasmtime::Result<bool> {
138 if !super::undefined::is_nullish(caller, value)? {
139 return Ok(true);
140 }
141 let name = as_struct(caller, name, "field name")?;
142 let string = intrinsic_types(&mut *caller)?.string.clone();
143 if StructType::eq(&name.ty(&*caller)?, &string) {
144 return Ok(true);
145 }
146 Ok(matches!(name.field(&mut *caller, 3)?, Val::I32(value) if value != 0))
147}
148
149fn enumerate(
150 caller: &mut Caller<'_, StoreData>,
151 obj: &Val,
152 kind: Enumerate,
153) -> wasmtime::Result<Val> {
154 if super::undefined::is_nullish(caller, obj)? {
155 return Err(wasmtime::Error::msg(format!(
156 "Object.{} called on null or undefined",
157 kind.method()
158 )));
159 }
160 let mut elems = Vec::new();
161 if let Some((names, values)) = shape_arrays(caller, obj)? {
162 let len = field_count(caller, obj)?;
163 elems.reserve(len as usize);
164 let error_slots = super::error::non_enumerable_slots(caller, obj)?;
165 for i in 0..len {
166 if error_slots.as_ref().is_some_and(|hidden| hidden.hides(i)) {
167 continue;
168 }
169 let name = names.get(&mut *caller, i)?;
170 let value = values.get(&mut *caller, i)?;
171 if !field_is_present(caller, &name, &value)? || is_accessor_slot(caller, &name)? {
172 continue;
173 }
174 let elem = match kind {
175 Enumerate::Keys => names.get(&mut *caller, i)?,
176 Enumerate::Values => values.get(&mut *caller, i)?,
177 Enumerate::Entries => {
178 let name = names.get(&mut *caller, i)?;
179 let value = values.get(&mut *caller, i)?;
180 let pair = write_submilli_array_struct(caller, &[name, value])?;
181 Val::AnyRef(Some(pair.to_anyref()))
182 }
183 };
184 elems.push(elem);
185 }
186 }
187 let arr = write_submilli_array_struct(caller, &elems)?;
188 Ok(Val::AnyRef(Some(arr.to_anyref())))
189}
190
191pub(crate) fn is_accessor_slot(
194 caller: &mut Caller<'_, StoreData>,
195 name: &Val,
196) -> wasmtime::Result<bool> {
197 let name = as_struct(caller, name, "field name")?;
198 let string = intrinsic_types(&mut *caller)?.string.clone();
199 if StructType::eq(&name.ty(&*caller)?, &string) {
200 return Ok(false);
201 }
202 Ok(matches!(name.field(&mut *caller, 3)?, Val::I32(-1)))
203}
204
205pub(crate) fn field_is_private(
208 caller: &mut Caller<'_, StoreData>,
209 name: &Val,
210) -> wasmtime::Result<bool> {
211 let name = as_struct(caller, name, "field name")?;
212 if name.ty(&*caller)?.fields().count() < 5 {
213 return Ok(false);
214 }
215 Ok(matches!(name.field(&mut *caller, 4)?, Val::I32(1)))
216}
217
218fn spread(
221 caller: &mut Caller<'_, StoreData>,
222 target: &Val,
223 source: &Val,
224 shape: &Val,
225 mask: &Val,
226) -> wasmtime::Result<Val> {
227 let intr = intrinsic_types(&mut *caller)?;
228 let mut entries = std::collections::BTreeMap::new();
229 let omitted = spread_omitted_fields(caller, mask)?;
230 for (source_index, object) in [target, source].into_iter().enumerate() {
231 let Some((names, values)) = shape_arrays(caller, object)? else {
232 continue;
233 };
234 for index in 0..field_count(caller, object)? {
235 let name = names.get(&mut *caller, index)?;
236 let value = values.get(&mut *caller, index)?;
237 if !field_is_present(caller, &name, &value)? || is_accessor_slot(caller, &name)? {
238 continue;
239 }
240 let units = read_string_units(caller, &name, FIELD_NAME)?;
241 if source_index == 1 && omitted.contains(&units) {
242 continue;
243 }
244 entries.insert(units, (copy_field_name(caller, name, true)?, value));
245 }
246 }
247 if let Some((names, _)) = shape_arrays(caller, shape)? {
248 for index in 0..field_count(caller, shape)? {
249 let name = names.get(&mut *caller, index)?;
250 let units = read_string_units(caller, &name, FIELD_NAME)?;
251 if let std::collections::btree_map::Entry::Vacant(entry) = entries.entry(units) {
252 entry.insert((
253 copy_field_name(caller, name, false)?,
254 super::undefined::value(caller)?,
255 ));
256 }
257 }
258 }
259 let (names, values): (Vec<_>, Vec<_>) = entries.into_values().unzip();
260 let names_pre = ArrayRefPre::new(&mut *caller, intr.field_names.clone());
261 let values_pre = ArrayRefPre::new(&mut *caller, intr.object_fields.clone());
262 let shape_pre = StructRefPre::new(&mut *caller, intr.object_shape.clone());
263 let names = ArrayRef::new_fixed(&mut *caller, &names_pre, &names)?;
264 let values = ArrayRef::new_fixed(&mut *caller, &values_pre, &values)?;
265 let vtable = host_object_vtable(caller)?;
266 let object = StructRef::new(
267 &mut *caller,
268 &shape_pre,
269 &[
270 vtable,
271 Val::AnyRef(Some(names.to_anyref())),
272 Val::AnyRef(Some(values.to_anyref())),
273 Val::AnyRef(None),
274 ],
275 )?;
276 Ok(Val::AnyRef(Some(object.to_anyref())))
277}
278
279fn copy_field_name(
280 caller: &mut Caller<'_, StoreData>,
281 name: Val,
282 present: bool,
283) -> wasmtime::Result<Val> {
284 let object = as_struct(caller, &name, "field name")?;
285 let string = intrinsic_types(&mut *caller)?.string.clone();
286 if StructType::eq(&object.ty(&*caller)?, &string) {
287 return Ok(name);
288 }
289 let ty = if present {
290 string
291 } else {
292 object.ty(&*caller)?
293 };
294 let mut fields = vec![
295 object.field(&mut *caller, 0)?,
296 object.field(&mut *caller, 1)?,
297 object.field(&mut *caller, 2)?,
298 ];
299 if !present {
300 fields.push(Val::I32(0));
301 if ty.fields().count() > 4 {
302 fields.push(object.field(&mut *caller, 4)?);
303 }
304 }
305 let pre = StructRefPre::new(&mut *caller, ty);
306 Ok(Val::AnyRef(Some(
307 StructRef::new(&mut *caller, &pre, &fields)?.to_anyref(),
308 )))
309}
310
311fn insert_field(
314 caller: &mut Caller<'_, StoreData>,
315 obj: &Val,
316 name: &Val,
317 value: &Val,
318) -> wasmtime::Result<()> {
319 let object = as_struct(caller, obj, "field insertion receiver")?;
320 let old_names = field_array(caller, &object, 1)?;
321 let old_values = field_array(caller, &object, 2)?;
322 let count = index::len(caller, &object)?;
323 let capacity = old_names.len(&mut *caller)?;
324 let new_count = count
325 .checked_add(1)
326 .filter(|n| *n <= i32::MAX as u32)
327 .ok_or_else(|| crate::runtime::host::range_error("object field count limit exceeded"))?;
328 let name = inserted_field_name(caller, name)?;
329 let (names, values, table) = if count == capacity {
330 let (names, values) = grow_fields(caller, &old_names, &old_values, &name)?;
331 let new_capacity = names.len(&mut *caller)?;
332 let table = index::build(caller, &names, count, new_capacity)?;
333 (names, values, table)
334 } else {
335 let table = match index::cached(caller, &object)? {
336 Some(table) => table,
337 None => index::build(caller, &old_names, count, capacity)?,
338 };
339 (old_names, old_values, table)
340 };
341 let bucket = index::empty_bucket(caller, &table, &name)?;
342 names.set(&mut *caller, count, name)?;
345 values.set(&mut *caller, count, *value)?;
346 table.set(&mut *caller, bucket, Val::I32(new_count as i32))?;
347 table.set(&mut *caller, 0, Val::I32(new_count as i32))?;
348 table.set(&mut *caller, 1, Val::I32(1))?;
349 object.set_field(&mut *caller, 1, Val::AnyRef(Some(names.to_anyref())))?;
350 object.set_field(&mut *caller, 2, Val::AnyRef(Some(values.to_anyref())))?;
351 object.set_field(
352 &mut *caller,
353 index::INDEX_FIELD,
354 Val::AnyRef(Some(table.to_anyref())),
355 )?;
356 Ok(())
357}
358
359fn grow_fields(
360 caller: &mut Caller<'_, StoreData>,
361 names: &Rooted<ArrayRef>,
362 values: &Rooted<ArrayRef>,
363 filler: &Val,
364) -> wasmtime::Result<(Rooted<ArrayRef>, Rooted<ArrayRef>)> {
365 let capacity = names.len(&mut *caller)?;
366 let value_len = values.len(&mut *caller)?;
367 let width = capacity
368 .checked_add(1)
369 .ok_or_else(|| crate::runtime::host::fatal_host_error("object guard row width overflow"))?;
370 let hidden = value_len
371 .checked_sub(capacity)
372 .filter(|n| n % width == 0)
373 .ok_or_else(|| crate::runtime::host::fatal_host_error("malformed object guard rows"))?;
374 let rows = hidden / width;
375 let new_capacity = capacity
376 .checked_mul(2)
377 .map(|n| n.max(8))
378 .filter(|n| *n < i32::MAX as u32)
379 .ok_or_else(|| crate::runtime::host::range_error("object field capacity limit exceeded"))?;
380 let new_width = new_capacity + 1;
381 let new_value_len = rows
382 .checked_mul(new_width)
383 .and_then(|n| n.checked_add(new_capacity))
384 .ok_or_else(|| {
385 crate::runtime::host::range_error("object payload capacity limit exceeded")
386 })?;
387 crate::runtime::fuel::charge(
388 &mut *caller,
389 crate::runtime::fuel::ELEM,
390 u64::from(new_capacity)
391 + u64::from(new_value_len)
392 + u64::from(capacity)
393 + u64::from(value_len),
394 )?;
395 let intr = intrinsic_types(&mut *caller)?;
396 let names_pre = ArrayRefPre::new(&mut *caller, intr.field_names.clone());
397 let values_pre = ArrayRefPre::new(&mut *caller, intr.object_fields.clone());
398 let new_names = ArrayRef::new(&mut *caller, &names_pre, filler, new_capacity)?;
399 let new_values = ArrayRef::new(&mut *caller, &values_pre, &Val::AnyRef(None), new_value_len)?;
400 for slot in 0..capacity {
401 let name = names.get(&mut *caller, slot)?;
402 let value = values.get(&mut *caller, slot)?;
403 new_names.set(&mut *caller, slot, name)?;
404 new_values.set(&mut *caller, slot, value)?;
405 }
406 for row in 0..rows {
407 let old_start = capacity + row * width;
408 let new_start = new_capacity + row * new_width;
409 for slot in 0..capacity {
410 let guard = values.get(&mut *caller, old_start + slot)?;
411 new_values.set(&mut *caller, new_start + slot, guard)?;
412 }
413 let context = values.get(&mut *caller, old_start + capacity)?;
414 new_values.set(&mut *caller, new_start + new_capacity, context)?;
415 }
416 Ok((new_names, new_values))
417}
418
419fn inserted_field_name(caller: &mut Caller<'_, StoreData>, name: &Val) -> wasmtime::Result<Val> {
422 use wasmtime::{FieldType, Finality, Mutability, StorageType};
423 let intr = intrinsic_types(&mut *caller)?;
424 let mut fields: Vec<_> = intr.string.fields().collect();
425 fields.push(FieldType::new(
426 Mutability::Var,
427 StorageType::ValType(ValType::I32),
428 ));
429 let ty = crate::runtime::gc_singleton::singleton_struct(
430 caller.engine(),
431 Finality::Final,
432 Some(intr.string.clone()),
433 fields,
434 )?;
435 let name = as_struct(caller, name, "inserted field name")?;
436 let values = [
437 name.field(&mut *caller, 0)?,
438 name.field(&mut *caller, 1)?,
439 name.field(&mut *caller, 2)?,
440 Val::I32(2),
441 ];
442 let pre = StructRefPre::new(&mut *caller, ty);
443 Ok(Val::AnyRef(Some(
444 StructRef::new(&mut *caller, &pre, &values)?.to_anyref(),
445 )))
446}
447
448pub(crate) fn field_was_inserted(
449 caller: &mut Caller<'_, StoreData>,
450 name: &Val,
451) -> wasmtime::Result<bool> {
452 let name = as_struct(caller, name, "field name")?;
453 let string = intrinsic_types(&mut *caller)?.string.clone();
454 if StructType::eq(&name.ty(&*caller)?, &string) {
455 return Ok(false);
456 }
457 Ok(matches!(name.field(&mut *caller, 3)?, Val::I32(2)))
458}
459
460fn spread_omitted_fields(
462 caller: &mut Caller<'_, StoreData>,
463 mask: &Val,
464) -> wasmtime::Result<std::collections::BTreeSet<Vec<u16>>> {
465 let mut omitted = std::collections::BTreeSet::new();
466 if let Some((names, values)) = shape_arrays(caller, mask)? {
467 for index in 0..field_count(caller, mask)? {
468 if !matches!(values.get(&mut *caller, index)?, Val::AnyRef(None)) {
469 let name = names.get(&mut *caller, index)?;
470 omitted.insert(read_string_units(caller, &name, FIELD_NAME)?);
471 }
472 }
473 }
474 Ok(omitted)
475}
476
477fn has_own(caller: &mut Caller<'_, StoreData>, obj: &Val, key: &Val) -> wasmtime::Result<bool> {
478 if super::undefined::is_nullish(caller, obj)? {
479 return Err(wasmtime::Error::msg(
480 "Object.hasOwn called on null or undefined",
481 ));
482 }
483 let Some((names, values)) = shape_arrays(caller, obj)? else {
484 return Ok(false);
485 };
486 let target = read_string_units(caller, key, FIELD_NAME)?;
487 let Some(slot) = find_data_slot(caller, obj, &target)? else {
488 return Ok(false);
489 };
490 let name = names.get(&mut *caller, slot)?;
491 let value = values.get(&mut *caller, slot)?;
492 field_is_present(caller, &name, &value)
493}
494
495fn same_value_bits(a: u64, b: u64) -> bool {
499 (f64::from_bits(a).is_nan() && f64::from_bits(b).is_nan()) || a == b
500}
501
502fn boxed_number_bits(caller: &mut Caller<'_, StoreData>, val: &Val) -> wasmtime::Result<u64> {
503 let st = as_struct(caller, val, "Object.is operand")?;
504 match st.field(&mut *caller, 1)? {
505 Val::F64(bits) => Ok(bits),
506 other => Err(wasmtime::Error::msg(format!(
507 "Object.is: boxed number payload is {other:?}, not f64"
508 ))),
509 }
510}
511
512async fn same_value(
513 caller: &mut Caller<'_, StoreData>,
514 a: &Val,
515 b: &Val,
516) -> wasmtime::Result<bool> {
517 match (a, b) {
518 (Val::AnyRef(None), Val::AnyRef(None)) => return Ok(true),
519 (Val::AnyRef(None), _) | (_, Val::AnyRef(None)) => return Ok(false),
520 _ => {}
521 }
522 let boxed_number = intrinsic_types(&mut *caller)?.boxed_number.clone();
526 if is_a(caller, a, &boxed_number)? && is_a(caller, b, &boxed_number)? {
527 let (a_bits, b_bits) = (boxed_number_bits(caller, a)?, boxed_number_bits(caller, b)?);
528 return Ok(same_value_bits(a_bits, b_bits));
529 }
530 match dispatch_vtable_slot(caller, a, 2, std::slice::from_ref(b)).await? {
531 Val::I32(v) => Ok(v != 0),
532 other => Err(wasmtime::Error::msg(format!(
533 "Object.is: equals returned {other:?}, expected i32"
534 ))),
535 }
536}
537
538pub fn install(linker: &mut Linker<StoreData>) -> wasmtime::Result<()> {
539 dynamic::install(linker)?;
540 let engine = linker.engine().clone();
541 let intr = build_intrinsic_types(&engine)?;
542 let obj = ValType::Ref(RefType::new(
543 true,
544 HeapType::ConcreteStruct(intr.object.clone()),
545 ));
546 let string = ValType::Ref(RefType::new(
547 false,
548 HeapType::ConcreteStruct(intrinsic_string_type(&engine)?),
549 ));
550 let array = ValType::Ref(RefType::new(
551 false,
552 HeapType::ConcreteStruct(intrinsic_array_type(&engine)?),
553 ));
554 let boolean = ValType::I32;
555 let ft = |params: Vec<ValType>, results: Vec<ValType>| FuncType::new(&engine, params, results);
556
557 for kind in [Enumerate::Keys, Enumerate::Values, Enumerate::Entries] {
558 register_host_fn(
559 linker,
560 MODULE_NAME,
561 ctor_key(kind.method()),
562 ft(vec![obj.clone()], vec![array.clone()]),
563 true,
564 move |caller, params, results| {
565 *abi_result(results, 0)? = enumerate(caller, abi_arg(params, 0)?, kind)?;
566 Ok(())
567 },
568 )?;
569 }
570
571 register_host_fn(
572 linker,
573 MODULE_NAME,
574 ctor_key("hasOwn"),
575 ft(vec![obj.clone(), string.clone()], vec![boolean.clone()]),
576 true,
577 |caller, params, results| {
578 *abi_result(results, 0)? = Val::I32(i32::from(has_own(
579 caller,
580 abi_arg(params, 0)?,
581 abi_arg(params, 1)?,
582 )?));
583 Ok(())
584 },
585 )?;
586
587 register_host_fn(
588 linker,
589 MODULE_NAME,
590 ctor_key("#insertField"),
591 ft(vec![obj.clone(), string, obj.clone()], vec![]),
592 true,
593 |caller, params, _| {
594 insert_field(
595 caller,
596 abi_arg(params, 0)?,
597 abi_arg(params, 1)?,
598 abi_arg(params, 2)?,
599 )
600 },
601 )?;
602
603 let shape = ValType::Ref(RefType::new(
604 false,
605 HeapType::ConcreteStruct(intr.object_shape.clone()),
606 ));
607 register_host_fn(
608 linker,
609 MODULE_NAME,
610 ctor_key("#spread"),
611 ft(
612 vec![obj.clone(), obj.clone(), obj.clone(), obj.clone()],
613 vec![shape],
614 ),
615 true,
616 |caller, params, results| {
617 *abi_result(results, 0)? = spread(
618 caller,
619 abi_arg(params, 0)?,
620 abi_arg(params, 1)?,
621 abi_arg(params, 2)?,
622 abi_arg(params, 3)?,
623 )?;
624 Ok(())
625 },
626 )?;
627 register_host_fn_async(
628 linker,
629 MODULE_NAME,
630 ctor_key("#toJson"),
631 ft(
632 vec![obj.clone()],
633 vec![ValType::Ref(RefType::new(
634 false,
635 HeapType::ConcreteStruct(intr.string.clone()),
636 ))],
637 ),
638 true,
639 |caller, params, results| {
640 Box::pin(async move {
641 let intr = intrinsic_types(&mut *caller)?;
642 *abi_result(results, 0)? = super::vtable::object_to_json(
643 caller,
644 abi_arg(params, 0)?,
645 &intr.raw_string,
646 &intr.string,
647 )
648 .await?;
649 Ok(())
650 })
651 },
652 )?;
653 register_host_fn(
654 linker,
655 MODULE_NAME,
656 ctor_key("#staticValue"),
657 ft(
658 vec![ValType::Ref(RefType::new(
659 false,
660 HeapType::ConcreteStruct(intr.string.clone()),
661 ))],
662 vec![obj.clone()],
663 ),
664 true,
665 |caller, params, results| {
666 *abi_result(results, 0)? = static_value::static_value(caller, abi_arg(params, 0)?)?;
667 Ok(())
668 },
669 )?;
670 register_host_fn_async(
671 linker,
672 MODULE_NAME,
673 ctor_key("is"),
674 ft(vec![obj.clone(), obj], vec![boolean]),
675 true,
676 |caller, params, results| {
677 Box::pin(async move {
678 *abi_result(results, 0)? = Val::I32(i32::from(
679 same_value(caller, abi_arg(params, 0)?, abi_arg(params, 1)?).await?,
680 ));
681 Ok(())
682 })
683 },
684 )
685}
686
687pub fn declare(defs: &mut PackageDeclaration) {
688 dynamic::declare(defs);
689 declare_method(
690 defs,
691 "#toJson",
692 ctor_key("#toJson"),
693 vec![Param::new("object", Type::Unknown)],
694 Type::String,
695 );
696 declare_method(
698 defs,
699 "#staticValue",
700 ctor_key("#staticValue"),
701 vec![Param::new("tag", Type::String)],
702 Type::Unknown,
703 );
704 declare_method(
705 defs,
706 "#insertField",
707 ctor_key("#insertField"),
708 vec![
709 Param::new("object", Type::Unknown),
710 Param::new("name", Type::String),
711 Param::new("value", Type::Unknown),
712 ],
713 Type::Void,
714 );
715 declare_method(
717 defs,
718 "#spread",
719 ctor_key("#spread"),
720 vec![
721 Param::new("target", Type::Unknown),
722 Param::new("source", Type::Unknown),
723 Param::new("shape", Type::Unknown),
724 Param::new("mask", Type::Unknown),
725 ],
726 Type::Object {
727 index: None,
728 fields: Default::default(),
729 },
730 );
731 let obj = || Param::new("obj", Type::Unknown);
735 declare_method(
736 defs,
737 "keys",
738 ctor_key("keys"),
739 vec![obj()],
740 Type::Array(Box::new(Type::String)),
741 );
742 declare_method(
743 defs,
744 "values",
745 ctor_key("values"),
746 vec![obj()],
747 Type::Array(Box::new(Type::Unknown)),
748 );
749 declare_method(
750 defs,
751 "entries",
752 ctor_key("entries"),
753 vec![obj()],
754 Type::Array(Box::new(Type::Tuple(
755 vec![Type::String, Type::Unknown].into(),
756 ))),
757 );
758 declare_method(
759 defs,
760 "hasOwn",
761 ctor_key("hasOwn"),
762 vec![obj(), Param::new("key", Type::String)],
763 Type::Boolean,
764 );
765 declare_method(
766 defs,
767 "is",
768 ctor_key("is"),
769 vec![
770 Param::with_default("a", Type::Unknown, crate::DefaultValue::Undefined),
771 Param::with_default("b", Type::Unknown, crate::DefaultValue::Undefined),
772 ],
773 Type::Boolean,
774 );
775}
776
777#[allow(clippy::too_many_lines)]
780pub(crate) fn declare_types(defs: &mut crate::PackageDeclaration) {
781 use crate::runtime::prelude::declaration::doc;
782 use crate::{
783 Dispatch, MethodSig, Param, Span, Type, TypeKind, TypeSymbol, ValueKind, ValueSymbol,
784 };
785 use std::collections::BTreeMap;
786 defs.types.insert(
787 "Object".to_string(),
788 TypeSymbol {
789 name: "Object".to_string(),
790 mangled_name: crate::mangle::prelude("Object"),
791 declaration_span: Span::at(crate::FileId::PRELUDE),
792 kind: TypeKind::Interface { index: None,
793 generics: Vec::new(),
794 methods: BTreeMap::from([
795 (
796 "toString".to_string(),
797 MethodSig {
798 optional: false,
799 generics: Vec::new(),
800 params: Vec::new(),
801 ret: Type::String,
802 predicate: None,
803 doc: doc("/** Returns `\"[object Object]\"`. */"),
804 },
805 ),
806 (
807 "toJson".to_string(),
808 MethodSig {
809 optional: false,
810 generics: Vec::new(),
811 params: Vec::new(),
812 ret: Type::String,
813 predicate: None,
814 doc: doc(
815 "/** Returns the JSON representation of this object — `\"{\"` + `\"key\":value-json` pairs joined with `\",\"` + `\"}\"`. Field iteration order matches the type's struct layout. */",
816 ),
817 },
818 ),
819 ]),
820 properties: BTreeMap::new(),
821 dispatch: Dispatch::VTable,
826 doc: doc("/** Universal base type for every object shape. */"),
827 },
828 },
829 );
830 defs.types.insert(
831 "ObjectConstructor".to_string(),
832 TypeSymbol {
833 name: "ObjectConstructor".to_string(),
834 mangled_name: crate::mangle::prelude("ObjectConstructor"),
835 declaration_span: Span::at(crate::FileId::PRELUDE),
836 kind: TypeKind::Interface { index: None,
837 generics: Vec::new(),
838 methods: BTreeMap::from([
839 (
840 "keys".to_string(),
841 MethodSig {
842 optional: false,
843 generics: Vec::new(),
844 params: vec![Param::new("obj", Type::Unknown)],
845 ret: Type::Array(Box::new(Type::String)),
846 predicate: None,
847 doc: doc(
848 "/**\n * Returns the object's field names in canonical sorted order (the same order JSON output uses).\n * An omitted optional field is not listed; one set to `undefined` or `null` is. Non-object values yield `[]`; `null` or `undefined` throws a catchable `Error`.\n * @param obj The object to enumerate.\n */",
849 ),
850 },
851 ),
852 (
853 "values".to_string(),
854 MethodSig {
855 optional: false,
856 generics: Vec::new(),
857 params: vec![Param::new("obj", Type::Unknown)],
858 ret: Type::Array(Box::new(Type::Unknown)),
859 predicate: None,
860 doc: doc(
861 "/**\n * Returns the object's field values, aligned with `Object.keys` order. Element types are erased to `unknown` — narrow with `typeof` / `as`.\n * Non-object values yield `[]`; `null` or `undefined` throws a catchable `Error`.\n * @param obj The object to enumerate.\n */",
862 ),
863 },
864 ),
865 (
866 "entries".to_string(),
867 MethodSig {
868 optional: false,
869 generics: Vec::new(),
870 params: vec![Param::new("obj", Type::Unknown)],
871 ret: Type::Array(Box::new(Type::Tuple(vec![
872 Type::String,
873 Type::Unknown,
874 ].into()))),
875 predicate: None,
876 doc: doc(
877 "/**\n * Returns `[name, value]` pairs in canonical sorted key order. Values are erased to `unknown` — narrow with `typeof` / `as`.\n * Non-object values yield `[]`; `null` or `undefined` throws a catchable `Error`.\n * @param obj The object to enumerate.\n */",
878 ),
879 },
880 ),
881 (
882 "hasOwn".to_string(),
883 MethodSig {
884 optional: false,
885 generics: Vec::new(),
886 params: vec![
887 Param::new("obj", Type::Unknown),
888 Param::new("key", Type::String),
889 ],
890 ret: Type::Boolean,
891 predicate: None,
892 doc: doc(
893 "/**\n * Returns `true` when `obj` has a field named `key`: an omitted optional field answers `false`, one holding `undefined` answers `true`. Non-object values have no fields; `null` or `undefined` throws a catchable `Error`.\n * @param obj The object to test.\n * @param key The field name.\n */",
894 ),
895 },
896 ),
897 (
898 "is".to_string(),
899 MethodSig {
900 optional: false,
901 generics: Vec::new(),
902 params: vec![
903 Param::with_default("a", Type::Unknown, crate::DefaultValue::Undefined),
904 Param::with_default("b", Type::Unknown, crate::DefaultValue::Undefined),
905 ],
906 ret: Type::Boolean,
907 predicate: None,
908 doc: doc(
909 "/**\n * SameValue comparison: like `===` but `Object.is(NaN, NaN)` is `true` and `Object.is(0, -0)` is `false`.\n * Object comparison follows the language's structural `===`, not JS reference identity.\n */",
910 ),
911 },
912 ),
913 ]),
914 properties: BTreeMap::new(),
915 dispatch: Dispatch::Static,
916 doc: doc(
917 "/** Constructor object for `Object` — the enumeration statics (`keys`/`values`/`entries`/`hasOwn`) and `is`. Accessed via the global `Object` binding. */",
918 ),
919 },
920 },
921 );
922
923 defs.values.insert(
924 "Object".to_string(),
925 ValueSymbol {
926 name: "Object".to_string(),
927 mangled_name: crate::mangle::prelude("Object"),
928 declaration_span: Span::at(crate::FileId::PRELUDE),
929 kind: ValueKind::Const {
930 ty: Type::prelude_interface("ObjectConstructor".to_string(), Vec::new()),
931 doc: doc(
932 "/** The `Object` namespace — `Object.keys(o)`, `Object.values(o)`, `Object.entries(o)`, `Object.hasOwn(o, k)`, `Object.is(a, b)`. */",
933 ),
934 },
935 },
936 );
937}
938
939#[cfg(test)]
940mod tests {
941 use super::same_value_bits;
942
943 #[test]
944 fn same_value_bit_rules() {
945 assert!(same_value_bits(f64::NAN.to_bits(), (-f64::NAN).to_bits()));
946 assert!(!same_value_bits(0f64.to_bits(), (-0f64).to_bits()));
947 assert!(same_value_bits((-0f64).to_bits(), (-0f64).to_bits()));
948 assert!(same_value_bits(1.5f64.to_bits(), 1.5f64.to_bits()));
949 assert!(!same_value_bits(1f64.to_bits(), 2f64.to_bits()));
950 }
951}