Merge pull request #871 from alexcrichton/better-setters

Support `#[wasm_bindgen(setter, js_name = ...)]`
This commit is contained in:
Alex Crichton 2018-09-21 21:39:28 -07:00 committed by GitHub
commit a663a9d410
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 104 additions and 40 deletions

View File

@ -167,6 +167,8 @@ pub struct ImportEnum {
#[derive(Clone)] #[derive(Clone)]
pub struct Function { pub struct Function {
pub name: String, pub name: String,
pub name_span: Span,
pub renamed_via_js_name: bool,
pub arguments: Vec<syn::ArgCaptured>, pub arguments: Vec<syn::ArgCaptured>,
pub ret: Option<syn::Type>, pub ret: Option<syn::Type>,
pub rust_attrs: Vec<syn::Attribute>, pub rust_attrs: Vec<syn::Attribute>,
@ -261,13 +263,20 @@ pub struct DictionaryField {
impl Program { impl Program {
pub(crate) fn shared(&self) -> Result<shared::Program, Diagnostic> { pub(crate) fn shared(&self) -> Result<shared::Program, Diagnostic> {
let mut errors = Vec::new();
let mut imports = Vec::new();
for import in self.imports.iter() {
match import.shared() {
Ok(i) => imports.push(i),
Err(e) => errors.push(e),
}
}
Diagnostic::from_vec(errors)?;
Ok(shared::Program { Ok(shared::Program {
exports: self.exports.iter().map(|a| a.shared()).collect(), exports: self.exports.iter().map(|a| a.shared()).collect(),
structs: self.structs.iter().map(|a| a.shared()).collect(), structs: self.structs.iter().map(|a| a.shared()).collect(),
enums: self.enums.iter().map(|a| a.shared()).collect(), enums: self.enums.iter().map(|a| a.shared()).collect(),
imports: self.imports.iter() imports,
.map(|a| a.shared())
.collect::<Result<_, Diagnostic>>()?,
version: shared::version(), version: shared::version(),
schema_version: shared::SCHEMA_VERSION.to_string(), schema_version: shared::SCHEMA_VERSION.to_string(),
}) })
@ -348,7 +357,7 @@ impl Import {
Ok(shared::Import { Ok(shared::Import {
module: self.module.clone(), module: self.module.clone(),
js_namespace: self.js_namespace.as_ref().map(|s| s.to_string()), js_namespace: self.js_namespace.as_ref().map(|s| s.to_string()),
kind: self.kind.shared(), kind: self.kind.shared()?,
}) })
} }
} }
@ -364,13 +373,13 @@ impl ImportKind {
} }
} }
fn shared(&self) -> shared::ImportKind { fn shared(&self) -> Result<shared::ImportKind, Diagnostic> {
match *self { Ok(match *self {
ImportKind::Function(ref f) => shared::ImportKind::Function(f.shared()), ImportKind::Function(ref f) => shared::ImportKind::Function(f.shared()?),
ImportKind::Static(ref f) => shared::ImportKind::Static(f.shared()), ImportKind::Static(ref f) => shared::ImportKind::Static(f.shared()),
ImportKind::Type(ref f) => shared::ImportKind::Type(f.shared()), ImportKind::Type(ref f) => shared::ImportKind::Type(f.shared()),
ImportKind::Enum(ref f) => shared::ImportKind::Enum(f.shared()), ImportKind::Enum(ref f) => shared::ImportKind::Enum(f.shared()),
} })
} }
} }
@ -383,16 +392,28 @@ impl ImportFunction {
/// If the rust object has a `fn set_xxx(&mut self, MyType)` style method, get the name /// If the rust object has a `fn set_xxx(&mut self, MyType)` style method, get the name
/// for a setter in javascript (in this case `xxx`, so you can write `obj.xxx = val`) /// for a setter in javascript (in this case `xxx`, so you can write `obj.xxx = val`)
fn infer_setter_property(&self) -> String { fn infer_setter_property(&self) -> Result<String, Diagnostic> {
let name = self.function.name.to_string(); let name = self.function.name.to_string();
if !name.starts_with("set_") {
panic!("error: setters must start with `set_`, found: {}", name); // if `#[wasm_bindgen(js_name = "...")]` is used then that explicitly
// because it was hand-written anyway.
if self.function.renamed_via_js_name {
return Ok(name)
} }
name[4..].to_string()
// Otherwise we infer names based on the Rust function name.
if !name.starts_with("set_") {
bail_span!(
syn::token::Pub(self.function.name_span),
"setters must start with `set_`, found: {}",
name,
);
}
Ok(name[4..].to_string())
} }
fn shared(&self) -> shared::ImportFunction { fn shared(&self) -> Result<shared::ImportFunction, Diagnostic> {
let shared_operation = |operation: &Operation| { let shared_operation = |operation: &Operation| -> Result<_, Diagnostic> {
let is_static = operation.is_static; let is_static = operation.is_static;
let kind = match &operation.kind { let kind = match &operation.kind {
OperationKind::Regular => shared::OperationKind::Regular, OperationKind::Regular => shared::OperationKind::Regular,
@ -405,14 +426,17 @@ impl ImportFunction {
OperationKind::Setter(s) => { OperationKind::Setter(s) => {
let s = s.as_ref().map(|s| s.to_string()); let s = s.as_ref().map(|s| s.to_string());
shared::OperationKind::Setter( shared::OperationKind::Setter(
s.unwrap_or_else(|| self.infer_setter_property()), match s {
) Some(s) => s,
None => self.infer_setter_property()?,
}
)
} }
OperationKind::IndexingGetter => shared::OperationKind::IndexingGetter, OperationKind::IndexingGetter => shared::OperationKind::IndexingGetter,
OperationKind::IndexingSetter => shared::OperationKind::IndexingSetter, OperationKind::IndexingSetter => shared::OperationKind::IndexingSetter,
OperationKind::IndexingDeleter => shared::OperationKind::IndexingDeleter, OperationKind::IndexingDeleter => shared::OperationKind::IndexingDeleter,
}; };
shared::Operation { is_static, kind } Ok(shared::Operation { is_static, kind })
}; };
let method = match self.kind { let method = match self.kind {
@ -424,7 +448,7 @@ impl ImportFunction {
let kind = match kind { let kind = match kind {
MethodKind::Constructor => shared::MethodKind::Constructor, MethodKind::Constructor => shared::MethodKind::Constructor,
MethodKind::Operation(op) => { MethodKind::Operation(op) => {
shared::MethodKind::Operation(shared_operation(op)) shared::MethodKind::Operation(shared_operation(op)?)
} }
}; };
Some(shared::MethodData { Some(shared::MethodData {
@ -435,14 +459,14 @@ impl ImportFunction {
ImportFunctionKind::Normal => None, ImportFunctionKind::Normal => None,
}; };
shared::ImportFunction { Ok(shared::ImportFunction {
shim: self.shim.to_string(), shim: self.shim.to_string(),
catch: self.catch, catch: self.catch,
variadic: self.variadic, variadic: self.variadic,
method, method,
structural: self.structural, structural: self.structural,
function: self.function.shared(), function: self.function.shared(),
} })
} }
} }

View File

@ -158,11 +158,11 @@ impl BindgenAttrs {
} }
/// Get the first js_name attribute /// Get the first js_name attribute
fn js_name(&self) -> Option<&str> { fn js_name(&self) -> Option<(&str, Span)> {
self.attrs self.attrs
.iter() .iter()
.filter_map(|a| match a { .filter_map(|a| match a {
BindgenAttr::JsName(s) => Some(&s[..]), BindgenAttr::JsName(s, span) => Some((&s[..], *span)),
_ => None, _ => None,
}).next() }).next()
} }
@ -221,7 +221,7 @@ pub enum BindgenAttr {
IndexingDeleter, IndexingDeleter,
Structural, Structural,
Readonly, Readonly,
JsName(String), JsName(String, Span),
JsClass(String), JsClass(String),
Extends(Ident), Extends(Ident),
Variadic, Variadic,
@ -294,11 +294,14 @@ impl Parse for BindgenAttr {
} }
if attr == "js_name" { if attr == "js_name" {
input.parse::<Token![=]>()?; input.parse::<Token![=]>()?;
let val = match input.parse::<syn::LitStr>() { let (val, span) = match input.parse::<syn::LitStr>() {
Ok(str) => str.value(), Ok(str) => (str.value(), str.span()),
Err(_) => input.parse::<AnyIdent>()?.0.to_string(), Err(_) => {
let ident = input.parse::<AnyIdent>()?.0;
(ident.to_string(), ident.span())
}
}; };
return Ok(BindgenAttr::JsName(val)) return Ok(BindgenAttr::JsName(val, span))
} }
Err(original.error("unknown attribute")) Err(original.error("unknown attribute"))
@ -387,11 +390,9 @@ impl<'a> ConvertToAst<(BindgenAttrs, &'a Option<String>)> for syn::ForeignItemFn
self, self,
(opts, module): (BindgenAttrs, &'a Option<String>), (opts, module): (BindgenAttrs, &'a Option<String>),
) -> Result<Self::Target, Diagnostic> { ) -> Result<Self::Target, Diagnostic> {
let default_name = self.ident.to_string();
let js_name = opts.js_name().unwrap_or(&default_name);
let wasm = function_from_decl( let wasm = function_from_decl(
js_name, &self.ident,
&opts,
self.decl.clone(), self.decl.clone(),
self.attrs.clone(), self.attrs.clone(),
self.vis.clone(), self.vis.clone(),
@ -516,7 +517,7 @@ impl<'a> ConvertToAst<(BindgenAttrs, &'a Option<String>)> for syn::ForeignItemFn
let data = (ns, &self.ident, module); let data = (ns, &self.ident, module);
format!( format!(
"__wbg_{}_{}", "__wbg_{}_{}",
js_name wasm.name
.chars() .chars()
.filter(|c| c.is_ascii_alphanumeric()) .filter(|c| c.is_ascii_alphanumeric())
.collect::<String>(), .collect::<String>(),
@ -544,6 +545,7 @@ impl ConvertToAst<BindgenAttrs> for syn::ForeignItemType {
assert_not_variadic(&attrs, &self)?; assert_not_variadic(&attrs, &self)?;
let js_name = attrs let js_name = attrs
.js_name() .js_name()
.map(|s| s.0)
.map_or_else(|| self.ident.to_string(), |s| s.to_string()); .map_or_else(|| self.ident.to_string(), |s| s.to_string());
let shim = format!("__wbg_instanceof_{}_{}", self.ident, ShortHash(&self.ident)); let shim = format!("__wbg_instanceof_{}_{}", self.ident, ShortHash(&self.ident));
Ok(ast::ImportKind::Type(ast::ImportType { Ok(ast::ImportKind::Type(ast::ImportType {
@ -569,7 +571,7 @@ impl<'a> ConvertToAst<(BindgenAttrs, &'a Option<String>)> for syn::ForeignItemSt
} }
assert_not_variadic(&opts, &self)?; assert_not_variadic(&opts, &self)?;
let default_name = self.ident.to_string(); let default_name = self.ident.to_string();
let js_name = opts.js_name().unwrap_or(&default_name); let js_name = opts.js_name().map(|p| p.0).unwrap_or(&default_name);
let shim = format!( let shim = format!(
"__wbg_static_accessor_{}_{}", "__wbg_static_accessor_{}_{}",
self.ident, self.ident,
@ -604,15 +606,14 @@ impl ConvertToAst<BindgenAttrs> for syn::ItemFn {
} }
assert_not_variadic(&attrs, &self)?; assert_not_variadic(&attrs, &self)?;
let default_name = self.ident.to_string(); Ok(function_from_decl(&self.ident, &attrs, self.decl, self.attrs, self.vis, false, None)?.0)
let name = attrs.js_name().unwrap_or(&default_name);
Ok(function_from_decl(name, self.decl, self.attrs, self.vis, false, None)?.0)
} }
} }
/// Construct a function (and gets the self type if appropriate) for our AST from a syn function. /// Construct a function (and gets the self type if appropriate) for our AST from a syn function.
fn function_from_decl( fn function_from_decl(
name: &str, decl_name: &syn::Ident,
opts: &BindgenAttrs,
decl: Box<syn::FnDecl>, decl: Box<syn::FnDecl>,
attrs: Vec<syn::Attribute>, attrs: Vec<syn::Attribute>,
vis: syn::Visibility, vis: syn::Visibility,
@ -683,9 +684,12 @@ fn function_from_decl(
syn::ReturnType::Type(_, ty) => Some(replace_self(*ty)), syn::ReturnType::Type(_, ty) => Some(replace_self(*ty)),
}; };
let js_name = opts.js_name();
Ok(( Ok((
ast::Function { ast::Function {
name: name.to_string(), name: js_name.map(|s| s.0.to_string()).unwrap_or(decl_name.to_string()),
name_span: js_name.map(|s| s.1).unwrap_or(decl_name.span()),
renamed_via_js_name: js_name.is_some(),
arguments, arguments,
ret, ret,
rust_vis: vis, rust_vis: vis,
@ -859,7 +863,8 @@ impl<'a, 'b> MacroParse<()> for (&'a Ident, &'b mut syn::ImplItem) {
}; };
let (function, method_self) = function_from_decl( let (function, method_self) = function_from_decl(
opts.js_name().unwrap_or(&method.sig.ident.to_string()), &method.sig.ident,
&opts,
Box::new(method.sig.decl.clone()), Box::new(method.sig.decl.clone()),
method.attrs.clone(), method.attrs.clone(),
method.vis.clone(), method.vis.clone(),

View File

@ -0,0 +1,19 @@
extern crate wasm_bindgen;
use wasm_bindgen::prelude::*;
#[wasm_bindgen]
extern "C" {
type A;
#[wasm_bindgen(setter, method)]
fn a(this: &A, b: i32);
#[wasm_bindgen(setter = x, method)]
fn b(this: &A, b: i32);
#[wasm_bindgen(setter, method, js_name = x)]
fn c(this: &A, b: i32);
}
fn main() {}

View File

@ -0,0 +1,8 @@
error: setters must start with `set_`, found: a
--> $DIR/invalid-setter.rs:10:8
|
10 | fn a(this: &A, b: i32);
| ^
error: aborting due to previous error

View File

@ -4,7 +4,7 @@ use std::ptr;
use backend; use backend;
use backend::util::{ident_ty, leading_colon_path_ty, raw_ident, rust_ident}; use backend::util::{ident_ty, leading_colon_path_ty, raw_ident, rust_ident};
use heck::{CamelCase, ShoutySnakeCase, SnakeCase}; use heck::{CamelCase, ShoutySnakeCase, SnakeCase};
use proc_macro2::Ident; use proc_macro2::{Ident, Span};
use syn; use syn;
use weedle; use weedle;
use weedle::attribute::{ExtendedAttributeList, ExtendedAttribute}; use weedle::attribute::{ExtendedAttributeList, ExtendedAttribute};
@ -304,6 +304,8 @@ impl<'src> FirstPassRecord<'src> {
Some(backend::ast::ImportFunction { Some(backend::ast::ImportFunction {
function: backend::ast::Function { function: backend::ast::Function {
name: js_name.to_string(), name: js_name.to_string(),
name_span: Span::call_site(),
renamed_via_js_name: false,
arguments, arguments,
ret: ret.clone(), ret: ret.clone(),
rust_attrs: vec![], rust_attrs: vec![],

View File

@ -53,8 +53,12 @@ extern {
fn new() -> RenameProperties; fn new() -> RenameProperties;
#[wasm_bindgen(getter = a, method)] #[wasm_bindgen(getter = a, method)]
fn test(this: &RenameProperties) -> i32; fn test(this: &RenameProperties) -> i32;
#[wasm_bindgen(getter, method, js_name = a)]
fn test2(this: &RenameProperties) -> i32;
#[wasm_bindgen(setter = a, method)] #[wasm_bindgen(setter = a, method)]
fn another(this: &RenameProperties, a: i32); fn another(this: &RenameProperties, a: i32);
#[wasm_bindgen(setter, method, js_name = a)]
fn another2(this: &RenameProperties, a: i32);
/// dox /// dox
pub type AssertImportDenyDocsWorks; pub type AssertImportDenyDocsWorks;
@ -154,6 +158,8 @@ fn rename_setter_getter() {
assert_eq!(a.test(), 1); assert_eq!(a.test(), 1);
a.another(2); a.another(2);
assert_eq!(a.test(), 2); assert_eq!(a.test(), 2);
a.another2(3);
assert_eq!(a.test2(), 3);
} }
/// dox /// dox