diff --git a/CHANGELOG.md b/CHANGELOG.md index f010e99fb8f..351468dce62 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,6 +14,15 @@ * Added JSDoc type annotations to C-style enums. [#4192](https://github.com/rustwasm/wasm-bindgen/pull/4192) +* Added support for C-style enums with negative discriminants. + [#4204](https://github.com/rustwasm/wasm-bindgen/pull/4204) + +* Added bindings for `MediaStreamTrack.getCapabilities`. + [#4236](https://github.com/rustwasm/wasm-bindgen/pull/4236) + +* Added WASM ABI support for `u128` and `i128` + [#4222](https://github.com/rustwasm/wasm-bindgen/pull/4222) + ### Changed * String enums now generate private TypeScript types but only if used. @@ -66,6 +75,9 @@ * Fixed calls to `JsCast::instanceof()` not respecting JavaScript namespaces. [#4241](https://github.com/rustwasm/wasm-bindgen/pull/4241) +* Fixed imports for functions using `this` and late binding. + [#4225](https://github.com/rustwasm/wasm-bindgen/pull/4225) + -------------------------------------------------------------------------------- ## [0.2.95](https://github.com/rustwasm/wasm-bindgen/compare/0.2.94...0.2.95) diff --git a/crates/backend/src/ast.rs b/crates/backend/src/ast.rs index d745f325f9e..0991dac66cf 100644 --- a/crates/backend/src/ast.rs +++ b/crates/backend/src/ast.rs @@ -445,6 +445,9 @@ pub struct Enum { pub rust_name: Ident, /// The name of this enum in JS code pub js_name: String, + /// Whether the variant values and hole are signed, meaning that they + /// represent the bits of a `i32` value. + pub signed: bool, /// The variants provided by this enum pub variants: Vec, /// The doc comments on this enum, if any diff --git a/crates/backend/src/codegen.rs b/crates/backend/src/codegen.rs index e5f3cfb9dd9..0cb52f3472c 100644 --- a/crates/backend/src/codegen.rs +++ b/crates/backend/src/codegen.rs @@ -1535,10 +1535,15 @@ impl ToTokens for ast::Enum { let name_len = name_str.len() as u32; let name_chars = name_str.chars().map(|c| c as u32); let hole = &self.hole; + let underlying = if self.signed { + quote! { i32 } + } else { + quote! { u32 } + }; let cast_clauses = self.variants.iter().map(|variant| { let variant_name = &variant.name; quote! { - if js == #enum_name::#variant_name as u32 { + if js == #enum_name::#variant_name as #underlying { #enum_name::#variant_name } } @@ -1548,20 +1553,20 @@ impl ToTokens for ast::Enum { (quote! { #[automatically_derived] impl #wasm_bindgen::convert::IntoWasmAbi for #enum_name { - type Abi = u32; + type Abi = #underlying; #[inline] - fn into_abi(self) -> u32 { - self as u32 + fn into_abi(self) -> #underlying { + self as #underlying } } #[automatically_derived] impl #wasm_bindgen::convert::FromWasmAbi for #enum_name { - type Abi = u32; + type Abi = #underlying; #[inline] - unsafe fn from_abi(js: u32) -> Self { + unsafe fn from_abi(js: #underlying) -> Self { #(#cast_clauses else)* { #wasm_bindgen::throw_str("invalid enum value passed") } @@ -1571,13 +1576,13 @@ impl ToTokens for ast::Enum { #[automatically_derived] impl #wasm_bindgen::convert::OptionFromWasmAbi for #enum_name { #[inline] - fn is_none(val: &u32) -> bool { *val == #hole } + fn is_none(val: &Self::Abi) -> bool { *val == #hole as #underlying } } #[automatically_derived] impl #wasm_bindgen::convert::OptionIntoWasmAbi for #enum_name { #[inline] - fn none() -> Self::Abi { #hole } + fn none() -> Self::Abi { #hole as #underlying } } #[automatically_derived] @@ -1597,7 +1602,7 @@ impl ToTokens for ast::Enum { #wasm_bindgen::JsValue { fn from(value: #enum_name) -> Self { - #wasm_bindgen::JsValue::from_f64((value as u32).into()) + #wasm_bindgen::JsValue::from_f64((value as #underlying).into()) } } @@ -1608,7 +1613,7 @@ impl ToTokens for ast::Enum { fn try_from_js_value(value: #wasm_bindgen::JsValue) -> #wasm_bindgen::__rt::core::result::Result::Error> { use #wasm_bindgen::__rt::core::convert::TryFrom; - let js = f64::try_from(&value)? as u32; + let js = f64::try_from(&value)? as #underlying; #wasm_bindgen::__rt::core::result::Result::Ok( #(#try_from_cast_clauses else)* { @@ -1894,9 +1899,9 @@ fn respan(input: TokenStream, span: &dyn ToTokens) -> TokenStream { for (i, token) in spans.into_iter().enumerate() { if i == 0 { - first_span = token.span(); + first_span = Span::call_site().located_at(token.span()); } - last_span = token.span(); + last_span = Span::call_site().located_at(token.span()); } let mut new_tokens = Vec::new(); diff --git a/crates/backend/src/encode.rs b/crates/backend/src/encode.rs index bc82ba5c336..baae8d72533 100644 --- a/crates/backend/src/encode.rs +++ b/crates/backend/src/encode.rs @@ -239,6 +239,7 @@ fn shared_function<'a>(func: &'a ast::Function, _intern: &'a Interner) -> Functi fn shared_enum<'a>(e: &'a ast::Enum, intern: &'a Interner) -> Enum<'a> { Enum { name: &e.js_name, + signed: e.signed, variants: e .variants .iter() diff --git a/crates/cli-support/src/descriptor.rs b/crates/cli-support/src/descriptor.rs index 5dec8d86d71..d1b129ed77a 100644 --- a/crates/cli-support/src/descriptor.rs +++ b/crates/cli-support/src/descriptor.rs @@ -19,6 +19,8 @@ tys! { U32 I64 U64 + I128 + U128 F32 F64 BOOLEAN @@ -55,6 +57,8 @@ pub enum Descriptor { U32, I64, U64, + I128, + U128, F32, F64, Boolean, @@ -132,11 +136,13 @@ impl Descriptor { I16 => Descriptor::I16, I32 => Descriptor::I32, I64 => Descriptor::I64, + I128 => Descriptor::I128, U8 if clamped => Descriptor::ClampedU8, U8 => Descriptor::U8, U16 => Descriptor::U16, U32 => Descriptor::U32, U64 => Descriptor::U64, + U128 => Descriptor::U128, F32 => Descriptor::F32, F64 => Descriptor::F64, BOOLEAN => Descriptor::Boolean, diff --git a/crates/cli-support/src/js/binding.rs b/crates/cli-support/src/js/binding.rs index 000634297d3..f79936e1985 100644 --- a/crates/cli-support/src/js/binding.rs +++ b/crates/cli-support/src/js/binding.rs @@ -620,6 +620,23 @@ fn instruction( ) } + fn int128_to_int64x2(val: &str) -> (String, String) { + // we don't need to perform any conversion here, because the JS + // WebAssembly API will automatically convert the bigints to 64 bits + // for us. This even allows us to ignore signedness. + let low = val.to_owned(); + let high = format!("{val} >> BigInt(64)"); + (low, high) + } + fn int64x2_to_int128(low: String, high: String, signed: bool) -> String { + let low = format!("BigInt.asUintN(64, {low})"); + if signed { + format!("({low} | ({high} << BigInt(64)))") + } else { + format!("({low} | (BigInt.asUintN(64, {high}) << BigInt(64)))") + } + } + match instr { Instruction::ArgGet(n) => { let arg = js.arg(*n).to_string(); @@ -712,6 +729,36 @@ fn instruction( } } + Instruction::Int128ToWasm => { + let val = js.pop(); + js.assert_bigint(&val); + let (low, high) = int128_to_int64x2(&val); + js.push(low); + js.push(high); + } + Instruction::WasmToInt128 { signed } => { + let high = js.pop(); + let low = js.pop(); + js.push(int64x2_to_int128(low, high, *signed)); + } + + Instruction::OptionInt128ToWasm => { + let val = js.pop(); + js.cx.expose_is_like_none(); + js.assert_optional_bigint(&val); + let (low, high) = int128_to_int64x2(&val); + js.push(format!("!isLikeNone({val})")); + js.push(format!("isLikeNone({val}) ? BigInt(0) : {low}")); + js.push(format!("isLikeNone({val}) ? BigInt(0) : {high}")); + } + Instruction::OptionWasmToInt128 { signed } => { + let high = js.pop(); + let low = js.pop(); + let present = js.pop(); + let val = int64x2_to_int128(low, high, *signed); + js.push(format!("{present} === 0 ? undefined : {val}")); + } + Instruction::WasmToStringEnum { name } => { let index = js.pop(); js.cx.expose_string_enum(name); @@ -983,6 +1030,8 @@ fn instruction( js.push(format!( "isLikeNone({val}) ? {zero} : {val}", zero = if *ty == ValType::I64 { + // We can't use bigint literals for now. See: + // https://github.com/rustwasm/wasm-bindgen/issues/4246 "BigInt(0)" } else { "0" @@ -1500,7 +1549,11 @@ fn adapter2ts(ty: &AdapterType, dst: &mut String, refs: Option<&mut HashSet dst.push_str("number"), - AdapterType::I64 | AdapterType::S64 | AdapterType::U64 => dst.push_str("bigint"), + AdapterType::I64 + | AdapterType::S64 + | AdapterType::U64 + | 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-support/src/js/mod.rs b/crates/cli-support/src/js/mod.rs index 5ab61f3c4bc..aff91c6cfcc 100644 --- a/crates/cli-support/src/js/mod.rs +++ b/crates/cli-support/src/js/mod.rs @@ -3062,6 +3062,21 @@ __wbg_set_wasm(wasm);" } } + if let JsImportName::Global { .. } | JsImportName::VendorPrefixed { .. } = js.name { + // We generally cannot import globals directly, because users can + // change most globals at runtime. + // + // An obvious example of this when the object literally changes + // (e.g. binding `foo.bar`), but polyfills can also change the + // object or fundtion. + // + // Late binding is another issue. The function might not even be + // defined when the Wasm module is instantiated. In such cases, + // there is an observable difference between a direct import and a + // JS shim calling the function. + return Ok(false); + } + self.expose_not_defined(); let name = self.import_name(js)?; let js = format!( diff --git a/crates/cli-support/src/wit/incoming.rs b/crates/cli-support/src/wit/incoming.rs index 5c2a2c4874e..c0ad9ad34bf 100644 --- a/crates/cli-support/src/wit/incoming.rs +++ b/crates/cli-support/src/wit/incoming.rs @@ -90,6 +90,20 @@ impl InstructionBuilder<'_, '_> { Descriptor::U32 => self.number(AdapterType::U32, WasmVT::I32), Descriptor::I64 => self.number(AdapterType::S64, WasmVT::I64), Descriptor::U64 => self.number(AdapterType::U64, WasmVT::I64), + Descriptor::I128 => { + self.instruction( + &[AdapterType::S128], + Instruction::Int128ToWasm, + &[AdapterType::I64, AdapterType::I64], + ); + } + Descriptor::U128 => { + self.instruction( + &[AdapterType::U128], + Instruction::Int128ToWasm, + &[AdapterType::I64, AdapterType::I64], + ); + } Descriptor::F32 => { self.get(AdapterType::F32); self.output.push(AdapterType::F32); @@ -285,6 +299,20 @@ impl InstructionBuilder<'_, '_> { Descriptor::F32 => self.in_option_sentinel64_f32(AdapterType::F32), Descriptor::F64 => self.in_option_native(ValType::F64), Descriptor::I64 | Descriptor::U64 => self.in_option_native(ValType::I64), + Descriptor::I128 => { + self.instruction( + &[AdapterType::S128.option()], + Instruction::OptionInt128ToWasm, + &[AdapterType::I32, AdapterType::I64, AdapterType::I64], + ); + } + Descriptor::U128 => { + self.instruction( + &[AdapterType::U128.option()], + Instruction::OptionInt128ToWasm, + &[AdapterType::I32, AdapterType::I64, AdapterType::I64], + ); + } Descriptor::Boolean => { self.instruction( &[AdapterType::Bool.option()], diff --git a/crates/cli-support/src/wit/mod.rs b/crates/cli-support/src/wit/mod.rs index 3e3e355c366..07a09860e0c 100644 --- a/crates/cli-support/src/wit/mod.rs +++ b/crates/cli-support/src/wit/mod.rs @@ -894,6 +894,7 @@ impl<'a> Context<'a> { } fn enum_(&mut self, enum_: decode::Enum<'_>) -> Result<(), Error> { + let signed = enum_.signed; let aux = AuxEnum { name: enum_.name.to_string(), comments: concatenate_comments(&enum_.comments), @@ -901,11 +902,12 @@ impl<'a> Context<'a> { .variants .iter() .map(|v| { - ( - v.name.to_string(), - v.value, - concatenate_comments(&v.comments), - ) + let value = if signed { + v.value as i32 as i64 + } else { + v.value as i64 + }; + (v.name.to_string(), value, concatenate_comments(&v.comments)) }) .collect(), generate_typescript: enum_.generate_typescript, diff --git a/crates/cli-support/src/wit/nonstandard.rs b/crates/cli-support/src/wit/nonstandard.rs index ac0f6d6eb6b..e43a49988df 100644 --- a/crates/cli-support/src/wit/nonstandard.rs +++ b/crates/cli-support/src/wit/nonstandard.rs @@ -170,7 +170,7 @@ pub struct AuxEnum { pub comments: String, /// A list of variants with their name, value and comments /// and whether typescript bindings should be generated for each variant - pub variants: Vec<(String, u32, String)>, + pub variants: Vec<(String, i64, String)>, /// Whether typescript bindings should be generated for this enum. pub generate_typescript: bool, } diff --git a/crates/cli-support/src/wit/outgoing.rs b/crates/cli-support/src/wit/outgoing.rs index 7a39af67d2a..2e34dd0d324 100644 --- a/crates/cli-support/src/wit/outgoing.rs +++ b/crates/cli-support/src/wit/outgoing.rs @@ -65,6 +65,20 @@ impl InstructionBuilder<'_, '_> { Descriptor::U32 => self.outgoing_i32(AdapterType::U32), Descriptor::I64 => self.outgoing_i64(AdapterType::I64), Descriptor::U64 => self.outgoing_i64(AdapterType::U64), + Descriptor::I128 => { + self.instruction( + &[AdapterType::I64, AdapterType::I64], + Instruction::WasmToInt128 { signed: true }, + &[AdapterType::S128], + ); + } + Descriptor::U128 => { + self.instruction( + &[AdapterType::I64, AdapterType::I64], + Instruction::WasmToInt128 { signed: false }, + &[AdapterType::U128], + ); + } Descriptor::F32 => { self.get(AdapterType::F32); self.output.push(AdapterType::F32); @@ -267,6 +281,20 @@ impl InstructionBuilder<'_, '_> { Descriptor::U64 => self.option_native(false, ValType::I64), Descriptor::F32 => self.out_option_sentinel64(AdapterType::F32), Descriptor::F64 => self.option_native(true, ValType::F64), + Descriptor::I128 => { + self.instruction( + &[AdapterType::I32, AdapterType::I64, AdapterType::I64], + Instruction::OptionWasmToInt128 { signed: true }, + &[AdapterType::S128.option()], + ); + } + Descriptor::U128 => { + self.instruction( + &[AdapterType::I32, AdapterType::I64, AdapterType::I64], + Instruction::OptionWasmToInt128 { signed: false }, + &[AdapterType::U128.option()], + ); + } Descriptor::Boolean => { self.instruction( &[AdapterType::I32], @@ -360,6 +388,8 @@ impl InstructionBuilder<'_, '_> { | Descriptor::F64 | Descriptor::I64 | Descriptor::U64 + | Descriptor::I128 + | Descriptor::U128 | Descriptor::Boolean | Descriptor::Char | Descriptor::Enum { .. } diff --git a/crates/cli-support/src/wit/standard.rs b/crates/cli-support/src/wit/standard.rs index 8d8c7bc99d7..7feee8986a2 100644 --- a/crates/cli-support/src/wit/standard.rs +++ b/crates/cli-support/src/wit/standard.rs @@ -75,10 +75,12 @@ pub enum AdapterType { S16, S32, S64, + S128, U8, U16, U32, U64, + U128, F32, F64, String, @@ -145,6 +147,18 @@ pub enum Instruction { output: AdapterType, }, + /// Pops a 128-bit integer and pushes 2 Wasm 64-bit ints. + Int128ToWasm, + /// Pops 2 Wasm 64-bit ints and pushes a 128-bit integer. + WasmToInt128 { + signed: bool, + }, + + OptionInt128ToWasm, + OptionWasmToInt128 { + signed: bool, + }, + /// Pops a Wasm `i32` and pushes the enum variant as a string WasmToStringEnum { name: String, diff --git a/crates/cli/tests/reference.rs b/crates/cli/tests/reference.rs index df9c7d27399..190df67b541 100644 --- a/crates/cli/tests/reference.rs +++ b/crates/cli/tests/reference.rs @@ -9,6 +9,11 @@ //! compilation. Use `BLESS=1` in the environment to automatically update all //! tests. //! +//! Note: Tests are run sequentially. In CI, tests are run ordered by name and +//! all tests will be run to show all errors. Outside of CI, recently modified +//! tests are run first and the runner will stop on the first failure. This is +//! done to make it faster to iterate on tests. +//! //! ## Dependencies //! //! By default, tests only have access to the `wasm-bindgen` and @@ -29,6 +34,24 @@ //! //! Multiple dependencies can be declared in a single test file using multiple //! `DEPENDENCY:` comments. +//! +//! ## Custom CLI flags +//! +//! By default, tests will use the `bundler` target. Custom CLI flags can be +//! passed to the `wasm-bindgen` CLI by declaring them in a comment at the top +//! of the test file. For example: +//! +//! ```rust +//! // FLAGS: --target=web --reference-types +//! ``` +//! +//! Multiple comments can be used to run the test multiple times with different +//! flags. +//! +//! ```rust +//! // FLAGS: --target=web +//! // FLAGS: --target=nodejs +//! ``` use anyhow::{bail, Result}; use assert_cmd::prelude::*; @@ -42,7 +65,7 @@ fn main() -> Result<()> { let filter = env::args().nth(1); let mut tests = Vec::new(); - let dir = env::current_dir()?.join("tests/reference"); + let dir = repo_root().join("crates/cli/tests/reference"); for entry in dir.read_dir()? { let path = entry?.path(); if path.extension().and_then(|s| s.to_str()) != Some("rs") { @@ -57,15 +80,32 @@ fn main() -> Result<()> { } tests.sort(); - let errs = tests - .iter() - .filter_map(|t| runtest(t).err().map(|e| (t, e))) - .collect::>(); + let is_ci = env::var("CI").is_ok(); + if !is_ci { + // sort test files by when they were last modified, so that we run the most + // recently modified tests first. This just makes iterating on tests a bit + // easier. + tests.sort_by_cached_key(|p| fs::metadata(p).unwrap().modified().unwrap()); + tests.reverse(); + } + + let mut errs_iter = tests.iter().filter_map(|t| { + println!(" {}", t.file_name().unwrap().to_string_lossy()); + runtest(t).err().map(|e| (t, e)) + }); - if errs.is_empty() { + let Some(first_error) = errs_iter.next() else { println!("{} tests passed", tests.len()); return Ok(()); + }; + + let mut errs = vec![first_error]; + if is_ci { + // one error should be enough for local testing to ensure fast iteration + // only find all errors in CI + errs.extend(errs_iter); } + eprintln!("failed tests:\n"); for (test, err) in errs { eprintln!("{} failure\n{}", test.display(), tab(&format!("{:?}", err))); @@ -79,6 +119,16 @@ fn runtest(test: &Path) -> Result<()> { let root = repo_root(); let root = root.display(); + // parse target declarations + let mut all_flags: Vec<_> = contents + .lines() + .filter_map(|l| l.strip_prefix("// FLAGS: ")) + .map(|l| l.trim()) + .collect(); + if all_flags.is_empty() { + all_flags.push(""); + } + // parse additional dependency declarations let dependencies = contents .lines() @@ -122,25 +172,58 @@ fn runtest(test: &Path) -> Result<()> { .join("debug") .join("reference_test.wasm"); - let mut bindgen = Command::cargo_bin("wasm-bindgen")?; - bindgen - .arg("--out-dir") - .arg(td.path()) - .arg(&wasm) - .arg("--remove-producers-section"); - if contents.contains("// enable-externref") { - bindgen.env("WASM_BINDGEN_EXTERNREF", "1"); - } - exec(&mut bindgen)?; + for (flags_index, &flags) in all_flags.iter().enumerate() { + // extract the target from the flags + let target = flags + .split_whitespace() + .find_map(|f| f.strip_prefix("--target=")) + .unwrap_or("bundler"); + + let out_dir = &td.path().join(target); + fs::create_dir(out_dir)?; + + let mut bindgen = Command::cargo_bin("wasm-bindgen")?; + bindgen + .arg("--out-dir") + .arg(out_dir) + .arg(&wasm) + .arg("--remove-producers-section"); + for flag in flags.split_whitespace() { + bindgen.arg(flag); + } + if contents.contains("// enable-externref") { + bindgen.env("WASM_BINDGEN_EXTERNREF", "1"); + } + exec(&mut bindgen)?; - if !contents.contains("async") { - let js = fs::read_to_string(td.path().join("reference_test_bg.js"))?; - assert_same(&js, &test.with_extension("js"))?; - let wat = sanitize_wasm(&td.path().join("reference_test_bg.wasm"))?; - assert_same(&wat, &test.with_extension("wat"))?; + // suffix the file name with the target + let test = if all_flags.len() > 1 { + let base_file_name = format!( + "{}-{}.rs", + test.file_stem().unwrap().to_string_lossy(), + flags_index + ); + test.with_file_name(base_file_name) + } else { + test.to_owned() + }; + + // bundler uses a different main JS file, because its + // reference_test.js just imports the reference_test_bg.js + let main_js_file = match target { + "bundler" => "reference_test_bg.js", + _ => "reference_test.js", + }; + + if !contents.contains("async") { + let js = fs::read_to_string(out_dir.join(main_js_file))?; + assert_same(&js, &test.with_extension("js"))?; + let wat = sanitize_wasm(&out_dir.join("reference_test_bg.wasm"))?; + assert_same(&wat, &test.with_extension("wat"))?; + } + let d_ts = fs::read_to_string(out_dir.join("reference_test.d.ts"))?; + assert_same(&d_ts, &test.with_extension("d.ts"))?; } - let d_ts = fs::read_to_string(td.path().join("reference_test.d.ts"))?; - assert_same(&d_ts, &test.with_extension("d.ts"))?; Ok(()) } @@ -224,19 +307,15 @@ fn diff(a: &str, b: &str) -> Result<()> { } fn target_dir() -> PathBuf { - let mut dir = env::current_exe().unwrap(); - dir.pop(); // current exe - if dir.ends_with("deps") { - dir.pop(); - } - dir.pop(); // debug and/or release - dir + repo_root().join("target/tests/reference") } fn repo_root() -> PathBuf { let mut repo_root = env::current_dir().unwrap(); - repo_root.pop(); // remove 'cli' - repo_root.pop(); // remove 'crates' + if repo_root.file_name() == Some("cli".as_ref()) { + repo_root.pop(); // remove 'cli' + repo_root.pop(); // remove 'crates' + } repo_root } diff --git a/crates/cli/tests/reference/enums.d.ts b/crates/cli/tests/reference/enums.d.ts index 1c830d483f9..da84bbb7740 100644 --- a/crates/cli/tests/reference/enums.d.ts +++ b/crates/cli/tests/reference/enums.d.ts @@ -4,6 +4,7 @@ export function enum_echo(color: Color): Color; export function option_enum_echo(color?: Color): Color | undefined; export function get_name(color: Color): ColorName; export function option_string_enum_echo(color?: ColorName): ColorName | undefined; +export function option_order(order?: Ordering): Ordering | undefined; /** * A color. */ @@ -27,6 +28,14 @@ export enum ImplicitDiscriminant { C = 42, D = 43, } +/** + * A C-style enum with negative discriminants. + */ +export enum Ordering { + Less = -1, + Equal = 0, + Greater = 1, +} /** * The name of a color. */ diff --git a/crates/cli/tests/reference/enums.js b/crates/cli/tests/reference/enums.js index a425ef2874f..76feae96525 100644 --- a/crates/cli/tests/reference/enums.js +++ b/crates/cli/tests/reference/enums.js @@ -62,6 +62,15 @@ export function option_string_enum_echo(color) { return __wbindgen_enum_ColorName[ret]; } +/** + * @param {Ordering | undefined} [order] + * @returns {Ordering | undefined} + */ +export function option_order(order) { + const ret = wasm.option_order(isLikeNone(order) ? 2 : order); + return ret === 2 ? undefined : ret; +} + /** * A color. * @enum {0 | 1 | 2} @@ -89,6 +98,15 @@ export const ImplicitDiscriminant = Object.freeze({ C: 42, "42": "C", D: 43, "43": "D", }); +/** + * A C-style enum with negative discriminants. + * @enum {-1 | 0 | 1} + */ +export const Ordering = Object.freeze({ + Less: -1, "-1": "Less", + Equal: 0, "0": "Equal", + Greater: 1, "1": "Greater", +}); const __wbindgen_enum_ColorName = ["green", "yellow", "red"]; diff --git a/crates/cli/tests/reference/enums.rs b/crates/cli/tests/reference/enums.rs index 480dba7ce89..553d63d9a4d 100644 --- a/crates/cli/tests/reference/enums.rs +++ b/crates/cli/tests/reference/enums.rs @@ -65,3 +65,16 @@ pub enum ImplicitDiscriminant { C = 42, D, } + +/// A C-style enum with negative discriminants. +#[wasm_bindgen] +pub enum Ordering { + Less = -1, + Equal = 0, + Greater = 1, +} + +#[wasm_bindgen] +pub fn option_order(order: Option) -> Option { + order +} diff --git a/crates/cli/tests/reference/enums.wat b/crates/cli/tests/reference/enums.wat index cc9bb74b586..dced57440b0 100644 --- a/crates/cli/tests/reference/enums.wat +++ b/crates/cli/tests/reference/enums.wat @@ -4,12 +4,14 @@ (func $option_enum_echo (;1;) (type 0) (param i32) (result i32)) (func $get_name (;2;) (type 0) (param i32) (result i32)) (func $option_string_enum_echo (;3;) (type 0) (param i32) (result i32)) + (func $option_order (;4;) (type 0) (param i32) (result i32)) (memory (;0;) 17) (export "memory" (memory 0)) (export "enum_echo" (func $enum_echo)) (export "option_enum_echo" (func $option_enum_echo)) (export "get_name" (func $get_name)) (export "option_string_enum_echo" (func $option_string_enum_echo)) + (export "option_order" (func $option_order)) (@custom "target_features" (after code) "\04+\0amultivalue+\0fmutable-globals+\0freference-types+\08sign-ext") ) diff --git a/crates/cli/tests/reference/import-catch.js b/crates/cli/tests/reference/import-catch.js deleted file mode 100644 index 3f6f772e0ae..00000000000 --- a/crates/cli/tests/reference/import-catch.js +++ /dev/null @@ -1,54 +0,0 @@ -let wasm; -export function __wbg_set_wasm(val) { - wasm = val; -} - - -const heap = new Array(128).fill(undefined); - -heap.push(undefined, null, true, false); - -let heap_next = heap.length; - -function addHeapObject(obj) { - if (heap_next === heap.length) heap.push(heap.length + 1); - const idx = heap_next; - heap_next = heap[idx]; - - heap[idx] = obj; - return idx; -} - -function handleError(f, args) { - try { - return f.apply(this, args); - } catch (e) { - wasm.__wbindgen_exn_store(addHeapObject(e)); - } -} - -function getObject(idx) { return heap[idx]; } - -function dropObject(idx) { - if (idx < 132) return; - heap[idx] = heap_next; - heap_next = idx; -} - -function takeObject(idx) { - const ret = getObject(idx); - dropObject(idx); - return ret; -} - -export function exported() { - const ret = wasm.exported(); - if (ret[1]) { - throw takeObject(ret[0]); - } -} - -export function __wbg_foo_95fe1a04017077db() { return handleError(function () { - foo(); -}, arguments) }; - diff --git a/crates/cli/tests/reference/import-catch.rs b/crates/cli/tests/reference/import-catch.rs deleted file mode 100644 index b6a6e58fbf9..00000000000 --- a/crates/cli/tests/reference/import-catch.rs +++ /dev/null @@ -1,12 +0,0 @@ -use wasm_bindgen::prelude::*; - -#[wasm_bindgen] -extern "C" { - #[wasm_bindgen(catch)] - fn foo() -> Result<(), JsValue>; -} - -#[wasm_bindgen] -pub fn exported() -> Result<(), JsValue> { - foo() -} diff --git a/crates/cli/tests/reference/import-catch.d.ts b/crates/cli/tests/reference/import.d.ts similarity index 100% rename from crates/cli/tests/reference/import-catch.d.ts rename to crates/cli/tests/reference/import.d.ts diff --git a/crates/cli/tests/reference/import.js b/crates/cli/tests/reference/import.js new file mode 100644 index 00000000000..229dd246115 --- /dev/null +++ b/crates/cli/tests/reference/import.js @@ -0,0 +1,95 @@ +let wasm; +export function __wbg_set_wasm(val) { + wasm = val; +} + + +const heap = new Array(128).fill(undefined); + +heap.push(undefined, null, true, false); + +let heap_next = heap.length; + +function addHeapObject(obj) { + if (heap_next === heap.length) heap.push(heap.length + 1); + const idx = heap_next; + heap_next = heap[idx]; + + heap[idx] = obj; + return idx; +} + +function handleError(f, args) { + try { + return f.apply(this, args); + } catch (e) { + wasm.__wbindgen_exn_store(addHeapObject(e)); + } +} + +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)); +} + +function getObject(idx) { return heap[idx]; } + +function dropObject(idx) { + if (idx < 132) return; + heap[idx] = heap_next; + heap_next = idx; +} + +function takeObject(idx) { + const ret = getObject(idx); + dropObject(idx); + return ret; +} + +export function exported() { + const ret = wasm.exported(); + if (ret[1]) { + throw takeObject(ret[0]); + } +} + +export function __wbg_add_dd5307a7ca6818d5(arg0, arg1) { + const ret = add(arg0, arg1); + return ret; +}; + +export function __wbg_barfromfoo_d097f3ec35aab47c() { + bar_from_foo(); +}; + +export function __wbg_catchme_a7bca7f3d5a5f319() { return handleError(function () { + catch_me(); +}, arguments) }; + +export function __wbg_nocatch_62552fa42a58590b() { + no_catch(); +}; + +export function __wbg_reload_90d82b22b83c1d99() { + window.location.reload(); +}; + +export function __wbg_write_d258674ff6f0ea8d(arg0, arg1) { + window.document.write(getStringFromWasm0(arg0, arg1)); +}; + diff --git a/crates/cli/tests/reference/import.rs b/crates/cli/tests/reference/import.rs new file mode 100644 index 00000000000..a7bea1953ed --- /dev/null +++ b/crates/cli/tests/reference/import.rs @@ -0,0 +1,34 @@ +use wasm_bindgen::prelude::*; + +#[wasm_bindgen] +extern "C" { + // Both `catch_me` and `no_catch` should be defined in the JS and invoke + // their respective JS function inside a JS shim function. This is + // important, because these 2 function may not be defined when the WASM + // module is instantiated. + #[wasm_bindgen(catch)] + fn catch_me() -> Result<(), JsValue>; + fn no_catch(); + + // Reload needs to be passed the right `this` parameter in JS. + #[wasm_bindgen(js_namespace = ["window", "location"])] + fn reload(); + #[wasm_bindgen(js_namespace = ["window", "document"])] + fn write(s: &str); + + // module import + #[wasm_bindgen(module = "./foo.js")] + fn bar_from_foo(); + #[wasm_bindgen(inline_js = "export function add(a,b) { return a + b; }")] + fn add(a: f64, b: f64) -> f64; +} + +#[wasm_bindgen] +pub fn exported() -> Result<(), JsValue> { + bar_from_foo(); + let _ = add(1.0, 2.0); + reload(); + write(""); + no_catch(); + catch_me() +} diff --git a/crates/cli/tests/reference/import-catch.wat b/crates/cli/tests/reference/import.wat similarity index 100% rename from crates/cli/tests/reference/import-catch.wat rename to crates/cli/tests/reference/import.wat diff --git a/crates/cli/tests/reference/int128.d.ts b/crates/cli/tests/reference/int128.d.ts new file mode 100644 index 00000000000..183cef4758a --- /dev/null +++ b/crates/cli/tests/reference/int128.d.ts @@ -0,0 +1,7 @@ +/* tslint:disable */ +/* eslint-disable */ +export function echo_i128(a: bigint): bigint; +export function echo_u128(a: bigint): bigint; +export function echo_option_i128(a?: bigint): bigint | undefined; +export function echo_option_u128(a?: bigint): bigint | undefined; +export function throw_i128(): bigint; diff --git a/crates/cli/tests/reference/int128.js b/crates/cli/tests/reference/int128.js new file mode 100644 index 00000000000..04b865d7fb1 --- /dev/null +++ b/crates/cli/tests/reference/int128.js @@ -0,0 +1,74 @@ +let wasm; +export function __wbg_set_wasm(val) { + wasm = val; +} + +/** + * @param {bigint} a + * @returns {bigint} + */ +export function echo_i128(a) { + const ret = wasm.echo_i128(a, a >> BigInt(64)); + return (BigInt.asUintN(64, ret[0]) | (ret[1] << BigInt(64))); +} + +/** + * @param {bigint} a + * @returns {bigint} + */ +export function echo_u128(a) { + const ret = wasm.echo_u128(a, a >> BigInt(64)); + return (BigInt.asUintN(64, ret[0]) | (BigInt.asUintN(64, ret[1]) << BigInt(64))); +} + +function isLikeNone(x) { + return x === undefined || x === null; +} +/** + * @param {bigint | undefined} [a] + * @returns {bigint | undefined} + */ +export function echo_option_i128(a) { + const ret = wasm.echo_option_i128(!isLikeNone(a), isLikeNone(a) ? BigInt(0) : a, isLikeNone(a) ? BigInt(0) : a >> BigInt(64)); + return ret[0] === 0 ? undefined : (BigInt.asUintN(64, ret[1]) | (ret[2] << BigInt(64))); +} + +/** + * @param {bigint | undefined} [a] + * @returns {bigint | undefined} + */ +export function echo_option_u128(a) { + const ret = wasm.echo_option_u128(!isLikeNone(a), isLikeNone(a) ? BigInt(0) : a, isLikeNone(a) ? BigInt(0) : a >> BigInt(64)); + return ret[0] === 0 ? undefined : (BigInt.asUintN(64, ret[1]) | (BigInt.asUintN(64, ret[2]) << BigInt(64))); +} + +const heap = new Array(128).fill(undefined); + +heap.push(undefined, null, true, false); + +function getObject(idx) { return heap[idx]; } + +let heap_next = heap.length; + +function dropObject(idx) { + if (idx < 132) return; + heap[idx] = heap_next; + heap_next = idx; +} + +function takeObject(idx) { + const ret = getObject(idx); + dropObject(idx); + return ret; +} +/** + * @returns {bigint} + */ +export function throw_i128() { + const ret = wasm.throw_i128(); + if (ret[3]) { + throw takeObject(ret[2]); + } + return (BigInt.asUintN(64, ret[0]) | (ret[1] << BigInt(64))); +} + diff --git a/crates/cli/tests/reference/int128.rs b/crates/cli/tests/reference/int128.rs new file mode 100644 index 00000000000..350faa34a43 --- /dev/null +++ b/crates/cli/tests/reference/int128.rs @@ -0,0 +1,28 @@ +use wasm_bindgen::prelude::*; + +#[wasm_bindgen] +pub fn echo_i128(a: i128) -> i128 { + a +} +#[wasm_bindgen] +pub fn echo_u128(a: u128) -> u128 { + a +} + +#[wasm_bindgen] +pub fn echo_option_i128(a: Option) -> Option { + a +} +#[wasm_bindgen] +pub fn echo_option_u128(a: Option) -> Option { + a +} + +#[wasm_bindgen] +pub fn throw_i128() -> Result { + Ok(0_i128) +} +// #[wasm_bindgen] +// pub fn throw_option_i128() -> Result, JsError> { +// Ok(None) +// } diff --git a/crates/cli/tests/reference/int128.wat b/crates/cli/tests/reference/int128.wat new file mode 100644 index 00000000000..61c7eab3ea3 --- /dev/null +++ b/crates/cli/tests/reference/int128.wat @@ -0,0 +1,19 @@ +(module $reference_test.wasm + (type (;0;) (func (result i64 i64 i32 i32))) + (type (;1;) (func (param i32 i64 i64) (result i32 i64 i64))) + (type (;2;) (func (param i64 i64) (result i64 i64))) + (func $"echo_option_i128 multivalue shim" (;0;) (type 1) (param i32 i64 i64) (result i32 i64 i64)) + (func $"echo_option_u128 multivalue shim" (;1;) (type 1) (param i32 i64 i64) (result i32 i64 i64)) + (func $"throw_i128 multivalue shim" (;2;) (type 0) (result i64 i64 i32 i32)) + (func $"echo_i128 multivalue shim" (;3;) (type 2) (param i64 i64) (result i64 i64)) + (func $"echo_u128 multivalue shim" (;4;) (type 2) (param i64 i64) (result i64 i64)) + (memory (;0;) 17) + (export "memory" (memory 0)) + (export "echo_i128" (func $"echo_i128 multivalue shim")) + (export "echo_u128" (func $"echo_u128 multivalue shim")) + (export "echo_option_i128" (func $"echo_option_i128 multivalue shim")) + (export "echo_option_u128" (func $"echo_option_u128 multivalue shim")) + (export "throw_i128" (func $"throw_i128 multivalue shim")) + (@custom "target_features" (after code) "\04+\0amultivalue+\0fmutable-globals+\0freference-types+\08sign-ext") +) + diff --git a/crates/cli/tests/reference/targets-0.d.ts b/crates/cli/tests/reference/targets-0.d.ts new file mode 100644 index 00000000000..a01199843fc --- /dev/null +++ b/crates/cli/tests/reference/targets-0.d.ts @@ -0,0 +1,3 @@ +/* tslint:disable */ +/* eslint-disable */ +export function add_that_might_fail(a: number, b: number): number; diff --git a/crates/cli/tests/reference/targets-0.js b/crates/cli/tests/reference/targets-0.js new file mode 100644 index 00000000000..5e2b7e8959b --- /dev/null +++ b/crates/cli/tests/reference/targets-0.js @@ -0,0 +1,20 @@ +let wasm; +export function __wbg_set_wasm(val) { + wasm = val; +} + +/** + * @param {number} a + * @param {number} b + * @returns {number} + */ +export function add_that_might_fail(a, b) { + const ret = wasm.add_that_might_fail(a, b); + return ret >>> 0; +} + +export function __wbg_random_5d40be260a2cfbac() { + const ret = Math.random(); + return ret; +}; + diff --git a/crates/cli/tests/reference/targets-0.wat b/crates/cli/tests/reference/targets-0.wat new file mode 100644 index 00000000000..b8d3e4b8c61 --- /dev/null +++ b/crates/cli/tests/reference/targets-0.wat @@ -0,0 +1,9 @@ +(module $reference_test.wasm + (type (;0;) (func (param i32 i32) (result i32))) + (func $add_that_might_fail (;0;) (type 0) (param i32 i32) (result i32)) + (memory (;0;) 17) + (export "memory" (memory 0)) + (export "add_that_might_fail" (func $add_that_might_fail)) + (@custom "target_features" (after code) "\04+\0amultivalue+\0fmutable-globals+\0freference-types+\08sign-ext") +) + diff --git a/crates/cli/tests/reference/targets-1.d.ts b/crates/cli/tests/reference/targets-1.d.ts new file mode 100644 index 00000000000..67e807819b3 --- /dev/null +++ b/crates/cli/tests/reference/targets-1.d.ts @@ -0,0 +1,33 @@ +/* tslint:disable */ +/* eslint-disable */ +export function add_that_might_fail(a: number, b: number): number; + +export type InitInput = RequestInfo | URL | Response | BufferSource | WebAssembly.Module; + +export interface InitOutput { + readonly memory: WebAssembly.Memory; + readonly add_that_might_fail: (a: number, b: number) => number; + readonly __wbindgen_export_0: WebAssembly.Table; + readonly __wbindgen_start: () => void; +} + +export type SyncInitInput = BufferSource | WebAssembly.Module; +/** +* Instantiates the given `module`, which can either be bytes or +* a precompiled `WebAssembly.Module`. +* +* @param {{ module: SyncInitInput }} module - Passing `SyncInitInput` directly is deprecated. +* +* @returns {InitOutput} +*/ +export function initSync(module: { module: SyncInitInput } | SyncInitInput): InitOutput; + +/** +* If `module_or_path` is {RequestInfo} or {URL}, makes a request and +* for everything else, calls `WebAssembly.instantiate` directly. +* +* @param {{ module_or_path: InitInput | Promise }} module_or_path - Passing `InitInput` directly is deprecated. +* +* @returns {Promise} +*/ +export default function __wbg_init (module_or_path?: { module_or_path: InitInput | Promise } | InitInput | Promise): Promise; diff --git a/crates/cli/tests/reference/targets-1.js b/crates/cli/tests/reference/targets-1.js new file mode 100644 index 00000000000..69dc3f36f14 --- /dev/null +++ b/crates/cli/tests/reference/targets-1.js @@ -0,0 +1,132 @@ +let wasm; + +/** + * @param {number} a + * @param {number} b + * @returns {number} + */ +export function add_that_might_fail(a, b) { + const ret = wasm.add_that_might_fail(a, b); + return ret >>> 0; +} + +async function __wbg_load(module, imports) { + if (typeof Response === 'function' && module instanceof Response) { + if (typeof WebAssembly.instantiateStreaming === 'function') { + try { + return await WebAssembly.instantiateStreaming(module, imports); + + } catch (e) { + if (module.headers.get('Content-Type') != 'application/wasm') { + console.warn("`WebAssembly.instantiateStreaming` failed because your server does not serve Wasm with `application/wasm` MIME type. Falling back to `WebAssembly.instantiate` which is slower. Original error:\n", e); + + } else { + throw e; + } + } + } + + const bytes = await module.arrayBuffer(); + return await WebAssembly.instantiate(bytes, imports); + + } else { + const instance = await WebAssembly.instantiate(module, imports); + + if (instance instanceof WebAssembly.Instance) { + return { instance, module }; + + } else { + return instance; + } + } +} + +function __wbg_get_imports() { + const imports = {}; + imports.wbg = {}; + imports.wbg.__wbg_random_5d40be260a2cfbac = function() { + const ret = Math.random(); + return ret; + }; + imports.wbg.__wbindgen_init_externref_table = function() { + 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); + ; + }; + + return imports; +} + +function __wbg_init_memory(imports, memory) { + +} + +function __wbg_finalize_init(instance, module) { + wasm = instance.exports; + __wbg_init.__wbindgen_wasm_module = module; + + + wasm.__wbindgen_start(); + return wasm; +} + +function initSync(module) { + if (wasm !== undefined) return wasm; + + + if (typeof module !== 'undefined') { + if (Object.getPrototypeOf(module) === Object.prototype) { + ({module} = module) + } else { + console.warn('using deprecated parameters for `initSync()`; pass a single object instead') + } + } + + const imports = __wbg_get_imports(); + + __wbg_init_memory(imports); + + if (!(module instanceof WebAssembly.Module)) { + module = new WebAssembly.Module(module); + } + + const instance = new WebAssembly.Instance(module, imports); + + return __wbg_finalize_init(instance, module); +} + +async function __wbg_init(module_or_path) { + if (wasm !== undefined) return wasm; + + + if (typeof module_or_path !== 'undefined') { + if (Object.getPrototypeOf(module_or_path) === Object.prototype) { + ({module_or_path} = module_or_path) + } else { + console.warn('using deprecated parameters for the initialization function; pass a single object instead') + } + } + + if (typeof module_or_path === 'undefined') { + module_or_path = new URL('reference_test_bg.wasm', import.meta.url); + } + const imports = __wbg_get_imports(); + + if (typeof module_or_path === 'string' || (typeof Request === 'function' && module_or_path instanceof Request) || (typeof URL === 'function' && module_or_path instanceof URL)) { + module_or_path = fetch(module_or_path); + } + + __wbg_init_memory(imports); + + const { instance, module } = await __wbg_load(await module_or_path, imports); + + return __wbg_finalize_init(instance, module); +} + +export { initSync }; +export default __wbg_init; diff --git a/crates/cli/tests/reference/targets-1.wat b/crates/cli/tests/reference/targets-1.wat new file mode 100644 index 00000000000..db2cb5a6b2e --- /dev/null +++ b/crates/cli/tests/reference/targets-1.wat @@ -0,0 +1,14 @@ +(module $reference_test.wasm + (type (;0;) (func)) + (type (;1;) (func (param i32 i32) (result i32))) + (import "wbg" "__wbindgen_init_externref_table" (func (;0;) (type 0))) + (func $add_that_might_fail (;1;) (type 1) (param i32 i32) (result i32)) + (table (;0;) 128 externref) + (memory (;0;) 17) + (export "memory" (memory 0)) + (export "add_that_might_fail" (func $add_that_might_fail)) + (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/targets-2.d.ts b/crates/cli/tests/reference/targets-2.d.ts new file mode 100644 index 00000000000..9e1c41bd645 --- /dev/null +++ b/crates/cli/tests/reference/targets-2.d.ts @@ -0,0 +1,25 @@ +declare namespace wasm_bindgen { + /* tslint:disable */ + /* eslint-disable */ + export function add_that_might_fail(a: number, b: number): number; + +} + +declare type InitInput = RequestInfo | URL | Response | BufferSource | WebAssembly.Module; + +declare interface InitOutput { + readonly memory: WebAssembly.Memory; + readonly add_that_might_fail: (a: number, b: number) => number; + readonly __wbindgen_export_0: WebAssembly.Table; + readonly __wbindgen_start: () => void; +} + +/** +* If `module_or_path` is {RequestInfo} or {URL}, makes a request and +* for everything else, calls `WebAssembly.instantiate` directly. +* +* @param {{ module_or_path: InitInput | Promise }} module_or_path - Passing `InitInput` directly is deprecated. +* +* @returns {Promise} +*/ +declare function wasm_bindgen (module_or_path?: { module_or_path: InitInput | Promise } | InitInput | Promise): Promise; diff --git a/crates/cli/tests/reference/targets-2.js b/crates/cli/tests/reference/targets-2.js new file mode 100644 index 00000000000..f1d867286d5 --- /dev/null +++ b/crates/cli/tests/reference/targets-2.js @@ -0,0 +1,139 @@ +let wasm_bindgen; +(function() { + const __exports = {}; + let script_src; + if (typeof document !== 'undefined' && document.currentScript !== null) { + script_src = new URL(document.currentScript.src, location.href).toString(); + } + let wasm = undefined; + /** + * @param {number} a + * @param {number} b + * @returns {number} + */ + __exports.add_that_might_fail = function(a, b) { + const ret = wasm.add_that_might_fail(a, b); + return ret >>> 0; + }; + + async function __wbg_load(module, imports) { + if (typeof Response === 'function' && module instanceof Response) { + if (typeof WebAssembly.instantiateStreaming === 'function') { + try { + return await WebAssembly.instantiateStreaming(module, imports); + + } catch (e) { + if (module.headers.get('Content-Type') != 'application/wasm') { + console.warn("`WebAssembly.instantiateStreaming` failed because your server does not serve Wasm with `application/wasm` MIME type. Falling back to `WebAssembly.instantiate` which is slower. Original error:\n", e); + + } else { + throw e; + } + } + } + + const bytes = await module.arrayBuffer(); + return await WebAssembly.instantiate(bytes, imports); + + } else { + const instance = await WebAssembly.instantiate(module, imports); + + if (instance instanceof WebAssembly.Instance) { + return { instance, module }; + + } else { + return instance; + } + } + } + + function __wbg_get_imports() { + const imports = {}; + imports.wbg = {}; + imports.wbg.__wbg_random_5d40be260a2cfbac = function() { + const ret = Math.random(); + return ret; + }; + imports.wbg.__wbindgen_init_externref_table = function() { + 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); + ; + }; + + return imports; + } + + function __wbg_init_memory(imports, memory) { + + } + + function __wbg_finalize_init(instance, module) { + wasm = instance.exports; + __wbg_init.__wbindgen_wasm_module = module; + + + wasm.__wbindgen_start(); + return wasm; + } + + function initSync(module) { + if (wasm !== undefined) return wasm; + + + if (typeof module !== 'undefined') { + if (Object.getPrototypeOf(module) === Object.prototype) { + ({module} = module) + } else { + console.warn('using deprecated parameters for `initSync()`; pass a single object instead') + } + } + + const imports = __wbg_get_imports(); + + __wbg_init_memory(imports); + + if (!(module instanceof WebAssembly.Module)) { + module = new WebAssembly.Module(module); + } + + const instance = new WebAssembly.Instance(module, imports); + + return __wbg_finalize_init(instance, module); + } + + async function __wbg_init(module_or_path) { + if (wasm !== undefined) return wasm; + + + if (typeof module_or_path !== 'undefined') { + if (Object.getPrototypeOf(module_or_path) === Object.prototype) { + ({module_or_path} = module_or_path) + } else { + console.warn('using deprecated parameters for the initialization function; pass a single object instead') + } + } + + if (typeof module_or_path === 'undefined' && typeof script_src !== 'undefined') { + module_or_path = script_src.replace(/\.js$/, '_bg.wasm'); + } + const imports = __wbg_get_imports(); + + if (typeof module_or_path === 'string' || (typeof Request === 'function' && module_or_path instanceof Request) || (typeof URL === 'function' && module_or_path instanceof URL)) { + module_or_path = fetch(module_or_path); + } + + __wbg_init_memory(imports); + + const { instance, module } = await __wbg_load(await module_or_path, imports); + + return __wbg_finalize_init(instance, module); + } + + wasm_bindgen = Object.assign(__wbg_init, { initSync }, __exports); + +})(); diff --git a/crates/cli/tests/reference/targets-2.wat b/crates/cli/tests/reference/targets-2.wat new file mode 100644 index 00000000000..db2cb5a6b2e --- /dev/null +++ b/crates/cli/tests/reference/targets-2.wat @@ -0,0 +1,14 @@ +(module $reference_test.wasm + (type (;0;) (func)) + (type (;1;) (func (param i32 i32) (result i32))) + (import "wbg" "__wbindgen_init_externref_table" (func (;0;) (type 0))) + (func $add_that_might_fail (;1;) (type 1) (param i32 i32) (result i32)) + (table (;0;) 128 externref) + (memory (;0;) 17) + (export "memory" (memory 0)) + (export "add_that_might_fail" (func $add_that_might_fail)) + (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/targets-3.d.ts b/crates/cli/tests/reference/targets-3.d.ts new file mode 100644 index 00000000000..a01199843fc --- /dev/null +++ b/crates/cli/tests/reference/targets-3.d.ts @@ -0,0 +1,3 @@ +/* tslint:disable */ +/* eslint-disable */ +export function add_that_might_fail(a: number, b: number): number; diff --git a/crates/cli/tests/reference/targets-3.js b/crates/cli/tests/reference/targets-3.js new file mode 100644 index 00000000000..92291a18bb0 --- /dev/null +++ b/crates/cli/tests/reference/targets-3.js @@ -0,0 +1,40 @@ + +let imports = {}; +imports['__wbindgen_placeholder__'] = module.exports; +let wasm; +/** + * @param {number} a + * @param {number} b + * @returns {number} + */ +module.exports.add_that_might_fail = function(a, b) { + const ret = wasm.add_that_might_fail(a, b); + return ret >>> 0; +}; + +module.exports.__wbg_random_5d40be260a2cfbac = function() { + const ret = Math.random(); + return ret; +}; + +module.exports.__wbindgen_init_externref_table = function() { + 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); + ; +}; + +const path = require('path').join(__dirname, 'reference_test_bg.wasm'); +const bytes = require('fs').readFileSync(path); + +const wasmModule = new WebAssembly.Module(bytes); +const wasmInstance = new WebAssembly.Instance(wasmModule, imports); +wasm = wasmInstance.exports; +module.exports.__wasm = wasm; + +wasm.__wbindgen_start(); + diff --git a/crates/cli/tests/reference/targets-3.wat b/crates/cli/tests/reference/targets-3.wat new file mode 100644 index 00000000000..ec8996a0fd3 --- /dev/null +++ b/crates/cli/tests/reference/targets-3.wat @@ -0,0 +1,14 @@ +(module $reference_test.wasm + (type (;0;) (func)) + (type (;1;) (func (param i32 i32) (result i32))) + (import "__wbindgen_placeholder__" "__wbindgen_init_externref_table" (func (;0;) (type 0))) + (func $add_that_might_fail (;1;) (type 1) (param i32 i32) (result i32)) + (table (;0;) 128 externref) + (memory (;0;) 17) + (export "memory" (memory 0)) + (export "add_that_might_fail" (func $add_that_might_fail)) + (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/targets-4.d.ts b/crates/cli/tests/reference/targets-4.d.ts new file mode 100644 index 00000000000..a01199843fc --- /dev/null +++ b/crates/cli/tests/reference/targets-4.d.ts @@ -0,0 +1,3 @@ +/* tslint:disable */ +/* eslint-disable */ +export function add_that_might_fail(a: number, b: number): number; diff --git a/crates/cli/tests/reference/targets-4.js b/crates/cli/tests/reference/targets-4.js new file mode 100644 index 00000000000..9dd2431bb72 --- /dev/null +++ b/crates/cli/tests/reference/targets-4.js @@ -0,0 +1,50 @@ +/** + * @param {number} a + * @param {number} b + * @returns {number} + */ +export function add_that_might_fail(a, b) { + const ret = wasm.add_that_might_fail(a, b); + return ret >>> 0; +} + +const imports = { + __wbindgen_placeholder__: { + __wbg_random_5d40be260a2cfbac: function() { + const ret = Math.random(); + return ret; + }, + __wbindgen_init_externref_table: function() { + 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); + ; + }, + }, + +}; + +const wasm_url = new URL('reference_test_bg.wasm', import.meta.url); +let wasmCode = ''; +switch (wasm_url.protocol) { + case 'file:': + wasmCode = await Deno.readFile(wasm_url); + break + case 'https:': + case 'http:': + wasmCode = await (await fetch(wasm_url)).arrayBuffer(); + break + default: + throw new Error(`Unsupported protocol: ${wasm_url.protocol}`); +} + +const wasmInstance = (await WebAssembly.instantiate(wasmCode, imports)).instance; +const wasm = wasmInstance.exports; +export const __wasm = wasm; + +wasm.__wbindgen_start(); + diff --git a/crates/cli/tests/reference/targets-4.wat b/crates/cli/tests/reference/targets-4.wat new file mode 100644 index 00000000000..ec8996a0fd3 --- /dev/null +++ b/crates/cli/tests/reference/targets-4.wat @@ -0,0 +1,14 @@ +(module $reference_test.wasm + (type (;0;) (func)) + (type (;1;) (func (param i32 i32) (result i32))) + (import "__wbindgen_placeholder__" "__wbindgen_init_externref_table" (func (;0;) (type 0))) + (func $add_that_might_fail (;1;) (type 1) (param i32 i32) (result i32)) + (table (;0;) 128 externref) + (memory (;0;) 17) + (export "memory" (memory 0)) + (export "add_that_might_fail" (func $add_that_might_fail)) + (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/targets.rs b/crates/cli/tests/reference/targets.rs new file mode 100644 index 00000000000..86e22ada3b7 --- /dev/null +++ b/crates/cli/tests/reference/targets.rs @@ -0,0 +1,19 @@ +// FLAGS: --target=bundler +// FLAGS: --target=web +// FLAGS: --target=no-modules +// FLAGS: --target=nodejs +// FLAGS: --target=deno + +use wasm_bindgen::prelude::*; + +#[wasm_bindgen] +extern "C" { + #[wasm_bindgen(js_namespace = Math)] + fn random() -> f64; +} + +#[wasm_bindgen] +pub fn add_that_might_fail(a: u32, b: u32) -> u32 { + assert!(random() > 0.5); + a + b +} diff --git a/crates/macro-support/src/parser.rs b/crates/macro-support/src/parser.rs index a2be7045d05..72bd849d859 100644 --- a/crates/macro-support/src/parser.rs +++ b/crates/macro-support/src/parser.rs @@ -1387,6 +1387,46 @@ fn string_enum( Ok(()) } +/// Represents a possibly negative numeric value as base 10 digits. +struct NumericValue<'a> { + negative: bool, + base10_digits: &'a str, +} +impl<'a> NumericValue<'a> { + fn from_expr(expr: &'a syn::Expr) -> Option { + match get_expr(expr) { + syn::Expr::Lit(syn::ExprLit { + lit: syn::Lit::Int(int_lit), + .. + }) => Some(Self { + negative: false, + base10_digits: int_lit.base10_digits(), + }), + syn::Expr::Unary(syn::ExprUnary { + op: syn::UnOp::Neg(_), + expr, + .. + }) => Self::from_expr(expr).map(|n| n.neg()), + _ => None, + } + } + + fn parse(&self) -> Option { + let mut value = self.base10_digits.parse::().ok()?; + if self.negative { + value = -value; + } + Some(value) + } + + fn neg(self) -> Self { + Self { + negative: !self.negative, + base10_digits: self.base10_digits, + } + } +} + impl<'a> MacroParse<(&'a mut TokenStream, BindgenAttrs)> for syn::ItemEnum { fn macro_parse( self, @@ -1437,54 +1477,73 @@ impl<'a> MacroParse<(&'a mut TokenStream, BindgenAttrs)> for syn::ItemEnum { _ => bail_span!(self, "only public enums are allowed with #[wasm_bindgen]"), } - let mut last_discriminant: Option = None; - let mut discriminate_map: HashMap = HashMap::new(); + // Go through all variants once first to determine whether the enum is + // signed or unsigned. We don't need to actually parse the discriminant + // values yet, we just need to know their sign. The actual parsing is + // done in a second pass. + let signed = self.variants.iter().any(|v| match &v.discriminant { + Some((_, expr)) => NumericValue::from_expr(expr).map_or(false, |n| n.negative), + None => false, + }); + let underlying_min = if signed { i32::MIN as i64 } else { 0 }; + let underlying_max = if signed { + i32::MAX as i64 + } else { + u32::MAX as i64 + }; + + let mut last_discriminant: Option = None; + let mut discriminant_map: HashMap = HashMap::new(); let variants = self .variants .iter() .map(|v| { - let value = match &v.discriminant { - Some((_, expr)) => match get_expr(expr) { - syn::Expr::Lit(syn::ExprLit { - attrs: _, - lit: syn::Lit::Int(int_lit), - }) => match int_lit.base10_digits().parse::() { - Ok(v) => v, - Err(_) => { - bail_span!( - int_lit, - "C-style enums with #[wasm_bindgen] can only support \ - numbers that can be represented as u32" - ); - } - }, - expr => bail_span!( + let value: i64 = match &v.discriminant { + Some((_, expr)) => match NumericValue::from_expr(expr).and_then(|n| n.parse()) { + Some(value) => value, + _ => bail_span!( expr, "C-style enums with #[wasm_bindgen] may only have \ - number literal values", + numeric literal values that fit in a 32-bit integer as discriminants. \ + Expressions or variables are not supported.", ), }, None => { // Use the same algorithm as rustc to determine the next discriminant // https://doc.rust-lang.org/reference/items/enumerations.html#implicit-discriminants - if let Some(last) = last_discriminant { - if let Some(value) = last.checked_add(1) { - value - } else { - bail_span!( - v, - "the discriminants of C-style enums with #[wasm_bindgen] must be representable as u32" - ); - } - } else { - 0 - } + last_discriminant.map_or(0, |last| last + 1) } }; + last_discriminant = Some(value); - if let Some(old) = discriminate_map.insert(value, v) { + // check that the value fits within the underlying type + let underlying = if signed { "i32" } else { "u32" }; + let numbers = if signed { "signed numbers" } else { "unsigned numbers" }; + if value < underlying_min { + bail_span!( + v, + "C-style enums with #[wasm_bindgen] can only support {0} that can be represented by `{2}`, \ + but `{1}` is too small for `{2}`", + numbers, + value, + underlying + ); + } + if value > underlying_max { + bail_span!( + v, + "C-style enums with #[wasm_bindgen] can only support {0} that can be represented by `{2}`, \ + but `{1}` is too large for `{2}`", + numbers, + value, + underlying + ); + } + + // detect duplicate discriminants + if let Some(old) = discriminant_map.insert(value, v) { bail_span!( v, "discriminant value `{}` is already used by {} in this enum", @@ -1496,21 +1555,26 @@ impl<'a> MacroParse<(&'a mut TokenStream, BindgenAttrs)> for syn::ItemEnum { let comments = extract_doc_comments(&v.attrs); Ok(ast::Variant { name: v.ident.clone(), - value, + // due to the above checks, we know that the value fits + // within 32 bits, so this cast doesn't lose any information + value: value as u32, comments, }) }) .collect::, Diagnostic>>()?; - let hole = (0..=u32::MAX) - .find(|v| !discriminate_map.contains_key(v)) - .unwrap(); + // To make all the code handling holes simpler, we only consider + // non-negative holes. This allows us to use `u32` to represent holes. + let hole = (0..=underlying_max) + .find(|v| !discriminant_map.contains_key(v)) + .unwrap() as u32; self.to_tokens(tokens); program.enums.push(ast::Enum { rust_name: self.ident, js_name, + signed, variants, comments, hole, diff --git a/crates/macro/README.md b/crates/macro/README.md index 353829869f2..71e35ab29ff 100644 --- a/crates/macro/README.md +++ b/crates/macro/README.md @@ -17,12 +17,10 @@ To add a test: * Create `ui-tests/my-awesome-test.rs` * Write an invalid `#[wasm_bindgen]` invocation, testing the error you're generating -* Execute `cargo test -p ui-tests`, the test will fail +* Execute `cargo test -p wasm-bindgen-macro --test ui`, the test will fail * From within the `ui-tests` folder, execute `./update-all-references.sh`. This should create a `my-awesome-test.stderr` file. -* Inspect `my-awesome-test.stderr` to make sure it looks ok -* Rerun `cargo test -p ui-tests` and your tests should pass! -Testing here is a work in progress, see -[#601](https://github.com/rustwasm/wasm-bindgen/issues/601) for more -information. + OR if you are on Windows, set the `TRYBUILD=overwrite` environment variable (this is done as `$env:TRYBUILD="overwrite"` [in powershell](https://stackoverflow.com/a/1333717/7595472)) and run the command again. +* Inspect `my-awesome-test.stderr` to make sure it looks ok +* Rerun `cargo test -p wasm-bindgen-macro --test ui` and your tests should pass! diff --git a/crates/macro/ui-tests/invalid-enums.rs b/crates/macro/ui-tests/invalid-enums.rs index e0241f1eedb..571a39fb7c9 100644 --- a/crates/macro/ui-tests/invalid-enums.rs +++ b/crates/macro/ui-tests/invalid-enums.rs @@ -46,13 +46,13 @@ pub enum H { #[wasm_bindgen] pub enum I { A = 4294967294, // = u32::MAX - 1 - B, // would be u32::MAX - C, // would be u32::MAX + 1 + B, // = u32::MAX + C, // = u32::MAX + 1 } #[wasm_bindgen] pub enum J { - A, // = 0 + A, // = 0 B = 0, // collision } @@ -63,4 +63,16 @@ pub enum K { C, // = 3 -> collision } +#[wasm_bindgen] +pub enum L { + A = -2147483648, // i32::MIN + B = -2147483649, // i32::MIN - 1 +} + +#[wasm_bindgen] +pub enum M { + A = -1, + B = 2147483648, // i32::MAX + 1 +} + fn main() {} diff --git a/crates/macro/ui-tests/invalid-enums.stderr b/crates/macro/ui-tests/invalid-enums.stderr index e2e5b1a6c04..270de3b54b1 100644 --- a/crates/macro/ui-tests/invalid-enums.stderr +++ b/crates/macro/ui-tests/invalid-enums.stderr @@ -10,17 +10,17 @@ error: enum variants with associated data are not supported with #[wasm_bindgen] 8 | D(u32), | ^^^^^ -error: C-style enums with #[wasm_bindgen] may only have number literal values +error: C-style enums with #[wasm_bindgen] may only have numeric literal values that fit in a 32-bit integer as discriminants. Expressions or variables are not supported. --> ui-tests/invalid-enums.rs:13:9 | 13 | X = 1 + 3, | ^^^^^ -error: C-style enums with #[wasm_bindgen] can only support numbers that can be represented as u32 - --> ui-tests/invalid-enums.rs:18:9 +error: C-style enums with #[wasm_bindgen] can only support unsigned numbers that can be represented by `u32`, but `4294967296` is too large for `u32` + --> ui-tests/invalid-enums.rs:18:5 | 18 | X = 4294967296, - | ^^^^^^^^^^ + | ^^^^^^^^^^^^^^ error: enums with #[wasm_bindgen] cannot mix string and non-string values --> ui-tests/invalid-enums.rs:23:9 @@ -46,10 +46,10 @@ error: discriminant value `1` is already used by A in this enum 43 | B = 1, // collision | ^^^^^ -error: the discriminants of C-style enums with #[wasm_bindgen] must be representable as u32 +error: C-style enums with #[wasm_bindgen] can only support unsigned numbers that can be represented by `u32`, but `4294967296` is too large for `u32` --> ui-tests/invalid-enums.rs:50:5 | -50 | C, // would be u32::MAX + 1 +50 | C, // = u32::MAX + 1 | ^ error: discriminant value `0` is already used by A in this enum @@ -63,3 +63,15 @@ error: discriminant value `3` is already used by A in this enum | 63 | C, // = 3 -> collision | ^ + +error: C-style enums with #[wasm_bindgen] can only support signed numbers that can be represented by `i32`, but `-2147483649` is too small for `i32` + --> ui-tests/invalid-enums.rs:69:5 + | +69 | B = -2147483649, // i32::MIN - 1 + | ^^^^^^^^^^^^^^^ + +error: C-style enums with #[wasm_bindgen] can only support signed numbers that can be represented by `i32`, but `2147483648` is too large for `i32` + --> ui-tests/invalid-enums.rs:75:5 + | +75 | B = 2147483648, // i32::MAX + 1 + | ^^^^^^^^^^^^^^ diff --git a/crates/macro/ui-tests/missing-catch.stderr b/crates/macro/ui-tests/missing-catch.stderr index 02a74e62f00..4fc6b3ed6de 100644 --- a/crates/macro/ui-tests/missing-catch.stderr +++ b/crates/macro/ui-tests/missing-catch.stderr @@ -1,6 +1,9 @@ error[E0277]: the trait bound `Result: FromWasmAbi` is not satisfied --> ui-tests/missing-catch.rs:6:9 | +3 | #[wasm_bindgen] + | --------------- in this procedural macro expansion +... 6 | pub fn foo() -> Result; | ^^^ the trait `FromWasmAbi` is not implemented for `Result` | @@ -12,5 +15,6 @@ error[E0277]: the trait bound `Result $DIR/pub-not-copy.rs:5:16 | +3 | #[wasm_bindgen] + | --------------- in this procedural macro expansion +4 | pub struct A { 5 | pub field: String, | ^^^^^^ the trait `std::marker::Copy` is not implemented for `String` | diff --git a/crates/macro/ui-tests/struct-fields.stderr b/crates/macro/ui-tests/struct-fields.stderr index 6e402b2d6ab..f38391b27d8 100644 --- a/crates/macro/ui-tests/struct-fields.stderr +++ b/crates/macro/ui-tests/struct-fields.stderr @@ -1,6 +1,9 @@ error[E0277]: the trait bound `Foo: std::marker::Copy` is not satisfied --> ui-tests/struct-fields.rs:10:12 | +8 | #[wasm_bindgen] + | --------------- in this procedural macro expansion +9 | struct Bar { 10 | pub a: Foo, | ^^^ the trait `std::marker::Copy` is not implemented for `Foo` | diff --git a/crates/macro/ui-tests/traits-not-implemented.stderr b/crates/macro/ui-tests/traits-not-implemented.stderr index 883648fae4c..82aeb56b6fe 100644 --- a/crates/macro/ui-tests/traits-not-implemented.stderr +++ b/crates/macro/ui-tests/traits-not-implemented.stderr @@ -12,6 +12,6 @@ error[E0277]: the trait bound `A: IntoWasmAbi` is not satisfied i16 i32 i64 - usize + i128 and $N others = note: this error originates in the attribute macro `wasm_bindgen` (in Nightly builds, run with -Z macro-backtrace for more info) diff --git a/crates/macro/ui-tests/unknown-type-in-import.stderr b/crates/macro/ui-tests/unknown-type-in-import.stderr index af11dc2c64e..9dc814804d0 100644 --- a/crates/macro/ui-tests/unknown-type-in-import.stderr +++ b/crates/macro/ui-tests/unknown-type-in-import.stderr @@ -8,3 +8,9 @@ help: you might be missing a type parameter | 6 | pub fn foo(a: A); | +++ + +error[E0412]: cannot find type `A` in this scope + --> ui-tests/unknown-type-in-import.rs:6:19 + | +6 | pub fn foo(a: A); + | ^ not found in this scope diff --git a/crates/shared/src/lib.rs b/crates/shared/src/lib.rs index 57faecfcd84..13b893e4685 100644 --- a/crates/shared/src/lib.rs +++ b/crates/shared/src/lib.rs @@ -122,6 +122,7 @@ macro_rules! shared_api { struct Enum<'a> { name: &'a str, + signed: bool, variants: Vec>, comments: Vec<&'a str>, generate_typescript: bool, diff --git a/crates/shared/src/schema_hash_approval.rs b/crates/shared/src/schema_hash_approval.rs index 4edb55c791c..ebc7341bb3c 100644 --- a/crates/shared/src/schema_hash_approval.rs +++ b/crates/shared/src/schema_hash_approval.rs @@ -8,7 +8,7 @@ // If the schema in this library has changed then: // 1. Bump the version in `crates/shared/Cargo.toml` // 2. Change the `SCHEMA_VERSION` in this library to this new Cargo.toml version -const APPROVED_SCHEMA_FILE_HASH: &str = "18290232323238297705"; +const APPROVED_SCHEMA_FILE_HASH: &str = "211103844299778814"; #[test] fn schema_version() { diff --git a/crates/web-sys/src/features/gen_MediaStreamTrack.rs b/crates/web-sys/src/features/gen_MediaStreamTrack.rs index 1e7007ffc23..bb8ac52e15f 100644 --- a/crates/web-sys/src/features/gen_MediaStreamTrack.rs +++ b/crates/web-sys/src/features/gen_MediaStreamTrack.rs @@ -129,6 +129,18 @@ extern "C" { #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `MediaStreamTrack`*"] pub fn clone(this: &MediaStreamTrack) -> MediaStreamTrack; + #[cfg(web_sys_unstable_apis)] + #[cfg(feature = "MediaTrackCapabilities")] + # [wasm_bindgen (method , structural , js_class = "MediaStreamTrack" , js_name = getCapabilities)] + #[doc = "The `getCapabilities()` method."] + #[doc = ""] + #[doc = "[MDN Documentation](https://developer.mozilla.org/en-US/docs/Web/API/MediaStreamTrack/getCapabilities)"] + #[doc = ""] + #[doc = "*This API requires the following crate features to be activated: `MediaStreamTrack`, `MediaTrackCapabilities`*"] + #[doc = ""] + #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] + #[doc = "[described in the `wasm-bindgen` guide](https://rustwasm.github.io/docs/wasm-bindgen/web-sys/unstable-apis.html)*"] + pub fn get_capabilities(this: &MediaStreamTrack) -> MediaTrackCapabilities; #[cfg(feature = "MediaTrackConstraints")] # [wasm_bindgen (method , structural , js_class = "MediaStreamTrack" , js_name = getConstraints)] #[doc = "The `getConstraints()` method."] diff --git a/crates/web-sys/src/features/gen_MediaTrackCapabilities.rs b/crates/web-sys/src/features/gen_MediaTrackCapabilities.rs index 1c6d82c5cb9..85d23016f20 100644 --- a/crates/web-sys/src/features/gen_MediaTrackCapabilities.rs +++ b/crates/web-sys/src/features/gen_MediaTrackCapabilities.rs @@ -53,6 +53,24 @@ extern "C" { #[wasm_bindgen(method, setter = "autoGainControl")] pub fn set_auto_gain_control(this: &MediaTrackCapabilities, val: &::wasm_bindgen::JsValue); #[cfg(web_sys_unstable_apis)] + #[doc = "Get the `backgroundBlur` field of this object."] + #[doc = ""] + #[doc = "*This API requires the following crate features to be activated: `MediaTrackCapabilities`*"] + #[doc = ""] + #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] + #[doc = "[described in the `wasm-bindgen` guide](https://rustwasm.github.io/docs/wasm-bindgen/web-sys/unstable-apis.html)*"] + #[wasm_bindgen(method, getter = "backgroundBlur")] + pub fn get_background_blur(this: &MediaTrackCapabilities) -> Option<::js_sys::Array>; + #[cfg(web_sys_unstable_apis)] + #[doc = "Change the `backgroundBlur` field of this object."] + #[doc = ""] + #[doc = "*This API requires the following crate features to be activated: `MediaTrackCapabilities`*"] + #[doc = ""] + #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] + #[doc = "[described in the `wasm-bindgen` guide](https://rustwasm.github.io/docs/wasm-bindgen/web-sys/unstable-apis.html)*"] + #[wasm_bindgen(method, setter = "backgroundBlur")] + pub fn set_background_blur(this: &MediaTrackCapabilities, val: &::wasm_bindgen::JsValue); + #[cfg(web_sys_unstable_apis)] #[cfg(feature = "ULongRange")] #[doc = "Get the `channelCount` field of this object."] #[doc = ""] @@ -328,6 +346,12 @@ impl MediaTrackCapabilities { self } #[cfg(web_sys_unstable_apis)] + #[deprecated = "Use `set_background_blur()` instead."] + pub fn background_blur(&mut self, val: &::wasm_bindgen::JsValue) -> &mut Self { + self.set_background_blur(val); + self + } + #[cfg(web_sys_unstable_apis)] #[cfg(feature = "ULongRange")] #[deprecated = "Use `set_channel_count()` instead."] pub fn channel_count(&mut self, val: &ULongRange) -> &mut Self { diff --git a/crates/web-sys/webidls/unstable/MediaTrackCapabilities.webidl b/crates/web-sys/webidls/unstable/MediaTrackCapabilities.webidl index ba6bd9c4607..e3f08031142 100644 --- a/crates/web-sys/webidls/unstable/MediaTrackCapabilities.webidl +++ b/crates/web-sys/webidls/unstable/MediaTrackCapabilities.webidl @@ -7,6 +7,10 @@ * https://w3c.github.io/mediacapture-main/#dom-mediatrackcapabilities */ +partial interface MediaStreamTrack { + MediaTrackCapabilities getCapabilities (); +}; + dictionary ULongRange { [Clamp] unsigned long max; [Clamp] unsigned long min; @@ -33,4 +37,5 @@ dictionary MediaTrackCapabilities { ULongRange channelCount; DOMString deviceId; DOMString groupId; + sequence backgroundBlur; }; diff --git a/crates/webidl/src/main.rs b/crates/webidl/src/main.rs index 4f70bb9bdba..3c7e869bf48 100644 --- a/crates/webidl/src/main.rs +++ b/crates/webidl/src/main.rs @@ -6,10 +6,7 @@ use std::path::PathBuf; use update_cargo_toml::update_cargo_toml_features; #[derive(Parser, Debug)] -#[structopt( - name = "wasm-bindgen-webidl", - about = "Converts WebIDL into wasm-bindgen compatible code." -)] +#[clap(about = "Converts WebIDL into wasm-bindgen compatible code.")] struct Opt { input_dir: PathBuf, diff --git a/guide/src/reference/attributes/on-rust-exports/constructor.md b/guide/src/reference/attributes/on-rust-exports/constructor.md index 3e5fc2d4deb..d37d4674bdf 100644 --- a/guide/src/reference/attributes/on-rust-exports/constructor.md +++ b/guide/src/reference/attributes/on-rust-exports/constructor.md @@ -35,7 +35,7 @@ console.log(f.get_contents()); ## Caveats -Starting from v0.2.48 there is a bug in `wasm-bindgen` which breaks inheritance of exported Rust structs from JavaScript side (see [#3213](https://github.com/rustwasm/wasm-bindgen/issues/3213)). If you want to inherit from a Rust struct such as: +In versions `>=v0.2.48, <0.2.88` of `wasm-bindgen`, there is a bug which breaks inheritance of exported Rust structs from JavaScript side (see [#3213](https://github.com/rustwasm/wasm-bindgen/issues/3213)). If you want to inherit from a Rust struct such as: ```rust use wasm_bindgen::prelude::*; diff --git a/guide/src/reference/types/bool.md b/guide/src/reference/types/bool.md index 6859de8c249..0ef43029250 100644 --- a/guide/src/reference/types/bool.md +++ b/guide/src/reference/types/bool.md @@ -4,6 +4,10 @@ |:---:|:---:|:---:|:---:|:---:|:---:|:---:| | Yes | No | No | Yes | Yes | Yes | A JavaScript boolean value | +> **Note**: Only [JavaScript `Boolean`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Boolean) values (`true` or `false`) are supported when calling into Rust. If you want to pass truthy or falsy values to Rust, convert them to a boolean using `Boolean(value)` first. +> +> If you are using TypeScript, you don't have to worry about this, as TypeScript will emit a compiler error if you try to pass a non-`boolean` value. + ## Example Rust Usage ```rust diff --git a/guide/src/reference/types/boxed-number-slices.md b/guide/src/reference/types/boxed-number-slices.md index 73cbf0d1891..6df85e18287 100644 --- a/guide/src/reference/types/boxed-number-slices.md +++ b/guide/src/reference/types/boxed-number-slices.md @@ -4,7 +4,7 @@ |:---:|:---:|:---:|:---:|:---:|:---:|:---:| | Yes | No | No | Yes | Yes | Yes | A JavaScript `TypedArray` of the appropriate type (`Int32Array`, `Uint8Array`, etc...) | -Note that the contents of the slice are copied into the JavaScript `TypedArray` +> **Note:** The contents of the slice are copied into a JavaScript [`TypedArray`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/TypedArray) from the Wasm linear memory when returning a boxed slice to JavaScript, and vice versa when receiving a JavaScript `TypedArray` as a boxed slice in Rust. diff --git a/guide/src/reference/types/char.md b/guide/src/reference/types/char.md index 168f93191b3..650adffa342 100644 --- a/guide/src/reference/types/char.md +++ b/guide/src/reference/types/char.md @@ -2,7 +2,15 @@ | `T` parameter | `&T` parameter | `&mut T` parameter | `T` return value | `Option` parameter | `Option` return value | JavaScript representation | |:---:|:---:|:---:|:---:|:---:|:---:|:---:| -| Yes | No | No | Yes | No | No | A JavaScript string value | +| Yes | No | No | Yes | Yes | Yes | A JavaScript string value | + +Since JavaScript doesn't have a character type, `char` is represented as a JavaScript string with one Unicode code point. + +> **Note**: [JavaScript strings uses UTF-16 encoding](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String#utf-16_characters_unicode_code_points_and_grapheme_clusters). This means that a single `char` may be represented by a string of length 1 or 2 in JavaScript, depending on the Unicode code point. See [`String.fromCodePoint`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/fromCodePoint) for more information. + +When passed into Rust, the `char` value of a JavaScript string is determined using [`codePointAt(0)`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/codePointAt). If the JavaScript string is empty or starts with an unpaired surrogate, a runtime error will be thrown. + +> **Note**: For more information about unpaired surrogates, see the [documentation for `str`](str.html). ## Example Rust Usage diff --git a/guide/src/reference/types/exported-rust-types.md b/guide/src/reference/types/exported-rust-types.md index deaad2fb359..2e190db43ae 100644 --- a/guide/src/reference/types/exported-rust-types.md +++ b/guide/src/reference/types/exported-rust-types.md @@ -4,9 +4,10 @@ |:---:|:---:|:---:|:---:|:---:|:---:|:---:| | Yes | Yes | Yes | Yes | Yes | Yes | Instances of a `wasm-bindgen`-generated JavaScript `class Whatever { ... }` | -> **Note**: Public fields implementing Copy have automatically generated getters/setters. -> To generate getters/setters for non-Copy public fields, use #[wasm_bindgen(getter_with_clone)] for the struct +> **Note**: Public fields implementing `Copy` have automatically generated getters/setters. +> To generate getters/setters for non-`Copy` public fields, use `#[wasm_bindgen(getter_with_clone)]` for the struct > or [implement getters/setters manually](https://rustwasm.github.io/wasm-bindgen/reference/attributes/on-rust-exports/getter-and-setter.html). + ## Example Rust Usage ```rust diff --git a/guide/src/reference/types/numbers.md b/guide/src/reference/types/numbers.md index 13742f53fd2..5cf376569ec 100644 --- a/guide/src/reference/types/numbers.md +++ b/guide/src/reference/types/numbers.md @@ -1,8 +1,61 @@ -# Numbers: `u8`, `i8`, `u16`, `i16`, `u32`, `i32`, `u64`, `i64`, `isize`, `usize`, `f32`, and `f64` +# Numbers: `u8`, `i8`, `u16`, `i16`, `u32`, `i32`, `u64`, `i64`, `u128`, `i128`, `isize`, `usize`, `f32`, and `f64` | `T` parameter | `&T` parameter | `&mut T` parameter | `T` return value | `Option` parameter | `Option` return value | JavaScript representation | |:---:|:---:|:---:|:---:|:---:|:---:|:---:| -| Yes | No | No | Yes | Yes | Yes | A JavaScript number value | +| Yes | No | No | Yes | Yes | Yes | A JavaScript number or bigint value | + +[JavaScript `Number`s](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number#number_encoding) are 64-bit floating point value under the hood and cannot accurately represent all of Rust's numeric types. `wasm-bindgen` will automatically use either [`BigInt`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/BigInt) or `Number` to accurately represent Rust's numeric types in JavaScript: + +- `u8`, `i8`, `u16`, `i16`, `u32`, `i32`, `isize`, `usize`, `f32`, and `f64` will be represented as `Number` in JavaScript. +- `u64`, `i64`, `u128`, and `i128` will be represented as `BigInt` in JavaScript. + +> **Note**: Wasm is currently a 32-bit architecture, so `isize` and `usize` are 32-bit integers and "fit" into a JavaScript `Number`. + +> **Note**: `u128` and `i128` require `wasm-bindgen` version 0.2.96 or later. + +## Converting from JavaScript to Rust + +`wasm-bindgen` will automatically handle the conversion of JavaScript numbers to Rust numeric types. The conversion rules are as follows: + +### `Number` to `u8`, `i8`, `u16`, `i16`, `u32`, `i32`, `isize`, and `usize` + +If the JavaScript number is `Infinity`, `-Infinity`, or `NaN`, then the Rust value will be 0. Otherwise, the JavaScript number will rounded towards zero (see [`Math.trunc`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/trunc) or [`f64::trunc`](https://doc.rust-lang.org/std/primitive.f64.html#method.trunc)). If the rounded number is too large or too small for the target integer type, it will wrap around. + +For example, if the target type is `i8`, Rust will see the following values for the following inputs: + +| JS input number | Rust value (`i8`) | +| --------------: | :---------------- | +| 42 | 42 | +| -42 | -42 | +| 1.999 | 1 | +| -1.999 | -1 | +| 127 | 127 | +| 128 | -128 | +| 255 | -1 | +| 256 | 0 | +| -0 | 0 | +| `±Infinity` | 0 | +| `NaN` | 0 | + +This is the same behavior as assigning the JavaScript `Number` to a [typed array](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/TypedArray) of the appropriate integer type in JavaScript, i.e. `new Uint8Array([value])[0]`. + +Except for the handling of `Infinity` and `-Infinity`, this is the same behavior as [casting](https://doc.rust-lang.org/reference/expressions/operator-expr.html#numeric-cast) `f64` to the appropriate integer type in Rust, i.e. `value_f64 as u32`. + +### `BigInt` to `u64`, `i64`, `u128`, and `i128` + +If the JavaScript `BigInt` is too large or too small for the target integer type, it will wrap around. + +This is the same behavior as assigning the JavaScript `BigInt` to a [typed array](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/TypedArray) for 64-bit integer types in JavaScript, i.e. `new Int64Array([value])[0]`. + +### `Number` to `f32` + +The JavaScript `Number` is converted to a Rust `f32` using the same rules as [casting](https://doc.rust-lang.org/reference/expressions/operator-expr.html#numeric-cast) `f64` to `f32` in Rust, i.e. `value_f64 as f32`. + +This is the same behavior as [`Math.fround`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/fround) or assigning the JavaScript `Number` to a [`Float32Array`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Float32Array) in JavaScript, i.e. `new Float32Array([value])[0]`. + +### `Number` to `f64` + +Since JavaScript numbers are 64-bit floating point values, converting a JavaScript `Number` to a Rust `f64` is a no-op. ## Example Rust Usage diff --git a/src/convert/impls.rs b/src/convert/impls.rs index 28dac9cfd83..c4827ddf5ad 100644 --- a/src/convert/impls.rs +++ b/src/convert/impls.rs @@ -29,6 +29,43 @@ impl WasmAbi for T { } } +impl WasmAbi for i128 { + type Prim1 = u64; + type Prim2 = u64; + type Prim3 = (); + type Prim4 = (); + + #[inline] + fn split(self) -> (u64, u64, (), ()) { + let low = self as u64; + let high = (self >> 64) as u64; + (low, high, (), ()) + } + + #[inline] + fn join(low: u64, high: u64, _: (), _: ()) -> Self { + ((high as u128) << 64 | low as u128) as i128 + } +} +impl WasmAbi for u128 { + type Prim1 = u64; + type Prim2 = u64; + type Prim3 = (); + type Prim4 = (); + + #[inline] + fn split(self) -> (u64, u64, (), ()) { + let low = self as u64; + let high = (self >> 64) as u64; + (low, high, (), ()) + } + + #[inline] + fn join(low: u64, high: u64, _: (), _: ()) -> Self { + (high as u128) << 64 | low as u128 + } +} + impl> WasmAbi for Option { /// Whether this `Option` is a `Some` value. type Prim1 = u32; @@ -101,6 +138,8 @@ macro_rules! type_wasm_native { type_wasm_native!( i64 as i64 u64 as u64 + i128 as i128 + u128 as u128 f64 as f64 ); diff --git a/src/describe.rs b/src/describe.rs index f7e9e83d2cf..ac78848d222 100644 --- a/src/describe.rs +++ b/src/describe.rs @@ -30,6 +30,8 @@ tys! { U32 I64 U64 + I128 + U128 F32 F64 BOOLEAN @@ -89,6 +91,8 @@ simple! { u32 => U32 i64 => I64 u64 => U64 + i128 => I128 + u128 => U128 isize => I32 usize => U32 f32 => F32 diff --git a/tests/wasm/optional_primitives.js b/tests/wasm/optional_primitives.js index d6755144a20..499dd236d30 100644 --- a/tests/wasm/optional_primitives.js +++ b/tests/wasm/optional_primitives.js @@ -13,6 +13,8 @@ exports.optional_i16_js_identity = a => a; exports.optional_u16_js_identity = a => a; exports.optional_i64_js_identity = a => a; exports.optional_u64_js_identity = a => a; +exports.optional_i128_js_identity = a => a; +exports.optional_u128_js_identity = a => a; exports.optional_bool_js_identity = a => a; exports.optional_char_js_identity = a => a; @@ -102,6 +104,22 @@ exports.js_works = () => { 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_i128_identity(wasm.optional_i128_none()), undefined); + assert.strictEqual(wasm.optional_i128_identity(wasm.optional_i128_zero()), BigInt('0')); + assert.strictEqual(wasm.optional_i128_identity(wasm.optional_i128_one()), BigInt('1')); + assert.strictEqual(wasm.optional_i128_identity(wasm.optional_i128_neg_one()), BigInt('-1')); + assert.strictEqual(wasm.optional_i128_identity(wasm.optional_i128_min()), -0x8000_0000_0000_0000_0000_0000_0000_0000n); + assert.strictEqual(wasm.optional_i128_identity(wasm.optional_i128_max()), 0x7FFF_FFFF_FFFF_FFFF_FFFF_FFFF_FFFF_FFFFn); + assert.strictEqual(wasm.optional_i128_identity(0x3_1415_9265_3598_7932_3846n), 0x3_1415_9265_3598_7932_3846n); + assert.strictEqual(wasm.optional_i128_identity(-0x3_1415_9265_3598_7932_3846n), -0x3_1415_9265_3598_7932_3846n); + + assert.strictEqual(wasm.optional_u128_identity(wasm.optional_u128_none()), undefined); + assert.strictEqual(wasm.optional_u128_identity(wasm.optional_u128_zero()), BigInt('0')); + assert.strictEqual(wasm.optional_u128_identity(wasm.optional_u128_one()), BigInt('1')); + assert.strictEqual(wasm.optional_u128_identity(wasm.optional_u128_min()), BigInt('0')); + assert.strictEqual(wasm.optional_u128_identity(wasm.optional_u128_max()), 0xFFFF_FFFF_FFFF_FFFF_FFFF_FFFF_FFFF_FFFFn); + assert.strictEqual(wasm.optional_u128_identity(0x3_1415_9265_3598_7932_3846n), 0x3_1415_9265_3598_7932_3846n); + assert.strictEqual(wasm.optional_bool_identity(wasm.optional_bool_none()), undefined); assert.strictEqual(wasm.optional_bool_identity(wasm.optional_bool_false()), false); assert.strictEqual(wasm.optional_bool_identity(wasm.optional_bool_true()), true); diff --git a/tests/wasm/optional_primitives.rs b/tests/wasm/optional_primitives.rs index 027bc18c99f..c3cd1e9fc95 100644 --- a/tests/wasm/optional_primitives.rs +++ b/tests/wasm/optional_primitives.rs @@ -15,6 +15,8 @@ extern "C" { fn optional_u16_js_identity(a: Option) -> Option; fn optional_i64_js_identity(a: Option) -> Option; fn optional_u64_js_identity(a: Option) -> Option; + fn optional_i128_js_identity(a: Option) -> Option; + fn optional_u128_js_identity(a: Option) -> Option; fn optional_bool_js_identity(a: Option) -> Option; fn optional_char_js_identity(a: Option) -> Option; @@ -396,6 +398,71 @@ pub fn optional_u64_identity(a: Option) -> Option { optional_u64_js_identity(a) } +#[wasm_bindgen] +pub fn optional_i128_none() -> Option { + None +} + +#[wasm_bindgen] +pub fn optional_i128_zero() -> Option { + Some(0) +} + +#[wasm_bindgen] +pub fn optional_i128_one() -> Option { + Some(1) +} + +#[wasm_bindgen] +pub fn optional_i128_neg_one() -> Option { + Some(-1) +} + +#[wasm_bindgen] +pub fn optional_i128_min() -> Option { + Some(i128::MIN) +} + +#[wasm_bindgen] +pub fn optional_i128_max() -> Option { + Some(i128::MAX) +} + +#[wasm_bindgen] +pub fn optional_i128_identity(a: Option) -> Option { + optional_i128_js_identity(a) +} + +#[wasm_bindgen] +pub fn optional_u128_none() -> Option { + None +} + +#[wasm_bindgen] +pub fn optional_u128_zero() -> Option { + Some(0) +} + +#[wasm_bindgen] +pub fn optional_u128_one() -> Option { + Some(1) +} + +#[wasm_bindgen] +pub fn optional_u128_min() -> Option { + Some(u128::MIN) +} + +#[wasm_bindgen] +pub fn optional_u128_max() -> Option { + Some(u128::MAX) +} + +#[wasm_bindgen] +pub fn optional_u128_identity(a: Option) -> Option { + optional_u128_js_identity(a) +} + #[wasm_bindgen] pub fn optional_bool_none() -> Option { None