From f1e2d6eb908649f19bf3a1612a5c87d31f2ed096 Mon Sep 17 00:00:00 2001 From: Michael Schmidt Date: Mon, 9 Dec 2024 15:26:11 +0100 Subject: [PATCH] Better TS type formatting for array types (#4346) --- crates/cli-support/src/descriptor.rs | 8 +- crates/cli-support/src/js/identifier.rs | 42 ++++++++++ crates/cli-support/src/js/mod.rs | 44 +--------- crates/cli/tests/reference/echo.d.ts | 8 +- crates/cli/tests/reference/echo.js | 16 ++-- .../cli/tests/reference/typescript-type.d.ts | 4 + crates/cli/tests/reference/typescript-type.js | 82 +++++++++++++++++++ crates/cli/tests/reference/typescript-type.rs | 13 +++ .../cli/tests/reference/typescript-type.wat | 23 ++++++ 9 files changed, 185 insertions(+), 55 deletions(-) create mode 100644 crates/cli-support/src/js/identifier.rs create mode 100644 crates/cli/tests/reference/typescript-type.d.ts create mode 100644 crates/cli/tests/reference/typescript-type.js create mode 100644 crates/cli/tests/reference/typescript-type.rs create mode 100644 crates/cli/tests/reference/typescript-type.wat diff --git a/crates/cli-support/src/descriptor.rs b/crates/cli-support/src/descriptor.rs index d1b129ed77a..d9b7fd63eab 100644 --- a/crates/cli-support/src/descriptor.rs +++ b/crates/cli-support/src/descriptor.rs @@ -1,5 +1,7 @@ use std::char; +use crate::js::identifier::is_valid_ident; + macro_rules! tys { ($($a:ident)*) => (tys! { @ ($($a)*) 0 }); (@ () $v:expr) => {}; @@ -306,7 +308,11 @@ impl VectorKind { VectorKind::F64 => "Float64Array".to_string(), VectorKind::Externref => "any[]".to_string(), VectorKind::NamedExternref(ref name) => { - format!("({})[]", name) + if is_valid_ident(name.as_str()) { + format!("{}[]", name) + } else { + format!("({})[]", name) + } } } } diff --git a/crates/cli-support/src/js/identifier.rs b/crates/cli-support/src/js/identifier.rs new file mode 100644 index 00000000000..51ebcc81f58 --- /dev/null +++ b/crates/cli-support/src/js/identifier.rs @@ -0,0 +1,42 @@ +/// Returns whether a character has the Unicode `ID_Start` properly. +/// +/// This is only ever-so-slightly different from `XID_Start` in a few edge +/// cases, so we handle those edge cases manually and delegate everything else +/// to `unicode-ident`. +fn is_id_start(c: char) -> bool { + match c { + '\u{037A}' | '\u{0E33}' | '\u{0EB3}' | '\u{309B}' | '\u{309C}' | '\u{FC5E}' + | '\u{FC5F}' | '\u{FC60}' | '\u{FC61}' | '\u{FC62}' | '\u{FC63}' | '\u{FDFA}' + | '\u{FDFB}' | '\u{FE70}' | '\u{FE72}' | '\u{FE74}' | '\u{FE76}' | '\u{FE78}' + | '\u{FE7A}' | '\u{FE7C}' | '\u{FE7E}' | '\u{FF9E}' | '\u{FF9F}' => true, + _ => unicode_ident::is_xid_start(c), + } +} + +/// Returns whether a character has the Unicode `ID_Continue` properly. +/// +/// This is only ever-so-slightly different from `XID_Continue` in a few edge +/// cases, so we handle those edge cases manually and delegate everything else +/// to `unicode-ident`. +fn is_id_continue(c: char) -> bool { + match c { + '\u{037A}' | '\u{309B}' | '\u{309C}' | '\u{FC5E}' | '\u{FC5F}' | '\u{FC60}' + | '\u{FC61}' | '\u{FC62}' | '\u{FC63}' | '\u{FDFA}' | '\u{FDFB}' | '\u{FE70}' + | '\u{FE72}' | '\u{FE74}' | '\u{FE76}' | '\u{FE78}' | '\u{FE7A}' | '\u{FE7C}' + | '\u{FE7E}' => true, + _ => unicode_ident::is_xid_continue(c), + } +} + +/// Returns whether a string is a valid JavaScript identifier. +/// Defined at https://tc39.es/ecma262/#prod-IdentifierName. +pub fn is_valid_ident(name: &str) -> bool { + !name.is_empty() + && name.chars().enumerate().all(|(i, char)| { + if i == 0 { + is_id_start(char) || char == '$' || char == '_' + } else { + is_id_continue(char) || char == '$' || char == '\u{200C}' || char == '\u{200D}' + } + }) +} diff --git a/crates/cli-support/src/js/mod.rs b/crates/cli-support/src/js/mod.rs index 9cdd2d7ddb0..a650ab76772 100644 --- a/crates/cli-support/src/js/mod.rs +++ b/crates/cli-support/src/js/mod.rs @@ -10,6 +10,7 @@ use crate::wit::{JsImport, JsImportName, NonstandardWitSection, WasmBindgenAux}; use crate::{reset_indentation, Bindgen, EncodeInto, OutputMode, PLACEHOLDER_MODULE}; use anyhow::{anyhow, bail, Context as _, Error}; use binding::TsReference; +use identifier::is_valid_ident; use std::borrow::Cow; use std::collections::{BTreeMap, BTreeSet, HashMap, HashSet}; use std::fmt; @@ -19,6 +20,7 @@ use std::path::{Path, PathBuf}; use walrus::{FunctionId, ImportId, MemoryId, Module, TableId, ValType}; mod binding; +pub mod identifier; pub struct Context<'a> { globals: String, @@ -4535,48 +4537,6 @@ fn require_class<'a>( .or_default() } -/// Returns whether a character has the Unicode `ID_Start` properly. -/// -/// This is only ever-so-slightly different from `XID_Start` in a few edge -/// cases, so we handle those edge cases manually and delegate everything else -/// to `unicode-ident`. -fn is_id_start(c: char) -> bool { - match c { - '\u{037A}' | '\u{0E33}' | '\u{0EB3}' | '\u{309B}' | '\u{309C}' | '\u{FC5E}' - | '\u{FC5F}' | '\u{FC60}' | '\u{FC61}' | '\u{FC62}' | '\u{FC63}' | '\u{FDFA}' - | '\u{FDFB}' | '\u{FE70}' | '\u{FE72}' | '\u{FE74}' | '\u{FE76}' | '\u{FE78}' - | '\u{FE7A}' | '\u{FE7C}' | '\u{FE7E}' | '\u{FF9E}' | '\u{FF9F}' => true, - _ => unicode_ident::is_xid_start(c), - } -} - -/// Returns whether a character has the Unicode `ID_Continue` properly. -/// -/// This is only ever-so-slightly different from `XID_Continue` in a few edge -/// cases, so we handle those edge cases manually and delegate everything else -/// to `unicode-ident`. -fn is_id_continue(c: char) -> bool { - match c { - '\u{037A}' | '\u{309B}' | '\u{309C}' | '\u{FC5E}' | '\u{FC5F}' | '\u{FC60}' - | '\u{FC61}' | '\u{FC62}' | '\u{FC63}' | '\u{FDFA}' | '\u{FDFB}' | '\u{FE70}' - | '\u{FE72}' | '\u{FE74}' | '\u{FE76}' | '\u{FE78}' | '\u{FE7A}' | '\u{FE7C}' - | '\u{FE7E}' => true, - _ => unicode_ident::is_xid_continue(c), - } -} - -/// Returns whether a string is a valid JavaScript identifier. -/// Defined at https://tc39.es/ecma262/#prod-IdentifierName. -fn is_valid_ident(name: &str) -> bool { - name.chars().enumerate().all(|(i, char)| { - if i == 0 { - is_id_start(char) || char == '$' || char == '_' - } else { - is_id_continue(char) || char == '$' || char == '\u{200C}' || char == '\u{200D}' - } - }) -} - /// Returns a string to tack on to the end of an expression to access a /// property named `name` of the object that expression resolves to. /// diff --git a/crates/cli/tests/reference/echo.d.ts b/crates/cli/tests/reference/echo.d.ts index 61fd4a7925e..5183992234d 100644 --- a/crates/cli/tests/reference/echo.d.ts +++ b/crates/cli/tests/reference/echo.d.ts @@ -33,9 +33,9 @@ export function echo_vec_uninit_u32(a: Uint32Array): Uint32Array; export function echo_vec_uninit_i32(a: Int32Array): Int32Array; export function echo_vec_uninit_u64(a: BigUint64Array): BigUint64Array; export function echo_vec_uninit_i64(a: BigInt64Array): BigInt64Array; -export function echo_vec_string(a: (string)[]): (string)[]; +export function echo_vec_string(a: string[]): string[]; export function echo_struct(a: Foo): Foo; -export function echo_vec_struct(a: (Foo)[]): (Foo)[]; +export function echo_vec_struct(a: Foo[]): Foo[]; export function echo_option_u8(a?: number | null): number | undefined; export function echo_option_i8(a?: number | null): number | undefined; export function echo_option_u16(a?: number | null): number | undefined; @@ -69,9 +69,9 @@ export function echo_option_vec_uninit_u32(a?: Uint32Array | null): Uint32Array export function echo_option_vec_uninit_i32(a?: Int32Array | null): Int32Array | undefined; export function echo_option_vec_uninit_u64(a?: BigUint64Array | null): BigUint64Array | undefined; export function echo_option_vec_uninit_i64(a?: BigInt64Array | null): BigInt64Array | undefined; -export function echo_option_vec_string(a?: (string)[] | null): (string)[] | undefined; +export function echo_option_vec_string(a?: string[] | null): string[] | undefined; export function echo_option_struct(a?: Foo | null): Foo | undefined; -export function echo_option_vec_struct(a?: (Foo)[] | null): (Foo)[] | undefined; +export function echo_option_vec_struct(a?: Foo[] | null): Foo[] | undefined; export class Foo { private constructor(); free(): void; diff --git a/crates/cli/tests/reference/echo.js b/crates/cli/tests/reference/echo.js index c3e9c47f835..b48c5e346e6 100644 --- a/crates/cli/tests/reference/echo.js +++ b/crates/cli/tests/reference/echo.js @@ -685,8 +685,8 @@ function getArrayJsValueFromWasm0(ptr, len) { return result; } /** - * @param {(string)[]} a - * @returns {(string)[]} + * @param {string[]} a + * @returns {string[]} */ export function echo_vec_string(a) { const ptr0 = passArrayJsValueToWasm0(a, wasm.__wbindgen_malloc); @@ -714,8 +714,8 @@ export function echo_struct(a) { } /** - * @param {(Foo)[]} a - * @returns {(Foo)[]} + * @param {Foo[]} a + * @returns {Foo[]} */ export function echo_vec_struct(a) { const ptr0 = passArrayJsValueToWasm0(a, wasm.__wbindgen_malloc); @@ -1145,8 +1145,8 @@ export function echo_option_vec_uninit_i64(a) { } /** - * @param {(string)[] | null} [a] - * @returns {(string)[] | undefined} + * @param {string[] | null} [a] + * @returns {string[] | undefined} */ export function echo_option_vec_string(a) { var ptr0 = isLikeNone(a) ? 0 : passArrayJsValueToWasm0(a, wasm.__wbindgen_malloc); @@ -1175,8 +1175,8 @@ export function echo_option_struct(a) { } /** - * @param {(Foo)[] | null} [a] - * @returns {(Foo)[] | undefined} + * @param {Foo[] | null} [a] + * @returns {Foo[] | undefined} */ export function echo_option_vec_struct(a) { var ptr0 = isLikeNone(a) ? 0 : passArrayJsValueToWasm0(a, wasm.__wbindgen_malloc); diff --git a/crates/cli/tests/reference/typescript-type.d.ts b/crates/cli/tests/reference/typescript-type.d.ts new file mode 100644 index 00000000000..fa398def952 --- /dev/null +++ b/crates/cli/tests/reference/typescript-type.d.ts @@ -0,0 +1,4 @@ +/* tslint:disable */ +/* eslint-disable */ +export function single(a: number | string): void; +export function slice(a: (number | string)[]): void; diff --git a/crates/cli/tests/reference/typescript-type.js b/crates/cli/tests/reference/typescript-type.js new file mode 100644 index 00000000000..c69ecd4a682 --- /dev/null +++ b/crates/cli/tests/reference/typescript-type.js @@ -0,0 +1,82 @@ +let wasm; +export function __wbg_set_wasm(val) { + wasm = val; +} + + +const lTextDecoder = typeof TextDecoder === 'undefined' ? (0, module.require)('util').TextDecoder : TextDecoder; + +let cachedTextDecoder = new lTextDecoder('utf-8', { ignoreBOM: true, fatal: true }); + +cachedTextDecoder.decode(); + +let cachedUint8ArrayMemory0 = null; + +function getUint8ArrayMemory0() { + if (cachedUint8ArrayMemory0 === null || cachedUint8ArrayMemory0.byteLength === 0) { + cachedUint8ArrayMemory0 = new Uint8Array(wasm.memory.buffer); + } + return cachedUint8ArrayMemory0; +} + +function getStringFromWasm0(ptr, len) { + ptr = ptr >>> 0; + return cachedTextDecoder.decode(getUint8ArrayMemory0().subarray(ptr, ptr + len)); +} +/** + * @param {number | string} a + */ +export function single(a) { + wasm.single(a); +} + +let cachedDataViewMemory0 = null; + +function getDataViewMemory0() { + if (cachedDataViewMemory0 === null || cachedDataViewMemory0.buffer.detached === true || (cachedDataViewMemory0.buffer.detached === undefined && cachedDataViewMemory0.buffer !== wasm.memory.buffer)) { + cachedDataViewMemory0 = new DataView(wasm.memory.buffer); + } + return cachedDataViewMemory0; +} + +let WASM_VECTOR_LEN = 0; + +function addToExternrefTable0(obj) { + const idx = wasm.__externref_table_alloc(); + wasm.__wbindgen_export_0.set(idx, obj); + return idx; +} + +function passArrayJsValueToWasm0(array, malloc) { + const ptr = malloc(array.length * 4, 4) >>> 0; + const mem = getDataViewMemory0(); + for (let i = 0; i < array.length; i++) { + mem.setUint32(ptr + 4 * i, addToExternrefTable0(array[i]), true); + } + WASM_VECTOR_LEN = array.length; + return ptr; +} +/** + * @param {(number | string)[]} a + */ +export function slice(a) { + const ptr0 = passArrayJsValueToWasm0(a, wasm.__wbindgen_malloc); + const len0 = WASM_VECTOR_LEN; + wasm.slice(ptr0, len0); +} + +export function __wbindgen_init_externref_table() { + const table = wasm.__wbindgen_export_0; + const offset = table.grow(4); + table.set(0, undefined); + table.set(offset + 0, undefined); + table.set(offset + 1, null); + table.set(offset + 2, true); + table.set(offset + 3, false); + ; +}; + +export function __wbindgen_throw(arg0, arg1) { + throw new Error(getStringFromWasm0(arg0, arg1)); +}; + diff --git a/crates/cli/tests/reference/typescript-type.rs b/crates/cli/tests/reference/typescript-type.rs new file mode 100644 index 00000000000..2f8b4810947 --- /dev/null +++ b/crates/cli/tests/reference/typescript-type.rs @@ -0,0 +1,13 @@ +use wasm_bindgen::prelude::*; + +#[wasm_bindgen] +extern "C" { + #[wasm_bindgen(typescript_type = "number | string")] + type CustomType; +} + +#[wasm_bindgen] +pub fn single(a: CustomType) {} + +#[wasm_bindgen] +pub fn slice(a: Vec) {} diff --git a/crates/cli/tests/reference/typescript-type.wat b/crates/cli/tests/reference/typescript-type.wat new file mode 100644 index 00000000000..9327a22ef7b --- /dev/null +++ b/crates/cli/tests/reference/typescript-type.wat @@ -0,0 +1,23 @@ +(module $reference_test.wasm + (type (;0;) (func)) + (type (;1;) (func (result i32))) + (type (;2;) (func (param i32 i32))) + (type (;3;) (func (param i32 i32) (result i32))) + (type (;4;) (func (param externref))) + (import "./reference_test_bg.js" "__wbindgen_init_externref_table" (func (;0;) (type 0))) + (func $__wbindgen_malloc (;1;) (type 3) (param i32 i32) (result i32)) + (func $slice (;2;) (type 2) (param i32 i32)) + (func $__externref_table_alloc (;3;) (type 1) (result i32)) + (func $"single externref shim" (;4;) (type 4) (param externref)) + (table (;0;) 128 externref) + (memory (;0;) 17) + (export "memory" (memory 0)) + (export "single" (func $"single externref shim")) + (export "slice" (func $slice)) + (export "__wbindgen_export_0" (table 0)) + (export "__externref_table_alloc" (func $__externref_table_alloc)) + (export "__wbindgen_malloc" (func $__wbindgen_malloc)) + (export "__wbindgen_start" (func 0)) + (@custom "target_features" (after code) "\04+\0amultivalue+\0fmutable-globals+\0freference-types+\08sign-ext") +) +