diff --git a/crates/cli-support/src/js/binding.rs b/crates/cli-support/src/js/binding.rs index 637c3a5edf7..91ae75f1103 100644 --- a/crates/cli-support/src/js/binding.rs +++ b/crates/cli-support/src/js/binding.rs @@ -575,6 +575,29 @@ impl<'a, 'b> JsBuilder<'a, 'b> { )); } + /// The given value is a number (`typeof val === 'number'`), it is + /// converted to a bigint. If the value is anything else, it will be + /// returned as is. + /// + /// If the number is not an integer, it will be truncated to an integer. + /// If the number is NaN, +Infinity, or -Infinity, the conversion will fail + /// and result in a runtime JS `RangeError`. + /// + /// The returned string the name of a variable that holds the converted + /// value. + fn number_to_bigint(&mut self, val: &str) -> String { + // This conversion relies on the WebAssembly API for correctness. The + // API automatically adjusts bigint values that are out of range, so we + // just have to pass it any bigint. We truncate the number to an integer + // to be consistent with the truncation the WebAssembly API does for + // 32-bit integers. + let new_var = format!("bigint{}", self.tmp()); + self.prelude(&format!( + "const {new_var} = typeof {val} === 'number' ? BigInt(Math.trunc({val})) : {val};" + )); + new_var + } + fn string_to_memory( &mut self, mem: walrus::MemoryId, @@ -731,7 +754,12 @@ fn instruction( } Instruction::Int64ToWasm => { - let val = js.pop(); + let mut val = js.pop(); + // Regular JS numbers are commonly used for integers >32 bits, + // because they can represent integers up to 2^53 exactly. To + // support this, we allow both number and bigint for 64-bit + // integers. + val = js.number_to_bigint(&val); js.assert_bigint(&val); js.push(val); } @@ -1034,9 +1062,11 @@ fn instruction( } Instruction::FromOptionNative { ty } => { - let val = js.pop(); + let mut val = js.pop(); js.cx.expose_is_like_none(); if *ty == ValType::I64 { + // same as for Instruction::IntToWasm + val = js.number_to_bigint(&val); js.assert_optional_bigint(&val); } else { js.assert_optional_number(&val); @@ -1576,9 +1606,11 @@ fn adapter2ts( | AdapterType::NonNull => dst.push_str("number"), AdapterType::I64 | AdapterType::S64 - | AdapterType::U64 - | AdapterType::S128 - | AdapterType::U128 => dst.push_str("bigint"), + | AdapterType::U64 => dst.push_str(match position { + TypePosition::Argument => "bigint | number", + TypePosition::Return => "bigint", + }), + AdapterType::S128 | AdapterType::U128 => dst.push_str("bigint"), AdapterType::String => dst.push_str("string"), AdapterType::Externref => dst.push_str("any"), AdapterType::Bool => dst.push_str("boolean"), diff --git a/crates/cli/tests/reference/add.d.ts b/crates/cli/tests/reference/add.d.ts index f11140413c4..8aa1b2c8d94 100644 --- a/crates/cli/tests/reference/add.d.ts +++ b/crates/cli/tests/reference/add.d.ts @@ -2,3 +2,7 @@ /* eslint-disable */ export function add_u32(a: number, b: number): number; export function add_i32(a: number, b: number): number; +export function add_u64(a: bigint | number, b: bigint | number): bigint; +export function add_i64(a: bigint | number, b: bigint | number): bigint; +export function add_option_u64(a: bigint | number | null | undefined, b: bigint | number): bigint | undefined; +export function add_option_i64(a: bigint | number | null | undefined, b: bigint | number): bigint | undefined; diff --git a/crates/cli/tests/reference/add.js b/crates/cli/tests/reference/add.js index f11b75ef83a..a17a6f390f9 100644 --- a/crates/cli/tests/reference/add.js +++ b/crates/cli/tests/reference/add.js @@ -23,6 +23,57 @@ export function add_i32(a, b) { return ret; } +/** + * @param {bigint | number} a + * @param {bigint | number} b + * @returns {bigint} + */ +export function add_u64(a, b) { + const bigint0 = typeof a === 'number' ? BigInt(Math.trunc(a)) : a; + const bigint1 = typeof b === 'number' ? BigInt(Math.trunc(b)) : b; + const ret = wasm.add_u64(bigint0, bigint1); + return BigInt.asUintN(64, ret); +} + +/** + * @param {bigint | number} a + * @param {bigint | number} b + * @returns {bigint} + */ +export function add_i64(a, b) { + const bigint0 = typeof a === 'number' ? BigInt(Math.trunc(a)) : a; + const bigint1 = typeof b === 'number' ? BigInt(Math.trunc(b)) : b; + const ret = wasm.add_i64(bigint0, bigint1); + return ret; +} + +function isLikeNone(x) { + return x === undefined || x === null; +} +/** + * @param {bigint | number | null | undefined} a + * @param {bigint | number} b + * @returns {bigint | undefined} + */ +export function add_option_u64(a, b) { + const bigint0 = typeof a === 'number' ? BigInt(Math.trunc(a)) : a; + const bigint1 = typeof b === 'number' ? BigInt(Math.trunc(b)) : b; + const ret = wasm.add_option_u64(!isLikeNone(bigint0), isLikeNone(bigint0) ? BigInt(0) : bigint0, bigint1); + return ret[0] === 0 ? undefined : BigInt.asUintN(64, ret[1]); +} + +/** + * @param {bigint | number | null | undefined} a + * @param {bigint | number} b + * @returns {bigint | undefined} + */ +export function add_option_i64(a, b) { + const bigint0 = typeof a === 'number' ? BigInt(Math.trunc(a)) : a; + const bigint1 = typeof b === 'number' ? BigInt(Math.trunc(b)) : b; + const ret = wasm.add_option_i64(!isLikeNone(bigint0), isLikeNone(bigint0) ? BigInt(0) : bigint0, bigint1); + return ret[0] === 0 ? undefined : ret[1]; +} + export function __wbindgen_init_externref_table() { const table = wasm.__wbindgen_export_0; const offset = table.grow(4); diff --git a/crates/cli/tests/reference/add.rs b/crates/cli/tests/reference/add.rs index f2cc3e7a05f..0b382cb8f28 100644 --- a/crates/cli/tests/reference/add.rs +++ b/crates/cli/tests/reference/add.rs @@ -9,3 +9,23 @@ pub fn add_u32(a: u32, b: u32) -> u32 { pub fn add_i32(a: i32, b: i32) -> i32 { a + b } + +#[wasm_bindgen] +pub fn add_u64(a: u64, b: u64) -> u64 { + a + b +} + +#[wasm_bindgen] +pub fn add_i64(a: i64, b: i64) -> i64 { + a + b +} + +#[wasm_bindgen] +pub fn add_option_u64(a: Option, b: u64) -> Option { + a.map(|a| a + b) +} + +#[wasm_bindgen] +pub fn add_option_i64(a: Option, b: i64) -> Option { + a.map(|a| a + b) +} diff --git a/crates/cli/tests/reference/add.wat b/crates/cli/tests/reference/add.wat index bc5cbff68cd..964ef276593 100644 --- a/crates/cli/tests/reference/add.wat +++ b/crates/cli/tests/reference/add.wat @@ -1,14 +1,24 @@ (module $reference_test.wasm (type (;0;) (func)) (type (;1;) (func (param i32 i32) (result i32))) + (type (;2;) (func (param i32 i64 i64) (result i32 i64))) + (type (;3;) (func (param i64 i64) (result i64))) (import "./reference_test_bg.js" "__wbindgen_init_externref_table" (func (;0;) (type 0))) (func $add_u32 (;1;) (type 1) (param i32 i32) (result i32)) (func $add_i32 (;2;) (type 1) (param i32 i32) (result i32)) + (func $add_u64 (;3;) (type 3) (param i64 i64) (result i64)) + (func $add_i64 (;4;) (type 3) (param i64 i64) (result i64)) + (func $"add_option_u64 multivalue shim" (;5;) (type 2) (param i32 i64 i64) (result i32 i64)) + (func $"add_option_i64 multivalue shim" (;6;) (type 2) (param i32 i64 i64) (result i32 i64)) (table (;0;) 128 externref) (memory (;0;) 17) (export "memory" (memory 0)) (export "add_u32" (func $add_u32)) (export "add_i32" (func $add_i32)) + (export "add_u64" (func $add_u64)) + (export "add_i64" (func $add_i64)) + (export "add_option_u64" (func $"add_option_u64 multivalue shim")) + (export "add_option_i64" (func $"add_option_i64 multivalue shim")) (export "__wbindgen_export_0" (table 0)) (export "__wbindgen_start" (func 0)) (@custom "target_features" (after code) "\04+\0amultivalue+\0fmutable-globals+\0freference-types+\08sign-ext") diff --git a/crates/cli/tests/reference/date.d.ts b/crates/cli/tests/reference/date.d.ts new file mode 100644 index 00000000000..ab20322f079 --- /dev/null +++ b/crates/cli/tests/reference/date.d.ts @@ -0,0 +1,3 @@ +/* tslint:disable */ +/* eslint-disable */ +export function date_now(): bigint; diff --git a/crates/cli/tests/reference/date.js b/crates/cli/tests/reference/date.js new file mode 100644 index 00000000000..95da99c8617 --- /dev/null +++ b/crates/cli/tests/reference/date.js @@ -0,0 +1,30 @@ +let wasm; +export function __wbg_set_wasm(val) { + wasm = val; +} + +/** + * @returns {bigint} + */ +export function date_now() { + const ret = wasm.date_now(); + return BigInt.asUintN(64, ret); +} + +export function __wbg_now_179748341dc011f4() { + const ret = Date.now(); + const bigint1 = typeof ret === 'number' ? BigInt(Math.trunc(ret)) : ret; + return bigint1; +}; + +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); + ; +}; + diff --git a/crates/cli/tests/reference/date.rs b/crates/cli/tests/reference/date.rs new file mode 100644 index 00000000000..3cafddd3d56 --- /dev/null +++ b/crates/cli/tests/reference/date.rs @@ -0,0 +1,14 @@ +use wasm_bindgen::prelude::*; + +#[wasm_bindgen] +extern "C" { + pub type Date; + + #[wasm_bindgen(static_method_of = Date)] + pub fn now() -> u64; +} + +#[wasm_bindgen] +pub fn date_now() -> u64 { + Date::now() +} diff --git a/crates/cli/tests/reference/date.wat b/crates/cli/tests/reference/date.wat new file mode 100644 index 00000000000..e4e4701b78d --- /dev/null +++ b/crates/cli/tests/reference/date.wat @@ -0,0 +1,14 @@ +(module $reference_test.wasm + (type (;0;) (func)) + (type (;1;) (func (result i64))) + (import "./reference_test_bg.js" "__wbindgen_init_externref_table" (func (;0;) (type 0))) + (func $date_now (;1;) (type 1) (result i64)) + (table (;0;) 128 externref) + (memory (;0;) 17) + (export "memory" (memory 0)) + (export "date_now" (func $date_now)) + (export "__wbindgen_export_0" (table 0)) + (export "__wbindgen_start" (func 0)) + (@custom "target_features" (after code) "\04+\0amultivalue+\0fmutable-globals+\0freference-types+\08sign-ext") +) + diff --git a/crates/cli/tests/reference/echo.d.ts b/crates/cli/tests/reference/echo.d.ts index 61fd4a7925e..f6705cda259 100644 --- a/crates/cli/tests/reference/echo.d.ts +++ b/crates/cli/tests/reference/echo.d.ts @@ -6,8 +6,8 @@ export function echo_u16(a: number): number; export function echo_i16(a: number): number; export function echo_u32(a: number): number; export function echo_i32(a: number): number; -export function echo_u64(a: bigint): bigint; -export function echo_i64(a: bigint): bigint; +export function echo_u64(a: bigint | number): bigint; +export function echo_i64(a: bigint | number): bigint; export function echo_u128(a: bigint): bigint; export function echo_i128(a: bigint): bigint; export function echo_usize(a: number): number; @@ -42,8 +42,8 @@ export function echo_option_u16(a?: number | null): number | undefined; export function echo_option_i16(a?: number | null): number | undefined; export function echo_option_u32(a?: number | null): number | undefined; export function echo_option_i32(a?: number | null): number | undefined; -export function echo_option_u64(a?: bigint | null): bigint | undefined; -export function echo_option_i64(a?: bigint | null): bigint | undefined; +export function echo_option_u64(a?: bigint | number | null): bigint | undefined; +export function echo_option_i64(a?: bigint | number | null): bigint | undefined; export function echo_option_u128(a?: bigint | null): bigint | undefined; export function echo_option_i128(a?: bigint | null): bigint | undefined; export function echo_option_usize(a?: number | null): number | undefined; diff --git a/crates/cli/tests/reference/echo.js b/crates/cli/tests/reference/echo.js index c3e9c47f835..4879b1cd3ea 100644 --- a/crates/cli/tests/reference/echo.js +++ b/crates/cli/tests/reference/echo.js @@ -214,20 +214,22 @@ export function echo_i32(a) { } /** - * @param {bigint} a + * @param {bigint | number} a * @returns {bigint} */ export function echo_u64(a) { - const ret = wasm.echo_u64(a); + const bigint0 = typeof a === 'number' ? BigInt(Math.trunc(a)) : a; + const ret = wasm.echo_u64(bigint0); return BigInt.asUintN(64, ret); } /** - * @param {bigint} a + * @param {bigint | number} a * @returns {bigint} */ export function echo_i64(a) { - const ret = wasm.echo_i64(a); + const bigint0 = typeof a === 'number' ? BigInt(Math.trunc(a)) : a; + const ret = wasm.echo_i64(bigint0); return ret; } @@ -781,20 +783,22 @@ export function echo_option_i32(a) { } /** - * @param {bigint | null} [a] + * @param {bigint | number | null} [a] * @returns {bigint | undefined} */ export function echo_option_u64(a) { - const ret = wasm.echo_option_u64(!isLikeNone(a), isLikeNone(a) ? BigInt(0) : a); + const bigint0 = typeof a === 'number' ? BigInt(Math.trunc(a)) : a; + const ret = wasm.echo_option_u64(!isLikeNone(bigint0), isLikeNone(bigint0) ? BigInt(0) : bigint0); return ret[0] === 0 ? undefined : BigInt.asUintN(64, ret[1]); } /** - * @param {bigint | null} [a] + * @param {bigint | number | null} [a] * @returns {bigint | undefined} */ export function echo_option_i64(a) { - const ret = wasm.echo_option_i64(!isLikeNone(a), isLikeNone(a) ? BigInt(0) : a); + const bigint0 = typeof a === 'number' ? BigInt(Math.trunc(a)) : a; + const ret = wasm.echo_option_i64(!isLikeNone(bigint0), isLikeNone(bigint0) ? BigInt(0) : bigint0); return ret[0] === 0 ? undefined : ret[1]; } diff --git a/crates/cli/tests/reference/wasm-export-types.d.ts b/crates/cli/tests/reference/wasm-export-types.d.ts index a5f399db59d..86c9297d781 100644 --- a/crates/cli/tests/reference/wasm-export-types.d.ts +++ b/crates/cli/tests/reference/wasm-export-types.d.ts @@ -1,6 +1,6 @@ /* tslint:disable */ /* eslint-disable */ -export function example(a: number, b: bigint, c: any, d: string): string; +export function example(a: number, b: bigint | number, c: any, d: string): string; export function example_128(a: bigint): bigint | undefined; export type InitInput = RequestInfo | URL | Response | BufferSource | WebAssembly.Module; diff --git a/crates/cli/tests/reference/wasm-export-types.js b/crates/cli/tests/reference/wasm-export-types.js index de290e90502..1515cab3f77 100644 --- a/crates/cli/tests/reference/wasm-export-types.js +++ b/crates/cli/tests/reference/wasm-export-types.js @@ -75,23 +75,24 @@ function passStringToWasm0(arg, malloc, realloc) { } /** * @param {number} a - * @param {bigint} b + * @param {bigint | number} b * @param {any} c * @param {string} d * @returns {string} */ export function example(a, b, c, d) { - let deferred2_0; - let deferred2_1; + let deferred3_0; + let deferred3_1; try { - const ptr0 = passStringToWasm0(d, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc); - const len0 = WASM_VECTOR_LEN; - const ret = wasm.example(a, b, c, ptr0, len0); - deferred2_0 = ret[0]; - deferred2_1 = ret[1]; + const bigint0 = typeof b === 'number' ? BigInt(Math.trunc(b)) : b; + const ptr1 = passStringToWasm0(d, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc); + const len1 = WASM_VECTOR_LEN; + const ret = wasm.example(a, bigint0, c, ptr1, len1); + deferred3_0 = ret[0]; + deferred3_1 = ret[1]; return getStringFromWasm0(ret[0], ret[1]); } finally { - wasm.__wbindgen_free(deferred2_0, deferred2_1, 1); + wasm.__wbindgen_free(deferred3_0, deferred3_1, 1); } } diff --git a/tests/wasm/optional_primitives.js b/tests/wasm/optional_primitives.js index 499dd236d30..8a2243018e6 100644 --- a/tests/wasm/optional_primitives.js +++ b/tests/wasm/optional_primitives.js @@ -97,12 +97,23 @@ exports.js_works = () => { assert.strictEqual(wasm.optional_i64_identity(wasm.optional_i64_neg_one()), BigInt('-1')); assert.strictEqual(wasm.optional_i64_identity(wasm.optional_i64_min()), BigInt('-9223372036854775808')); assert.strictEqual(wasm.optional_i64_identity(wasm.optional_i64_max()), BigInt('9223372036854775807')); + assert.strictEqual(wasm.optional_i64_identity(0), BigInt('0')); + assert.strictEqual(wasm.optional_i64_identity(1.99), BigInt('1')); + assert.strictEqual(wasm.optional_i64_identity(-1.99), BigInt('-1')); + assert.throws(() => wasm.optional_i64_identity(NaN)); + assert.throws(() => wasm.optional_i64_identity(Infinity)); assert.strictEqual(wasm.optional_u64_identity(wasm.optional_u64_none()), undefined); assert.strictEqual(wasm.optional_u64_identity(wasm.optional_u64_zero()), BigInt('0')); assert.strictEqual(wasm.optional_u64_identity(wasm.optional_u64_one()), BigInt('1')); assert.strictEqual(wasm.optional_u64_identity(wasm.optional_u64_min()), BigInt('0')); assert.strictEqual(wasm.optional_u64_identity(wasm.optional_u64_max()), BigInt('18446744073709551615')); + assert.strictEqual(wasm.optional_u64_identity(0), BigInt('0')); + assert.strictEqual(wasm.optional_u64_identity(1.99), BigInt('1')); + assert.strictEqual(wasm.optional_u64_identity(-1n), BigInt('18446744073709551615')); // because modulo 2^64 + assert.strictEqual(wasm.optional_u64_identity(-1.99), BigInt('18446744073709551615')); // because modulo 2^64 + assert.throws(() => wasm.optional_u64_identity(NaN)); + assert.throws(() => wasm.optional_u64_identity(Infinity)); assert.strictEqual(wasm.optional_i128_identity(wasm.optional_i128_none()), undefined); assert.strictEqual(wasm.optional_i128_identity(wasm.optional_i128_zero()), BigInt('0'));