Skip to content

Typescript Generic Types #4455

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

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

Conversation

rouzwelt
Copy link
Contributor

@rouzwelt rouzwelt commented Mar 18, 2025

Motivation

resolves #4451
Currently if a function/method has a generic return or arguments type like:

pub fn some_fn(arg: SomeType<String>) -> SomeType<u8> {}
// or
pub fn some_fn(arg: SomeType<String>) -> Result<SomeType<u8>, JsValue> {}

where SomeType is just plain struct that impls wasm traits manually (not using wasm_bindgen macro on its definition, ie not js class) the generated typescript bindings wont correctly map the generic types and doesnt actually include them:

// for both cases above
export function some_fn(arg: SomeType): SomeType

Although user can use unchecked_return_type and unchecked_param_type attribute to specify them, but this is not really ideal, it is fine and ok if there are a couple of functions to apply those attrs to, but if there are tons of functions and methods, it wont really end up good as they are unchecked and bug prone, however if the nested types can be processed regularly like the main type, this can simply be resolved, so on typescript we would get:

// for both cases above
export function some_fn(arg: SomeType<string>): SomeType<number>

Proposed Solution

Implement some logic to parse and map the types deeply, possibly recursively, until all nested types are parsed and accounted for in generated typescript.
There is a mapper currently that maps the rust return types to their equivalent js/ts types, but looks like it just checks 1 level deep, and doesnt go deeper than that, however with some tweaks, the same logic can be applied on nested types to correctly map the return type for the js/ts bindings.

We need to parse the args/return syn::Types separately and map them with their nested syn::Types into their corresponding WasmDescribe which later on can be encoded and then decoded into Descriptor and next into AdapterType so can be appended lastly to the parent TS type as generic if they happen to be generic.


High-Level Implementation Details

We we would use native encoded/decoded WasmDescribe::inform() for return and arguments types, meaning we will encode their wasm informs in the backend::codegen for Export and then decode it at cli-support::descriptor::Function which now has 2 new more fields:
inner_ret_map and args_ty_map, these 2 field hold the Descriptor which now has 1 more variant called Descriptor::Generic which exclusively added for this purpose and doesnt have any effect on any previous functionalities:

Descriptor::Generic {
    ty: Box<Descriptor>,
    args: Box<[Descriptor]>,
}

that can hold the type map for both args and return type, now at cli-support::wit::register_export_adapter we convert those inner_ret_map and args_ty_map fields into AdapterType using a newly added function called describe_map_to_adapter where it just maps the given Descriptor to its AdapterType by also handling of nested generic types.
Lastly when building the ts signature and js doc for the binding function/method, we will convert those generic AdapterTypes into their corresponding js/ts types and append them to the main parent type.

New tests are added under typescript-tests crate testing complex nested generic types to be correctly mapped to their ts signature.
This feature doesnt change any of the previous functionalities and logic, it simply is a new piece of logic that separately maps a syn::Type with any of its nested syn::Types to their WasmDescribe and then Descriptor and after that to AdapterType completely separate to the how args and return type are resolved, and at the very last step of generating the ts signature, if the arg/return type is a generic, it will just append the generic to the actual parent type.


Note

An attribute can be added to be able to turn this off for a given function/method, I have not added such attribute in this PR, but if that's desired, I can add it.

@rouzwelt
Copy link
Contributor Author

any @maintainers here?

@rouzwelt
Copy link
Contributor Author

@daxpedda hey there, sorry to ping you, this PR has been waiting on a review for some time now, would be nice if you or any other maintainers can take a look at it.
Thanks in advance

@rouzwelt
Copy link
Contributor Author

rouzwelt commented Mar 29, 2025

@alexcrichton @fitzgen hey guys, would be nice to get a review on this whenever you guys got some time.

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.

Support handling of generic types for exported typescript function signature
1 participant