Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for cfg_attr for struct fields #4351

Merged
merged 6 commits into from
Jan 12, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,10 @@
* Add bindings to `Date.to_locale_time_string_with_options`.
[#4384](https://github.com/rustwasm/wasm-bindgen/pull/4384)

* `#[wasm_bindgen]` now correctly applies `#[cfg(...)]`s in `struct`s.
[#4351](https://github.com/rustwasm/wasm-bindgen/pull/4351)


### Changed

* Optional parameters are now typed as `T | undefined | null` to reflect the actual JS behavior.
Expand Down
37 changes: 35 additions & 2 deletions crates/macro-support/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,10 @@ extern crate wasm_bindgen_backend as backend;
extern crate wasm_bindgen_shared as shared;

pub use crate::parser::BindgenAttrs;
use crate::parser::MacroParse;
use crate::parser::{ConvertToAst, MacroParse};
use backend::{Diagnostic, TryToTokens};
use proc_macro2::TokenStream;
use quote::quote;
use quote::ToTokens;
use quote::TokenStreamExt;
use syn::parse::{Parse, ParseStream, Result as SynResult};
Expand All @@ -24,9 +25,25 @@ mod parser;
/// Takes the parsed input from a `#[wasm_bindgen]` macro and returns the generated bindings
pub fn expand(attr: TokenStream, input: TokenStream) -> Result<TokenStream, Diagnostic> {
parser::reset_attrs_used();
// if struct is encountered, add `derive` attribute and let everything happen there (workaround
// to help parsing cfg_attr correctly).
let item = syn::parse2::<syn::Item>(input)?;
let opts = syn::parse2(attr)?;
if let syn::Item::Struct(s) = item {
let opts: BindgenAttrs = syn::parse2(attr.clone())?;
let wasm_bindgen = opts
.wasm_bindgen()
.cloned()
.unwrap_or_else(|| syn::parse_quote! { wasm_bindgen });

let item = quote! {
#[derive(#wasm_bindgen::__rt::BindgenedStruct)]
#[wasm_bindgen(#attr)]
#s
};
return Ok(item);
}

let opts = syn::parse2(attr)?;
let mut tokens = proc_macro2::TokenStream::new();
let mut program = backend::ast::Program::default();
item.macro_parse(&mut program, (Some(opts), &mut tokens))?;
Expand Down Expand Up @@ -168,3 +185,19 @@ impl Parse for ClassMarker {
})
}
}

pub fn expand_struct_marker(item: TokenStream) -> Result<TokenStream, Diagnostic> {
parser::reset_attrs_used();

let mut s: syn::ItemStruct = syn::parse2(item)?;

let mut program = backend::ast::Program::default();
program.structs.push((&mut s).convert(&program)?);

let mut tokens = proc_macro2::TokenStream::new();
program.try_to_tokens(&mut tokens)?;

parser::check_unused_attrs(&mut tokens);

Ok(tokens)
}
24 changes: 9 additions & 15 deletions crates/macro-support/src/parser.rs
Original file line number Diff line number Diff line change
Expand Up @@ -216,7 +216,7 @@ macro_rules! methods {
};

(@method $name:ident, $variant:ident(Span, String, Span)) => {
fn $name(&self) -> Option<(&str, Span)> {
pub(crate) fn $name(&self) -> Option<(&str, Span)> {
self.attrs
.iter()
.find_map(|a| match &a.1 {
Expand All @@ -230,7 +230,7 @@ macro_rules! methods {
};

(@method $name:ident, $variant:ident(Span, JsNamespace, Vec<Span>)) => {
fn $name(&self) -> Option<(JsNamespace, &[Span])> {
pub(crate) fn $name(&self) -> Option<(JsNamespace, &[Span])> {
self.attrs
.iter()
.find_map(|a| match &a.1 {
Expand All @@ -245,7 +245,7 @@ macro_rules! methods {

(@method $name:ident, $variant:ident(Span, $($other:tt)*)) => {
#[allow(unused)]
fn $name(&self) -> Option<&$($other)*> {
pub(crate) fn $name(&self) -> Option<&$($other)*> {
self.attrs
.iter()
.find_map(|a| match &a.1 {
Expand All @@ -260,7 +260,7 @@ macro_rules! methods {

(@method $name:ident, $variant:ident($($other:tt)*)) => {
#[allow(unused)]
fn $name(&self) -> Option<&$($other)*> {
pub(crate) fn $name(&self) -> Option<&$($other)*> {
self.attrs
.iter()
.find_map(|a| match &a.1 {
Expand Down Expand Up @@ -493,7 +493,7 @@ impl Parse for AnyIdent {
///
/// Used to convert syn tokens into an AST, that we can then use to generate glue code. The context
/// (`Ctx`) is used to pass in the attributes from the `#[wasm_bindgen]`, if needed.
trait ConvertToAst<Ctx> {
pub(crate) trait ConvertToAst<Ctx> {
/// What we are converting to.
type Target;
/// Convert into our target.
Expand All @@ -502,20 +502,19 @@ trait ConvertToAst<Ctx> {
fn convert(self, context: Ctx) -> Result<Self::Target, Diagnostic>;
}

impl ConvertToAst<(&ast::Program, BindgenAttrs)> for &mut syn::ItemStruct {
impl ConvertToAst<&ast::Program> for &mut syn::ItemStruct {
type Target = ast::Struct;

fn convert(
self,
(program, attrs): (&ast::Program, BindgenAttrs),
) -> Result<Self::Target, Diagnostic> {
fn convert(self, program: &ast::Program) -> Result<Self::Target, Diagnostic> {
if !self.generics.params.is_empty() {
bail_span!(
self.generics,
"structs with #[wasm_bindgen] cannot have lifetime or \
type parameters currently"
);
}
let attrs = BindgenAttrs::find(&mut self.attrs)?;

let mut fields = Vec::new();
let js_name = attrs
.js_name()
Expand Down Expand Up @@ -1231,11 +1230,6 @@ impl<'a> MacroParse<(Option<BindgenAttrs>, &'a mut TokenStream)> for syn::Item {
wasm_bindgen_futures: program.wasm_bindgen_futures.clone(),
});
}
syn::Item::Struct(mut s) => {
let opts = opts.unwrap_or_default();
program.structs.push((&mut s).convert((program, opts))?);
s.to_tokens(tokens);
}
syn::Item::Impl(mut i) => {
let opts = opts.unwrap_or_default();
(&mut i).macro_parse(program, opts)?;
Expand Down
13 changes: 13 additions & 0 deletions crates/macro/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -60,3 +60,16 @@ pub fn __wasm_bindgen_class_marker(attr: TokenStream, input: TokenStream) -> Tok
Err(diagnostic) => (quote! { #diagnostic }).into(),
}
}

#[proc_macro_derive(BindgenedStruct, attributes(wasm_bindgen))]
pub fn __wasm_bindgen_struct_marker(item: TokenStream) -> TokenStream {
match wasm_bindgen_macro_support::expand_struct_marker(item.into()) {
Ok(tokens) => {
if cfg!(feature = "xxx_debug_only_print_generated_code") {
println!("{}", tokens);
}
tokens.into()
}
Err(diagnostic) => (quote! { #diagnostic }).into(),
}
}
14 changes: 14 additions & 0 deletions crates/macro/ui-tests/import-keyword.stderr
Original file line number Diff line number Diff line change
Expand Up @@ -57,3 +57,17 @@ error: enum cannot use the JS keyword `switch` as its name
|
63 | pub enum switch {
| ^^^^^^

warning: type `class` should have an upper camel case name
--> ui-tests/import-keyword.rs:59:12
|
59 | pub struct class;
| ^^^^^ help: convert the identifier to upper camel case (notice the capitalization): `Class`
|
= note: `#[warn(non_camel_case_types)]` on by default

warning: type `true` should have an upper camel case name
--> ui-tests/import-keyword.rs:61:12
|
61 | pub struct r#true; // forbid value-like keywords
| ^^^^^^ help: convert the identifier to upper camel case: `True`
2 changes: 1 addition & 1 deletion crates/macro/ui-tests/pub-not-copy.stderr
Original file line number Diff line number Diff line change
Expand Up @@ -12,4 +12,4 @@ note: required by a bound in `assert_copy`
|
3 | #[wasm_bindgen]
| ^^^^^^^^^^^^^^^ required by this bound in `assert_copy`
= note: this error originates in the attribute macro `wasm_bindgen` (in Nightly builds, run with -Z macro-backtrace for more info)
= note: this error originates in the derive macro `wasm_bindgen::__rt::BindgenedStruct` (in Nightly builds, run with -Z macro-backtrace for more info)
2 changes: 1 addition & 1 deletion crates/macro/ui-tests/struct-fields.stderr
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ note: required by a bound in `__wbg_get_bar_a::assert_copy`
|
8 | #[wasm_bindgen]
| ^^^^^^^^^^^^^^^ required by this bound in `assert_copy`
= note: this error originates in the attribute macro `wasm_bindgen` (in Nightly builds, run with -Z macro-backtrace for more info)
= note: this error originates in the derive macro `wasm_bindgen::__rt::BindgenedStruct` (in Nightly builds, run with -Z macro-backtrace for more info)

error[E0277]: the trait bound `Foo: Clone` is not satisfied
--> ui-tests/struct-fields.rs:12:12
Expand Down
2 changes: 2 additions & 0 deletions src/rt/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@ pub extern crate std;

pub mod marker;

pub use wasm_bindgen_macro::BindgenedStruct;

/// Wrapper around [`Lazy`] adding `Send + Sync` when `atomics` is not enabled.
pub struct LazyCell<T, F = fn() -> T>(Wrapper<Lazy<T, F>>);

Expand Down
7 changes: 7 additions & 0 deletions tests/wasm/classes.js
Original file line number Diff line number Diff line change
Expand Up @@ -170,6 +170,13 @@ exports.js_renamed_field = () => {
x.foo();
}

exports.js_conditional_skip = () => {
const x = new wasm.ConditionalSkipClass();
assert.strictEqual(x.skipped_field, undefined);
assert.ok(x.not_skipped_field === 42);
assert.strictEqual(x.needs_clone, 'foo');
}

exports.js_conditional_bindings = () => {
const x = new wasm.ConditionalBindings();
x.free();
Expand Down
36 changes: 36 additions & 0 deletions tests/wasm/classes.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ extern "C" {
fn js_access_fields();
fn js_renamed_export();
fn js_renamed_field();
fn js_conditional_skip();
fn js_conditional_bindings();

fn js_assert_none(a: Option<OptionClass>);
Expand Down Expand Up @@ -480,6 +481,41 @@ fn renamed_field() {
js_renamed_field();
}

#[cfg_attr(
target_arch = "wasm32",
wasm_bindgen(inspectable, js_name = "ConditionalSkipClass")
daxpedda marked this conversation as resolved.
Show resolved Hide resolved
)]
pub struct ConditionalSkip {
/// [u8; 8] cannot be passed to JS, so this won't compile without `skip`
#[cfg_attr(target_arch = "wasm32", wasm_bindgen(skip))]
daxpedda marked this conversation as resolved.
Show resolved Hide resolved
pub skipped_field: [u8; 8],

/// this field shouldn't be skipped as predicate is false
#[cfg_attr(all(target_arch = "wasm32", target_arch = "x86"), wasm_bindgen(skip))]
pub not_skipped_field: u32,

/// String struct field requires `getter_with_clone` to compile
#[cfg_attr(target_arch = "wasm32", wasm_bindgen(getter_with_clone))]
pub needs_clone: String,
}

#[wasm_bindgen(js_class = "ConditionalSkipClass")]
impl ConditionalSkip {
#[wasm_bindgen(constructor)]
pub fn new() -> ConditionalSkip {
ConditionalSkip {
skipped_field: [0u8; 8],
not_skipped_field: 42,
needs_clone: "foo".to_string(),
}
}
}

#[wasm_bindgen_test]
fn conditional_skip() {
js_conditional_skip();
}

#[cfg_attr(target_arch = "wasm32", wasm_bindgen)]
pub struct ConditionalBindings {}

Expand Down
Loading