Skip to content

Commit 8acb195

Browse files
authored
Add support for cfg_attr for struct fields (#4351)
1 parent b34ac03 commit 8acb195

File tree

10 files changed

+122
-19
lines changed

10 files changed

+122
-19
lines changed

CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,10 @@
2323
* Add bindings to `Date.to_locale_time_string_with_options`.
2424
[#4384](https://github.com/rustwasm/wasm-bindgen/pull/4384)
2525

26+
* `#[wasm_bindgen]` now correctly applies `#[cfg(...)]`s in `struct`s.
27+
[#4351](https://github.com/rustwasm/wasm-bindgen/pull/4351)
28+
29+
2630
### Changed
2731

2832
* Optional parameters are now typed as `T | undefined | null` to reflect the actual JS behavior.

crates/macro-support/src/lib.rs

Lines changed: 35 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,9 +12,10 @@ extern crate wasm_bindgen_backend as backend;
1212
extern crate wasm_bindgen_shared as shared;
1313

1414
pub use crate::parser::BindgenAttrs;
15-
use crate::parser::MacroParse;
15+
use crate::parser::{ConvertToAst, MacroParse};
1616
use backend::{Diagnostic, TryToTokens};
1717
use proc_macro2::TokenStream;
18+
use quote::quote;
1819
use quote::ToTokens;
1920
use quote::TokenStreamExt;
2021
use syn::parse::{Parse, ParseStream, Result as SynResult};
@@ -24,9 +25,25 @@ mod parser;
2425
/// Takes the parsed input from a `#[wasm_bindgen]` macro and returns the generated bindings
2526
pub fn expand(attr: TokenStream, input: TokenStream) -> Result<TokenStream, Diagnostic> {
2627
parser::reset_attrs_used();
28+
// if struct is encountered, add `derive` attribute and let everything happen there (workaround
29+
// to help parsing cfg_attr correctly).
2730
let item = syn::parse2::<syn::Item>(input)?;
28-
let opts = syn::parse2(attr)?;
31+
if let syn::Item::Struct(s) = item {
32+
let opts: BindgenAttrs = syn::parse2(attr.clone())?;
33+
let wasm_bindgen = opts
34+
.wasm_bindgen()
35+
.cloned()
36+
.unwrap_or_else(|| syn::parse_quote! { wasm_bindgen });
37+
38+
let item = quote! {
39+
#[derive(#wasm_bindgen::__rt::BindgenedStruct)]
40+
#[wasm_bindgen(#attr)]
41+
#s
42+
};
43+
return Ok(item);
44+
}
2945

46+
let opts = syn::parse2(attr)?;
3047
let mut tokens = proc_macro2::TokenStream::new();
3148
let mut program = backend::ast::Program::default();
3249
item.macro_parse(&mut program, (Some(opts), &mut tokens))?;
@@ -168,3 +185,19 @@ impl Parse for ClassMarker {
168185
})
169186
}
170187
}
188+
189+
pub fn expand_struct_marker(item: TokenStream) -> Result<TokenStream, Diagnostic> {
190+
parser::reset_attrs_used();
191+
192+
let mut s: syn::ItemStruct = syn::parse2(item)?;
193+
194+
let mut program = backend::ast::Program::default();
195+
program.structs.push((&mut s).convert(&program)?);
196+
197+
let mut tokens = proc_macro2::TokenStream::new();
198+
program.try_to_tokens(&mut tokens)?;
199+
200+
parser::check_unused_attrs(&mut tokens);
201+
202+
Ok(tokens)
203+
}

crates/macro-support/src/parser.rs

Lines changed: 9 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -250,7 +250,7 @@ macro_rules! methods {
250250
};
251251

252252
(@method $name:ident, $variant:ident(Span, String, Span)) => {
253-
fn $name(&self) -> Option<(&str, Span)> {
253+
pub(crate) fn $name(&self) -> Option<(&str, Span)> {
254254
self.attrs
255255
.iter()
256256
.find_map(|a| match &a.1 {
@@ -264,7 +264,7 @@ macro_rules! methods {
264264
};
265265

266266
(@method $name:ident, $variant:ident(Span, JsNamespace, Vec<Span>)) => {
267-
fn $name(&self) -> Option<(JsNamespace, &[Span])> {
267+
pub(crate) fn $name(&self) -> Option<(JsNamespace, &[Span])> {
268268
self.attrs
269269
.iter()
270270
.find_map(|a| match &a.1 {
@@ -279,7 +279,7 @@ macro_rules! methods {
279279

280280
(@method $name:ident, $variant:ident(Span, $($other:tt)*)) => {
281281
#[allow(unused)]
282-
fn $name(&self) -> Option<&$($other)*> {
282+
pub(crate) fn $name(&self) -> Option<&$($other)*> {
283283
self.attrs
284284
.iter()
285285
.find_map(|a| match &a.1 {
@@ -294,7 +294,7 @@ macro_rules! methods {
294294

295295
(@method $name:ident, $variant:ident($($other:tt)*)) => {
296296
#[allow(unused)]
297-
fn $name(&self) -> Option<&$($other)*> {
297+
pub(crate) fn $name(&self) -> Option<&$($other)*> {
298298
self.attrs
299299
.iter()
300300
.find_map(|a| match &a.1 {
@@ -527,7 +527,7 @@ impl Parse for AnyIdent {
527527
///
528528
/// Used to convert syn tokens into an AST, that we can then use to generate glue code. The context
529529
/// (`Ctx`) is used to pass in the attributes from the `#[wasm_bindgen]`, if needed.
530-
trait ConvertToAst<Ctx> {
530+
pub(crate) trait ConvertToAst<Ctx> {
531531
/// What we are converting to.
532532
type Target;
533533
/// Convert into our target.
@@ -536,20 +536,19 @@ trait ConvertToAst<Ctx> {
536536
fn convert(self, context: Ctx) -> Result<Self::Target, Diagnostic>;
537537
}
538538

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

542-
fn convert(
543-
self,
544-
(program, attrs): (&ast::Program, BindgenAttrs),
545-
) -> Result<Self::Target, Diagnostic> {
542+
fn convert(self, program: &ast::Program) -> Result<Self::Target, Diagnostic> {
546543
if !self.generics.params.is_empty() {
547544
bail_span!(
548545
self.generics,
549546
"structs with #[wasm_bindgen] cannot have lifetime or \
550547
type parameters currently"
551548
);
552549
}
550+
let attrs = BindgenAttrs::find(&mut self.attrs)?;
551+
553552
let mut fields = Vec::new();
554553
let js_name = attrs
555554
.js_name()
@@ -1366,11 +1365,6 @@ impl<'a> MacroParse<(Option<BindgenAttrs>, &'a mut TokenStream)> for syn::Item {
13661365
wasm_bindgen_futures: program.wasm_bindgen_futures.clone(),
13671366
});
13681367
}
1369-
syn::Item::Struct(mut s) => {
1370-
let opts = opts.unwrap_or_default();
1371-
program.structs.push((&mut s).convert((program, opts))?);
1372-
s.to_tokens(tokens);
1373-
}
13741368
syn::Item::Impl(mut i) => {
13751369
let opts = opts.unwrap_or_default();
13761370
(&mut i).macro_parse(program, opts)?;

crates/macro/src/lib.rs

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,3 +60,16 @@ pub fn __wasm_bindgen_class_marker(attr: TokenStream, input: TokenStream) -> Tok
6060
Err(diagnostic) => (quote! { #diagnostic }).into(),
6161
}
6262
}
63+
64+
#[proc_macro_derive(BindgenedStruct, attributes(wasm_bindgen))]
65+
pub fn __wasm_bindgen_struct_marker(item: TokenStream) -> TokenStream {
66+
match wasm_bindgen_macro_support::expand_struct_marker(item.into()) {
67+
Ok(tokens) => {
68+
if cfg!(feature = "xxx_debug_only_print_generated_code") {
69+
println!("{}", tokens);
70+
}
71+
tokens.into()
72+
}
73+
Err(diagnostic) => (quote! { #diagnostic }).into(),
74+
}
75+
}

crates/macro/ui-tests/import-keyword.stderr

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,3 +57,17 @@ error: enum cannot use the JS keyword `switch` as its name
5757
|
5858
63 | pub enum switch {
5959
| ^^^^^^
60+
61+
warning: type `class` should have an upper camel case name
62+
--> ui-tests/import-keyword.rs:59:12
63+
|
64+
59 | pub struct class;
65+
| ^^^^^ help: convert the identifier to upper camel case (notice the capitalization): `Class`
66+
|
67+
= note: `#[warn(non_camel_case_types)]` on by default
68+
69+
warning: type `true` should have an upper camel case name
70+
--> ui-tests/import-keyword.rs:61:12
71+
|
72+
61 | pub struct r#true; // forbid value-like keywords
73+
| ^^^^^^ help: convert the identifier to upper camel case: `True`

crates/macro/ui-tests/pub-not-copy.stderr

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,4 +12,4 @@ note: required by a bound in `assert_copy`
1212
|
1313
3 | #[wasm_bindgen]
1414
| ^^^^^^^^^^^^^^^ required by this bound in `assert_copy`
15-
= note: this error originates in the attribute macro `wasm_bindgen` (in Nightly builds, run with -Z macro-backtrace for more info)
15+
= note: this error originates in the derive macro `wasm_bindgen::__rt::BindgenedStruct` (in Nightly builds, run with -Z macro-backtrace for more info)

crates/macro/ui-tests/struct-fields.stderr

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ note: required by a bound in `__wbg_get_bar_a::assert_copy`
1212
|
1313
8 | #[wasm_bindgen]
1414
| ^^^^^^^^^^^^^^^ required by this bound in `assert_copy`
15-
= note: this error originates in the attribute macro `wasm_bindgen` (in Nightly builds, run with -Z macro-backtrace for more info)
15+
= note: this error originates in the derive macro `wasm_bindgen::__rt::BindgenedStruct` (in Nightly builds, run with -Z macro-backtrace for more info)
1616

1717
error[E0277]: the trait bound `Foo: Clone` is not satisfied
1818
--> ui-tests/struct-fields.rs:12:12

src/rt/mod.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,8 @@ pub extern crate std;
1919

2020
pub mod marker;
2121

22+
pub use wasm_bindgen_macro::BindgenedStruct;
23+
2224
/// Wrapper around [`Lazy`] adding `Send + Sync` when `atomics` is not enabled.
2325
pub struct LazyCell<T, F = fn() -> T>(Wrapper<Lazy<T, F>>);
2426

tests/wasm/classes.js

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -170,6 +170,13 @@ exports.js_renamed_field = () => {
170170
x.foo();
171171
}
172172

173+
exports.js_conditional_skip = () => {
174+
const x = new wasm.ConditionalSkipClass();
175+
assert.strictEqual(x.skipped_field, undefined);
176+
assert.ok(x.not_skipped_field === 42);
177+
assert.strictEqual(x.needs_clone, 'foo');
178+
}
179+
173180
exports.js_conditional_bindings = () => {
174181
const x = new wasm.ConditionalBindings();
175182
x.free();

tests/wasm/classes.rs

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ extern "C" {
2424
fn js_access_fields();
2525
fn js_renamed_export();
2626
fn js_renamed_field();
27+
fn js_conditional_skip();
2728
fn js_conditional_bindings();
2829

2930
fn js_assert_none(a: Option<OptionClass>);
@@ -480,6 +481,41 @@ fn renamed_field() {
480481
js_renamed_field();
481482
}
482483

484+
#[cfg_attr(
485+
target_arch = "wasm32",
486+
wasm_bindgen(inspectable, js_name = "ConditionalSkipClass")
487+
)]
488+
pub struct ConditionalSkip {
489+
/// [u8; 8] cannot be passed to JS, so this won't compile without `skip`
490+
#[cfg_attr(target_arch = "wasm32", wasm_bindgen(skip))]
491+
pub skipped_field: [u8; 8],
492+
493+
/// this field shouldn't be skipped as predicate is false
494+
#[cfg_attr(all(target_arch = "wasm32", target_arch = "x86"), wasm_bindgen(skip))]
495+
pub not_skipped_field: u32,
496+
497+
/// String struct field requires `getter_with_clone` to compile
498+
#[cfg_attr(target_arch = "wasm32", wasm_bindgen(getter_with_clone))]
499+
pub needs_clone: String,
500+
}
501+
502+
#[wasm_bindgen(js_class = "ConditionalSkipClass")]
503+
impl ConditionalSkip {
504+
#[wasm_bindgen(constructor)]
505+
pub fn new() -> ConditionalSkip {
506+
ConditionalSkip {
507+
skipped_field: [0u8; 8],
508+
not_skipped_field: 42,
509+
needs_clone: "foo".to_string(),
510+
}
511+
}
512+
}
513+
514+
#[wasm_bindgen_test]
515+
fn conditional_skip() {
516+
js_conditional_skip();
517+
}
518+
483519
#[cfg_attr(target_arch = "wasm32", wasm_bindgen)]
484520
pub struct ConditionalBindings {}
485521

0 commit comments

Comments
 (0)