From dc22965e7130ca0626dbe93cd4ca7e0d450a14c8 Mon Sep 17 00:00:00 2001 From: Nick Fitzgerald Date: Tue, 25 Sep 2018 11:20:09 -0700 Subject: [PATCH 1/7] js-sys: Add doc comment for `js_sys::Iter` --- crates/js-sys/src/lib.rs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/crates/js-sys/src/lib.rs b/crates/js-sys/src/lib.rs index 0dd9c5be6a9..b633a192489 100644 --- a/crates/js-sys/src/lib.rs +++ b/crates/js-sys/src/lib.rs @@ -1176,6 +1176,9 @@ extern { pub fn next(this: &Iterator) -> Result; } +/// An iterator over the JS `Symbol.iterator` iteration protocol. +/// +/// Use the `IntoIterator for &js_sys::Iterator` implementation to create this. pub struct Iter<'a> { js: &'a Iterator, state: IterState, From 6edb871c36dc299d0818d91119ddab764b18c7f6 Mon Sep 17 00:00:00 2001 From: Nick Fitzgerald Date: Tue, 25 Sep 2018 11:21:27 -0700 Subject: [PATCH 2/7] js-sy: Add a doc comment for `js_sys::IntoIter` --- crates/js-sys/src/lib.rs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/crates/js-sys/src/lib.rs b/crates/js-sys/src/lib.rs index b633a192489..598ceb16988 100644 --- a/crates/js-sys/src/lib.rs +++ b/crates/js-sys/src/lib.rs @@ -1184,6 +1184,9 @@ pub struct Iter<'a> { state: IterState, } +/// An iterator over the JS `Symbol.iterator` iteration protocol. +/// +/// Use the `IntoIterator for js_sys::Iterator` implementation to create this. pub struct IntoIter { js: Iterator, state: IterState, From e3d2ea26288b0a68d58bbfa166dc0f4f37a3e81c Mon Sep 17 00:00:00 2001 From: Nick Fitzgerald Date: Tue, 25 Sep 2018 11:55:28 -0700 Subject: [PATCH 3/7] js-sys: Catch exceptions thrown in Reflect APIs Proxies passed to Reflect APIs can throw for any of these operations and it is a bit of a mess. --- crates/backend/src/codegen.rs | 4 +- crates/js-sys/src/lib.rs | 92 ++++++++++++++++--------- crates/js-sys/tests/wasm/JSON.rs | 2 +- crates/js-sys/tests/wasm/JsString.rs | 20 +++--- crates/js-sys/tests/wasm/MapIterator.rs | 4 +- crates/js-sys/tests/wasm/Object.rs | 20 +++--- crates/js-sys/tests/wasm/Reflect.js | 16 +++++ crates/js-sys/tests/wasm/Reflect.rs | 83 +++++++++++++++------- crates/js-sys/tests/wasm/WebAssembly.rs | 5 +- examples/wasm-in-wasm/src/lib.rs | 17 +++-- 10 files changed, 171 insertions(+), 92 deletions(-) diff --git a/crates/backend/src/codegen.rs b/crates/backend/src/codegen.rs index e3cc2287481..782d953bffb 100644 --- a/crates/backend/src/codegen.rs +++ b/crates/backend/src/codegen.rs @@ -1210,11 +1210,13 @@ impl ToTokens for ast::DictionaryField { (quote! { pub fn #name(&mut self, val: #ty) -> &mut Self { use wasm_bindgen::JsValue; - ::js_sys::Reflect::set( + let r = ::js_sys::Reflect::set( self.obj.as_ref(), &JsValue::from(stringify!(#name)), &JsValue::from(val), ); + debug_assert!(r.is_ok(), "setting properties should never fail on our dictionary objects"); + let _ = r; self } }).to_tokens(tokens); diff --git a/crates/js-sys/src/lib.rs b/crates/js-sys/src/lib.rs index 598ceb16988..27229d2f80b 100644 --- a/crates/js-sys/src/lib.rs +++ b/crates/js-sys/src/lib.rs @@ -2289,47 +2289,67 @@ extern "C" { /// /// [MDN documentation](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Reflect/apply) #[wasm_bindgen(static_method_of = Reflect, catch)] - pub fn apply(target: &Function, this_argument: &JsValue, arguments_list: &Array) - -> Result; + pub fn apply( + target: &Function, + this_argument: &JsValue, + arguments_list: &Array, + ) -> Result; /// The static `Reflect.construct()` method acts like the new operator, but /// as a function. It is equivalent to calling `new target(...args)`. It /// gives also the added option to specify a different prototype. /// /// [MDN documentation](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Reflect/construct) - #[wasm_bindgen(static_method_of = Reflect)] - pub fn construct(target: &Function, arguments_list: &Array) -> JsValue; - #[wasm_bindgen(static_method_of = Reflect, js_name = construct)] - pub fn construct_with_new_target(target: &Function, arguments_list: &Array, new_target: &Function) -> JsValue; + #[wasm_bindgen(static_method_of = Reflect, catch)] + pub fn construct(target: &Function, arguments_list: &Array) -> Result; + + /// The static `Reflect.construct()` method acts like the new operator, but + /// as a function. It is equivalent to calling `new target(...args)`. It + /// gives also the added option to specify a different prototype. + /// + /// [MDN documentation](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Reflect/construct) + #[wasm_bindgen(static_method_of = Reflect, js_name = construct, catch)] + pub fn construct_with_new_target( + target: &Function, + arguments_list: &Array, + new_target: &Function, + ) -> Result; /// The static `Reflect.defineProperty()` method is like /// `Object.defineProperty()` but returns a `Boolean`. /// /// [MDN documentation](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Reflect/defineProperty) - #[wasm_bindgen(static_method_of = Reflect, js_name = defineProperty)] - pub fn define_property(target: &Object, property_key: &JsValue, attributes: &Object) -> bool; + #[wasm_bindgen(static_method_of = Reflect, js_name = defineProperty, catch)] + pub fn define_property( + target: &Object, + property_key: &JsValue, + attributes: &Object, + ) -> Result; /// The static `Reflect.deleteProperty()` method allows to delete /// properties. It is like the `delete` operator as a function. /// /// [MDN documentation](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Reflect/deleteProperty) - #[wasm_bindgen(static_method_of = Reflect, js_name = deleteProperty)] - pub fn delete_property(target: &Object, key: &JsValue) -> bool; + #[wasm_bindgen(static_method_of = Reflect, js_name = deleteProperty, catch)] + pub fn delete_property(target: &Object, key: &JsValue) -> Result; /// The static `Reflect.get()` method works like getting a property from /// an object (`target[propertyKey]`) as a function. /// /// [MDN documentation](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Reflect/get) - #[wasm_bindgen(static_method_of = Reflect)] - pub fn get(target: &JsValue, key: &JsValue) -> JsValue; + #[wasm_bindgen(static_method_of = Reflect, catch)] + pub fn get(target: &JsValue, key: &JsValue) -> Result; /// The static `Reflect.getOwnPropertyDescriptor()` method is similar to /// `Object.getOwnPropertyDescriptor()`. It returns a property descriptor /// of the given property if it exists on the object, `undefined` otherwise. /// /// [MDN documentation](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Reflect/getOwnPropertyDescriptor) - #[wasm_bindgen(static_method_of = Reflect, js_name = getOwnPropertyDescriptor)] - pub fn get_own_property_descriptor(target: &Object, property_key: &JsValue) -> JsValue; + #[wasm_bindgen(static_method_of = Reflect, js_name = getOwnPropertyDescriptor, catch)] + pub fn get_own_property_descriptor( + target: &Object, + property_key: &JsValue, + ) -> Result; /// The static `Reflect.getPrototypeOf()` method is almost the same /// method as `Object.getPrototypeOf()`. It returns the prototype @@ -2337,30 +2357,30 @@ extern "C" { /// the specified object. /// /// [MDN documentation](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Reflect/getPrototypeOf) - #[wasm_bindgen(static_method_of = Reflect, js_name = getPrototypeOf)] - pub fn get_prototype_of(target: &JsValue) -> Object; + #[wasm_bindgen(static_method_of = Reflect, js_name = getPrototypeOf, catch)] + pub fn get_prototype_of(target: &JsValue) -> Result; /// The static `Reflect.has()` method works like the in operator as a /// function. /// /// [MDN documentation](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Reflect/has) - #[wasm_bindgen(static_method_of = Reflect)] - pub fn has(target: &JsValue, property_key: &JsValue) -> bool; + #[wasm_bindgen(static_method_of = Reflect, catch)] + pub fn has(target: &JsValue, property_key: &JsValue) -> Result; /// The static `Reflect.isExtensible()` method determines if an object is /// extensible (whether it can have new properties added to it). It is /// similar to `Object.isExtensible()`, but with some differences. /// /// [MDN documentation](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Reflect/isExtensible) - #[wasm_bindgen(static_method_of = Reflect, js_name = isExtensible)] - pub fn is_extensible(target: &Object) -> bool; + #[wasm_bindgen(static_method_of = Reflect, js_name = isExtensible, catch)] + pub fn is_extensible(target: &Object) -> Result; /// The static `Reflect.ownKeys()` method returns an array of the /// target object's own property keys. /// /// [MDN documentation](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Reflect/ownKeys) - #[wasm_bindgen(static_method_of = Reflect, js_name = ownKeys)] - pub fn own_keys(target: &JsValue) -> Array; + #[wasm_bindgen(static_method_of = Reflect, js_name = ownKeys, catch)] + pub fn own_keys(target: &JsValue) -> Result; /// The static `Reflect.preventExtensions()` method prevents new /// properties from ever being added to an object (i.e. prevents @@ -2368,17 +2388,27 @@ extern "C" { /// `Object.preventExtensions()`, but with some differences. /// /// [MDN documentation](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Reflect/preventExtensions) - #[wasm_bindgen(static_method_of = Reflect, js_name = preventExtensions)] - pub fn prevent_extensions(target: &Object) -> bool; + #[wasm_bindgen(static_method_of = Reflect, js_name = preventExtensions, catch)] + pub fn prevent_extensions(target: &Object) -> Result; /// The static `Reflect.set()` method works like setting a /// property on an object. /// /// [MDN documentation](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Reflect/set) - #[wasm_bindgen(static_method_of = Reflect)] - pub fn set(target: &JsValue, property_key: &JsValue, value: &JsValue) -> bool; - #[wasm_bindgen(static_method_of = Reflect, js_name = set)] - pub fn set_with_receiver(target: &JsValue, property_key: &JsValue, value: &JsValue, receiver: &JsValue) -> bool; + #[wasm_bindgen(static_method_of = Reflect, catch)] + pub fn set(target: &JsValue, property_key: &JsValue, value: &JsValue) -> Result; + + /// The static `Reflect.set()` method works like setting a + /// property on an object. + /// + /// [MDN documentation](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Reflect/set) + #[wasm_bindgen(static_method_of = Reflect, js_name = set, catch)] + pub fn set_with_receiver( + target: &JsValue, + property_key: &JsValue, + value: &JsValue, + receiver: &JsValue, + ) -> Result; /// The static `Reflect.setPrototypeOf()` method is the same /// method as `Object.setPrototypeOf()`. It sets the prototype @@ -2386,13 +2416,13 @@ extern "C" { /// object to another object or to null. /// /// [MDN documentation](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Reflect/setPrototypeOf) - #[wasm_bindgen(static_method_of = Reflect, js_name = setPrototypeOf)] - pub fn set_prototype_of(target: &Object, prototype: &JsValue) -> bool; + #[wasm_bindgen(static_method_of = Reflect, js_name = setPrototypeOf, catch)] + pub fn set_prototype_of(target: &Object, prototype: &JsValue) -> Result; } // RegExp #[wasm_bindgen] -extern { +extern "C" { #[wasm_bindgen(extends = Object)] #[derive(Clone, Debug)] pub type RegExp; diff --git a/crates/js-sys/tests/wasm/JSON.rs b/crates/js-sys/tests/wasm/JSON.rs index d38bae3bc59..affbf3f78e0 100644 --- a/crates/js-sys/tests/wasm/JSON.rs +++ b/crates/js-sys/tests/wasm/JSON.rs @@ -72,7 +72,7 @@ fn stringify() { fn stringify_error() { let func = Function::new_no_args("throw new Error(\"rust really rocks\")"); let obj = Object::new(); - Reflect::set(obj.as_ref(), &JsValue::from("toJSON"), func.as_ref()); + Reflect::set(obj.as_ref(), &JsValue::from("toJSON"), func.as_ref()).unwrap(); let result = JSON::stringify(&JsValue::from(obj)); assert!(result.is_err()); diff --git a/crates/js-sys/tests/wasm/JsString.rs b/crates/js-sys/tests/wasm/JsString.rs index 5d5b59753e6..0240dbdf047 100644 --- a/crates/js-sys/tests/wasm/JsString.rs +++ b/crates/js-sys/tests/wasm/JsString.rs @@ -168,7 +168,7 @@ fn locale_compare() { assert!(js_b.locale_compare(a, &locales, &options) < 0); locales.push(&"en".into()); - Reflect::set(options.as_ref(), &"sensitivity".into(), &"base".into()); + Reflect::set(options.as_ref(), &"sensitivity".into(), &"base".into()).unwrap(); assert_eq!(js_a.locale_compare(a, &locales, &options), 0); assert_eq!(js_a.locale_compare(b, &locales, &options), 0); @@ -211,7 +211,7 @@ fn locale_compare() { assert!(js_ten.locale_compare(two, &locales, &options) > 0); let locales = Array::new(); - Reflect::set(options.as_ref(), &"numeric".into(), &JsValue::TRUE); + Reflect::set(options.as_ref(), &"numeric".into(), &JsValue::TRUE).unwrap(); assert!(js_two.locale_compare(ten, &locales, &options) < 0); assert!(js_ten.locale_compare(two, &locales, &options) > 0); @@ -224,8 +224,8 @@ fn match_() { let result = JsString::from(s).match_(&re); let obj = result.unwrap(); - assert_eq!(Reflect::get(obj.as_ref(), &"0".into()), "T"); - assert_eq!(Reflect::get(obj.as_ref(), &"1".into()), "I"); + assert_eq!(Reflect::get(obj.as_ref(), &"0".into()).unwrap(), "T"); + assert_eq!(Reflect::get(obj.as_ref(), &"1".into()).unwrap(), "I"); let result = JsString::from("foo").match_(&re); assert!(result.is_none()); @@ -235,11 +235,11 @@ fn match_() { let result = JsString::from(s).match_(&re); let obj = result.unwrap(); - assert_eq!(Reflect::get(obj.as_ref(), &"0".into()), "see Chapter 3.4.5.1"); - assert_eq!(Reflect::get(obj.as_ref(), &"1".into()), "Chapter 3.4.5.1"); - assert_eq!(Reflect::get(obj.as_ref(), &"2".into()), ".1"); - assert_eq!(Reflect::get(obj.as_ref(), &"index".into()), 22); - assert_eq!(Reflect::get(obj.as_ref(), &"input".into()), s); + assert_eq!(Reflect::get(obj.as_ref(), &"0".into()).unwrap(), "see Chapter 3.4.5.1"); + assert_eq!(Reflect::get(obj.as_ref(), &"1".into()).unwrap(), "Chapter 3.4.5.1"); + assert_eq!(Reflect::get(obj.as_ref(), &"2".into()).unwrap(), ".1"); + assert_eq!(Reflect::get(obj.as_ref(), &"index".into()).unwrap(), 22); + assert_eq!(Reflect::get(obj.as_ref(), &"input".into()).unwrap(), s); } #[wasm_bindgen_test] @@ -494,7 +494,7 @@ fn value_of() { fn raw() { let call_site = Object::new(); let raw = Array::of3(&"foo".into(), &"bar".into(), &"123".into()); - Reflect::set(&call_site.as_ref(), &"raw".into(), &raw.into()); + Reflect::set(&call_site.as_ref(), &"raw".into(), &raw.into()).unwrap(); assert_eq!(JsString::raw_2(&call_site, "5", "JavaScript").unwrap(), "foo5barJavaScript123"); let substitutions = Array::of2(&"5".into(), &"JavaScript".into()); assert_eq!(JsString::raw(&call_site, &substitutions).unwrap(), "foo5barJavaScript123"); diff --git a/crates/js-sys/tests/wasm/MapIterator.rs b/crates/js-sys/tests/wasm/MapIterator.rs index ee76a85eab8..3e28a204409 100644 --- a/crates/js-sys/tests/wasm/MapIterator.rs +++ b/crates/js-sys/tests/wasm/MapIterator.rs @@ -11,8 +11,8 @@ fn entries() { let next = entries.next().unwrap(); assert_eq!(next.done(), false); assert!(next.value().is_object()); - assert_eq!(Reflect::get(&next.value(), &0.into()), "uno"); - assert_eq!(Reflect::get(&next.value(), &1.into()), 1); + assert_eq!(Reflect::get(&next.value(), &0.into()).unwrap(), "uno"); + assert_eq!(Reflect::get(&next.value(), &1.into()).unwrap(), 1); let next = entries.next().unwrap(); assert!(next.done()); diff --git a/crates/js-sys/tests/wasm/Object.rs b/crates/js-sys/tests/wasm/Object.rs index 52e6fa504df..e89d16f51db 100644 --- a/crates/js-sys/tests/wasm/Object.rs +++ b/crates/js-sys/tests/wasm/Object.rs @@ -57,23 +57,23 @@ fn assign() { let c = JsValue::from("c"); let target = Object::new(); - Reflect::set(target.as_ref(), a.as_ref(), a.as_ref()); + Reflect::set(target.as_ref(), a.as_ref(), a.as_ref()).unwrap(); let src1 = Object::new(); - Reflect::set(src1.as_ref(), &a, &c); + Reflect::set(src1.as_ref(), &a, &c).unwrap(); let src2 = Object::new(); - Reflect::set(src2.as_ref(), &b, &b); + Reflect::set(src2.as_ref(), &b, &b).unwrap(); let src3 = Object::new(); - Reflect::set(src3.as_ref(), &c, &c); + Reflect::set(src3.as_ref(), &c, &c).unwrap(); let res = Object::assign3(&target, &src1, &src2, &src3); assert!(Object::is(target.as_ref(), res.as_ref())); - assert_eq!(Reflect::get(target.as_ref(), &a), c); - assert_eq!(Reflect::get(target.as_ref(), &b), b); - assert_eq!(Reflect::get(target.as_ref(), &c), c); + assert_eq!(Reflect::get(target.as_ref(), &a).unwrap(), c); + assert_eq!(Reflect::get(target.as_ref(), &b).unwrap(), b); + assert_eq!(Reflect::get(target.as_ref(), &c).unwrap(), c); } #[wasm_bindgen_test] @@ -102,8 +102,8 @@ fn define_properties() { let descriptor = DefinePropertyAttrs::from(JsValue::from(Object::new())); descriptor.set_value(&42.into()); let descriptor = JsValue::from(descriptor); - Reflect::set(props.as_ref(), &JsValue::from("bar"), &descriptor); - Reflect::set(props.as_ref(), &JsValue::from("car"), &descriptor); + Reflect::set(props.as_ref(), &JsValue::from("bar"), &descriptor).unwrap(); + Reflect::set(props.as_ref(), &JsValue::from("car"), &descriptor).unwrap(); let foo = foo_42(); let foo = Object::define_properties(&foo, &props); assert!(foo.has_own_property(&"bar".into())); @@ -136,7 +136,7 @@ fn get_own_property_descriptor() { fn get_own_property_descriptors() { let foo = foo_42(); let descriptors = Object::get_own_property_descriptors(&foo); - let foo_desc = Reflect::get(&descriptors, &"foo".into()); + let foo_desc = Reflect::get(&descriptors, &"foo".into()).unwrap(); assert_eq!(PropertyDescriptor::from(foo_desc).value(), 42); } diff --git a/crates/js-sys/tests/wasm/Reflect.js b/crates/js-sys/tests/wasm/Reflect.js index ffba2069a16..98169e2e1ba 100644 --- a/crates/js-sys/tests/wasm/Reflect.js +++ b/crates/js-sys/tests/wasm/Reflect.js @@ -23,3 +23,19 @@ exports.Rectangle2 = class { return x === y; } }; + +exports.throw_all_the_time = () => new Proxy({}, { + getPrototypeOf() { throw new Error("nope"); }, + setPrototypeOf() { throw new Error("nope"); }, + isExtensible() { throw new Error("nope"); }, + preventExtensions() { throw new Error("nope"); }, + getOwnPropertyDescriptor() { throw new Error("nope"); }, + defineProperty() { throw new Error("nope"); }, + has() { throw new Error("nope"); }, + get() { throw new Error("nope"); }, + set() { throw new Error("nope"); }, + deleteProperty() { throw new Error("nope"); }, + ownKeys() { throw new Error("nope"); }, + apply() { throw new Error("nope"); }, + construct() { throw new Error("nope"); }, +}); diff --git a/crates/js-sys/tests/wasm/Reflect.rs b/crates/js-sys/tests/wasm/Reflect.rs index da2c5217a7b..47b5ac4ab04 100644 --- a/crates/js-sys/tests/wasm/Reflect.rs +++ b/crates/js-sys/tests/wasm/Reflect.rs @@ -21,6 +21,8 @@ extern { fn x_jsval(this: &Rectangle) -> JsValue; #[wasm_bindgen(method, setter, structural)] fn set_x(this: &Rectangle, x: u32); + + fn throw_all_the_time() -> Object; } #[wasm_bindgen] @@ -51,7 +53,7 @@ fn construct() { let args = Array::new(); args.push(&10.into()); args.push(&10.into()); - let instance = Reflect::construct(&RECTANGLE_CLASS, &args); + let instance = Reflect::construct(&RECTANGLE_CLASS, &args).unwrap(); assert_eq!(Rectangle::from(instance).x(), 10); } @@ -64,7 +66,7 @@ fn construct_with_new_target() { &RECTANGLE_CLASS, &args, &RECTANGLE2_CLASS, - ); + ).unwrap(); assert_eq!(Rectangle::from(instance).x(), 10); } @@ -73,7 +75,7 @@ fn define_property() { let value = DefinePropertyAttrs::from(JsValue::from(Object::new())); value.set_value(&42.into()); let obj = Object::from(JsValue::from(value)); - assert!(Reflect::define_property(&obj, &"key".into(), &obj)); + assert!(Reflect::define_property(&obj, &"key".into(), &obj).unwrap()); } #[wasm_bindgen_test] @@ -82,13 +84,13 @@ fn delete_property() { r.set_x(10); let obj = Object::from(JsValue::from(r.clone())); - Reflect::delete_property(&obj, &"x".into()); + Reflect::delete_property(&obj, &"x".into()).unwrap(); assert!(r.x_jsval().is_undefined()); let array = Array::new(); array.push(&1.into()); let obj = Object::from(JsValue::from(array)); - Reflect::delete_property(&obj, &0.into()); + Reflect::delete_property(&obj, &0.into()).unwrap(); let array = Array::from(&JsValue::from(obj)); assert!(array.length() == 1); array.for_each(&mut |x, _, _| assert!(x.is_undefined())); @@ -100,7 +102,7 @@ fn get() { r.set_x(10); let obj = JsValue::from(r.clone()); - assert_eq!(Reflect::get(&obj, &"x".into()), 10); + assert_eq!(Reflect::get(&obj, &"x".into()).unwrap(), 10); } #[wasm_bindgen_test] @@ -109,41 +111,41 @@ fn get_own_property_descriptor() { r.set_x(10); let obj = Object::from(JsValue::from(r.clone())); - let desc = Reflect::get_own_property_descriptor(&obj, &"x".into()); + let desc = Reflect::get_own_property_descriptor(&obj, &"x".into()).unwrap(); assert_eq!(PropertyDescriptor::from(desc).value(), 10); - let desc = Reflect::get_own_property_descriptor(&obj, &"foo".into()); + let desc = Reflect::get_own_property_descriptor(&obj, &"foo".into()).unwrap(); assert!(PropertyDescriptor::from(desc).value().is_undefined()); } #[wasm_bindgen_test] fn get_prototype_of() { - let proto = JsValue::from(Reflect::get_prototype_of(&Object::new().into())); + let proto = JsValue::from(Reflect::get_prototype_of(&Object::new().into()).unwrap()); assert_eq!(proto, *OBJECT_PROTOTYPE); - let proto = JsValue::from(Reflect::get_prototype_of(&Array::new().into())); + let proto = JsValue::from(Reflect::get_prototype_of(&Array::new().into()).unwrap()); assert_eq!(proto, *ARRAY_PROTOTYPE); } #[wasm_bindgen_test] fn has() { let obj = JsValue::from(Rectangle::new()); - assert!(Reflect::has(&obj, &"x".into())); - assert!(!Reflect::has(&obj, &"foo".into())); + assert!(Reflect::has(&obj, &"x".into()).unwrap()); + assert!(!Reflect::has(&obj, &"foo".into()).unwrap()); } #[wasm_bindgen_test] fn is_extensible() { let obj = Object::from(JsValue::from(Rectangle::new())); - assert!(Reflect::is_extensible(&obj)); - Reflect::prevent_extensions(&obj); - assert!(!Reflect::is_extensible(&obj)); + assert!(Reflect::is_extensible(&obj).unwrap()); + Reflect::prevent_extensions(&obj).unwrap(); + assert!(!Reflect::is_extensible(&obj).unwrap()); let obj = Object::seal(&Object::new()); - assert!(!Reflect::is_extensible(&obj)); + assert!(!Reflect::is_extensible(&obj).unwrap()); } #[wasm_bindgen_test] fn own_keys() { let obj = JsValue::from(Rectangle::new()); - let keys = Reflect::own_keys(&obj); + let keys = Reflect::own_keys(&obj).unwrap(); assert!(keys.length() == 2); keys.for_each(&mut |k, _, _| { assert!(k == "x" || k == "y"); @@ -153,32 +155,32 @@ fn own_keys() { #[wasm_bindgen_test] fn prevent_extensions() { let obj = Object::new(); - Reflect::prevent_extensions(&obj); - assert!(!Reflect::is_extensible(&obj)); + Reflect::prevent_extensions(&obj).unwrap(); + assert!(!Reflect::is_extensible(&obj).unwrap()); } #[wasm_bindgen_test] fn set() { let obj = JsValue::from(Object::new()); - assert!(Reflect::set(&obj, &"key".into(), &"value".into())); - assert_eq!(Reflect::get(&obj, &"key".into()), "value"); + assert!(Reflect::set(&obj, &"key".into(), &"value".into()).unwrap()); + assert_eq!(Reflect::get(&obj, &"key".into()).unwrap(), "value"); } #[wasm_bindgen_test] fn set_with_receiver() { let obj1 = JsValue::from(Object::new()); let obj2 = JsValue::from(Object::new()); - assert!(Reflect::set_with_receiver(&obj2, &"key".into(), &"value".into(), &obj2)); - assert!(Reflect::get(&obj1, &"key".into()).is_undefined()); - assert_eq!(Reflect::get(&obj2, &"key".into()), "value"); + assert!(Reflect::set_with_receiver(&obj2, &"key".into(), &"value".into(), &obj2).unwrap()); + assert!(Reflect::get(&obj1, &"key".into()).unwrap().is_undefined()); + assert_eq!(Reflect::get(&obj2, &"key".into()).unwrap(), "value"); } #[wasm_bindgen_test] fn set_prototype_of() { let obj = Object::new(); - assert!(Reflect::set_prototype_of(&obj, &JsValue::null())); + assert!(Reflect::set_prototype_of(&obj, &JsValue::null()).unwrap()); let obj = JsValue::from(obj); - assert_eq!(JsValue::from(Reflect::get_prototype_of(&obj)), JsValue::null()); + assert_eq!(JsValue::from(Reflect::get_prototype_of(&obj).unwrap()), JsValue::null()); } #[wasm_bindgen_test] @@ -192,3 +194,32 @@ fn reflect_extends() { assert!(reflect.is_instance_of::()); let _: &Object = reflect.as_ref(); } + +#[wasm_bindgen_test] +fn reflect_bindings_handle_proxies_that_just_throw_for_everything() { + let p = throw_all_the_time(); + + let desc = Object::new(); + Reflect::set(desc.as_ref(), &"value".into(), &1.into()).unwrap(); + assert!(Reflect::define_property(&p, &"a".into(), &desc).is_err()); + + assert!(Reflect::delete_property(&p, &"a".into()).is_err()); + + assert!(Reflect::get(p.as_ref(), &"a".into()).is_err()); + + assert!(Reflect::get_own_property_descriptor(&p, &"a".into()).is_err()); + + assert!(Reflect::get_prototype_of(p.as_ref()).is_err()); + + assert!(Reflect::has(p.as_ref(), &"a".into()).is_err()); + + assert!(Reflect::is_extensible(&p).is_err()); + + assert!(Reflect::own_keys(p.as_ref()).is_err()); + + assert!(Reflect::prevent_extensions(&p).is_err()); + + assert!(Reflect::set(p.as_ref(), &"a".into(), &1.into()).is_err()); + + assert!(Reflect::set_prototype_of(&p, Object::new().as_ref()).is_err()); +} diff --git a/crates/js-sys/tests/wasm/WebAssembly.rs b/crates/js-sys/tests/wasm/WebAssembly.rs index 65385ec5938..f67c742b73d 100644 --- a/crates/js-sys/tests/wasm/WebAssembly.rs +++ b/crates/js-sys/tests/wasm/WebAssembly.rs @@ -178,7 +178,7 @@ fn webassembly_instance() { // Has expected exports. let exports = instance.exports(); - assert!(Reflect::has(exports.as_ref(), &"exported_func".into())); + assert!(Reflect::has(exports.as_ref(), &"exported_func".into()).unwrap()); } #[wasm_bindgen_test(async)] @@ -201,6 +201,7 @@ fn instantiate_streaming() -> impl Future { .map(|obj| { assert!( Reflect::get(obj.as_ref(), &"instance".into()) + .unwrap() .is_instance_of::() ); }) @@ -209,7 +210,7 @@ fn instantiate_streaming() -> impl Future { #[wasm_bindgen_test] fn memory_works() { let obj = Object::new(); - Reflect::set(obj.as_ref(), &"initial".into(), &1.into()); + Reflect::set(obj.as_ref(), &"initial".into(), &1.into()).unwrap(); let mem = WebAssembly::Memory::new(&obj).unwrap(); assert!(mem.is_instance_of::()); assert!(mem.is_instance_of::()); diff --git a/examples/wasm-in-wasm/src/lib.rs b/examples/wasm-in-wasm/src/lib.rs index f2382534af6..3496a3e75be 100644 --- a/examples/wasm-in-wasm/src/lib.rs +++ b/examples/wasm-in-wasm/src/lib.rs @@ -1,9 +1,9 @@ -extern crate wasm_bindgen; extern crate js_sys; +extern crate wasm_bindgen; +use js_sys::{Function, Object, Reflect, Uint8Array, WebAssembly}; use wasm_bindgen::prelude::*; use wasm_bindgen::JsCast; -use js_sys::{WebAssembly, Object, Reflect, Function, Uint8Array}; // lifted from the `console_log` example #[wasm_bindgen] @@ -30,22 +30,21 @@ pub fn run() -> Result<(), JsValue> { // (aka do a memory allocation in Rust) it'll cause the buffer to change. // That means we can't actually do any memory allocations after we do this // until we pass it back to JS. - let my_memory = Uint8Array::new(&my_memory.buffer()) - .subarray( - WASM.as_ptr() as u32, - WASM.as_ptr() as u32 + WASM.len() as u32, - ); + let my_memory = Uint8Array::new(&my_memory.buffer()).subarray( + WASM.as_ptr() as u32, + WASM.as_ptr() as u32 + WASM.len() as u32, + ); let a = WebAssembly::Module::new(my_memory.as_ref())?; let b = WebAssembly::Instance::new(&a, &Object::new())?; let c = b.exports(); - let add = Reflect::get(c.as_ref(), &"add".into()) + let add = Reflect::get(c.as_ref(), &"add".into())? .dyn_into::() .expect("add export wasn't a function"); let three = add.call2(&JsValue::undefined(), &1.into(), &2.into())?; console_log!("1 + 2 = {:?}", three); - let mem = Reflect::get(c.as_ref(), &"memory".into()) + let mem = Reflect::get(c.as_ref(), &"memory".into())? .dyn_into::() .expect("memory export wasn't a `WebAssembly.Memory`"); console_log!("created module has {} pages of memory", mem.grow(0)); From f9cd329b14402e17d5628e59df96ed8d0f427cdb Mon Sep 17 00:00:00 2001 From: Nick Fitzgerald Date: Tue, 25 Sep 2018 12:30:04 -0700 Subject: [PATCH 4/7] 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 27229d2f80b..867f5033d6b 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 00000000000..79b8c500677 --- /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 00000000000..7c540837651 --- /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 c72a998d304..3dd249c3904 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; From 7db28b454845f086c840d8848c80259998926eb6 Mon Sep 17 00:00:00 2001 From: Nick Fitzgerald Date: Tue, 25 Sep 2018 12:31:22 -0700 Subject: [PATCH 5/7] js-sys: run rustfmt --- crates/js-sys/src/lib.rs | 106 +++++++++++++++++++++++++++------------ 1 file changed, 73 insertions(+), 33 deletions(-) diff --git a/crates/js-sys/src/lib.rs b/crates/js-sys/src/lib.rs index 867f5033d6b..249e89a4812 100644 --- a/crates/js-sys/src/lib.rs +++ b/crates/js-sys/src/lib.rs @@ -20,12 +20,12 @@ extern crate wasm_bindgen; -use std::mem; use std::fmt; +use std::mem; -use std::sync::atomic::{AtomicUsize, ATOMIC_USIZE_INIT, Ordering::SeqCst}; -use wasm_bindgen::JsCast; +use std::sync::atomic::{AtomicUsize, Ordering::SeqCst, ATOMIC_USIZE_INIT}; use wasm_bindgen::prelude::*; +use wasm_bindgen::JsCast; // When adding new imports: // @@ -304,14 +304,22 @@ extern "C" { /// /// [MDN documentation](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/Reduce) #[wasm_bindgen(method)] - pub fn reduce(this: &Array, predicate: &mut FnMut(JsValue, JsValue, u32, Array) -> JsValue, initial_value: &JsValue) -> JsValue; + pub fn reduce( + this: &Array, + predicate: &mut FnMut(JsValue, JsValue, u32, Array) -> JsValue, + initial_value: &JsValue, + ) -> JsValue; /// The reduceRight() method applies a function against an accumulator and each value /// of the array (from right-to-left) to reduce it to a single value. /// /// [MDN documentation](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/ReduceRight) #[wasm_bindgen(method, js_name = reduceRight)] - pub fn reduce_right(this: &Array, predicate: &mut FnMut(JsValue, JsValue, u32, Array) -> JsValue, initial_value: &JsValue) -> JsValue; + pub fn reduce_right( + this: &Array, + predicate: &mut FnMut(JsValue, JsValue, u32, Array) -> JsValue, + initial_value: &JsValue, + ) -> JsValue; /// The reverse() method reverses an array in place. The first array /// element becomes the last, and the last array element becomes the first. @@ -831,14 +839,25 @@ extern "C" { /// /// [MDN documentation](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Function/call) #[wasm_bindgen(method, catch, js_name = call)] - pub fn call2(this: &Function, context: &JsValue, arg1: &JsValue, arg2: &JsValue) -> Result; + pub fn call2( + this: &Function, + context: &JsValue, + arg1: &JsValue, + arg2: &JsValue, + ) -> Result; /// The `call()` method calls a function with a given this value and /// arguments provided individually. /// /// [MDN documentation](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Function/call) #[wasm_bindgen(method, catch, js_name = call)] - pub fn call3(this: &Function, context: &JsValue, arg1: &JsValue, arg2: &JsValue, arg3: &JsValue) -> Result; + pub fn call3( + this: &Function, + context: &JsValue, + arg1: &JsValue, + arg2: &JsValue, + arg3: &JsValue, + ) -> Result; /// The bind() method creates a new function that, when called, has its this keyword set to the provided value, /// with a given sequence of arguments preceding any provided when the new function is called. @@ -885,7 +904,7 @@ impl Function { // Generator #[wasm_bindgen] -extern { +extern "C" { #[wasm_bindgen(extends = Object)] #[derive(Clone, Debug)] pub type Generator; @@ -1072,7 +1091,7 @@ extern "C" { // Map #[wasm_bindgen] -extern { +extern "C" { #[wasm_bindgen(extends = Object)] #[derive(Clone, Debug)] pub type Map; @@ -1134,7 +1153,7 @@ extern { // Map Iterator #[wasm_bindgen] -extern { +extern "C" { /// The entries() method returns a new Iterator object that contains /// the [key, value] pairs for each element in the Map object in /// insertion order. @@ -1160,7 +1179,7 @@ extern { // Iterator #[wasm_bindgen] -extern { +extern "C" { /// Any object that conforms to the JS iterator protocol. For example, /// something returned by `myArray[Symbol.iterator]()`. /// @@ -1201,7 +1220,10 @@ impl<'a> IntoIterator for &'a Iterator { type IntoIter = Iter<'a>; fn into_iter(self) -> Iter<'a> { - Iter { js: self, state: IterState::new() } + Iter { + js: self, + state: IterState::new(), + } } } @@ -1218,7 +1240,10 @@ impl IntoIterator for Iterator { type IntoIter = IntoIter; fn into_iter(self) -> IntoIter { - IntoIter { js: self, state: IterState::new() } + IntoIter { + js: self, + state: IterState::new(), + } } } @@ -1237,13 +1262,13 @@ impl IterState { fn next(&mut self, js: &Iterator) -> Option> { if self.done { - return None + return None; } let next = match js.next() { Ok(val) => val, Err(e) => { self.done = true; - return Some(Err(e)) + return Some(Err(e)); } }; if next.done() { @@ -1283,7 +1308,7 @@ pub fn try_iter(val: &JsValue) -> Result, JsValue> { // IteratorNext #[wasm_bindgen] -extern { +extern "C" { /// The result of calling `next()` on a JS iterator. /// /// [MDN documentation](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Iteration_protocols) @@ -1400,7 +1425,6 @@ extern "C" { #[wasm_bindgen(static_method_of = Math)] pub fn cos(x: f64) -> f64; - /// The Math.cosh() function returns the hyperbolic cosine of a number, /// that can be expressed using the constant e. /// @@ -2028,7 +2052,8 @@ extern "C" { /// /// [MDN documentation](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/assign) #[wasm_bindgen(static_method_of = Object, js_name = assign)] - pub fn assign3(target: &Object, source1: &Object, source2: &Object, source3: &Object) -> Object; + pub fn assign3(target: &Object, source1: &Object, source2: &Object, source3: &Object) + -> Object; /// The Object.create() method creates a new object, using an existing /// object to provide the newly created object's prototype. @@ -2245,7 +2270,7 @@ impl Object { // Proxy #[wasm_bindgen] -extern { +extern "C" { #[derive(Clone, Debug)] pub type Proxy; @@ -2267,7 +2292,7 @@ extern { // RangeError #[wasm_bindgen] -extern { +extern "C" { /// The RangeError object indicates an error when a value is not in the set /// or range of allowed values. /// @@ -2286,7 +2311,7 @@ extern { // ReferenceError #[wasm_bindgen] -extern { +extern "C" { /// The ReferenceError object represents an error when a non-existent /// variable is referenced. /// @@ -2625,7 +2650,7 @@ extern "C" { // Set #[wasm_bindgen] -extern { +extern "C" { #[wasm_bindgen(extends = Object)] #[derive(Clone, Debug)] pub type Set; @@ -2681,7 +2706,7 @@ extern { // SetIterator #[wasm_bindgen] -extern { +extern "C" { /// The `entries()` method returns a new Iterator object that contains an /// array of [value, value] for each element in the Set object, in insertion /// order. For Set objects there is no key like in Map objects. However, to @@ -2710,7 +2735,7 @@ extern { // SyntaxError #[wasm_bindgen] -extern { +extern "C" { /// A SyntaxError is thrown when the JavaScript engine encounters tokens or /// token order that does not conform to the syntax of the language when /// parsing code. @@ -2731,7 +2756,7 @@ extern { // TypeError #[wasm_bindgen] -extern { +extern "C" { /// The TypeError object represents an error when a value is not of the /// expected type. /// @@ -2964,7 +2989,7 @@ extern "C" { // URIError #[wasm_bindgen] -extern { +extern "C" { /// The URIError object represents an error when a global URI handling /// function was used in a wrong way. /// @@ -3508,7 +3533,12 @@ extern "C" { /// /// [MDN documentation](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/localeCompare) #[wasm_bindgen(method, js_class = "String", js_name = localeCompare)] - pub fn locale_compare(this: &JsString, compare_string: &str, locales: &Array, options: &Object) -> i32; + pub fn locale_compare( + this: &JsString, + compare_string: &str, + locales: &Array, + options: &Object, + ) -> i32; /// The match() method retrieves the matches when matching a string against a regular expression. /// @@ -3560,14 +3590,22 @@ extern "C" { /// [MDN documentation](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/replace) #[wasm_bindgen(method, js_class = "String", js_name = replace)] - pub fn replace_with_function(this: &JsString, pattern: &str, replacement: &Function) -> JsString; + pub fn replace_with_function( + this: &JsString, + pattern: &str, + replacement: &Function, + ) -> JsString; #[wasm_bindgen(method, js_class = "String", js_name = replace)] pub fn replace_by_pattern(this: &JsString, pattern: &RegExp, replacement: &str) -> JsString; /// [MDN documentation](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/replace) #[wasm_bindgen(method, js_class = "String", js_name = replace)] - pub fn replace_by_pattern_with_function(this: &JsString, pattern: &RegExp, replacement: &Function) -> JsString; + pub fn replace_by_pattern_with_function( + this: &JsString, + pattern: &RegExp, + replacement: &Function, + ) -> JsString; /// The search() method executes a search for a match between /// a regular expression and this String object. @@ -4210,7 +4248,7 @@ pub mod Intl { // Promise #[wasm_bindgen] -extern { +extern "C" { /// The `Promise` object represents the eventual completion (or failure) of /// an asynchronous operation, and its resulting value. /// @@ -4289,9 +4327,11 @@ extern { /// Same as `then`, only with both arguments provided. #[wasm_bindgen(method, js_name = then)] - pub fn then2(this: &Promise, - resolve: &Closure, - reject: &Closure) -> Promise; + pub fn then2( + this: &Promise, + resolve: &Closure, + reject: &Closure, + ) -> Promise; /// The `finally()` method returns a `Promise`. When the promise is settled, /// whether fulfilled or rejected, the specified callback function is From dfd0f534f95e20c0c05c5a4dfe70607e2d8d7e34 Mon Sep 17 00:00:00 2001 From: Nick Fitzgerald Date: Tue, 25 Sep 2018 13:31:54 -0700 Subject: [PATCH 6/7] guide: Add section about iterating over JS values --- guide/src/SUMMARY.md | 1 + .../src/reference/iterating-over-js-values.md | 95 +++++++++++++++++++ 2 files changed, 96 insertions(+) create mode 100644 guide/src/reference/iterating-over-js-values.md diff --git a/guide/src/SUMMARY.md b/guide/src/SUMMARY.md index 60d0502ac01..27397df0439 100644 --- a/guide/src/SUMMARY.md +++ b/guide/src/SUMMARY.md @@ -30,6 +30,7 @@ - [Passing Rust Closures to JS](./reference/passing-rust-closures-to-js.md) - [Receiving JS Closures in Rust](./reference/receiving-js-closures-in-rust.md) - [`Promise`s and `Future`s](./reference/js-promises-and-rust-futures.md) + - [Iterating over JS Values](./reference/iterating-over-js-values.md) - [No ES Modules](./reference/no-esm.md) - [Arbitrary Data with Serde](./reference/arbitrary-data-with-serde.md) - [Accessing Properties of Untyped JS Values](./reference/accessing-properties-of-untyped-js-values.md) diff --git a/guide/src/reference/iterating-over-js-values.md b/guide/src/reference/iterating-over-js-values.md new file mode 100644 index 00000000000..73ef6d994b4 --- /dev/null +++ b/guide/src/reference/iterating-over-js-values.md @@ -0,0 +1,95 @@ +# Iterating over JavaScript Values + +## Methods That Return `js_sys::Iterator` + +Some JavaScript collections have methods for iterating over their values or +keys: + +* [`Map::values`](https://rustwasm.github.io/wasm-bindgen/api/js_sys/struct.Map.html#method.values) +* [`Set::keys`](https://rustwasm.github.io/wasm-bindgen/api/js_sys/struct.Set.html#method.keys) +* etc... + +These methods return +[`js_sys::Iterator`](https://rustwasm.github.io/wasm-bindgen/api/js_sys/struct.Iterator.html), +which is the Rust representation of a JavaScript object that has a `next` method +that either returns the next item in the iteration, notes that iteration has +completed, or throws an error. That is, `js_sys::Iterator` represents an object +that implements [the duck-typed JavaScript iteration +protocol](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Iteration_protocols). + +`js_sys::Iterator` can be converted into a Rust iterator either by reference +(into +[`js_sys::Iter<'a>`](https://rustwasm.github.io/wasm-bindgen/api/js_sys/struct.Iter.html)) +or by value (into +[`js_sys::IntoIter`](https://rustwasm.github.io/wasm-bindgen/api/js_sys/struct.IntoIter.html)). The +Rust iterator will yield items of type `Result`. If it yields an +`Ok(...)`, then the JS iterator protocol returned an element. If it yields an +`Err(...)`, then the JS iterator protocol threw an exception. + +```rust +extern crate js_sys; +extern crate wasm_bindgen; +use wasm_bindgen::prelude::*; + +#[wasm_bindgen] +pub fn count_strings_in_set(set: &js_sys::Set) -> u32 { + let mut count = 0; + + // Call `keys` to get an iterator over the set's elements. Because this is + // in a `for ... in ...` loop, Rust will automatically call its + // `IntoIterator` trait implementation to convert it into a Rust iterator. + for x in set.keys() { + // We know the built-in iterator for set elements won't throw + // exceptions, so just unwrap the element. If this was an untrusted + // iterator, we might want to explicitly handle the case where it throws + // an exception instead of returning a `{ value, done }` object. + let x = x.unwrap(); + + // If `x` is a string, increment our count of strings in the set! + if x.is_string() { + count += 1; + } + } + + count +} +``` + +## Iterating Over Any JavaScript Object that Implements the Iterator Protocol + +You could manually test for whether an object implements JS's duck-typed +iterator protocol, and if so, convert it into a `js_sys::Iterator` that you can +finally iterate over. You don't need to do this by-hand, however, since we +bundled this up as [the `js_sys::try_iter` +function!](https://rustwasm.github.io/wasm-bindgen/api/js_sys/fn.try_iter.html) + +For example, we can write a function that collects the numbers from any JS +iterable and returns them as an `Array`: + +```rust +extern crate js_sys; +extern crate wasm_bindgen; +use wasm_bindgen::prelude::*; + +#[wasm_bindgen] +pub fn collect_numbers(some_iterable: &JsValue) -> Result { + let nums = js_sys::Array::new(); + + let iterator = match js_sys::try_iter(some_iterable)?.ok_or_else(|| { + "need to pass iterable JS values!".into() + })?; + + for x in iterator { + // If the iterator's `next` method throws an error, propagate it + // up to the caller. + let x = x?; + + // If `x` is a number, add it to our array of numbers! + if x.is_f64() { + nums.push(&x); + } + } + + Ok(nums) +} +``` From a920656e09568198bce2c20e2ac737be1c3d6787 Mon Sep 17 00:00:00 2001 From: Nick Fitzgerald Date: Tue, 25 Sep 2018 13:34:05 -0700 Subject: [PATCH 7/7] guide: Update untyped JS values section to handle fallibility of Reflect::* APIs --- .../reference/accessing-properties-of-untyped-js-values.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/guide/src/reference/accessing-properties-of-untyped-js-values.md b/guide/src/reference/accessing-properties-of-untyped-js-values.md index 5acd128c8a1..4b4f0aa9a26 100644 --- a/guide/src/reference/accessing-properties-of-untyped-js-values.md +++ b/guide/src/reference/accessing-properties-of-untyped-js-values.md @@ -18,7 +18,7 @@ A function that returns the value of a property. #### Rust Usage ```rust -let value = js_sys::Reflect::get(&target, &property_key); +let value = js_sys::Reflect::get(&target, &property_key)?; ``` #### JavaScript Equivalent @@ -37,7 +37,7 @@ the update was successful. #### Rust Usage ```rust -js_sys::Reflect::set(&target, &property_key, &value); +js_sys::Reflect::set(&target, &property_key, &value)?; ``` #### JavaScript Equivalent @@ -56,7 +56,7 @@ an own or inherited property exists on the target. #### Rust Usage ```rust -if js_sys::Reflect::has(&target, &property_key) { +if js_sys::Reflect::has(&target, &property_key)? { // ... } else { // ...