-
-
Notifications
You must be signed in to change notification settings - Fork 1.2k
Implement support for WebIDL dictionaries #702
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
ohanar
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Mostly looks good. The main thing that I'm concerned about is exposing the JS Object that is being used under the hood, I'm not sure that is necessarily forward compatible with host binding support. I would remove the conversion impls there, and add getters so that dictionaries are usable as a return type (getters should take into account default values).
| } | ||
| } | ||
| } | ||
| dst[start..].sort_by_key(|f| f.name.clone()); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is sorting by the snake case identifier, rather than the original identifier, which could produce the wrong result in edge cases:
dictionary Example {
boolean fooBaz;
boolean foo_bar;
};That is correctly ordered per the spec, but this line will reorder it to [foo_bar, foo_baz].
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good point! I'll add a comment because I think this is largely good enough for now, it's basically just generating the order of required fields for the constructor in any case.
| &JsValue::from(val), | ||
| ); | ||
| self | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Without corresponding getters, if a dictionary is returned, it is clumsy to use (have to upcast it to an Object, which I have concerns about).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
True! The limitation to not providing getters right now is that there isn't a great way to compile them in and generate code which works. I think we'll definitely want to add getters though! (this is just an initial implementation)
crates/backend/src/codegen.rs
Outdated
| } | ||
|
|
||
| // interop w/ Object | ||
| impl From<#name> for Object { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't think there is any guarantee that we could do this with the host bindings proposal. In particular, I don't see any mention that WebIDL dictionaries have to be ECMAScript Objects. (This is really more of a concern for the AsRef and AsMut impls than the conversion impl, but I think we should implement all or none.)
| let name = &self.name; | ||
| let ty = &self.ty; | ||
| (quote! { | ||
| pub fn #name(&mut self, val: #ty) -> &mut Self { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You use pub here, even though the Dictionary might not have a public visibility.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
True! Dictionaries are only generated by WebIDL though so they'll always be pub anyway. I think I'll remove the visibility field there and just hardcode everything to public
crates/webidl/src/first_pass.rs
Outdated
| impl<'src> FirstPass<'src, ()> for weedle::PartialDictionaryDefinition<'src> { | ||
| fn first_pass(&'src self, record: &mut FirstPassRecord<'src>, (): ()) -> Result<()> { | ||
| record.dictionaries.entry(self.identifier.0) | ||
| .or_insert_with(Default::default) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe or_default() here (and above)?
crates/backend/src/codegen.rs
Outdated
| impl<'a> OptionIntoWasmAbi for &'a #name { | ||
| fn none() -> Self::Abi { <&'a Object>::none() } | ||
| } | ||
| impl<'a> OptionFromWasmAbi for #name { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Unused lifetime.
| #name { obj: Object::from_abi(abi, extra) } | ||
| } | ||
| } | ||
|
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Missing RefFromWasmAbi.
|
One places that uses a Dictionary is AudioContext.webidl: dictionary AudioContextOptions {
float sampleRate = 0;
};
[Pref="dom.webaudio.enabled",
Constructor(optional AudioContextOptions contextOptions)]
interface AudioContext : BaseAudioContext {
//...
}While the |
| } | ||
| } | ||
| for d in self.dictionaries.iter() { | ||
| d.to_tokens(tokens); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is ToTokens not implemented for &[T] or something? It seems like we shouldn't need to write this loop out by hand.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nah it's not currently implemented because I think it's ambiguous whether you want bracket delimiters or everything just concatenated together
| .map(|f| &f.ty) | ||
| .collect::<Vec<_>>(); | ||
| let required_names2 = required_names; | ||
| let required_names3 = required_names; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is this still necessary even though required_names is a reference (and therefore is Copy)? I thought the trick of making a thing a reference usually fixed the ownership issues with quasi quoting...
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Unfortunately yeah, it has to do with the mystery of the quote! macro, although I'm not quite sure why. If the same variable name is used below it generates a bunch of scary compiler errors.
c1b3964 to
7e2a90b
Compare
|
Thanks for taking a look @ohanar! It's a good point that I don't think we quite know how this is going to map to the host bindings proposal. We could definitely be more conservative and expose none of the internal details about I'm not sure what the best route here is. I don't think we know how this is gonna end up with host bindings (these functions may just not ever be bound with host bindings?). In any case though I think it's definitely the case that we're going to publish multiple major versions of |
7e2a90b to
205bca9
Compare
|
@eminence turned out to be a bug, should be fixed now! |
205bca9 to
e4e5271
Compare
crates/webidl/src/idl_type.rs
Outdated
| // Note that these map to `f64` because JS doesn't really have a | ||
| // native type for u64, so WebIDL surely just means a JS number | ||
| IdlType::LongLong => Some(ident_ty(raw_ident("f64"))), | ||
| IdlType::UnsignedLongLong => Some(ident_ty(raw_ident("f64"))), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is a pretty subtle update in this PR, but lots of dictionaries apparently have long long specified in them. It's unclear what WebIDL could mean other than f64 for JS because 64-bit integers aren't possible, so these two now map to f64
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
From the spec:
The long long type is a signed integer type that has values in the range [−9223372036854775808, 9223372036854775807].
In other words, WebIDL says a long long is a 64-bit integer. Presumably with host-bindings this won't be an issue, it is however an issue with current day shims. I don't think we should change all the signatures of methods just because of modern day limitations.
|
@alexcrichton My vote would be to be on the more conservative side. I don't see that much value added by having the ability to convert to an Object, so the cost/value ratio doesn't really add up for me. |
e4e5271 to
1ddb758
Compare
|
@ohanar makes sense yeah, but the JS object under the hood is currently what's powering dictionaries-in-dictionaries, so I'm not sure we can avoid exposing this detail without removing that functionality? I don't want to be too conservative at the loss of functionality in the sense that we have tons of time to iterate on this crate. The most recent commit should also avoid modifying the WebIDL and manually skips unimplemented dictionaries and i64/u64 in dictionaries. |
|
Well, really conversion to a JSValue is needed to power dictionaries-in-dictionaries, not conversion to js_sys::Object (which was my main complaint). |
|
@ohanar oh interesting, can you expand on why |
|
With the latest fix, I confirm that the missing web audio constructors are now available, and that I was able to successfully get a sine-wave demo working! This obviously isn't a rigorous test of every dictionary, but I'm very happy that this particular one is working 😃 🎶 |
1ddb758 to
6af417e
Compare
This commit adds support for generating bindings for dictionaries defined in
WebIDL. Dictionaries are associative arrays which are simply objects in JS with
named keys and some values. In Rust given a dictionary like:
dictionary Foo {
long field;
};
we'll generate a struct like:
pub struct Foo {
obj: js_sys::Object,
}
impl Foo {
pub fn new() -> Foo { /* make a blank object */ }
pub fn field(&mut self, val: i32) -> &mut Self {
// set the field using `js_sys::Reflect`
}
}
// plus a bunch of AsRef, From, and wasm abi impls
At the same time this adds support for partial dictionaries and dictionary
inheritance. All dictionary fields are optional by default and hence only have
builder-style setters, but dictionaries can also have required fields. Required
fields are exposed as arguments to the `new` constructor.
Closes wasm-bindgen#241
6af417e to
d6e4819
Compare
|
I've gone ahead and removed the asref/asmut/from implementations for |
|
So I guess my concern is that Objects are much more general maps than dictionaries (which are more akin to structs), and I would be a bit surprised if they were re-used for host bindings, just out of concern of efficiency. On the other hand, I would be less surprised if dictionaries were still opaque data structures, so they would presumably be a subtype of anyref, which will likely be the underlying type for a JsValue in the future. This is obviously me making guesses, but I generally want to take as conservative of an approach as reasonably possible, just so we can minimize potential breakage in the future. |
This commit adds support for generating bindings for dictionaries defined in
WebIDL. Dictionaries are associative arrays which are simply objects in JS with
named keys and some values. In Rust given a dictionary like:
we'll generate a struct like:
At the same time this adds support for partial dictionaries and dictionary
inheritance. All dictionary fields are optional by default and hence only have
builder-style setters, but dictionaries can also have required fields. Required
fields are exposed as arguments to the
newconstructor.Closes #241