diff --git a/crates/backend/src/ast.rs b/crates/backend/src/ast.rs index 4c317cea..75c73860 100644 --- a/crates/backend/src/ast.rs +++ b/crates/backend/src/ast.rs @@ -79,6 +79,7 @@ pub struct Import { pub enum ImportModule { None, Named(String, Span), + RawNamed(String, Span), Inline(usize, Span), } @@ -96,6 +97,10 @@ impl Hash for ImportModule { 2u8.hash(h); idx.hash(h); } + ImportModule::RawNamed(name, _) => { + 3u8.hash(h); + name.hash(h); + } } } } diff --git a/crates/backend/src/codegen.rs b/crates/backend/src/codegen.rs index bd0fd226..522b7e6b 100644 --- a/crates/backend/src/codegen.rs +++ b/crates/backend/src/codegen.rs @@ -844,6 +844,18 @@ impl ToTokens for ast::ImportEnum { } } + #[allow(clippy::all)] + impl wasm_bindgen::convert::OptionIntoWasmAbi for #name { + #[inline] + fn none() -> Self::Abi { Object::none() } + } + + #[allow(clippy::all)] + impl wasm_bindgen::convert::OptionFromWasmAbi for #name { + #[inline] + fn is_none(abi: &Self::Abi) -> bool { Object::is_none(abi) } + } + #[allow(clippy::all)] impl From<#name> for wasm_bindgen::JsValue { fn from(obj: #name) -> wasm_bindgen::JsValue { diff --git a/crates/backend/src/encode.rs b/crates/backend/src/encode.rs index abc8c613..2e327d20 100644 --- a/crates/backend/src/encode.rs +++ b/crates/backend/src/encode.rs @@ -208,6 +208,7 @@ fn shared_import<'a>(i: &'a ast::Import, intern: &'a Interner) -> Result { ImportModule::Named(intern.resolve_import_module(m, *span)?) } + ast::ImportModule::RawNamed(m, _span) => ImportModule::RawNamed(intern.intern_str(m)), ast::ImportModule::Inline(idx, _) => ImportModule::Inline(*idx as u32), ast::ImportModule::None => ImportModule::None, }, diff --git a/crates/cli-support/src/js/mod.rs b/crates/cli-support/src/js/mod.rs index 57e90368..cdd2ea57 100644 --- a/crates/cli-support/src/js/mod.rs +++ b/crates/cli-support/src/js/mod.rs @@ -2836,6 +2836,7 @@ impl<'a, 'b> SubContext<'a, 'b> { // not sure how to import them. let is_local_snippet = match import.module { decode::ImportModule::Named(s) => self.cx.local_modules.contains_key(s), + decode::ImportModule::RawNamed(_) => false, decode::ImportModule::Inline(_) => true, decode::ImportModule::None => false, }; @@ -2921,11 +2922,13 @@ impl<'a, 'b> SubContext<'a, 'b> { name, field, }, - decode::ImportModule::Named(module) => Import::Module { - module, - name, - field, - }, + decode::ImportModule::Named(module) | decode::ImportModule::RawNamed(module) => { + Import::Module { + module, + name, + field, + } + } decode::ImportModule::Inline(idx) => { let offset = *self .cx diff --git a/crates/macro-support/src/parser.rs b/crates/macro-support/src/parser.rs index 574af3c2..d94a4680 100644 --- a/crates/macro-support/src/parser.rs +++ b/crates/macro-support/src/parser.rs @@ -33,6 +33,7 @@ macro_rules! attrgen { (static_method_of, StaticMethodOf(Span, Ident)), (js_namespace, JsNamespace(Span, Ident)), (module, Module(Span, String, Span)), + (raw_module, RawModule(Span, String, Span)), (inline_js, InlineJs(Span, String, Span)), (getter, Getter(Span, Option)), (setter, Setter(Span, Option)), @@ -1085,24 +1086,28 @@ impl MacroParse for syn::ItemForeignMod { )); } } - let module = match opts.module() { - Some((name, span)) => { - if opts.inline_js().is_some() { - let msg = "cannot specify both `module` and `inline_js`"; - errors.push(Diagnostic::span_error(span, msg)); - } - ast::ImportModule::Named(name.to_string(), span) + let module = if let Some((name, span)) = opts.module() { + if opts.inline_js().is_some() { + let msg = "cannot specify both `module` and `inline_js`"; + errors.push(Diagnostic::span_error(span, msg)); } - None => { - match opts.inline_js() { - Some((js, span)) => { - let i = program.inline_js.len(); - program.inline_js.push(js.to_string()); - ast::ImportModule::Inline(i, span) - } - None => ast::ImportModule::None - } + if opts.raw_module().is_some() { + let msg = "cannot specify both `module` and `raw_module`"; + errors.push(Diagnostic::span_error(span, msg)); } + ast::ImportModule::Named(name.to_string(), span) + } else if let Some((name, span)) = opts.raw_module() { + if opts.inline_js().is_some() { + let msg = "cannot specify both `raw_module` and `inline_js`"; + errors.push(Diagnostic::span_error(span, msg)); + } + ast::ImportModule::RawNamed(name.to_string(), span) + } else if let Some((js, span)) = opts.inline_js() { + let i = program.inline_js.len(); + program.inline_js.push(js.to_string()); + ast::ImportModule::Inline(i, span) + } else { + ast::ImportModule::None }; for item in self.items.into_iter() { if let Err(e) = item.macro_parse(program, module.clone()) { diff --git a/crates/shared/src/lib.rs b/crates/shared/src/lib.rs index 9b9356f2..5bd0072c 100644 --- a/crates/shared/src/lib.rs +++ b/crates/shared/src/lib.rs @@ -28,6 +28,7 @@ macro_rules! shared_api { enum ImportModule<'a> { None, Named(&'a str), + RawNamed(&'a str), Inline(u32), } diff --git a/crates/typescript-tests/index.ts b/crates/typescript-tests/index.ts deleted file mode 100644 index f36c5192..00000000 --- a/crates/typescript-tests/index.ts +++ /dev/null @@ -1,11 +0,0 @@ -import * as wbg from './pkg/typescript_tests'; -import * as wasm from './pkg/typescript_tests_bg'; - -const a1: (a: string) => void = wbg.greet; -const a2: (a: number, b: number) => void = wasm.greet; -const a3: WebAssembly.Memory = wasm.memory; - -const c = new wbg.A(); -wbg.A.other(); -c.foo(); -c.free(); diff --git a/crates/typescript-tests/src/custom_section.rs b/crates/typescript-tests/src/custom_section.rs new file mode 100644 index 00000000..e1f5c1fc --- /dev/null +++ b/crates/typescript-tests/src/custom_section.rs @@ -0,0 +1,11 @@ +use wasm_bindgen::prelude::*; + +#[wasm_bindgen(typescript_custom_section)] +const TS_INTERFACE_EXPORT: &'static str = r" + interface Height { height: number; } +"; + +#[wasm_bindgen] +pub struct Person { + pub height: u32, +} \ No newline at end of file diff --git a/crates/typescript-tests/src/custom_section.ts b/crates/typescript-tests/src/custom_section.ts new file mode 100644 index 00000000..6420ea6d --- /dev/null +++ b/crates/typescript-tests/src/custom_section.ts @@ -0,0 +1,3 @@ +import * as wbg from '../pkg/typescript_tests'; + +const height: wbg.Height = new wbg.Person(); \ No newline at end of file diff --git a/crates/typescript-tests/src/lib.rs b/crates/typescript-tests/src/lib.rs index ea10b7b2..5e927e5a 100644 --- a/crates/typescript-tests/src/lib.rs +++ b/crates/typescript-tests/src/lib.rs @@ -1,20 +1,4 @@ -use wasm_bindgen::prelude::*; - -#[wasm_bindgen] -pub fn greet(_: &str) {} - -#[wasm_bindgen] -struct A { -} - -#[wasm_bindgen] -impl A { - #[wasm_bindgen(constructor)] - pub fn new() -> A { - A {} - } - - pub fn other() {} - - pub fn foo(&self) {} -} +mod custom_section; +mod opt_args_and_ret; +mod simple_fn; +mod simple_struct; \ No newline at end of file diff --git a/crates/typescript-tests/src/memory.ts b/crates/typescript-tests/src/memory.ts new file mode 100644 index 00000000..b1095b00 --- /dev/null +++ b/crates/typescript-tests/src/memory.ts @@ -0,0 +1,3 @@ +import * as wasm from '../pkg/typescript_tests_bg'; + +const memory: WebAssembly.Memory = wasm.memory; diff --git a/crates/typescript-tests/src/opt_args_and_ret.rs b/crates/typescript-tests/src/opt_args_and_ret.rs new file mode 100644 index 00000000..a84f2bfe --- /dev/null +++ b/crates/typescript-tests/src/opt_args_and_ret.rs @@ -0,0 +1,6 @@ +use wasm_bindgen::prelude::*; + +#[wasm_bindgen] +pub fn opt_fn(_a: Option) -> Option { + None +} \ No newline at end of file diff --git a/crates/typescript-tests/src/opt_args_and_ret.ts b/crates/typescript-tests/src/opt_args_and_ret.ts new file mode 100644 index 00000000..d0bd20a4 --- /dev/null +++ b/crates/typescript-tests/src/opt_args_and_ret.ts @@ -0,0 +1,3 @@ +import * as wbg from '../pkg/typescript_tests'; + +const opt_fn: (a: number | undefined) => number | undefined = wbg.opt_fn; \ No newline at end of file diff --git a/crates/typescript-tests/src/simple_fn.rs b/crates/typescript-tests/src/simple_fn.rs new file mode 100644 index 00000000..29a7d4aa --- /dev/null +++ b/crates/typescript-tests/src/simple_fn.rs @@ -0,0 +1,4 @@ +use wasm_bindgen::prelude::*; + +#[wasm_bindgen] +pub fn greet(_: &str) {} \ No newline at end of file diff --git a/crates/typescript-tests/src/simple_fn.ts b/crates/typescript-tests/src/simple_fn.ts new file mode 100644 index 00000000..f8b91d08 --- /dev/null +++ b/crates/typescript-tests/src/simple_fn.ts @@ -0,0 +1,5 @@ +import * as wbg from '../pkg/typescript_tests'; +import * as wasm from '../pkg/typescript_tests_bg'; + +const wbg_greet: (a: string) => void = wbg.greet; +const wasm_greet: (a: number, b: number) => void = wasm.greet; \ No newline at end of file diff --git a/crates/typescript-tests/src/simple_struct.rs b/crates/typescript-tests/src/simple_struct.rs new file mode 100644 index 00000000..d15e5031 --- /dev/null +++ b/crates/typescript-tests/src/simple_struct.rs @@ -0,0 +1,17 @@ +use wasm_bindgen::prelude::*; + +#[wasm_bindgen] +pub struct A { +} + +#[wasm_bindgen] +impl A { + #[wasm_bindgen(constructor)] + pub fn new() -> A { + A {} + } + + pub fn other() {} + + pub fn foo(&self) {} +} \ No newline at end of file diff --git a/crates/typescript-tests/src/simple_struct.ts b/crates/typescript-tests/src/simple_struct.ts new file mode 100644 index 00000000..95830399 --- /dev/null +++ b/crates/typescript-tests/src/simple_struct.ts @@ -0,0 +1,6 @@ +import * as wbg from '../pkg/typescript_tests'; + +const a = new wbg.A(); +wbg.A.other(); +a.foo(); +a.free(); diff --git a/crates/typescript-tests/tsconfig.json b/crates/typescript-tests/tsconfig.json index bdd06907..90892979 100644 --- a/crates/typescript-tests/tsconfig.json +++ b/crates/typescript-tests/tsconfig.json @@ -9,6 +9,6 @@ "baseUrl": "." }, "include": [ - "index.ts" + "src/*.ts" ] } diff --git a/crates/web-sys/Cargo.toml b/crates/web-sys/Cargo.toml index 5956f168..931bb2b5 100644 --- a/crates/web-sys/Cargo.toml +++ b/crates/web-sys/Cargo.toml @@ -814,6 +814,9 @@ RtcRtpSender = [] RtcRtpSourceEntry = [] RtcRtpSourceEntryType = [] RtcRtpSynchronizationSource = [] +RtcRtpTransceiver = [] +RtcRtpTransceiverDirection = [] +RtcRtpTransceiverInit = [] RtcRtxParameters = [] RtcSdpType = [] RtcSessionDescription = [] @@ -826,6 +829,7 @@ RtcStatsReport = [] RtcStatsReportInternal = [] RtcStatsType = [] RtcTrackEvent = [] +RtcTrackEventInit = [] RtcTransportStats = [] RtcdtmfSender = [] RtcdtmfToneChangeEvent = [] diff --git a/crates/web-sys/tests/wasm/main.rs b/crates/web-sys/tests/wasm/main.rs index d4c01b8d..f27d6af7 100644 --- a/crates/web-sys/tests/wasm/main.rs +++ b/crates/web-sys/tests/wasm/main.rs @@ -48,6 +48,7 @@ pub mod pre_element; pub mod progress_element; pub mod quote_element; pub mod response; +pub mod rtc_rtp_transceiver_direction; pub mod script_element; pub mod select_element; pub mod slot_element; diff --git a/crates/web-sys/tests/wasm/rtc_rtp_transceiver_direction.rs b/crates/web-sys/tests/wasm/rtc_rtp_transceiver_direction.rs new file mode 100644 index 00000000..15dbe7aa --- /dev/null +++ b/crates/web-sys/tests/wasm/rtc_rtp_transceiver_direction.rs @@ -0,0 +1,92 @@ +use wasm_bindgen::{prelude::*, JsCast}; +use wasm_bindgen_futures::JsFuture; +use wasm_bindgen_test::*; + +use futures::{ + future::{ok, IntoFuture}, + Future, +}; + +use web_sys::{ + RtcPeerConnection, RtcRtpTransceiver, RtcRtpTransceiverDirection, RtcRtpTransceiverInit, + RtcSessionDescriptionInit, +}; + +#[wasm_bindgen( + inline_js = "export function is_unified_avail() { return Object.keys(RTCRtpTransceiver.prototype).indexOf('currentDirection')>-1; }" +)] +extern "C" { + /// Available in FF since forever, in Chrome since 72, in Safari since 12.1 + fn is_unified_avail() -> bool; +} + +#[wasm_bindgen_test(async)] +fn rtc_rtp_transceiver_direction() -> Box> { + if !is_unified_avail() { + return Box::new(Ok(()).into_future()); + } + + let mut tr_init: RtcRtpTransceiverInit = RtcRtpTransceiverInit::new(); + + let pc1: RtcPeerConnection = RtcPeerConnection::new().unwrap(); + + let tr1: RtcRtpTransceiver = pc1.add_transceiver_with_str_and_init( + "audio", + tr_init.direction(RtcRtpTransceiverDirection::Sendonly), + ); + assert_eq!(tr1.direction(), RtcRtpTransceiverDirection::Sendonly); + assert_eq!(tr1.current_direction(), None); + + let pc2: RtcPeerConnection = RtcPeerConnection::new().unwrap(); + + let r = exchange_sdps(pc1, pc2).and_then(move |(_, p2)| { + assert_eq!(tr1.direction(), RtcRtpTransceiverDirection::Sendonly); + assert_eq!( + tr1.current_direction(), + Some(RtcRtpTransceiverDirection::Sendonly) + ); + + let tr2: RtcRtpTransceiver = js_sys::try_iter(&p2.get_transceivers()) + .unwrap() + .unwrap() + .next() + .unwrap() + .unwrap() + .unchecked_into(); + + assert_eq!(tr2.direction(), RtcRtpTransceiverDirection::Recvonly); + assert_eq!( + tr2.current_direction(), + Some(RtcRtpTransceiverDirection::Recvonly) + ); + + Ok(()) + }); + + Box::new(r) +} + +fn exchange_sdps( + p1: RtcPeerConnection, + p2: RtcPeerConnection, +) -> impl Future { + JsFuture::from(p1.create_offer()) + .and_then(move |offer| { + let offer = offer.unchecked_into::(); + JsFuture::from(p1.set_local_description(&offer)).join4( + JsFuture::from(p2.set_remote_description(&offer)), + Ok(p1), + Ok(p2), + ) + }) + .and_then(|(_, _, p1, p2)| JsFuture::from(p2.create_answer()).join3(Ok(p1), Ok(p2))) + .and_then(|(answer, p1, p2)| { + let answer = answer.unchecked_into::(); + JsFuture::from(p2.set_local_description(&answer)).join4( + JsFuture::from(p1.set_remote_description(&answer)), + Ok(p1), + Ok(p2), + ) + }) + .and_then(|(_, _, p1, p2)| Ok((p1, p2))) +} diff --git a/crates/web-sys/webidls/unavailable_option_primitive/RTCRtpTransceiver.webidl b/crates/web-sys/webidls/enabled/RTCRtpTransceiver.webidl similarity index 100% rename from crates/web-sys/webidls/unavailable_option_primitive/RTCRtpTransceiver.webidl rename to crates/web-sys/webidls/enabled/RTCRtpTransceiver.webidl diff --git a/crates/webidl-tests/enums.js b/crates/webidl-tests/enums.js index fa23ec81..1750f69e 100644 --- a/crates/webidl-tests/enums.js +++ b/crates/webidl-tests/enums.js @@ -18,4 +18,12 @@ global.Shape = class Shape { getShape() { return this.kind; } + + get shapeTypeNone() { + return null; + } + + get shapeTypeSome() { + return this.kind; + } }; diff --git a/crates/webidl-tests/enums.rs b/crates/webidl-tests/enums.rs index b5e0949e..23f258c2 100644 --- a/crates/webidl-tests/enums.rs +++ b/crates/webidl-tests/enums.rs @@ -35,3 +35,17 @@ fn invalid_enum_return() { _ => {} // Success }; } + +#[wasm_bindgen_test] +fn read_optional_enum_attribute_none() { + let shape = Shape::new(ShapeType::Circle).unwrap(); + let shape_type: Option = shape.shape_type_none(); + assert_eq!(shape_type, None); +} + +#[wasm_bindgen_test] +fn read_optional_enum_attribute_some() { + let shape = Shape::new(ShapeType::Circle).unwrap(); + let shape_type: Option = shape.shape_type_some(); + assert_eq!(shape_type, Some(ShapeType::Circle)); +} diff --git a/crates/webidl-tests/enums.webidl b/crates/webidl-tests/enums.webidl index acd043b3..b200888d 100644 --- a/crates/webidl-tests/enums.webidl +++ b/crates/webidl-tests/enums.webidl @@ -12,4 +12,8 @@ interface Shape { [Pure] ShapeType getShape(); + + readonly attribute ShapeType? shapeTypeNone; + + readonly attribute ShapeType? shapeTypeSome; }; diff --git a/guide/src/SUMMARY.md b/guide/src/SUMMARY.md index 077c50cb..530b1286 100644 --- a/guide/src/SUMMARY.md +++ b/guide/src/SUMMARY.md @@ -66,6 +66,7 @@ - [`js_namespace`](./reference/attributes/on-js-imports/js_namespace.md) - [`method`](./reference/attributes/on-js-imports/method.md) - [`module = "blah"`](./reference/attributes/on-js-imports/module.md) + - [`raw_module = "blah"`](./reference/attributes/on-js-imports/raw_module.md) - [`static_method_of = Blah`](./reference/attributes/on-js-imports/static_method_of.md) - [`structural`](./reference/attributes/on-js-imports/structural.md) - [`variadic`](./reference/attributes/on-js-imports/variadic.md) diff --git a/guide/src/reference/attributes/on-js-imports/module.md b/guide/src/reference/attributes/on-js-imports/module.md index 4f6b4408..24643e42 100644 --- a/guide/src/reference/attributes/on-js-imports/module.md +++ b/guide/src/reference/attributes/on-js-imports/module.md @@ -31,3 +31,8 @@ generates JavaScript import glue like: ```js let illmatic = this.illmatic; ``` + +Note that if the string specified with `module` starts with `./`, `../`, or `/` +then it's interpreted as a path to a [local JS snippet](../../js-snippets.html). +If this doesn't work for your use case you might be interested in the +[`raw_module` attribute](raw_module.html) diff --git a/guide/src/reference/attributes/on-js-imports/raw_module.md b/guide/src/reference/attributes/on-js-imports/raw_module.md new file mode 100644 index 00000000..1f8da2f3 --- /dev/null +++ b/guide/src/reference/attributes/on-js-imports/raw_module.md @@ -0,0 +1,19 @@ +# `raw_module = "blah"` + +This attribute performs exactly the same purpose as the [`module` +attribute](module.html) on JS imports, but it does not attempt to interpret +paths starting with `./`, `../`, or `/` as JS snippets. For example: + +```rust +#[wasm_bindgen(raw_module = "./some/js/file.js")] +extern "C" { + fn the_function(); +} +``` + +Note that if you use this attribute with a relative or absolute path, it's +likely up to the final bundler or project to assign meaning to that path. This +typically means that the JS file or module will be resolved relative to the +final location of the wasm file itself. That means that `raw_module` is likely +unsuitable for libraries on crates.io, but may be usable within end-user +applications. diff --git a/package.json b/package.json index 22697755..febc4025 100644 --- a/package.json +++ b/package.json @@ -4,7 +4,7 @@ "serve": "webpack-dev-server -p" }, "devDependencies": { - "@wasm-tool/wasm-pack-plugin": "0.2.5", + "@wasm-tool/wasm-pack-plugin": "0.2.7", "html-webpack-plugin": "^3.2.0", "text-encoding": "^0.7.0", "webpack": "^4.29.4",