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

Refactor #[wasm_bindgen] attribute parsing #2880

Closed
wants to merge 7 commits into from
Closed
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
1 change: 1 addition & 0 deletions crates/backend/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ extra-traits = ["syn/extra-traits"]

[dependencies]
bumpalo = "3.0.0"
darling = "0.14.1"
lazy_static = "1.0.2"
log = "0.4"
proc-macro2 = "1.0"
Expand Down
6 changes: 6 additions & 0 deletions crates/backend/src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,12 @@ impl From<Error> for Diagnostic {
}
}

impl From<darling::Error> for Diagnostic {
fn from(err: darling::Error) -> Self {
Self::from(Error::from(err))
}
}

fn extract_spans(node: &dyn ToTokens) -> Option<(Span, Span)> {
let mut t = TokenStream::new();
node.to_tokens(&mut t);
Expand Down
1 change: 1 addition & 0 deletions crates/macro-support/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ extra-traits = ["syn/extra-traits"]
strict-macro = []

[dependencies]
darling = "0.14.1"
syn = { version = '1.0.67', features = ['visit', 'full'] }
quote = '1.0'
proc-macro2 = "1.0"
Expand Down
18 changes: 7 additions & 11 deletions crates/macro-support/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,32 +11,30 @@ extern crate syn;
extern crate wasm_bindgen_backend as backend;
extern crate wasm_bindgen_shared as shared;

pub use crate::parser::BindgenAttrs;
use crate::parser::MacroParse;
use backend::{Diagnostic, TryToTokens};
use proc_macro2::TokenStream;
use quote::ToTokens;
use quote::TokenStreamExt;
use syn::parse::{Parse, ParseStream, Result as SynResult};

mod meta;
mod opt;
mod rhs;

pub(crate) use opt::ParseAttr;

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();
let item = syn::parse2::<syn::Item>(input)?;
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))?;
item.macro_parse(&mut program, (attr, &mut tokens))?;
program.try_to_tokens(&mut tokens)?;

// If we successfully got here then we should have used up all attributes
// and considered all of them to see if they were used. If one was forgotten
// that's a bug on our end, so sanity check here.
parser::assert_all_attrs_checked();

Ok(tokens)
}

Expand All @@ -45,13 +43,11 @@ pub fn expand_class_marker(
attr: TokenStream,
input: TokenStream,
) -> Result<TokenStream, Diagnostic> {
parser::reset_attrs_used();
let mut item = syn::parse2::<syn::ImplItemMethod>(input)?;
let opts: ClassMarker = syn::parse2(attr)?;

let mut program = backend::ast::Program::default();
item.macro_parse(&mut program, (&opts.class, &opts.js_class))?;
parser::assert_all_attrs_checked(); // same as above

// This is where things are slightly different, we are being expanded in the
// context of an impl so we can't inject arbitrary item-like tokens into the
Expand Down
161 changes: 161 additions & 0 deletions crates/macro-support/src/meta.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,161 @@
use std::convert::TryFrom;

use proc_macro2::TokenStream;
use quote::{quote, ToTokens};
use syn::{
ext::IdentExt,
parse::{Parse, ParseStream},
punctuated::Punctuated,
spanned::Spanned,
Ident, Path,
};

/// Convert `self` into a valid `syn::Lit`, preserving spans.
pub trait IntoLit {
/// Convert `self` into a valid literal.
///
/// Implementers should set the span of the returned literal to match that of `self`.
fn into_lit(self) -> syn::Lit;
}

impl IntoLit for syn::Lit {
fn into_lit(self) -> syn::Lit {
self
}
}

/// Create a `syn::Lit` by calling `quote!` and wrapping the contents in quotation marks.
pub fn quote_lit<T: Spanned + ToTokens>(v: &T) -> syn::Lit {
syn::LitStr::new(&quote!(#v).to_string(), v.span()).into()
}

/// A `syn::Meta` that allows values other than literals.
#[derive(Debug, Clone)]
pub enum Meta<T> {
Path(Path),
NameValue(MetaNameValue<T>),
List(MetaList<T>),
}

impl<T: Parse> Meta<T> {
pub fn with_body(path: Path, body: TokenStream) -> syn::Result<Self> {
if body.is_empty() {
Ok(Self::Path(path))
} else {
syn::parse2(quote!(#path(#body)))
}
}
}

impl<T: IntoLit> From<Meta<T>> for syn::Meta {
fn from(v: Meta<T>) -> Self {
match v {
Meta::Path(path) => Self::Path(path),
Meta::List(list) => Self::List(list.into()),
Meta::NameValue(nv) => Self::NameValue(nv.into()),
}
}
}

impl<T: Parse> Parse for Meta<T> {
fn parse(input: ParseStream) -> syn::Result<Self> {
// Look for paths, allowing for the possibility of keywords as idents
let path = if input.fork().parse::<Path>().is_ok() {
input.parse::<Path>()
} else {
input.call(Ident::parse_any).map(Path::from)
}?;

// Decide which variant is being looked at.
if input.peek(Token![=]) {
let eq_token = input.parse::<Token![=]>()?;
let lit = input.parse::<T>()?;
Ok(Self::NameValue(MetaNameValue {
path,
lit,
eq_token,
}))
} else if input.peek(syn::token::Paren) {
let content;
Ok(Self::List(MetaList {
path,
paren_token: parenthesized!(content in input),
nested: content.parse_terminated(NestedMeta::<T>::parse)?,
}))
} else {
Ok(Self::Path(path))
}
}
}

/// Try to parse the body of an attribute as `Self`.
impl<T: Parse> TryFrom<syn::Attribute> for Meta<T> {
type Error = syn::Error;

fn try_from(value: syn::Attribute) -> Result<Self, Self::Error> {
let syn::Attribute { path, tokens, .. } = value;
syn::parse2(quote::quote!(#path #tokens))
}
}

#[derive(Debug, Clone)]
pub enum NestedMeta<T> {
Meta(Meta<T>),
Lit(T),
}

impl<T: IntoLit> From<NestedMeta<T>> for syn::NestedMeta {
fn from(v: NestedMeta<T>) -> Self {
match v {
NestedMeta::Meta(m) => Self::Meta(m.into()),
NestedMeta::Lit(l) => Self::Lit(l.into_lit()),
}
}
}

/// This will only attempt to parse `Meta<T>`.
impl<T: Parse> Parse for NestedMeta<T> {
fn parse(input: ParseStream) -> syn::Result<Self> {
input.parse().map(Self::Meta)
}
}

#[derive(Debug, Clone)]
pub struct MetaList<T> {
pub path: Path,
pub paren_token: syn::token::Paren,
pub nested: Punctuated<NestedMeta<T>, syn::token::Comma>,
}

impl<T: IntoLit> From<MetaList<T>> for syn::MetaList {
fn from(v: MetaList<T>) -> Self {
syn::MetaList {
paren_token: v.paren_token,
path: v.path,
nested: v.nested.into_iter().map(syn::NestedMeta::from).collect(),
}
}
}

#[derive(Debug, Clone)]
pub struct MetaNameValue<T> {
pub path: Path,
pub eq_token: syn::token::Eq,
pub lit: T,
}

impl<T: IntoLit> From<MetaNameValue<T>> for syn::MetaNameValue {
fn from(
MetaNameValue {
path,
lit,
eq_token,
}: MetaNameValue<T>,
) -> Self {
Self {
eq_token,
path,
lit: lit.into_lit(),
}
}
}
Loading