Skip to content

Commit

Permalink
Add importing strings as JsString (#4055)
Browse files Browse the repository at this point in the history
  • Loading branch information
daxpedda authored Aug 6, 2024
1 parent 33ea44f commit 0b1cce6
Show file tree
Hide file tree
Showing 12 changed files with 252 additions and 35 deletions.
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,9 @@
* Added an experimental Node.JS ES module target, in comparison the current `node` target uses CommonJS, with `--target experimental-nodejs-module` or when testing with `wasm_bindgen_test_configure!(run_in_node_experimental)`.
[#4027](https://github.com/rustwasm/wasm-bindgen/pull/4027)

* Added importing strings as `JsString` through `#[wasm_bindgen(static_string)] static STRING: JsString = "a string literal";`.
[#4055](https://github.com/rustwasm/wasm-bindgen/pull/4055)

### Changed

* Stabilize Web Share API.
Expand Down
26 changes: 26 additions & 0 deletions crates/backend/src/ast.rs
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,8 @@ pub struct Program {
pub inline_js: Vec<String>,
/// Path to wasm_bindgen
pub wasm_bindgen: Path,
/// Path to js_sys
pub js_sys: Path,
/// Path to wasm_bindgen_futures
pub wasm_bindgen_futures: Path,
}
Expand All @@ -44,6 +46,7 @@ impl Default for Program {
typescript_custom_sections: Default::default(),
inline_js: Default::default(),
wasm_bindgen: syn::parse_quote! { wasm_bindgen },
js_sys: syn::parse_quote! { js_sys },
wasm_bindgen_futures: syn::parse_quote! { wasm_bindgen_futures },
}
}
Expand Down Expand Up @@ -160,6 +163,8 @@ pub enum ImportKind {
Function(ImportFunction),
/// Importing a static value
Static(ImportStatic),
/// Importing a static string
String(ImportString),
/// Importing a type/class
Type(ImportType),
/// Importing a JS enum
Expand Down Expand Up @@ -272,6 +277,26 @@ pub struct ImportStatic {
pub wasm_bindgen: Path,
}

/// The type of a static string being imported
#[cfg_attr(feature = "extra-traits", derive(Debug, PartialEq, Eq))]
#[derive(Clone)]
pub struct ImportString {
/// The visibility of this static string in Rust
pub vis: syn::Visibility,
/// The type specified by the user, which we only use to show an error if the wrong type is used.
pub ty: syn::Type,
/// The name of the shim function used to access this static
pub shim: Ident,
/// The name of this static on the Rust side
pub rust_name: Ident,
/// Path to wasm_bindgen
pub wasm_bindgen: Path,
/// Path to js_sys
pub js_sys: Path,
/// The string to export.
pub string: String,
}

/// The metadata for a type being imported
#[cfg_attr(feature = "extra-traits", derive(Debug, PartialEq, Eq))]
#[derive(Clone)]
Expand Down Expand Up @@ -502,6 +527,7 @@ impl ImportKind {
match *self {
ImportKind::Function(_) => true,
ImportKind::Static(_) => false,
ImportKind::String(_) => false,
ImportKind::Type(_) => false,
ImportKind::Enum(_) => false,
}
Expand Down
101 changes: 67 additions & 34 deletions crates/backend/src/codegen.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ use quote::quote_spanned;
use quote::{quote, ToTokens};
use std::collections::{HashMap, HashSet};
use std::sync::Mutex;
use syn::parse_quote;
use syn::spanned::Spanned;
use wasm_bindgen_shared as shared;

Expand Down Expand Up @@ -846,6 +847,7 @@ impl TryToTokens for ast::ImportKind {
match *self {
ast::ImportKind::Function(ref f) => f.try_to_tokens(tokens)?,
ast::ImportKind::Static(ref s) => s.to_tokens(tokens),
ast::ImportKind::String(ref s) => s.to_tokens(tokens),
ast::ImportKind::Type(ref t) => t.to_tokens(tokens),
ast::ImportKind::Enum(ref e) => e.to_tokens(tokens),
}
Expand Down Expand Up @@ -1477,6 +1479,7 @@ impl<'a> ToTokens for DescribeImport<'a> {
let f = match *self.kind {
ast::ImportKind::Function(ref f) => f,
ast::ImportKind::Static(_) => return,
ast::ImportKind::String(_) => return,
ast::ImportKind::Type(_) => return,
ast::ImportKind::Enum(_) => return,
};
Expand Down Expand Up @@ -1641,44 +1644,19 @@ impl ToTokens for ast::Enum {

impl ToTokens for ast::ImportStatic {
fn to_tokens(&self, into: &mut TokenStream) {
let name = &self.rust_name;
let ty = &self.ty;
let shim_name = &self.shim;
let vis = &self.vis;
let wasm_bindgen = &self.wasm_bindgen;

let abi_ret = quote! {
#wasm_bindgen::convert::WasmRet<<#ty as #wasm_bindgen::convert::FromWasmAbi>::Abi>
};
(quote! {
#[automatically_derived]
#vis static #name: #wasm_bindgen::JsStatic<#ty> = {
fn init() -> #ty {
#[link(wasm_import_module = "__wbindgen_placeholder__")]
#[cfg(all(target_arch = "wasm32", target_os = "unknown"))]
extern "C" {
fn #shim_name() -> #abi_ret;
}

#[cfg(not(all(target_arch = "wasm32", target_os = "unknown")))]
unsafe fn #shim_name() -> #abi_ret {
panic!("cannot access imported statics on non-wasm targets")
}

unsafe {
<#ty as #wasm_bindgen::convert::FromWasmAbi>::from_abi(#shim_name().join())
}
}
thread_local!(static _VAL: #ty = init(););
#wasm_bindgen::JsStatic {
__inner: &_VAL,
}
};
})
static_import(
&self.vis,
&self.rust_name,
&self.wasm_bindgen,
ty,
ty,
&self.shim,
)
.to_tokens(into);

Descriptor {
ident: shim_name,
ident: &self.shim,
inner: quote! {
<#ty as WasmDescribe>::describe();
},
Expand All @@ -1689,6 +1667,61 @@ impl ToTokens for ast::ImportStatic {
}
}

impl ToTokens for ast::ImportString {
fn to_tokens(&self, into: &mut TokenStream) {
let js_sys = &self.js_sys;
let actual_ty: syn::Type = parse_quote!(#js_sys::JsString);

static_import(
&self.vis,
&self.rust_name,
&self.wasm_bindgen,
&actual_ty,
&self.ty,
&self.shim,
)
.to_tokens(into);
}
}

fn static_import(
vis: &syn::Visibility,
name: &Ident,
wasm_bindgen: &syn::Path,
actual_ty: &syn::Type,
ty: &syn::Type,
shim_name: &Ident,
) -> TokenStream {
let abi_ret = quote! {
#wasm_bindgen::convert::WasmRet<<#ty as #wasm_bindgen::convert::FromWasmAbi>::Abi>
};
quote! {
#[automatically_derived]
#vis static #name: #wasm_bindgen::JsStatic<#actual_ty> = {
fn init() -> #ty {
#[link(wasm_import_module = "__wbindgen_placeholder__")]
#[cfg(all(target_arch = "wasm32", target_os = "unknown"))]
extern "C" {
fn #shim_name() -> #abi_ret;
}

#[cfg(not(all(target_arch = "wasm32", target_os = "unknown")))]
unsafe fn #shim_name() -> #abi_ret {
panic!("cannot access imported statics on non-wasm targets")
}

unsafe {
<#ty as #wasm_bindgen::convert::FromWasmAbi>::from_abi(#shim_name().join())
}
}
thread_local!(static _VAL: #ty = init(););
#wasm_bindgen::JsStatic {
__inner: &_VAL,
}
};
}
}

/// Emits the necessary glue tokens for "descriptor", generating an appropriate
/// symbol name as well as attributes around the descriptor function itself.
struct Descriptor<'a, T> {
Expand Down
8 changes: 8 additions & 0 deletions crates/backend/src/encode.rs
Original file line number Diff line number Diff line change
Expand Up @@ -297,6 +297,7 @@ fn shared_import_kind<'a>(
Ok(match i {
ast::ImportKind::Function(f) => ImportKind::Function(shared_import_function(f, intern)?),
ast::ImportKind::Static(f) => ImportKind::Static(shared_import_static(f, intern)),
ast::ImportKind::String(f) => ImportKind::String(shared_import_string(f, intern)),
ast::ImportKind::Type(f) => ImportKind::Type(shared_import_type(f, intern)),
ast::ImportKind::Enum(f) => ImportKind::Enum(shared_import_enum(f, intern)),
})
Expand Down Expand Up @@ -332,6 +333,13 @@ fn shared_import_static<'a>(i: &'a ast::ImportStatic, intern: &'a Interner) -> I
}
}

fn shared_import_string<'a>(i: &'a ast::ImportString, intern: &'a Interner) -> ImportString<'a> {
ImportString {
shim: intern.intern(&i.shim),
string: &i.string,
}
}

fn shared_import_type<'a>(i: &'a ast::ImportType, intern: &'a Interner) -> ImportType<'a> {
ImportType {
name: &i.js_name,
Expand Down
13 changes: 13 additions & 0 deletions crates/cli-support/src/js/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3067,6 +3067,19 @@ impl<'a> Context<'a> {
self.import_name(js)
}

AuxImport::String(string) => {
assert!(kind == AdapterJsImportKind::Normal);
assert!(!variadic);
assert_eq!(args.len(), 0);

let mut escaped = String::with_capacity(string.len());
string.chars().for_each(|c| match c {
'`' | '\\' | '$' => escaped.extend(['\\', c]),
_ => escaped.extend([c]),
});
Ok(format!("`{escaped}`"))
}

AuxImport::Closure {
dtor,
mutable,
Expand Down
27 changes: 27 additions & 0 deletions crates/cli-support/src/wit/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -567,6 +567,7 @@ impl<'a> Context<'a> {
match &import.kind {
decode::ImportKind::Function(f) => self.import_function(&import, f),
decode::ImportKind::Static(s) => self.import_static(&import, s),
decode::ImportKind::String(s) => self.import_string(s),
decode::ImportKind::Type(t) => self.import_type(&import, t),
decode::ImportKind::Enum(_) => Ok(()),
}
Expand Down Expand Up @@ -803,6 +804,32 @@ impl<'a> Context<'a> {
Ok(())
}

fn import_string(&mut self, string: &decode::ImportString<'_>) -> Result<(), Error> {
let (import_id, _id) = match self.function_imports.get(string.shim) {
Some(pair) => *pair,
None => return Ok(()),
};

// Register the signature of this imported shim
let id = self.import_adapter(
import_id,
Function {
arguments: Vec::new(),
shim_idx: 0,
ret: Descriptor::Externref,
inner_ret: None,
},
AdapterJsImportKind::Normal,
)?;

// And then save off that this function is is an instanceof shim for an
// imported item.
self.aux
.import_map
.insert(id, AuxImport::String(string.string.to_owned()));
Ok(())
}

fn import_type(
&mut self,
import: &decode::Import<'_>,
Expand Down
3 changes: 3 additions & 0 deletions crates/cli-support/src/wit/nonstandard.rs
Original file line number Diff line number Diff line change
Expand Up @@ -220,6 +220,9 @@ pub enum AuxImport {
/// `JsImport`.
Static(JsImport),

/// This import is expected to be a shim that returns an exported `JsString`.
String(String),

/// This import is intended to manufacture a JS closure with the given
/// signature and then return that back to Rust.
Closure {
Expand Down
Loading

0 comments on commit 0b1cce6

Please sign in to comment.