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

Add null to argument types of optional parameters #4188

Draft
wants to merge 14 commits into
base: main
Choose a base branch
from

Conversation

RunDevelopment
Copy link
Contributor

All functions with Option<T> arguments take both undefined and null and generally treat them as equivalent (see isLikeNone). Despite both undefined and null being accepted, the generated type definitions only allowed undefined. Example:

#[wasm_bindgen]
pub fn echo(a: Option<String>) -> Option<String> {
    a
}
export function echo(a?: string): string | undefined

This is unnecessarily restrictive, as echo(null) would also work (see isLikeNone) and return undefined just like echo(undefined). This also results in worse ergonomics as many DOM APIs return value | null (e.g. textContent), which currently requires dev to unnecessarily map null to undefined (e.g. echo(element.textContent ?? undefined)).

To bring the generated type definitions in line with the actual runtime behavior of the JS glue code, I changed the type of optional parameters from T | undefined to T | undefined | null. E.g. the function signature of echo is the following with this PR:

export function echo(a?: string | null): string | undefined

This accurately represent the runtime behavior of the function and results in better ergonomics.

Note: This PR only affects function arguments. The return type is still T | undefined, as it should be.

Tests

To test that this (and other features I have planned) are implemented correctly, I added a single large test file: echo.rs. This test file just contains echo/identity functions for various types to check the glue code of various input/output types.

Copy link
Collaborator

@daxpedda daxpedda left a comment

Choose a reason for hiding this comment

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

LGTM!

Missing a changelog entry.

crates/cli-support/src/js/binding.rs Outdated Show resolved Hide resolved
@daxpedda daxpedda added the waiting for author Waiting for author to respond label Oct 12, 2024
CHANGELOG.md Outdated Show resolved Hide resolved
Copy link
Collaborator

@daxpedda daxpedda left a comment

Choose a reason for hiding this comment

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

Small nit left, otherwise this LGTM.

CHANGELOG.md Show resolved Hide resolved
dst.push_str(" | undefined");
adapter2ts(ty, position, dst, refs);
dst.push_str(match position {
TypePosition::Argument => " | undefined | null",
Copy link
Collaborator

Choose a reason for hiding this comment

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

Suggested change
TypePosition::Argument => " | undefined | null",
TypePosition::Argument => unreachable!(),

We always handle this case manually, right?

Copy link
Contributor Author

@RunDevelopment RunDevelopment Oct 14, 2024

Choose a reason for hiding this comment

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

We don't for variadic arguments (bindings.rs line 419). Though I'm not sure how relevant that is, since varargs probably don't allow Option<T>.

But I also don't like the idea of adapter2ts knowing with what types it is called. This feels like a bit of global reasoning to me.

WDYT?

Copy link
Collaborator

Choose a reason for hiding this comment

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

Hm, aren't we handling all Option<T> manually before they ever arrive in adapter2ts()?
I'm just concerned because this code is actually incorrect, this function can't handle this case correctly.

Ideally we move Option<T> handling entirely into this function, but this could be done in a separate PR.

So unless I'm missing something, I think the whole AdapterType::Option(ty) => { ... } statement should yield unreachable!() imo.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Hm, aren't we handling all Option<T> manually before they ever arrive in adapter2ts()?

As I said, not for varargs AFAICT.

if let (Some(name), Some(ty)) = (variadic_arg, arg_tys.last()) {
ret.push_str("@param {...");
adapter2ts(ty, TypePosition::Argument, &mut ret, None);
ret.push_str("} ");

But I also don't know if varargs even allow Option<T> since Vec<Option<T>> is not ABI compatible rn. But that's very much global reasoning.

Copy link
Collaborator

@daxpedda daxpedda Oct 15, 2024

Choose a reason for hiding this comment

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

I'm just concerned because this code is actually incorrect, this function can't handle this case correctly.

Lets say varargs do support Option<T>, would the output be correct?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yes, the output would be correct for documenting varargs in JSDoc AFAICT. E.g. Option<u32> would produce:

@param {...number | null | undefined} name

@RunDevelopment
Copy link
Contributor Author

I just realized that return and argument types not being the same is a problem for generated getter-setter pairs.

@RunDevelopment
Copy link
Contributor Author

Fixed it in #4202. After #4202 is in, I need to update this PR again, so I'll mark it as a draft for the time being.

@RunDevelopment RunDevelopment marked this pull request as draft October 15, 2024 16:14
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
waiting for author Waiting for author to respond
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants