blob: 3a15081f7706eaa6a1c5408b5c61fbe45fa98e31 [file] [log] [blame]
use std::cell::Cell;
use backend::ast;
use backend::util::{ident_ty, ShortHash};
use backend::Diagnostic;
use proc_macro2::{Delimiter, Ident, Span, TokenStream, TokenTree};
use quote::ToTokens;
use shared;
use syn;
use syn::parse::{Parse, ParseStream, Result as SynResult};
thread_local!(static ATTRS: AttributeParseState = Default::default());
#[derive(Default)]
struct AttributeParseState {
parsed: Cell<usize>,
checks: Cell<usize>,
}
/// Parsed attributes from a `#[wasm_bindgen(..)]`.
#[cfg_attr(feature = "extra-traits", derive(Debug, PartialEq, Eq))]
pub struct BindgenAttrs {
/// List of parsed attributes
pub attrs: Vec<(Cell<bool>, BindgenAttr)>,
}
macro_rules! attrgen {
($mac:ident) => {
$mac! {
(catch, Catch(Span)),
(constructor, Constructor(Span)),
(method, Method(Span)),
(static_method_of, StaticMethodOf(Span, Ident)),
(js_namespace, JsNamespace(Span, Ident)),
(module, Module(Span, String, Span)),
(raw_module, RawModule(Span, String, Span)),
(inline_js, InlineJs(Span, String, Span)),
(getter, Getter(Span, Option<Ident>)),
(setter, Setter(Span, Option<Ident>)),
(indexing_getter, IndexingGetter(Span)),
(indexing_setter, IndexingSetter(Span)),
(indexing_deleter, IndexingDeleter(Span)),
(structural, Structural(Span)),
(r#final, Final(Span)),
(readonly, Readonly(Span)),
(js_name, JsName(Span, String, Span)),
(js_class, JsClass(Span, String, Span)),
(inspectable, Inspectable(Span)),
(is_type_of, IsTypeOf(Span, syn::Expr)),
(extends, Extends(Span, syn::Path)),
(vendor_prefix, VendorPrefix(Span, Ident)),
(variadic, Variadic(Span)),
(typescript_custom_section, TypescriptCustomSection(Span)),
(skip_typescript, SkipTypescript(Span)),
(start, Start(Span)),
(skip, Skip(Span)),
(typescript_type, TypeScriptType(Span, String, Span)),
// For testing purposes only.
(assert_no_shim, AssertNoShim(Span)),
}
};
}
macro_rules! methods {
($(($name:ident, $variant:ident($($contents:tt)*)),)*) => {
$(methods!(@method $name, $variant($($contents)*));)*
#[cfg(feature = "strict-macro")]
fn check_used(self) -> Result<(), Diagnostic> {
// Account for the fact this method was called
ATTRS.with(|state| state.checks.set(state.checks.get() + 1));
let mut errors = Vec::new();
for (used, attr) in self.attrs.iter() {
if used.get() {
continue
}
// The check below causes rustc to crash on powerpc64 platforms
// with an LLVM error. To avoid this, we instead use #[cfg()]
// and duplicate the function below. See #58516 for details.
/*if !cfg!(feature = "strict-macro") {
continue
}*/
let span = match attr {
$(BindgenAttr::$variant(span, ..) => span,)*
};
errors.push(Diagnostic::span_error(*span, "unused #[wasm_bindgen] attribute"));
}
Diagnostic::from_vec(errors)
}
#[cfg(not(feature = "strict-macro"))]
fn check_used(self) -> Result<(), Diagnostic> {
// Account for the fact this method was called
ATTRS.with(|state| state.checks.set(state.checks.get() + 1));
Ok(())
}
};
(@method $name:ident, $variant:ident(Span, String, Span)) => {
fn $name(&self) -> Option<(&str, Span)> {
self.attrs
.iter()
.filter_map(|a| match &a.1 {
BindgenAttr::$variant(_, s, span) => {
a.0.set(true);
Some((&s[..], *span))
}
_ => None,
})
.next()
}
};
(@method $name:ident, $variant:ident(Span, $($other:tt)*)) => {
#[allow(unused)]
fn $name(&self) -> Option<&$($other)*> {
self.attrs
.iter()
.filter_map(|a| match &a.1 {
BindgenAttr::$variant(_, s) => {
a.0.set(true);
Some(s)
}
_ => None,
})
.next()
}
};
(@method $name:ident, $variant:ident($($other:tt)*)) => {
#[allow(unused)]
fn $name(&self) -> Option<&$($other)*> {
self.attrs
.iter()
.filter_map(|a| match &a.1 {
BindgenAttr::$variant(s) => {
a.0.set(true);
Some(s)
}
_ => None,
})
.next()
}
};
}
impl BindgenAttrs {
/// Find and parse the wasm_bindgen attributes.
fn find(attrs: &mut Vec<syn::Attribute>) -> Result<BindgenAttrs, Diagnostic> {
let mut ret = BindgenAttrs::default();
loop {
let pos = attrs
.iter()
.enumerate()
.find(|&(_, ref m)| m.path.segments[0].ident == "wasm_bindgen")
.map(|a| a.0);
let pos = match pos {
Some(i) => i,
None => return Ok(ret),
};
let attr = attrs.remove(pos);
let mut tts = attr.tokens.clone().into_iter();
let group = match tts.next() {
Some(TokenTree::Group(d)) => d,
Some(_) => bail_span!(attr, "malformed #[wasm_bindgen] attribute"),
None => continue,
};
if tts.next().is_some() {
bail_span!(attr, "malformed #[wasm_bindgen] attribute");
}
if group.delimiter() != Delimiter::Parenthesis {
bail_span!(attr, "malformed #[wasm_bindgen] attribute");
}
let mut attrs: BindgenAttrs = syn::parse2(group.stream())?;
ret.attrs.extend(attrs.attrs.drain(..));
attrs.check_used()?;
}
}
attrgen!(methods);
}
impl Default for BindgenAttrs {
fn default() -> BindgenAttrs {
// Add 1 to the list of parsed attribute sets. We'll use this counter to
// sanity check that we call `check_used` an appropriate number of
// times.
ATTRS.with(|state| state.parsed.set(state.parsed.get() + 1));
BindgenAttrs { attrs: Vec::new() }
}
}
impl Parse for BindgenAttrs {
fn parse(input: ParseStream) -> SynResult<Self> {
let mut attrs = BindgenAttrs::default();
if input.is_empty() {
return Ok(attrs);
}
let opts = syn::punctuated::Punctuated::<_, syn::token::Comma>::parse_terminated(input)?;
attrs.attrs = opts.into_iter().map(|c| (Cell::new(false), c)).collect();
Ok(attrs)
}
}
macro_rules! gen_bindgen_attr {
($(($method:ident, $($variants:tt)*),)*) => {
/// The possible attributes in the `#[wasm_bindgen]`.
#[cfg_attr(feature = "extra-traits", derive(Debug, PartialEq, Eq))]
pub enum BindgenAttr {
$($($variants)*,)*
}
}
}
attrgen!(gen_bindgen_attr);
impl Parse for BindgenAttr {
fn parse(input: ParseStream) -> SynResult<Self> {
let original = input.fork();
let attr: AnyIdent = input.parse()?;
let attr = attr.0;
let attr_span = attr.span();
let attr_string = attr.to_string();
let raw_attr_string = format!("r#{}", attr_string);
macro_rules! parsers {
($(($name:ident, $($contents:tt)*),)*) => {
$(
if attr_string == stringify!($name) || raw_attr_string == stringify!($name) {
parsers!(
@parser
$($contents)*
);
}
)*
};
(@parser $variant:ident(Span)) => ({
return Ok(BindgenAttr::$variant(attr_span));
});
(@parser $variant:ident(Span, Ident)) => ({
input.parse::<Token![=]>()?;
let ident = input.parse::<AnyIdent>()?.0;
return Ok(BindgenAttr::$variant(attr_span, ident))
});
(@parser $variant:ident(Span, Option<Ident>)) => ({
if input.parse::<Token![=]>().is_ok() {
let ident = input.parse::<AnyIdent>()?.0;
return Ok(BindgenAttr::$variant(attr_span, Some(ident)))
} else {
return Ok(BindgenAttr::$variant(attr_span, None));
}
});
(@parser $variant:ident(Span, syn::Path)) => ({
input.parse::<Token![=]>()?;
return Ok(BindgenAttr::$variant(attr_span, input.parse()?));
});
(@parser $variant:ident(Span, syn::Expr)) => ({
input.parse::<Token![=]>()?;
return Ok(BindgenAttr::$variant(attr_span, input.parse()?));
});
(@parser $variant:ident(Span, String, Span)) => ({
input.parse::<Token![=]>()?;
let (val, span) = match input.parse::<syn::LitStr>() {
Ok(str) => (str.value(), str.span()),
Err(_) => {
let ident = input.parse::<AnyIdent>()?.0;
(ident.to_string(), ident.span())
}
};
return Ok(BindgenAttr::$variant(attr_span, val, span))
});
}
attrgen!(parsers);
return Err(original.error("unknown attribute"));
}
}
struct AnyIdent(Ident);
impl Parse for AnyIdent {
fn parse(input: ParseStream) -> SynResult<Self> {
input.step(|cursor| match cursor.ident() {
Some((ident, remaining)) => Ok((AnyIdent(ident), remaining)),
None => Err(cursor.error("expected an identifier")),
})
}
}
/// Conversion trait with context.
///
/// 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> {
/// What we are converting to.
type Target;
/// Convert into our target.
///
/// Since this is used in a procedural macro, use panic to fail.
fn convert(self, context: Ctx) -> Result<Self::Target, Diagnostic>;
}
impl<'a> ConvertToAst<BindgenAttrs> for &'a mut syn::ItemStruct {
type Target = ast::Struct;
fn convert(self, attrs: BindgenAttrs) -> Result<Self::Target, Diagnostic> {
if self.generics.params.len() > 0 {
bail_span!(
self.generics,
"structs with #[wasm_bindgen] cannot have lifetime or \
type parameters currently"
);
}
let mut fields = Vec::new();
let js_name = attrs
.js_name()
.map(|s| s.0.to_string())
.unwrap_or(self.ident.to_string());
let is_inspectable = attrs.inspectable().is_some();
for (i, field) in self.fields.iter_mut().enumerate() {
match field.vis {
syn::Visibility::Public(..) => {}
_ => continue,
}
let (name_str, member) = match &field.ident {
Some(ident) => (ident.to_string(), syn::Member::Named(ident.clone())),
None => (i.to_string(), syn::Member::Unnamed(i.into())),
};
let attrs = BindgenAttrs::find(&mut field.attrs)?;
assert_not_variadic(&attrs)?;
if attrs.skip().is_some() {
attrs.check_used()?;
continue;
}
let comments = extract_doc_comments(&field.attrs);
let getter = shared::struct_field_get(&js_name, &name_str);
let setter = shared::struct_field_set(&js_name, &name_str);
fields.push(ast::StructField {
name: member,
struct_name: self.ident.clone(),
readonly: attrs.readonly().is_some(),
ty: field.ty.clone(),
getter: Ident::new(&getter, Span::call_site()),
setter: Ident::new(&setter, Span::call_site()),
comments,
generate_typescript: attrs.skip_typescript().is_none(),
});
attrs.check_used()?;
}
let generate_typescript = attrs.skip_typescript().is_none();
let comments: Vec<String> = extract_doc_comments(&self.attrs);
attrs.check_used()?;
Ok(ast::Struct {
rust_name: self.ident.clone(),
js_name,
fields,
comments,
is_inspectable,
generate_typescript,
})
}
}
impl<'a> ConvertToAst<(BindgenAttrs, &'a ast::ImportModule)> for syn::ForeignItemFn {
type Target = ast::ImportKind;
fn convert(
self,
(opts, module): (BindgenAttrs, &'a ast::ImportModule),
) -> Result<Self::Target, Diagnostic> {
let wasm = function_from_decl(
&self.sig.ident,
&opts,
self.sig.clone(),
self.attrs.clone(),
self.vis.clone(),
false,
None,
)?
.0;
let catch = opts.catch().is_some();
let variadic = opts.variadic().is_some();
let js_ret = if catch {
// TODO: this assumes a whole bunch:
//
// * The outer type is actually a `Result`
// * The error type is a `JsValue`
// * The actual type is the first type parameter
//
// should probably fix this one day...
extract_first_ty_param(wasm.ret.as_ref())?
} else {
wasm.ret.clone()
};
let operation_kind = operation_kind(&opts);
let kind = if opts.method().is_some() {
let class = wasm.arguments.get(0).ok_or_else(|| {
err_span!(self, "imported methods must have at least one argument")
})?;
let class = match &*class.ty {
syn::Type::Reference(syn::TypeReference {
mutability: None,
elem,
..
}) => &**elem,
_ => bail_span!(
class.ty,
"first argument of method must be a shared reference"
),
};
let class_name = match *class {
syn::Type::Path(syn::TypePath {
qself: None,
ref path,
}) => path,
_ => bail_span!(class, "first argument of method must be a path"),
};
let class_name = extract_path_ident(class_name)?;
let class_name = opts
.js_class()
.map(|p| p.0.into())
.unwrap_or_else(|| class_name.to_string());
let kind = ast::MethodKind::Operation(ast::Operation {
is_static: false,
kind: operation_kind,
});
ast::ImportFunctionKind::Method {
class: class_name,
ty: class.clone(),
kind,
}
} else if let Some(cls) = opts.static_method_of() {
let class = opts
.js_class()
.map(|p| p.0.into())
.unwrap_or_else(|| cls.to_string());
let ty = ident_ty(cls.clone());
let kind = ast::MethodKind::Operation(ast::Operation {
is_static: true,
kind: operation_kind,
});
ast::ImportFunctionKind::Method { class, ty, kind }
} else if opts.constructor().is_some() {
let class = match js_ret {
Some(ref ty) => ty,
_ => bail_span!(self, "constructor returns must be bare types"),
};
let class_name = match *class {
syn::Type::Path(syn::TypePath {
qself: None,
ref path,
}) => path,
_ => bail_span!(self, "return value of constructor must be a bare path"),
};
let class_name = extract_path_ident(class_name)?;
let class_name = opts
.js_class()
.map(|p| p.0.into())
.unwrap_or_else(|| class_name.to_string());
ast::ImportFunctionKind::Method {
class: class_name.to_string(),
ty: class.clone(),
kind: ast::MethodKind::Constructor,
}
} else {
ast::ImportFunctionKind::Normal
};
let shim = {
let ns = match kind {
ast::ImportFunctionKind::Normal => (0, "n"),
ast::ImportFunctionKind::Method { ref class, .. } => (1, &class[..]),
};
let data = (ns, &self.sig.ident, module);
format!(
"__wbg_{}_{}",
wasm.name
.chars()
.filter(|c| c.is_ascii_alphanumeric())
.collect::<String>(),
ShortHash(data)
)
};
if let Some(span) = opts.r#final() {
if opts.structural().is_some() {
let msg = "cannot specify both `structural` and `final`";
return Err(Diagnostic::span_error(*span, msg));
}
}
let assert_no_shim = opts.assert_no_shim().is_some();
let ret = ast::ImportKind::Function(ast::ImportFunction {
function: wasm,
assert_no_shim,
kind,
js_ret,
catch,
variadic,
structural: opts.structural().is_some() || opts.r#final().is_none(),
rust_name: self.sig.ident.clone(),
shim: Ident::new(&shim, Span::call_site()),
doc_comment: None,
});
opts.check_used()?;
Ok(ret)
}
}
impl ConvertToAst<BindgenAttrs> for syn::ForeignItemType {
type Target = ast::ImportKind;
fn convert(self, attrs: BindgenAttrs) -> Result<Self::Target, Diagnostic> {
assert_not_variadic(&attrs)?;
let js_name = attrs
.js_name()
.map(|s| s.0)
.map_or_else(|| self.ident.to_string(), |s| s.to_string());
let typescript_type = attrs.typescript_type().map(|s| s.0.to_string());
let is_type_of = attrs.is_type_of().cloned();
let shim = format!("__wbg_instanceof_{}_{}", self.ident, ShortHash(&self.ident));
let mut extends = Vec::new();
let mut vendor_prefixes = Vec::new();
for (used, attr) in attrs.attrs.iter() {
match attr {
BindgenAttr::Extends(_, e) => {
extends.push(e.clone());
used.set(true);
}
BindgenAttr::VendorPrefix(_, e) => {
vendor_prefixes.push(e.clone());
used.set(true);
}
_ => {}
}
}
attrs.check_used()?;
Ok(ast::ImportKind::Type(ast::ImportType {
vis: self.vis,
attrs: self.attrs,
doc_comment: None,
instanceof_shim: shim,
is_type_of,
rust_name: self.ident,
typescript_type,
js_name,
extends,
vendor_prefixes,
}))
}
}
impl<'a> ConvertToAst<(BindgenAttrs, &'a ast::ImportModule)> for syn::ForeignItemStatic {
type Target = ast::ImportKind;
fn convert(
self,
(opts, module): (BindgenAttrs, &'a ast::ImportModule),
) -> Result<Self::Target, Diagnostic> {
if self.mutability.is_some() {
bail_span!(self.mutability, "cannot import mutable globals yet")
}
assert_not_variadic(&opts)?;
let default_name = self.ident.to_string();
let js_name = opts
.js_name()
.map(|p| p.0)
.unwrap_or(&default_name)
.to_string();
let shim = format!(
"__wbg_static_accessor_{}_{}",
self.ident,
ShortHash((&js_name, module, &self.ident)),
);
opts.check_used()?;
Ok(ast::ImportKind::Static(ast::ImportStatic {
ty: *self.ty,
vis: self.vis,
rust_name: self.ident.clone(),
js_name,
shim: Ident::new(&shim, Span::call_site()),
}))
}
}
impl ConvertToAst<BindgenAttrs> for syn::ItemFn {
type Target = ast::Function;
fn convert(self, attrs: BindgenAttrs) -> Result<Self::Target, Diagnostic> {
match self.vis {
syn::Visibility::Public(_) => {}
_ => bail_span!(self, "can only #[wasm_bindgen] public functions"),
}
if self.sig.constness.is_some() {
bail_span!(
self.sig.constness,
"can only #[wasm_bindgen] non-const functions"
);
}
if self.sig.unsafety.is_some() {
bail_span!(self.sig.unsafety, "can only #[wasm_bindgen] safe functions");
}
assert_not_variadic(&attrs)?;
let ret = function_from_decl(
&self.sig.ident,
&attrs,
self.sig.clone(),
self.attrs,
self.vis,
false,
None,
)?;
attrs.check_used()?;
Ok(ret.0)
}
}
/// Construct a function (and gets the self type if appropriate) for our AST from a syn function.
fn function_from_decl(
decl_name: &syn::Ident,
opts: &BindgenAttrs,
sig: syn::Signature,
attrs: Vec<syn::Attribute>,
vis: syn::Visibility,
allow_self: bool,
self_ty: Option<&Ident>,
) -> Result<(ast::Function, Option<ast::MethodSelf>), Diagnostic> {
if sig.variadic.is_some() {
bail_span!(sig.variadic, "can't #[wasm_bindgen] variadic functions");
}
if sig.generics.params.len() > 0 {
bail_span!(
sig.generics,
"can't #[wasm_bindgen] functions with lifetime or type parameters",
);
}
assert_no_lifetimes(&sig)?;
let syn::Signature { inputs, output, .. } = sig;
let replace_self = |t: syn::Type| {
let self_ty = match self_ty {
Some(i) => i,
None => return t,
};
let path = match t {
syn::Type::Path(syn::TypePath { qself: None, path }) => path,
other => return other,
};
let new_path = if path.segments.len() == 1 && path.segments[0].ident == "Self" {
self_ty.clone().into()
} else {
path
};
syn::Type::Path(syn::TypePath {
qself: None,
path: new_path,
})
};
let mut method_self = None;
let arguments = inputs
.into_iter()
.filter_map(|arg| match arg {
syn::FnArg::Typed(mut c) => {
c.ty = Box::new(replace_self(*c.ty));
Some(c)
}
syn::FnArg::Receiver(r) => {
if !allow_self {
panic!("arguments cannot be `self`")
}
assert!(method_self.is_none());
if r.reference.is_none() {
method_self = Some(ast::MethodSelf::ByValue);
} else if r.mutability.is_some() {
method_self = Some(ast::MethodSelf::RefMutable);
} else {
method_self = Some(ast::MethodSelf::RefShared);
}
None
}
})
.collect::<Vec<_>>();
let ret = match output {
syn::ReturnType::Default => None,
syn::ReturnType::Type(_, ty) => Some(replace_self(*ty)),
};
let (name, name_span, renamed_via_js_name) =
if let Some((js_name, js_name_span)) = opts.js_name() {
(js_name.to_string(), js_name_span, true)
} else {
(decl_name.to_string(), decl_name.span(), false)
};
Ok((
ast::Function {
arguments,
name_span,
name,
renamed_via_js_name,
ret,
rust_attrs: attrs,
rust_vis: vis,
r#async: sig.asyncness.is_some(),
generate_typescript: opts.skip_typescript().is_none(),
},
method_self,
))
}
pub(crate) trait MacroParse<Ctx> {
/// Parse the contents of an object into our AST, with a context if necessary.
///
/// The context is used to have access to the attributes on `#[wasm_bindgen]`, and to allow
/// writing to the output `TokenStream`.
fn macro_parse(self, program: &mut ast::Program, context: Ctx) -> Result<(), Diagnostic>;
}
impl<'a> MacroParse<(Option<BindgenAttrs>, &'a mut TokenStream)> for syn::Item {
fn macro_parse(
self,
program: &mut ast::Program,
(opts, tokens): (Option<BindgenAttrs>, &'a mut TokenStream),
) -> Result<(), Diagnostic> {
match self {
syn::Item::Fn(mut f) => {
let no_mangle = f
.attrs
.iter()
.enumerate()
.filter_map(|(i, m)| m.parse_meta().ok().map(|m| (i, m)))
.find(|(_, m)| m.path().is_ident("no_mangle"));
match no_mangle {
Some((i, _)) => {
f.attrs.remove(i);
}
_ => {}
}
let comments = extract_doc_comments(&f.attrs);
f.to_tokens(tokens);
let opts = opts.unwrap_or_default();
if opts.start().is_some() {
if f.sig.generics.params.len() > 0 {
bail_span!(&f.sig.generics, "the start function cannot have generics",);
}
if f.sig.inputs.len() > 0 {
bail_span!(&f.sig.inputs, "the start function cannot have arguments",);
}
}
let method_kind = ast::MethodKind::Operation(ast::Operation {
is_static: true,
kind: operation_kind(&opts),
});
let rust_name = f.sig.ident.clone();
let start = opts.start().is_some();
program.exports.push(ast::Export {
comments,
function: f.convert(opts)?,
js_class: None,
method_kind,
method_self: None,
rust_class: None,
rust_name,
start,
});
}
syn::Item::Struct(mut s) => {
let opts = opts.unwrap_or_default();
program.structs.push((&mut s).convert(opts)?);
s.to_tokens(tokens);
}
syn::Item::Impl(mut i) => {
let opts = opts.unwrap_or_default();
(&mut i).macro_parse(program, opts)?;
i.to_tokens(tokens);
}
syn::Item::ForeignMod(mut f) => {
let opts = match opts {
Some(opts) => opts,
None => BindgenAttrs::find(&mut f.attrs)?,
};
f.macro_parse(program, opts)?;
}
syn::Item::Enum(mut e) => {
let opts = match opts {
Some(opts) => opts,
None => BindgenAttrs::find(&mut e.attrs)?,
};
e.macro_parse(program, (tokens, opts))?;
}
syn::Item::Const(mut c) => {
let opts = match opts {
Some(opts) => opts,
None => BindgenAttrs::find(&mut c.attrs)?,
};
c.macro_parse(program, opts)?;
}
_ => {
bail_span!(
self,
"#[wasm_bindgen] can only be applied to a function, \
struct, enum, impl, or extern block",
);
}
}
Ok(())
}
}
impl<'a> MacroParse<BindgenAttrs> for &'a mut syn::ItemImpl {
fn macro_parse(
self,
_program: &mut ast::Program,
opts: BindgenAttrs,
) -> Result<(), Diagnostic> {
if self.defaultness.is_some() {
bail_span!(
self.defaultness,
"#[wasm_bindgen] default impls are not supported"
);
}
if self.unsafety.is_some() {
bail_span!(
self.unsafety,
"#[wasm_bindgen] unsafe impls are not supported"
);
}
if let Some((_, path, _)) = &self.trait_ {
bail_span!(path, "#[wasm_bindgen] trait impls are not supported");
}
if self.generics.params.len() > 0 {
bail_span!(
self.generics,
"#[wasm_bindgen] generic impls aren't supported"
);
}
let name = match *self.self_ty {
syn::Type::Path(syn::TypePath {
qself: None,
ref path,
}) => path,
_ => bail_span!(
self.self_ty,
"unsupported self type in #[wasm_bindgen] impl"
),
};
let mut errors = Vec::new();
for item in self.items.iter_mut() {
if let Err(e) = prepare_for_impl_recursion(item, &name, &opts) {
errors.push(e);
}
}
Diagnostic::from_vec(errors)?;
opts.check_used()?;
Ok(())
}
}
// Prepare for recursion into an `impl` block. Here we want to attach an
// internal attribute, `__wasm_bindgen_class_marker`, with any metadata we need
// to pass from the impl to the impl item. Recursive macro expansion will then
// expand the `__wasm_bindgen_class_marker` attribute.
//
// Note that we currently do this because inner items may have things like cfgs
// on them, so we want to expand the impl first, let the insides get cfg'd, and
// then go for the rest.
fn prepare_for_impl_recursion(
item: &mut syn::ImplItem,
class: &syn::Path,
impl_opts: &BindgenAttrs,
) -> Result<(), Diagnostic> {
let method = match item {
syn::ImplItem::Method(m) => m,
syn::ImplItem::Const(_) => {
bail_span!(
&*item,
"const definitions aren't supported with #[wasm_bindgen]"
);
}
syn::ImplItem::Type(_) => bail_span!(
&*item,
"type definitions in impls aren't supported with #[wasm_bindgen]"
),
syn::ImplItem::Macro(_) => {
// In theory we want to allow this, but we have no way of expanding
// the macro and then placing our magical attributes on the expanded
// functions. As a result, just disallow it for now to hopefully
// ward off buggy results from this macro.
bail_span!(&*item, "macros in impls aren't supported");
}
syn::ImplItem::Verbatim(_) => panic!("unparsed impl item?"),
other => bail_span!(other, "failed to parse this item as a known item"),
};
let ident = extract_path_ident(class)?;
let js_class = impl_opts
.js_class()
.map(|s| s.0.to_string())
.unwrap_or(ident.to_string());
method.attrs.insert(
0,
syn::Attribute {
pound_token: Default::default(),
style: syn::AttrStyle::Outer,
bracket_token: Default::default(),
path: syn::parse_quote! { wasm_bindgen::prelude::__wasm_bindgen_class_marker },
tokens: quote::quote! { (#class = #js_class) }.into(),
},
);
Ok(())
}
impl<'a, 'b> MacroParse<(&'a Ident, &'a str)> for &'b mut syn::ImplItemMethod {
fn macro_parse(
self,
program: &mut ast::Program,
(class, js_class): (&'a Ident, &'a str),
) -> Result<(), Diagnostic> {
match self.vis {
syn::Visibility::Public(_) => {}
_ => return Ok(()),
}
if self.defaultness.is_some() {
panic!("default methods are not supported");
}
if self.sig.constness.is_some() {
bail_span!(
self.sig.constness,
"can only #[wasm_bindgen] non-const functions",
);
}
if self.sig.unsafety.is_some() {
bail_span!(self.sig.unsafety, "can only bindgen safe functions",);
}
let opts = BindgenAttrs::find(&mut self.attrs)?;
let comments = extract_doc_comments(&self.attrs);
let (function, method_self) = function_from_decl(
&self.sig.ident,
&opts,
self.sig.clone(),
self.attrs.clone(),
self.vis.clone(),
true,
Some(class),
)?;
let method_kind = if opts.constructor().is_some() {
ast::MethodKind::Constructor
} else {
let is_static = method_self.is_none();
let kind = operation_kind(&opts);
ast::MethodKind::Operation(ast::Operation { is_static, kind })
};
program.exports.push(ast::Export {
comments,
function,
js_class: Some(js_class.to_string()),
method_kind,
method_self,
rust_class: Some(class.clone()),
rust_name: self.sig.ident.clone(),
start: false,
});
opts.check_used()?;
Ok(())
}
}
fn import_enum(enum_: syn::ItemEnum, program: &mut ast::Program) -> Result<(), Diagnostic> {
let mut variants = vec![];
let mut variant_values = vec![];
for v in enum_.variants.iter() {
match v.fields {
syn::Fields::Unit => (),
_ => bail_span!(v.fields, "only C-Style enums allowed with #[wasm_bindgen]"),
}
match &v.discriminant {
Some((
_,
syn::Expr::Lit(syn::ExprLit {
attrs: _,
lit: syn::Lit::Str(str_lit),
}),
)) => {
variants.push(v.ident.clone());
variant_values.push(str_lit.value());
}
Some((_, expr)) => bail_span!(
expr,
"enums with #[wasm_bidngen] cannot mix string and non-string values",
),
None => {
bail_span!(v, "all variants must have a value");
}
}
}
program.imports.push(ast::Import {
module: ast::ImportModule::None,
js_namespace: None,
kind: ast::ImportKind::Enum(ast::ImportEnum {
vis: enum_.vis,
name: enum_.ident,
variants,
variant_values,
rust_attrs: enum_.attrs,
}),
});
Ok(())
}
impl<'a> MacroParse<(&'a mut TokenStream, BindgenAttrs)> for syn::ItemEnum {
fn macro_parse(
self,
program: &mut ast::Program,
(tokens, opts): (&'a mut TokenStream, BindgenAttrs),
) -> Result<(), Diagnostic> {
if self.variants.len() == 0 {
bail_span!(self, "cannot export empty enums to JS");
}
let generate_typescript = opts.skip_typescript().is_none();
opts.check_used()?;
// Check if the first value is a string literal
match self.variants[0].discriminant {
Some((
_,
syn::Expr::Lit(syn::ExprLit {
attrs: _,
lit: syn::Lit::Str(_),
}),
)) => {
return import_enum(self, program);
}
_ => {}
}
let has_discriminant = self.variants[0].discriminant.is_some();
match self.vis {
syn::Visibility::Public(_) => {}
_ => bail_span!(self, "only public enums are allowed with #[wasm_bindgen]"),
}
let variants = self
.variants
.iter()
.enumerate()
.map(|(i, v)| {
match v.fields {
syn::Fields::Unit => (),
_ => bail_span!(v.fields, "only C-Style enums allowed with #[wasm_bindgen]"),
}
// Require that everything either has a discriminant or doesn't.
// We don't really want to get in the business of emulating how
// rustc assigns values to enums.
if v.discriminant.is_some() != has_discriminant {
bail_span!(
v,
"must either annotate discriminant of all variants or none"
);
}
let value = match &v.discriminant {
Some((
_,
syn::Expr::Lit(syn::ExprLit {
attrs: _,
lit: syn::Lit::Int(int_lit),
}),
)) => match int_lit.base10_digits().parse::<u32>() {
Ok(v) => v,
Err(_) => {
bail_span!(
int_lit,
"enums with #[wasm_bindgen] can only support \
numbers that can be represented as u32"
);
}
},
None => i as u32,
Some((_, expr)) => bail_span!(
expr,
"enums with #[wasm_bidngen] may only have \
number literal values",
),
};
Ok(ast::Variant {
name: v.ident.clone(),
value,
})
})
.collect::<Result<Vec<_>, Diagnostic>>()?;
let mut values = variants.iter().map(|v| v.value).collect::<Vec<_>>();
values.sort();
let hole = values
.windows(2)
.filter_map(|window| {
if window[0] + 1 != window[1] {
Some(window[0] + 1)
} else {
None
}
})
.next()
.unwrap_or(*values.last().unwrap() + 1);
for value in values {
assert!(hole != value);
}
let comments = extract_doc_comments(&self.attrs);
self.to_tokens(tokens);
program.enums.push(ast::Enum {
name: self.ident,
variants,
comments,
hole,
generate_typescript,
});
Ok(())
}
}
impl MacroParse<BindgenAttrs> for syn::ItemConst {
fn macro_parse(self, program: &mut ast::Program, opts: BindgenAttrs) -> Result<(), Diagnostic> {
// Shortcut
if opts.typescript_custom_section().is_none() {
bail_span!(self, "#[wasm_bindgen] will not work on constants unless you are defining a #[wasm_bindgen(typescript_custom_section)].");
}
match *self.expr {
syn::Expr::Lit(syn::ExprLit {
lit: syn::Lit::Str(litstr),
..
}) => {
program.typescript_custom_sections.push(litstr.value());
}
_ => {
bail_span!(self, "Expected a string literal to be used with #[wasm_bindgen(typescript_custom_section)].");
}
}
opts.check_used()?;
Ok(())
}
}
impl MacroParse<BindgenAttrs> for syn::ItemForeignMod {
fn macro_parse(self, program: &mut ast::Program, opts: BindgenAttrs) -> Result<(), Diagnostic> {
let mut errors = Vec::new();
match self.abi.name {
Some(ref l) if l.value() == "C" => {}
None => {}
Some(ref other) => {
errors.push(err_span!(
other,
"only foreign mods with the `C` ABI are allowed"
));
}
}
let module = if let Some((name, span)) = opts.module() {
if opts.inline_js().is_some() {
let msg = "cannot specify both `module` and `inline_js`";
errors.push(Diagnostic::span_error(span, msg));
}
if opts.raw_module().is_some() {
let msg = "cannot specify both `module` and `raw_module`";
errors.push(Diagnostic::span_error(span, msg));
}
ast::ImportModule::Named(name.to_string(), span)
} else if let Some((name, span)) = opts.raw_module() {
if opts.inline_js().is_some() {
let msg = "cannot specify both `raw_module` and `inline_js`";
errors.push(Diagnostic::span_error(span, msg));
}
ast::ImportModule::RawNamed(name.to_string(), span)
} else if let Some((js, span)) = opts.inline_js() {
let i = program.inline_js.len();
program.inline_js.push(js.to_string());
ast::ImportModule::Inline(i, span)
} else {
ast::ImportModule::None
};
for item in self.items.into_iter() {
if let Err(e) = item.macro_parse(program, module.clone()) {
errors.push(e);
}
}
Diagnostic::from_vec(errors)?;
opts.check_used()?;
Ok(())
}
}
impl MacroParse<ast::ImportModule> for syn::ForeignItem {
fn macro_parse(
mut self,
program: &mut ast::Program,
module: ast::ImportModule,
) -> Result<(), Diagnostic> {
let item_opts = {
let attrs = match self {
syn::ForeignItem::Fn(ref mut f) => &mut f.attrs,
syn::ForeignItem::Type(ref mut t) => &mut t.attrs,
syn::ForeignItem::Static(ref mut s) => &mut s.attrs,
_ => panic!("only foreign functions/types allowed for now"),
};
BindgenAttrs::find(attrs)?
};
let js_namespace = item_opts.js_namespace().cloned();
let kind = match self {
syn::ForeignItem::Fn(f) => f.convert((item_opts, &module))?,
syn::ForeignItem::Type(t) => t.convert(item_opts)?,
syn::ForeignItem::Static(s) => s.convert((item_opts, &module))?,
_ => panic!("only foreign functions/types allowed for now"),
};
program.imports.push(ast::Import {
module,
js_namespace,
kind,
});
Ok(())
}
}
/// Get the first type parameter of a generic type, errors on incorrect input.
fn extract_first_ty_param(ty: Option<&syn::Type>) -> Result<Option<syn::Type>, Diagnostic> {
let t = match ty {
Some(t) => t,
None => return Ok(None),
};
let path = match *t {
syn::Type::Path(syn::TypePath {
qself: None,
ref path,
}) => path,
_ => bail_span!(t, "must be Result<...>"),
};
let seg = path
.segments
.last()
.ok_or_else(|| err_span!(t, "must have at least one segment"))?;
let generics = match seg.arguments {
syn::PathArguments::AngleBracketed(ref t) => t,
_ => bail_span!(t, "must be Result<...>"),
};
let generic = generics
.args
.first()
.ok_or_else(|| err_span!(t, "must have at least one generic parameter"))?;
let ty = match generic {
syn::GenericArgument::Type(t) => t,
other => bail_span!(other, "must be a type parameter"),
};
match ty {
syn::Type::Tuple(t) if t.elems.len() == 0 => return Ok(None),
_ => {}
}
Ok(Some(ty.clone()))
}
/// Extract the documentation comments from a Vec of attributes
fn extract_doc_comments(attrs: &[syn::Attribute]) -> Vec<String> {
attrs
.iter()
.filter_map(|a| {
// if the path segments include an ident of "doc" we know this
// this is a doc comment
if a.path.segments.iter().any(|s| s.ident.to_string() == "doc") {
Some(
// We want to filter out any Puncts so just grab the Literals
a.tokens.clone().into_iter().filter_map(|t| match t {
TokenTree::Literal(lit) => {
// this will always return the quoted string, we deal with
// that in the cli when we read in the comments
Some(lit.to_string())
}
_ => None,
}),
)
} else {
None
}
})
//Fold up the [[String]] iter we created into Vec<String>
.fold(vec![], |mut acc, a| {
acc.extend(a);
acc
})
}
/// Check there are no lifetimes on the function.
fn assert_no_lifetimes(sig: &syn::Signature) -> Result<(), Diagnostic> {
struct Walk {
diagnostics: Vec<Diagnostic>,
}
impl<'ast> syn::visit::Visit<'ast> for Walk {
fn visit_lifetime(&mut self, i: &'ast syn::Lifetime) {
self.diagnostics.push(err_span!(
&*i,
"it is currently not sound to use lifetimes in function \
signatures"
));
}
}
let mut walk = Walk {
diagnostics: Vec::new(),
};
syn::visit::Visit::visit_signature(&mut walk, sig);
Diagnostic::from_vec(walk.diagnostics)
}
/// This method always fails if the BindgenAttrs contain variadic
fn assert_not_variadic(attrs: &BindgenAttrs) -> Result<(), Diagnostic> {
if let Some(span) = attrs.variadic() {
let msg = "the `variadic` attribute can only be applied to imported \
(`extern`) functions";
return Err(Diagnostic::span_error(*span, msg));
}
Ok(())
}
/// Extracts the last ident from the path
fn extract_path_ident(path: &syn::Path) -> Result<Ident, Diagnostic> {
for segment in path.segments.iter() {
match segment.arguments {
syn::PathArguments::None => {}
_ => bail_span!(path, "paths with type parameters are not supported yet"),
}
}
match path.segments.last() {
Some(value) => Ok(value.ident.clone()),
None => {
bail_span!(path, "empty idents are not supported");
}
}
}
pub fn reset_attrs_used() {
ATTRS.with(|state| {
state.parsed.set(0);
state.checks.set(0);
})
}
pub fn assert_all_attrs_checked() {
ATTRS.with(|state| {
assert_eq!(state.parsed.get(), state.checks.get());
})
}
fn operation_kind(opts: &BindgenAttrs) -> ast::OperationKind {
let mut operation_kind = ast::OperationKind::Regular;
if let Some(g) = opts.getter() {
operation_kind = ast::OperationKind::Getter(g.clone());
}
if let Some(s) = opts.setter() {
operation_kind = ast::OperationKind::Setter(s.clone());
}
if opts.indexing_getter().is_some() {
operation_kind = ast::OperationKind::IndexingGetter;
}
if opts.indexing_setter().is_some() {
operation_kind = ast::OperationKind::IndexingSetter;
}
if opts.indexing_deleter().is_some() {
operation_kind = ast::OperationKind::IndexingDeleter;
}
operation_kind
}