Skip to content

Commit 92a7912

Browse files
committed
Support undeclared static imports
1 parent 94b2dc6 commit 92a7912

File tree

10 files changed

+173
-29
lines changed

10 files changed

+173
-29
lines changed

CHANGELOG.md

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,11 +8,14 @@
88
* Add support for multi-threading in Node.js.
99
[#4318](https://github.com/rustwasm/wasm-bindgen/pull/4318)
1010

11-
### Changed
12-
1311
* Add clear error message to communicate new feature resolver version requirements.
1412
[#4312](https://github.com/rustwasm/wasm-bindgen/pull/4312)
1513

14+
### Changed
15+
16+
* `static FOO: Option<T>` now returns `None` if undeclared in JS instead of throwing an error in JS.
17+
[#4319](https://github.com/rustwasm/wasm-bindgen/pull/4319)
18+
1619
### Fixed
1720

1821
* Fix macro-hygiene for calls to `std::thread_local!`.

crates/cli-support/src/js/mod.rs

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2730,7 +2730,7 @@ __wbg_set_wasm(wasm);"
27302730
| AuxImport::Value(AuxValue::Setter(js, ..))
27312731
| AuxImport::ValueWithThis(js, ..)
27322732
| AuxImport::Instanceof(js)
2733-
| AuxImport::Static(js)
2733+
| AuxImport::Static { js, .. }
27342734
| AuxImport::StructuralClassGetter(js, ..)
27352735
| AuxImport::StructuralClassSetter(js, ..)
27362736
| AuxImport::IndexingGetterOfClass(js)
@@ -3265,11 +3265,22 @@ __wbg_set_wasm(wasm);"
32653265
Ok("result".to_owned())
32663266
}
32673267

3268-
AuxImport::Static(js) => {
3268+
AuxImport::Static { js, optional } => {
32693269
assert!(kind == AdapterJsImportKind::Normal);
32703270
assert!(!variadic);
32713271
assert_eq!(args.len(), 0);
3272-
self.import_name(js)
3272+
let js = self.import_name(js)?;
3273+
3274+
if *optional {
3275+
writeln!(
3276+
prelude,
3277+
"const result = typeof {js} === 'undefined' ? null : {js};"
3278+
)
3279+
.unwrap();
3280+
Ok("result".to_owned())
3281+
} else {
3282+
Ok(js)
3283+
}
32733284
}
32743285

32753286
AuxImport::String(string) => {

crates/cli-support/src/wit/mod.rs

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -788,6 +788,7 @@ impl<'a> Context<'a> {
788788
None => return Ok(()),
789789
Some(d) => d,
790790
};
791+
let optional = matches!(descriptor, Descriptor::Option(_));
791792

792793
// Register the signature of this imported shim
793794
let id = self.import_adapter(
@@ -803,8 +804,10 @@ impl<'a> Context<'a> {
803804

804805
// And then save off that this function is is an instanceof shim for an
805806
// imported item.
806-
let import = self.determine_import(import, static_.name)?;
807-
self.aux.import_map.insert(id, AuxImport::Static(import));
807+
let js = self.determine_import(import, static_.name)?;
808+
self.aux
809+
.import_map
810+
.insert(id, AuxImport::Static { js, optional });
808811
Ok(())
809812
}
810813

crates/cli-support/src/wit/nonstandard.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -233,7 +233,7 @@ pub enum AuxImport {
233233

234234
/// This import is expected to be a shim that returns the JS value named by
235235
/// `JsImport`.
236-
Static(JsImport),
236+
Static { js: JsImport, optional: bool },
237237

238238
/// This import is expected to be a shim that returns an exported `JsString`.
239239
String(String),
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
/* tslint:disable */
2+
/* eslint-disable */
3+
export function exported(): void;

crates/cli/tests/reference/static.js

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
let wasm;
2+
export function __wbg_set_wasm(val) {
3+
wasm = val;
4+
}
5+
6+
7+
function isLikeNone(x) {
8+
return x === undefined || x === null;
9+
}
10+
11+
function addToExternrefTable0(obj) {
12+
const idx = wasm.__externref_table_alloc();
13+
wasm.__wbindgen_export_1.set(idx, obj);
14+
return idx;
15+
}
16+
17+
const lTextDecoder = typeof TextDecoder === 'undefined' ? (0, module.require)('util').TextDecoder : TextDecoder;
18+
19+
let cachedTextDecoder = new lTextDecoder('utf-8', { ignoreBOM: true, fatal: true });
20+
21+
cachedTextDecoder.decode();
22+
23+
let cachedUint8ArrayMemory0 = null;
24+
25+
function getUint8ArrayMemory0() {
26+
if (cachedUint8ArrayMemory0 === null || cachedUint8ArrayMemory0.byteLength === 0) {
27+
cachedUint8ArrayMemory0 = new Uint8Array(wasm.memory.buffer);
28+
}
29+
return cachedUint8ArrayMemory0;
30+
}
31+
32+
function getStringFromWasm0(ptr, len) {
33+
ptr = ptr >>> 0;
34+
return cachedTextDecoder.decode(getUint8ArrayMemory0().subarray(ptr, ptr + len));
35+
}
36+
37+
export function exported() {
38+
wasm.exported();
39+
}
40+
41+
export function __wbg_static_accessor_NAMESPACE_OPTIONAL_c9a4344c544120f4() {
42+
const result = typeof test.NAMESPACE_OPTIONAL === 'undefined' ? null : test.NAMESPACE_OPTIONAL;
43+
const ret = result;
44+
return isLikeNone(ret) ? 0 : addToExternrefTable0(ret);
45+
};
46+
47+
export function __wbg_static_accessor_NAMESPACE_PLAIN_784c8d7f5bbac62a() {
48+
const ret = test.NAMESPACE_PLAIN;
49+
return ret;
50+
};
51+
52+
export function __wbg_static_accessor_OPTIONAL_ade71b6402851d0c() {
53+
const result = typeof OPTIONAL === 'undefined' ? null : OPTIONAL;
54+
const ret = result;
55+
return isLikeNone(ret) ? 0 : addToExternrefTable0(ret);
56+
};
57+
58+
export function __wbg_static_accessor_PLAIN_c0f08eb2f0db194c() {
59+
const ret = PLAIN;
60+
return ret;
61+
};
62+
63+
export function __wbindgen_init_externref_table() {
64+
const table = wasm.__wbindgen_export_1;
65+
const offset = table.grow(4);
66+
table.set(0, undefined);
67+
table.set(offset + 0, undefined);
68+
table.set(offset + 1, null);
69+
table.set(offset + 2, true);
70+
table.set(offset + 3, false);
71+
;
72+
};
73+
74+
export function __wbindgen_throw(arg0, arg1) {
75+
throw new Error(getStringFromWasm0(arg0, arg1));
76+
};
77+

crates/cli/tests/reference/static.rs

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
// DEPENDENCY: js-sys = { path = '{root}/crates/js-sys' }
2+
3+
use wasm_bindgen::prelude::*;
4+
use js_sys::Number;
5+
6+
#[wasm_bindgen]
7+
extern "C" {
8+
#[wasm_bindgen(thread_local_v2)]
9+
static PLAIN: JsValue;
10+
#[wasm_bindgen(thread_local_v2)]
11+
static OPTIONAL: Option<Number>;
12+
#[wasm_bindgen(thread_local_v2, js_namespace = test)]
13+
static NAMESPACE_PLAIN: JsValue;
14+
#[wasm_bindgen(thread_local_v2, js_namespace = test)]
15+
static NAMESPACE_OPTIONAL: Option<Number>;
16+
}
17+
18+
#[wasm_bindgen]
19+
pub fn exported() {
20+
let _ = PLAIN.with(JsValue::clone);
21+
let _ = OPTIONAL.with(Option::clone);
22+
let _ = NAMESPACE_PLAIN.with(JsValue::clone);
23+
let _ = NAMESPACE_OPTIONAL.with(Option::clone);
24+
}

crates/cli/tests/reference/static.wat

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
(module $reference_test.wasm
2+
(type (;0;) (func))
3+
(type (;1;) (func (result i32)))
4+
(import "./reference_test_bg.js" "__wbindgen_init_externref_table" (func (;0;) (type 0)))
5+
(func $__externref_table_alloc (;1;) (type 1) (result i32))
6+
(func $exported (;2;) (type 0))
7+
(table (;0;) 128 externref)
8+
(memory (;0;) 17)
9+
(export "memory" (memory 0))
10+
(export "exported" (func $exported))
11+
(export "__externref_table_alloc" (func $__externref_table_alloc))
12+
(export "__wbindgen_export_1" (table 0))
13+
(export "__wbindgen_start" (func 0))
14+
(@custom "target_features" (after code) "\04+\0amultivalue+\0fmutable-globals+\0freference-types+\08sign-ext")
15+
)
16+

crates/js-sys/src/lib.rs

Lines changed: 14 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -6055,14 +6055,6 @@ pub fn global() -> Object {
60556055
}
60566056

60576057
fn get_global_object() -> Object {
6058-
// This is a bit wonky, but we're basically using `#[wasm_bindgen]`
6059-
// attributes to synthesize imports so we can access properties of the
6060-
// form:
6061-
//
6062-
// * `globalThis.globalThis`
6063-
// * `self.self`
6064-
// * ... (etc)
6065-
//
60666058
// Accessing the global object is not an easy thing to do, and what we
60676059
// basically want is `globalThis` but we can't rely on that existing
60686060
// everywhere. In the meantime we've got the fallbacks mentioned in:
@@ -6076,26 +6068,27 @@ pub fn global() -> Object {
60766068
extern "C" {
60776069
type Global;
60786070

6079-
#[wasm_bindgen(getter, catch, static_method_of = Global, js_class = globalThis, js_name = globalThis)]
6080-
fn get_global_this() -> Result<Object, JsValue>;
6071+
#[wasm_bindgen(thread_local_v2, js_name = globalThis)]
6072+
static GLOBAL_THIS: Option<Object>;
60816073

6082-
#[wasm_bindgen(getter, catch, static_method_of = Global, js_class = self, js_name = self)]
6083-
fn get_self() -> Result<Object, JsValue>;
6074+
#[wasm_bindgen(thread_local_v2, js_name = self)]
6075+
static SELF: Option<Object>;
60846076

6085-
#[wasm_bindgen(getter, catch, static_method_of = Global, js_class = window, js_name = window)]
6086-
fn get_window() -> Result<Object, JsValue>;
6077+
#[wasm_bindgen(thread_local_v2, js_name = window)]
6078+
static WINDOW: Option<Object>;
60876079

6088-
#[wasm_bindgen(getter, catch, static_method_of = Global, js_class = global, js_name = global)]
6089-
fn get_global() -> Result<Object, JsValue>;
6080+
#[wasm_bindgen(thread_local_v2, js_name = global)]
6081+
static GLOBAL: Option<Object>;
60906082
}
60916083

60926084
// The order is important: in Firefox Extension Content Scripts `globalThis`
60936085
// is a Sandbox (not Window), so `globalThis` must be checked after `window`.
6094-
let static_object = Global::get_self()
6095-
.or_else(|_| Global::get_window())
6096-
.or_else(|_| Global::get_global_this())
6097-
.or_else(|_| Global::get_global());
6098-
if let Ok(obj) = static_object {
6086+
let static_object = SELF
6087+
.with(Option::clone)
6088+
.or_else(|| WINDOW.with(Option::clone))
6089+
.or_else(|| GLOBAL_THIS.with(Option::clone))
6090+
.or_else(|| GLOBAL.with(Option::clone));
6091+
if let Some(obj) = static_object {
60996092
if !obj.is_undefined() {
61006093
return obj;
61016094
}

guide/src/reference/static-js-objects.md

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,20 @@ extern "C" {
6565
}
6666
```
6767

68+
## Undeclared
69+
70+
When accessing an in JS undeclared value, it will throw in JS. This can be accounted for by using `Option`.
71+
72+
```rust
73+
extern "C" {
74+
type Crypto;
75+
#[wasm_bindgen(thread_local_v2, js_name = crypto)]
76+
static CRYPTO: Option<Crypto>;
77+
}
78+
```
79+
80+
If `crypto` is undeclared in JS, it will simply return `None` in Rust.
81+
6882
## Static strings
6983

7084
Strings can be imported to avoid going through `TextDecoder/Encoder` when requiring just a `JsString`. This can be useful when dealing with environments where `TextDecoder/Encoder` is not available, like in audio worklets.

0 commit comments

Comments
 (0)