From f9cd329b14402e17d5628e59df96ed8d0f427cdb Mon Sep 17 00:00:00 2001 From: Nick Fitzgerald Date: Tue, 25 Sep 2018 12:30:04 -0700 Subject: [PATCH] js-sys: Add `js_sys::try_iter` for iterating over any `JsValue` Fixes #776 --- crates/js-sys/src/lib.rs | 26 ++++++++++++++++++++ crates/js-sys/tests/wasm/Iterator.js | 19 +++++++++++++++ crates/js-sys/tests/wasm/Iterator.rs | 36 ++++++++++++++++++++++++++++ crates/js-sys/tests/wasm/main.rs | 1 + 4 files changed, 82 insertions(+) create mode 100644 crates/js-sys/tests/wasm/Iterator.js create mode 100644 crates/js-sys/tests/wasm/Iterator.rs diff --git a/crates/js-sys/src/lib.rs b/crates/js-sys/src/lib.rs index 27229d2f8..867f5033d 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 000000000..79b8c5006 --- /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 000000000..7c5408376 --- /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 c72a998d3..3dd249c39 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;