diff --git a/crates/perry-runtime/src/array/iterator.rs b/crates/perry-runtime/src/array/iterator.rs index bf70ee469d..d479cabfdc 100644 --- a/crates/perry-runtime/src/array/iterator.rs +++ b/crates/perry-runtime/src/array/iterator.rs @@ -1300,12 +1300,32 @@ fn throw_iterator_result_not_object() -> ! { #[no_mangle] pub extern "C" fn js_iterator_next_result(iter_f64: f64) -> f64 { let next = named_field(iter_f64, b"next"); - if !is_callable_value(next) { + let result = if is_callable_value(next) { + let prev_this = crate::object::js_implicit_this_set(iter_f64); + let result = unsafe { crate::closure::js_native_call_value(next, std::ptr::null(), 0) }; + crate::object::js_implicit_this_set(prev_this); + result + } else if next.to_bits() == crate::value::TAG_UNDEFINED + && is_builtin_iterator_class_id(crate::value::js_nanbox_get_pointer(iter_f64) as usize) + { + // Buffer iterators expose `next` through the native class-id tower, + // rather than as a stored closure. The general iterator drain uses + // this same fallback; without it IteratorNext (and therefore + // Uint8Array destructuring) treats an ordinary buffer iterator as if + // it had no callable `next` at all. Native dispatch still checks an + // own `next` override before advancing the builtin iterator. + unsafe { + crate::object::js_native_call_method( + iter_f64, + b"next".as_ptr() as *const i8, + 4, + std::ptr::null(), + 0, + ) + } + } else { crate::closure::throw_not_callable(); - } - let prev_this = crate::object::js_implicit_this_set(iter_f64); - let result = unsafe { crate::closure::js_native_call_value(next, std::ptr::null(), 0) }; - crate::object::js_implicit_this_set(prev_this); + }; if !is_object_like_value(result) { iter_bt_dump("js_iterator_next_result", result); throw_iterator_result_not_object(); diff --git a/crates/perry-runtime/src/object/field_get_set/has_property.rs b/crates/perry-runtime/src/object/field_get_set/has_property.rs index 9248eed248..41a4f70437 100644 --- a/crates/perry-runtime/src/object/field_get_set/has_property.rs +++ b/crates/perry-runtime/src/object/field_get_set/has_property.rs @@ -730,6 +730,24 @@ pub extern "C" fn js_object_has_property(obj: f64, key: f64) -> f64 { } { return nanbox_true; } + // A Uint8Array is represented by a registered buffer, but + // ordinary properties written through the typed-array + // [[Set]] path live in TYPED_ARRAY_OWN_PROPS. The + // lookup_typed_array_kind arm above can never see this + // receiver, and the legacy buffer table below is a + // different store. Ask the buffer-aware typed-array own + // property helper as well, matching Object.keys, + // hasOwnProperty, and getOwnPropertyDescriptor. + let key_str = crate::value::js_get_string_pointer_unified(key) + as *const crate::StringHeader; + if unsafe { + crate::typedarray_props::typed_array_has_own_property( + obj_addr as *const crate::typedarray::TypedArrayHeader, + key_str, + ) + } { + return nanbox_true; + } // #6406: the Buffer-specific surface the %TypedArray% chain // above does NOT cover — a user own-property (`buf.foo = v`) // and the `Buffer.prototype` methods (`readUInt8`, diff --git a/test-files/test_gap_9347_uint8array_destructuring.ts b/test-files/test_gap_9347_uint8array_destructuring.ts new file mode 100644 index 0000000000..5aae6988f7 --- /dev/null +++ b/test-files/test_gap_9347_uint8array_destructuring.ts @@ -0,0 +1,29 @@ +// Uint8Array uses Perry's buffer representation while Int32Array uses the +// typed-array representation. Both must drive the iterator protocol during +// binding and assignment destructuring. +function makeValues(value: T): T { + value[0] = 6; + value[1] = 7; + value[2] = 8; + return value; +} + +function binding(label: string, value: Uint8Array | Int32Array): void { + const [first, ...rest] = value; + console.log(label, first, rest.join(",")); +} + +function assignment(label: string, value: Uint8Array | Int32Array): void { + let first = 0; + let rest: number[] = []; + [first, ...rest] = value; + console.log(label, first, rest.join(",")); +} + +const i32 = makeValues(new Int32Array(3)); +const u8 = makeValues(new Uint8Array(3)); + +binding("binding-i32", i32); +binding("binding-u8", u8); +assignment("assignment-i32", i32); +assignment("assignment-u8", u8); diff --git a/test-files/test_gap_9347_uint8array_in_expando.ts b/test-files/test_gap_9347_uint8array_in_expando.ts new file mode 100644 index 0000000000..a684b8f7ce --- /dev/null +++ b/test-files/test_gap_9347_uint8array_in_expando.ts @@ -0,0 +1,15 @@ +// Uint8Array uses Perry's buffer representation while Int32Array uses the +// typed-array representation. Ordinary own properties must be visible to the +// `in` operator on both, including when the write is type-erased. +function inspect(label: string, value: Uint8Array | Int32Array): void { + (value as any).extra = 9; + console.log( + label, + "extra" in value, + value.hasOwnProperty("extra"), + Object.keys(value).join(","), + ); +} + +inspect("i32", new Int32Array(1)); +inspect("u8", new Uint8Array(1));