diff --git a/crates/js-sys/src/lib.rs b/crates/js-sys/src/lib.rs index 27229d2f..867f5033 100644 --- a/crates/js-sys/src/lib.rs +++ b/crates/js-sys/src/lib.rs @@ -1255,6 +1255,32 @@ impl IterState { } } +/// Create an iterator over `val` using the JS iteration protocol and +/// `Symbol.iterator`. +pub fn try_iter(val: &JsValue) -> Result, JsValue> { + let iter_sym = Symbol::iterator(); + let iter_fn = Reflect::get(val, iter_sym.as_ref())?; + if !iter_fn.is_function() { + return Ok(None); + } + + let iter_fn: Function = iter_fn.unchecked_into(); + let it = iter_fn.call0(val)?; + if !it.is_object() { + return Ok(None); + } + + let next = JsValue::from("next"); + let next = Reflect::get(&it, &next)?; + + Ok(if next.is_function() { + let it: Iterator = it.unchecked_into(); + Some(it.into_iter()) + } else { + None + }) +} + // IteratorNext #[wasm_bindgen] extern { diff --git a/crates/js-sys/tests/wasm/Iterator.js b/crates/js-sys/tests/wasm/Iterator.js new file mode 100644 index 00000000..79b8c500 --- /dev/null +++ b/crates/js-sys/tests/wasm/Iterator.js @@ -0,0 +1,19 @@ +exports.get_iterable = () => ["one", "two", "three"]; + +exports.get_not_iterable = () => new Object; + +exports.get_symbol_iterator_throws = () => ({ + [Symbol.iterator]: () => { throw new Error("nope"); }, +}); + +exports.get_symbol_iterator_not_function = () => ({ + [Symbol.iterator]: 5, +}); + +exports.get_symbol_iterator_returns_not_object = () => ({ + [Symbol.iterator]: () => 5, +}); + +exports.get_symbol_iterator_returns_object_without_next = () => ({ + [Symbol.iterator]: () => new Object, +}); diff --git a/crates/js-sys/tests/wasm/Iterator.rs b/crates/js-sys/tests/wasm/Iterator.rs new file mode 100644 index 00000000..7c540837 --- /dev/null +++ b/crates/js-sys/tests/wasm/Iterator.rs @@ -0,0 +1,36 @@ +use js_sys::*; +use wasm_bindgen::prelude::*; +use wasm_bindgen_test::*; + +#[wasm_bindgen(module = "tests/wasm/Iterator.js")] +extern "C" { + fn get_iterable() -> JsValue; + + fn get_not_iterable() -> JsValue; + + fn get_symbol_iterator_throws() -> JsValue; + + fn get_symbol_iterator_not_function() -> JsValue; + + fn get_symbol_iterator_returns_not_object() -> JsValue; + + fn get_symbol_iterator_returns_object_without_next() -> JsValue; +} + +#[wasm_bindgen_test] +fn try_iter_handles_iteration_protocol() { + assert_eq!( + try_iter(&get_iterable()) + .unwrap() + .unwrap() + .map(|x| x.unwrap().as_string().unwrap()) + .collect::>(), + vec!["one", "two", "three"] + ); + + assert!(try_iter(&get_not_iterable()).unwrap().is_none()); + assert!(try_iter(&get_symbol_iterator_throws()).is_err()); + assert!(try_iter(&get_symbol_iterator_not_function()).unwrap().is_none()); + assert!(try_iter(&get_symbol_iterator_returns_not_object()).unwrap().is_none()); + assert!(try_iter(&get_symbol_iterator_returns_object_without_next()).unwrap().is_none()); +} diff --git a/crates/js-sys/tests/wasm/main.rs b/crates/js-sys/tests/wasm/main.rs index c72a998d..3dd249c3 100755 --- a/crates/js-sys/tests/wasm/main.rs +++ b/crates/js-sys/tests/wasm/main.rs @@ -19,6 +19,7 @@ pub mod EvalError; pub mod Function; pub mod Generator; pub mod Intl; +pub mod Iterator; pub mod JsString; pub mod JSON; pub mod Map;