Skip to content
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

Implement getters for WebIDL dictionaries #3933

Open
wants to merge 9 commits into
base: main
Choose a base branch
from

Conversation

pablosichert
Copy link

@pablosichert pablosichert commented Apr 24, 2024

Heyo,

this PR implements getters for WebIDL dictionaries. The diff is bit noisy due to the autogenerated files; you can look at the actual changes to the code generation in the last two files crates/webidl/src/generator.rs and crates/webidl/src/lib.rs and corresponding tests in crates/webidl-tests/dictionary.rs.

Currently, setters are implemented as my_property(&mut self, val: ...). To avoid breaking backwards compatibility, I have generated a new trait MyDictionaryTypeGetters for every MyDictionaryType. It contains definitions in the form of my_property(&self) -> ....

Personally, I would suggest to rename all setters to set_my_property and place the my_property getters back in the impl MyDictionaryType as conventionally used in Rust. Preferably in another PR since as already mentioned would break backwards compatibility.

For the dictionary getter return types I have applied the same logic that is currently used for Interface getters:

let ty = type_
.type_
.to_idl_type(self)
.to_syn_type(TypePosition::Return)
.unwrap_or(None);

"Getter",
quote!(getter,),
quote!( pub fn #name(#this) -> #ty; ),

This should close #1793 and #2921, and address point 1 of #3921.

Comment on lines +933 to +942
#unstable_attr
#getter_trait_doc_comment
pub trait #getter_trait_name {
#(#getter_trait_fields)*
}

#unstable_attr
impl #getter_trait_name for #name {
#(#getter_trait_impl_fields)*
}
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's the main addition of this PR...

Comment on lines +709 to +713
#unstable_attr
#cfg_features
#doc_comment
#unstable_docs
fn #name(&self) -> #return_ty;
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

...with getter signatures that are defined here...

Comment on lines +742 to +744
fn #name(&self) -> #return_ty {
self.#shim_name()
}
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

...and implemented here

@MOZGIII
Copy link

MOZGIII commented Apr 24, 2024

Personally, I would suggest to rename all setters to set_my_property and place the my_property getters back in the impl MyDictionaryType as conventionally used in Rust. Preferably in another PR since as already mentioned would break backwards compatibility.

I agree. We should bite the bullet, break the compatibility and do it (in another PR ofc).

@MOZGIII
Copy link

MOZGIII commented Apr 24, 2024

I like the idea of having traits here. We could probably go even further - have a trait with a WebIDL-generated declaration of the whole interface of a given type - not just for getters. That way we can include getters, setters and methods (both static and not).
The possible benefit from this would be nicer composability of wasm types.

I think we could create an issue to discuss this further, if this sounds like a viable direction to explore.

@pablosichert
Copy link
Author

The possible benefit from this would be nicer composability of wasm types.

I think you are referring to what I have an mind, but just to elaborate the thought: I'm not fully sure yet how hard it would be to make it possible, but if that would allow e.g. implementing FooGetters for Bar where Bar: Foo, that would be great. Currently you need to explicitly downcast the type.

Comment on lines 62 to 84
let mut many_types = ManyTypes::new();
many_types.a("a");
many_types.n1(1);
many_types.n2(2);
many_types.n3(3);
many_types.n4(4);
many_types.n5(5);
many_types.n6(6);
let mut other_dict = OtherDict::new();
other_dict.a(42);
many_types.c(&other_dict);

{
use crate::generated::ManyTypesGetters;
assert_eq!(many_types.a(), "a");
assert_eq!(many_types.n1(), 1);
assert_eq!(many_types.n2(), 2);
assert_eq!(many_types.n3(), 3);
assert_eq!(many_types.n4(), 4);
assert_eq!(many_types.n5(), 5);
assert_eq!(many_types.n6(), 6);
assert_eq!(many_types.c(), other_dict);
}
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unfortunately, due to the signature name clash this is a bit awkward to use right now.

If you move the use crate::generated::ManyTypesGetters; to the parent block, this test breaks.

@MOZGIII
Copy link

MOZGIII commented Apr 24, 2024

See #3934


Let's take a WritableStream for example.

If it was a trait, could just implement it for a JsValue, like so: impl WritableStream for JsValue { ... } (with some codegen of course).

Then, if needed, we could very elegantly extend WritableStream with WebTransportSendStream with an explicit trait WebTransportSendStream: WritableStream { ... } - and also implement the WebTransportSendStream for JsValue.


With the given WebIDL:

WebIDL

[Exposed=*, Transferable]
interface WritableStream {
  constructor(optional object underlyingSink, optional QueuingStrategy strategy = {});

  readonly attribute boolean locked;

  Promise<undefined> abort(optional any reason);
  Promise<undefined> close();
  WritableStreamDefaultWriter getWriter();
};
[Exposed=(Window,Worker), SecureContext, Transferable]
interface WebTransportSendStream : WritableStream {
  attribute WebTransportSendGroup? sendGroup;
  attribute long long sendOrder;
  Promise<WebTransportSendStreamStats> getStats();
  WebTransportWriter getWriter();
};

The traits would look somewhat like this:

Traits

trait WritableStream: Sized + JsAny {
    fn new() -> Self;
    fn new_with_underlying_sink(underlying_sink: &impl JsObject) -> Self;
    // ...

    fn locked(&self) -> bool;

    fn abort(&self) -> Promise<()>;
    fn abort_with_reason(&self, reason: &impl JsAny) -> Promise<()>;

    fn close(&self) -> Promise<()>;

    fn get_writer(&self) -> Result<impl WritableStreamDefaultWriter>;
}

trait WebTransportSendStream: WritableStream + JsAny {
    fn send_group(&self) -> Option<impl WebTransportSendGroup>;
    fn set_send_group(&self, value: &impl WebTransportSendGroup) -> Option<impl WebTransportSendGroup>;
    fn unset_send_group(&self);

    fn send_order(&self) -> i64;
    fn set_send_order(&self, value: i64);
    fn unset_send_order(&self);
    
    fn get_stats(&self) -> Promise<impl WebTransportSendStreamStats>;

    fn get_writer(&self) -> Result<impl WebTransportWriter>;
}

and impls like this:

Impls

impl WritableStream for JsValue {
    fn new() -> Self {
        __shim_WritableStream_new()
    }

    fn new_with_underlying_sink(underlying_sink: &impl JsObject) -> Self {
        __shim_WritableStream_new_with_underlying_sink(underlying_sink)
    }

    // ... and so on.
}

impl WebTransportSendStream for JsValue {
    fn send_group(&self) -> Option<impl WebTransportSendGroup> {
        __shim_WebTransportSendStream_send_group()
    }

    // ... and so on.
}


A couple things to note:

  • the idea of having a JsAny type, that would work somewhat like a dyn std::any::Any and enable typechecking of the underlying type of any JsValue:

    let random_value: JsValue = ...;
    let stream: impl WebTransportSendStream = JsAny::as_unchecked<dyn WebTransportSendStream>(random_value); // might also have an `instanceof` and/or custom guard variants.

    UPD: no! actually, what we just need is this: trait JsAny { fn as_js_value(self: Self) -> JsValue }; JsValue already has all the impls, so this effectively allows us to cast impl WebTransportSendStream to JsValue and from there to anything:

    let random_value: JsValue = ...;
    // Works cause of impl WebTransportSendStream for JsValue.
    let stream: impl WebTransportSendStream = random_value;
    // Works cause WebTransportSendStream: WritableStream.
    let stream: impl WritableStream = stream;
    //  Doesn't work, as this type of generalization is not necessarily ture: not every `impl WritableStream` is a `impl WebTransportSendStream`
    let stream: impl WebTransportSendStream = stream;
    //  This works though.
    let stream: impl WebTransportSendStream = JsAny::as_js_value(stream);

    This might look scary - but you would just always use generics or trait object if an issue is encountered here.

    Also, we should consider having something like this: JsValue<dyn WebTransportSendStream> - the JsValue is already effectively a pointer to a value, but this way it can also carry on the information about the associated API.

    Then we'd implement the traits for impl WebTransportSendStream for JsValue<dyn WebTransportSendStream> { ... } to provide the type restrictions...

  • the shims being standalone functions instead of being implementation details of a given type.

@pablosichert
Copy link
Author

Cool, tests pass now.

Just a note that this will break for people that use web_sys::* in their code, but I guess they should be aware that they are opening up to that danger by using a wildcard.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Add getters for WebIDL's dictionaries.
2 participants