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

Integration with wasm-bindgen #196

Closed
indietyp opened this issue Dec 22, 2023 · 5 comments
Closed

Integration with wasm-bindgen #196

indietyp opened this issue Dec 22, 2023 · 5 comments

Comments

@indietyp
Copy link

Utilizing "typescript_custom_section" (https://rustwasm.github.io/docs/wasm-bindgen/reference/attributes/on-rust-exports/typescript_custom_section.html) we could emit type bindings directly.

This seems to be non-trivial though, as it requires a const string.

(btw sorry for all these issues, I am just very excited about this amazing project and its potential)

@oscartbeaumont
Copy link
Member

oscartbeaumont commented Dec 24, 2023

This would be cool but I think it is way more difficult than you would expect.

Specta works fully at runtime making use of Rust's trait system to resolve types while Tauri bindgen works fully through static analysis which is just looking at the file content.

As a workaround, you could technically generate a types.rs file with the bindgen typescript section when your application starts up or in a unit test but you would then have to call the bindgen CLI again. I am assuming the bindgen CLI doesn't check if the file was actually imported (like mod types;) so you could probs still start your app without the file existing.

If Specta ever had a CLI we could definitely support this but I really don't think that will happen. Resolving types using static analysis would be both significantly less reliable and require us to maintain a completely different implementation. At that point you'd probs be best with Typeshare with it's drawbacks.

For future me:

  • A Specta CLI is hard because we can't rely on traits to resolve types like we can with the macros.
  • What if impl uuid::Uuid for Type (in an external crate) how can the CLI know we have an implementation for it?
  • A macro could shield the type (Eg. a: some_type!()) or a pub struct String(); (replacing a std type in the same file), or declare types we can't see.
  • We could generate and execute Rust code to resolve the types but it will be slow and at that point why wouldn't you just run your whole program or break the types out into another crate. I did a prototype of this on Specta CLI #200

@indietyp
Copy link
Author

Yes, right now I don't think it is possible, the only real way I see this working would be with support in wasm-pack or wasm-bindgen for a post-processing step that then invokes a binary or function of some sort (or a specta CLI as mentioned). I don't see native support happening tho.

Right now, what I think the best way is (although a bit yank) is to have an example like collect_types.rs, which then prints the generated .d.ts and replace that one with the one that was generated by wasm-bindgen.

Another thing to mention is that the current specta proc-macro clashes with the wasm_bindgen macro (switching them just moved the problem elsewhere), but I don't see a way around that, as both change the generated output quite a bit.

@oscartbeaumont
Copy link
Member

current specta proc-macro clashes with the wasm_bindgen macro

Can you share the error, that is definitely something I can look into fixing.

@indietyp
Copy link
Author

indietyp commented Dec 24, 2023

#[wasm_bindgen::prelude::wasm_bindgen]
#[specta::specta]
pub async fn create_account(
    client: &__wasm::AccountServiceClient,
    args: wasm_bindgen::JsValue,
) -> Result<wasm_bindgen::JsValue, wasm_bindgen::JsValue> {
    let args: CheckAccountGroupPermission = serde_wasm_bindgen::from_value(args)?;

    let value =
        client.client.call(args).await.map_err(|error| {
            match serde_wasm_bindgen::to_value(&error) {
                Ok(value) => value,
                Err(error) => error.into(),
            }
        })?;

    serde_wasm_bindgen::to_value(&value).map_err(Into::into)
}

error:

error: specta: expected string literal. Eg. `"somestring"`
  --> libs/@local/hash-graph-rpc/src/specification/account.rs:29:1
   |
29 | #[wasm_bindgen::prelude::wasm_bindgen]
   | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
   |
   = note: this error originates in the attribute macro `wasm_bindgen::prelude::wasm_bindgen` (in Nightly builds, run with -Z macro-backtrace for more info)

and if we switch 'em:

#[specta::specta]
#[wasm_bindgen::prelude::wasm_bindgen]
pub async fn create_account(
    client: &__wasm::AccountServiceClient,
    args: wasm_bindgen::JsValue,
) -> Result<wasm_bindgen::JsValue, wasm_bindgen::JsValue> {
    let args: CheckAccountGroupPermission = serde_wasm_bindgen::from_value(args)?;

    let value =
        client.client.call(args).await.map_err(|error| {
            match serde_wasm_bindgen::to_value(&error) {
                Ok(value) => value,
                Err(error) => error.into(),
            }
        })?;

    serde_wasm_bindgen::to_value(&value).map_err(Into::into)
}

error:

error: custom attribute panicked
  --> libs/@local/hash-graph-rpc/src/specification/account.rs:29:1
   |
29 | #[specta::specta]
   | ^^^^^^^^^^^^^^^^^
   |
   = help: message: Attribute path must be an ident

The code isn't great, but that's because I am generating it via a declarative macro^^

@oscartbeaumont
Copy link
Member

Okay so found and fixed a couple of things.

Using pub fn's with specta::specta was flat out broken - #205

and then given the input:

#[wasm_bindgen::prelude::wasm_bindgen]
#[specta]
pub fn testing() {}

Rust gives Specta the tokens:

"#[allow(dead_code)] pub fn testing() {}"
"pub unsafe extern \"C\" fn __wasm_bindgen_generated_testing() -> wasm_bindgen ::\nconvert :: WasmRet < < () as wasm_bindgen :: convert :: ReturnWasmAbi > :: Abi\n>\n{\n    let _ret = { let _ret = testing() ; _ret } ; < () as wasm_bindgen ::\n    convert :: ReturnWasmAbi > :: return_abi(_ret).into()\n}"

For this case, we can't really work out the types without reimplementing a bunch of the wasm_bindgen logic which I would rather not so I have put an error to make the DX a bit nicer.

Screenshot 2023-12-24 at 8 15 32 pm

While if you switch around the macros:

#[specta]
#[wasm_bindgen::prelude::wasm_bindgen]
pub fn testing() {}

gives Specta the tokens:

#[wasm_bindgen :: prelude :: wasm_bindgen] pub fn testing() {}

and this was just broken due to a bug in Specta's macros parsing of paths (it tried to cast it to an ident). You could transform your code to the following and it should start working. I have fixed this on main.

#[specta]
#[wasm_bindgen]
pub fn demo() {}

Hopefully that helps!

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

No branches or pull requests

2 participants