From 8fc0e2b085ea555354ba3094e66cd0bc94add0af Mon Sep 17 00:00:00 2001 From: Mauro Gentile <62186646+gents83@users.noreply.github.com> Date: Mon, 30 Jan 2023 09:15:17 +0100 Subject: [PATCH] Add support for setlike interfaces (#3268) * Add support for setlike interfaces * Fixing setlike test suite --- .../src/features/gen_GpuSupportedFeatures.rs | 69 ++++++ crates/webidl-tests/globals.js | 81 +++++-- crates/webidl-tests/main.rs | 1 + crates/webidl-tests/setlike.rs | 90 ++++++++ .../webidls/enabled/setlike.webidl | 9 + crates/webidl/src/first_pass.rs | 212 +++++++++++++++++- 6 files changed, 441 insertions(+), 21 deletions(-) create mode 100644 crates/webidl-tests/setlike.rs create mode 100644 crates/webidl-tests/webidls/enabled/setlike.webidl diff --git a/crates/web-sys/src/features/gen_GpuSupportedFeatures.rs b/crates/web-sys/src/features/gen_GpuSupportedFeatures.rs index da11a61c5cd..7d893c05c8f 100644 --- a/crates/web-sys/src/features/gen_GpuSupportedFeatures.rs +++ b/crates/web-sys/src/features/gen_GpuSupportedFeatures.rs @@ -15,4 +15,73 @@ extern "C" { #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://rustwasm.github.io/docs/wasm-bindgen/web-sys/unstable-apis.html)*"] pub type GpuSupportedFeatures; + #[cfg(web_sys_unstable_apis)] + # [wasm_bindgen (structural , method , getter , js_class = "GPUSupportedFeatures" , js_name = size)] + #[doc = "Getter for the `size` field of this object."] + #[doc = ""] + #[doc = "[MDN Documentation](https://developer.mozilla.org/en-US/docs/Web/API/GPUSupportedFeatures/size)"] + #[doc = ""] + #[doc = "*This API requires the following crate features to be activated: `GpuSupportedFeatures`*"] + #[doc = ""] + #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] + #[doc = "[described in the `wasm-bindgen` guide](https://rustwasm.github.io/docs/wasm-bindgen/web-sys/unstable-apis.html)*"] + pub fn size(this: &GpuSupportedFeatures) -> u32; + #[cfg(web_sys_unstable_apis)] + # [wasm_bindgen (method , structural , js_class = "GPUSupportedFeatures" , js_name = entries)] + #[doc = "The `entries()` method."] + #[doc = ""] + #[doc = "[MDN Documentation](https://developer.mozilla.org/en-US/docs/Web/API/GPUSupportedFeatures/entries)"] + #[doc = ""] + #[doc = "*This API requires the following crate features to be activated: `GpuSupportedFeatures`*"] + #[doc = ""] + #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] + #[doc = "[described in the `wasm-bindgen` guide](https://rustwasm.github.io/docs/wasm-bindgen/web-sys/unstable-apis.html)*"] + pub fn entries(this: &GpuSupportedFeatures) -> ::js_sys::Iterator; + #[cfg(web_sys_unstable_apis)] + # [wasm_bindgen (catch , method , structural , js_class = "GPUSupportedFeatures" , js_name = forEach)] + #[doc = "The `forEach()` method."] + #[doc = ""] + #[doc = "[MDN Documentation](https://developer.mozilla.org/en-US/docs/Web/API/GPUSupportedFeatures/forEach)"] + #[doc = ""] + #[doc = "*This API requires the following crate features to be activated: `GpuSupportedFeatures`*"] + #[doc = ""] + #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] + #[doc = "[described in the `wasm-bindgen` guide](https://rustwasm.github.io/docs/wasm-bindgen/web-sys/unstable-apis.html)*"] + pub fn for_each( + this: &GpuSupportedFeatures, + callback: &::js_sys::Function, + ) -> Result<(), JsValue>; + #[cfg(web_sys_unstable_apis)] + # [wasm_bindgen (method , structural , js_class = "GPUSupportedFeatures" , js_name = has)] + #[doc = "The `has()` method."] + #[doc = ""] + #[doc = "[MDN Documentation](https://developer.mozilla.org/en-US/docs/Web/API/GPUSupportedFeatures/has)"] + #[doc = ""] + #[doc = "*This API requires the following crate features to be activated: `GpuSupportedFeatures`*"] + #[doc = ""] + #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] + #[doc = "[described in the `wasm-bindgen` guide](https://rustwasm.github.io/docs/wasm-bindgen/web-sys/unstable-apis.html)*"] + pub fn has(this: &GpuSupportedFeatures, value: &str) -> bool; + #[cfg(web_sys_unstable_apis)] + # [wasm_bindgen (method , structural , js_class = "GPUSupportedFeatures" , js_name = keys)] + #[doc = "The `keys()` method."] + #[doc = ""] + #[doc = "[MDN Documentation](https://developer.mozilla.org/en-US/docs/Web/API/GPUSupportedFeatures/keys)"] + #[doc = ""] + #[doc = "*This API requires the following crate features to be activated: `GpuSupportedFeatures`*"] + #[doc = ""] + #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] + #[doc = "[described in the `wasm-bindgen` guide](https://rustwasm.github.io/docs/wasm-bindgen/web-sys/unstable-apis.html)*"] + pub fn keys(this: &GpuSupportedFeatures) -> ::js_sys::Iterator; + #[cfg(web_sys_unstable_apis)] + # [wasm_bindgen (method , structural , js_class = "GPUSupportedFeatures" , js_name = values)] + #[doc = "The `values()` method."] + #[doc = ""] + #[doc = "[MDN Documentation](https://developer.mozilla.org/en-US/docs/Web/API/GPUSupportedFeatures/values)"] + #[doc = ""] + #[doc = "*This API requires the following crate features to be activated: `GpuSupportedFeatures`*"] + #[doc = ""] + #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] + #[doc = "[described in the `wasm-bindgen` guide](https://rustwasm.github.io/docs/wasm-bindgen/web-sys/unstable-apis.html)*"] + pub fn values(this: &GpuSupportedFeatures) -> ::js_sys::Iterator; } diff --git a/crates/webidl-tests/globals.js b/crates/webidl-tests/globals.js index 1a2d6693e69..bf70171c1ca 100644 --- a/crates/webidl-tests/globals.js +++ b/crates/webidl-tests/globals.js @@ -1,6 +1,6 @@ const strictEqual = require('assert').strictEqual; -exports.noop = function () {}; +exports.noop = function () { }; global.TestArrays = class { strings(x) { @@ -97,11 +97,11 @@ global.ArrayBufferTest = class { }; global.TakeCallbackInterface = class { - a() {} - b() {} + a() { } + b() { } }; -global.assert_dict_c = function(c) { +global.assert_dict_c = function (c) { strictEqual(c.a, 1); strictEqual(c.b, 2); strictEqual(c.c, 3); @@ -112,17 +112,17 @@ global.assert_dict_c = function(c) { strictEqual(c.h, 8); }; -global.mk_dict_a = function() { +global.mk_dict_a = function () { return {}; }; -global.assert_dict_required = function(c) { +global.assert_dict_required = function (c) { strictEqual(c.a, 3); strictEqual(c.b, "a"); strictEqual(c.c, 4); }; -global.assert_camel_case = function(dict) { +global.assert_camel_case = function (dict) { strictEqual(dict.wierd_fieldName, 1); } @@ -219,6 +219,57 @@ global.TestReadWriteMapLike = class extends global.TestReadOnlyMapLike { } }; +global.TestReadOnlySetLike = class { + constructor() { + this.set = new Set(); + this.set.add('a'); + this.set.add('b'); + this.set.add('c'); + } + + entries() { + return this.set.entries(); + } + + forEach(callback, thisArg) { + return this.set.forEach(callback, thisArg); + } + + has(value) { + return this.set.has(value); + } + + keys() { + return this.set.keys(); + } + + values() { + return this.set.values(); + } + + get size() { + return this.set.size; + } +}; + +global.TestReadWriteSetLike = class extends global.TestReadOnlySetLike { + constructor() { + super(); + } + + add(value) { + return this.set.add(value); + } + + delete(value) { + return this.set.delete(value); + } + + clear() { + return this.set.clear(); + } +}; + global.math_test = { pow(base, exp) { return Math.pow(base, exp); @@ -233,7 +284,7 @@ global.GetNoInterfaceObject = class { static get() { return { number: 3, - foo: () => {}, + foo: () => { }, } } }; @@ -266,7 +317,7 @@ global.NamedConstructorParent = class NamedConstructor { this._value = 0; } - get value(){ + get value() { return this._value; } }; @@ -296,7 +347,7 @@ global.StaticMethod = class StaticMethod { StaticMethod.value = 0; global.StaticProperty = class StaticProperty { - static get value(){ + static get value() { return StaticProperty._value; } @@ -308,14 +359,14 @@ global.StaticProperty = class StaticProperty { StaticProperty._value = 0; global.UndefinedMethod = class UndefinedMethod { - constructor() {} + constructor() { } ok_method() { return true; } }; global.NullableMethod = class NullableMethod { - constructor() {} + constructor() { } opt(a) { if (a == undefined) { return undefined; @@ -355,14 +406,14 @@ global.Indexing = function () { }; global.OptionalAndUnionArguments = class OptionalAndUnionArguments { - constructor() {} + constructor() { } m(a, b = true, c = 123, d = 456) { return [typeof a, a, typeof b, b, typeof c, c, typeof d, d].join(', '); } }; global.Variadic = class Variadic { - constructor() {} + constructor() { } sum(...values) { return values.reduce((a, b) => a + b, 0); } @@ -402,7 +453,7 @@ global.MixinFoo = class MixinFoo { }; global.Overloads = class { - foo() {} + foo() { } }; global.InvokeCallback = class { diff --git a/crates/webidl-tests/main.rs b/crates/webidl-tests/main.rs index 99a0129f5e6..de1e5046670 100644 --- a/crates/webidl-tests/main.rs +++ b/crates/webidl-tests/main.rs @@ -30,6 +30,7 @@ pub mod maplike; pub mod namespace; pub mod no_interface; pub mod promise; +pub mod setlike; pub mod simple; pub mod throws; pub mod unstable; diff --git a/crates/webidl-tests/setlike.rs b/crates/webidl-tests/setlike.rs new file mode 100644 index 00000000000..d77dd61c16e --- /dev/null +++ b/crates/webidl-tests/setlike.rs @@ -0,0 +1,90 @@ +use crate::generated::*; +use wasm_bindgen::prelude::*; +use wasm_bindgen_test::*; + +macro_rules! read_test_suite { + ($setlike:ty, $name:ident) => { + #[wasm_bindgen_test] + fn $name() { + // { "a", "b", "c" } + let setlike = <$setlike>::new().unwrap(); + + // readonly attribute unsigned long size; + assert_eq!(setlike.size(), 3); + + // boolean has(V value); + assert!(setlike.has("a")); + assert!(setlike.has("b")); + assert!(setlike.has("c")); + assert!(!setlike.has("d")); + + // { "a", "b", "c" } + let cb = Closure::wrap(Box::new(|value: String| match value.as_str() { + "a" => assert_eq!(value, "a"), + "b" => assert_eq!(value, "b"), + "c" => assert_eq!(value, "c"), + _ => panic!("unexpected key"), + }) as Box); + + setlike.for_each(cb.as_ref().unchecked_ref()).unwrap(); + + let mut entries_vec = vec![]; + + for entry in setlike.entries().into_iter() { + let entry = entry.unwrap(); + let pair = entry.dyn_into::().unwrap(); + let value = pair.get(1).as_string().unwrap(); + + entries_vec.push(value); + } + + assert_eq!( + &entries_vec, + &["a".to_string(), "b".to_string(), "c".to_string()] + ); + + let mut keys_vec = vec![]; + + for key in setlike.keys().into_iter() { + let key = key.unwrap(); + keys_vec.push(key.as_string().unwrap()); + } + + assert_eq!( + &keys_vec, + &["a".to_string(), "b".to_string(), "c".to_string()] + ); + + let mut values_vec = vec![]; + + for value in setlike.values().into_iter() { + let value = value.unwrap(); + values_vec.push(value.as_string().unwrap()); + } + + assert_eq!( + &values_vec, + &["a".to_string(), "b".to_string(), "c".to_string()] + ); + } + }; +} + +read_test_suite!(TestReadOnlySetLike, read_readonly_setlike); +read_test_suite!(TestReadWriteSetLike, read_setlike); + +#[wasm_bindgen_test] +fn write_setlike() { + // { "a", "b", "c" } + let setlike = TestReadWriteSetLike::new().unwrap(); + + setlike.add("d"); + assert_eq!(setlike.size(), 4); + + assert!(setlike.delete("d")); + assert_eq!(setlike.size(), 3); + + // undefined clear(); + setlike.clear(); + assert_eq!(setlike.size(), 0); +} diff --git a/crates/webidl-tests/webidls/enabled/setlike.webidl b/crates/webidl-tests/webidls/enabled/setlike.webidl new file mode 100644 index 00000000000..8638cd25d05 --- /dev/null +++ b/crates/webidl-tests/webidls/enabled/setlike.webidl @@ -0,0 +1,9 @@ +[Constructor()] +interface TestReadOnlySetLike { + readonly setlike; +}; + +[Constructor()] +interface TestReadWriteSetLike { + setlike; +}; diff --git a/crates/webidl/src/first_pass.rs b/crates/webidl/src/first_pass.rs index 8d7cd22e5f9..68e4a8c129d 100644 --- a/crates/webidl/src/first_pass.rs +++ b/crates/webidl/src/first_pass.rs @@ -544,12 +544,13 @@ impl<'src> FirstPass<'src, (&'src str, ApiStability)> for weedle::interface::Int Ok(()) } InterfaceMember::Constructor(constr) => constr.first_pass(record, ctx), + InterfaceMember::Maplike(ml) => ml.first_pass(record, ctx), + InterfaceMember::Setlike(sl) => sl.first_pass(record, ctx), + // TODO InterfaceMember::Iterable(_iterable) => { log::warn!("Unsupported WebIDL iterable interface member: {:?}", self); Ok(()) } - InterfaceMember::Maplike(ml) => ml.first_pass(record, ctx), - // TODO InterfaceMember::Stringifier(_) => { log::warn!( "Unsupported WebIDL Stringifier interface member: {:?}", @@ -557,10 +558,6 @@ impl<'src> FirstPass<'src, (&'src str, ApiStability)> for weedle::interface::Int ); Ok(()) } - InterfaceMember::Setlike(_) => { - log::warn!("Unsupported WebIDL Setlike interface member: {:?}", self); - Ok(()) - } InterfaceMember::AsyncIterable(_iterable) => { log::warn!( "Unsupported WebIDL async iterable interface member: {:?}", @@ -870,6 +867,209 @@ impl<'src> FirstPass<'src, (&'src str, ApiStability)> } } +impl<'src> FirstPass<'src, (&'src str, ApiStability)> + for weedle::interface::SetlikeInterfaceMember<'src> +{ + fn first_pass( + &'src self, + record: &mut FirstPassRecord<'src>, + ctx: (&'src str, ApiStability), + ) -> Result<()> { + let value_ty = &self.generics.body; + let value_arg = || Arg { + name: "value", + ty: &value_ty.type_, + optional: false, + variadic: false, + }; + + let undefined_ret = || ReturnType::Undefined(term!(undefined)); + + // readonly attribute unsigned long size; + record + .interfaces + .get_mut(ctx.0) + .unwrap() + .attributes + .push(AttributeInterfaceData { + definition: &AttributeInterfaceMember { + attributes: None, + modifier: None, + readonly: Some(term!(readonly)), + attribute: term!(attribute), + type_: AttributedType { + attributes: None, + type_: Type::Single(SingleType::NonAny(NonAnyType::Integer(MayBeNull { + type_: IntegerType::Long(LongType { + unsigned: Some(term!(unsigned)), + long: term!(long), + }), + q_mark: None, + }))), + }, + identifier: Identifier("size"), + semi_colon: term!(;), + }, + stability: ctx.1, + }); + + // boolean has(V value); + first_pass_operation( + record, + FirstPassOperationType::Interface, + ctx.0, + &[OperationId::Operation(Some("has"))], + [value_arg()], + &ReturnType::Type(Type::Single(SingleType::NonAny(NonAnyType::Boolean( + MayBeNull { + type_: term!(boolean), + q_mark: None, + }, + )))), + &None, + false, + ctx.1, + ); + + // callback SetlikeForEachCallback = undefined (V value); + // TODO: the signature of the callback is erased, could we keep it? + let foreach_callback_arg = Arg { + name: "callback", + ty: &Type::Single(SingleType::NonAny(NonAnyType::Identifier(MayBeNull { + type_: Identifier("SetlikeForEachCallback"), + q_mark: None, + }))), + optional: false, + variadic: false, + }; + + record.callbacks.insert("SetlikeForEachCallback"); + + // [Throws] undefined forEach(SetlikeForEachCallback cb); + first_pass_operation( + record, + FirstPassOperationType::Interface, + ctx.0, + &[OperationId::Operation(Some("forEach"))], + [foreach_callback_arg], + &undefined_ret(), + &THROWS_ATTR, + false, + ctx.1, + ); + + // TODO: iterators could have stronger types by generating specialised interfaces for each + // maplike/setlike. Right now, `value` is always `any`. + + // declare the iterator interface + record.iterators.insert("SetlikeIterator"); + + // [NewObject] SetlikeIterator entries(); + first_pass_operation( + record, + FirstPassOperationType::Interface, + ctx.0, + &[OperationId::Operation(Some("entries"))], + &[], + &ReturnType::Type(Type::Single(SingleType::NonAny(NonAnyType::Identifier( + MayBeNull { + type_: Identifier("SetlikeIterator"), + q_mark: None, + }, + )))), + &NEW_OBJECT_ATTR, + false, + ctx.1, + ); + + // [NewObject] SetlikeIterator keys(); + first_pass_operation( + record, + FirstPassOperationType::Interface, + ctx.0, + &[OperationId::Operation(Some("keys"))], + &[], + &ReturnType::Type(Type::Single(SingleType::NonAny(NonAnyType::Identifier( + MayBeNull { + type_: Identifier("SetlikeIterator"), + q_mark: None, + }, + )))), + &NEW_OBJECT_ATTR, + false, + ctx.1, + ); + + // [NewObject] SetlikeIterator values(); + first_pass_operation( + record, + FirstPassOperationType::Interface, + ctx.0, + &[OperationId::Operation(Some("values"))], + &[], + &ReturnType::Type(Type::Single(SingleType::NonAny(NonAnyType::Identifier( + MayBeNull { + type_: Identifier("SetlikeIterator"), + q_mark: None, + }, + )))), + &NEW_OBJECT_ATTR, + false, + ctx.1, + ); + + // add writeable interface if *not* readonly + if self.readonly.is_none() { + // undefined clear(); + first_pass_operation( + record, + FirstPassOperationType::Interface, + ctx.0, + &[OperationId::Operation(Some("clear"))], + &[], + &undefined_ret(), + &None, + false, + ctx.1, + ); + + // boolean delete(V value); + first_pass_operation( + record, + FirstPassOperationType::Interface, + ctx.0, + &[OperationId::Operation(Some("delete"))], + [value_arg()], + &ReturnType::Type(Type::Single(SingleType::NonAny(NonAnyType::Boolean( + MayBeNull { + type_: term!(boolean), + q_mark: None, + }, + )))), + &None, + false, + ctx.1, + ); + + // TODO: `add` actually returns `this` but we don't have a way to express that just yet + // undefined add(V value); + first_pass_operation( + record, + FirstPassOperationType::Interface, + ctx.0, + &[OperationId::Operation(Some("add"))], + [value_arg()], + &undefined_ret(), + &None, + false, + ctx.1, + ); + } + + Ok(()) + } +} + impl<'src> FirstPass<'src, (&'src str, ApiStability)> for weedle::interface::AttributeInterfaceMember<'src> {