Support by-value self methods (#348)

Refactor slightly to use the same internal support that the other reference
conversions are using.

Closes #329
This commit is contained in:
Alex Crichton 2018-06-28 20:09:11 -05:00 committed by GitHub
parent 9a3ff77ea9
commit e55af85edc
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 143 additions and 43 deletions

View File

@ -17,13 +17,19 @@ pub struct Program {
#[cfg_attr(feature = "extra-traits", derive(Debug, PartialEq, Eq))] #[cfg_attr(feature = "extra-traits", derive(Debug, PartialEq, Eq))]
pub struct Export { pub struct Export {
pub class: Option<Ident>, pub class: Option<Ident>,
pub method: bool, pub method_self: Option<MethodSelf>,
pub mutable: bool,
pub constructor: Option<String>, pub constructor: Option<String>,
pub function: Function, pub function: Function,
pub comments: Vec<String>, pub comments: Vec<String>,
} }
#[cfg_attr(feature = "extra-traits", derive(Debug, PartialEq, Eq))]
pub enum MethodSelf {
ByValue,
RefMutable,
RefShared,
}
#[cfg_attr(feature = "extra-traits", derive(Debug, PartialEq, Eq))] #[cfg_attr(feature = "extra-traits", derive(Debug, PartialEq, Eq))]
pub struct Import { pub struct Import {
pub module: Option<String>, pub module: Option<String>,
@ -170,8 +176,7 @@ impl Program {
f.to_tokens(tokens); f.to_tokens(tokens);
self.exports.push(Export { self.exports.push(Export {
class: None, class: None,
method: false, method_self: None,
mutable: false,
constructor: None, constructor: None,
function: Function::from(f, opts), function: Function::from(f, opts),
comments, comments,
@ -263,7 +268,7 @@ impl Program {
None None
}; };
let (function, mutable) = Function::from_decl( let (function, method_self) = Function::from_decl(
&method.sig.ident, &method.sig.ident,
Box::new(method.sig.decl.clone()), Box::new(method.sig.decl.clone()),
method.attrs.clone(), method.attrs.clone(),
@ -274,8 +279,7 @@ impl Program {
self.exports.push(Export { self.exports.push(Export {
class: Some(class.clone()), class: Some(class.clone()),
method: mutable.is_some(), method_self,
mutable: mutable.unwrap_or(false),
constructor, constructor,
function, function,
comments, comments,
@ -529,7 +533,7 @@ impl Function {
opts: BindgenAttrs, opts: BindgenAttrs,
vis: syn::Visibility, vis: syn::Visibility,
allow_self: bool, allow_self: bool,
) -> (Function, Option<bool>) { ) -> (Function, Option<MethodSelf>) {
if decl.variadic.is_some() { if decl.variadic.is_some() {
panic!("can't bindgen variadic functions") panic!("can't bindgen variadic functions")
} }
@ -541,17 +545,23 @@ impl Function {
let syn::FnDecl { inputs, output, .. } = { *decl }; let syn::FnDecl { inputs, output, .. } = { *decl };
let mut mutable = None; let mut method_self = None;
let arguments = inputs let arguments = inputs
.into_iter() .into_iter()
.filter_map(|arg| match arg { .filter_map(|arg| match arg {
syn::FnArg::Captured(c) => Some(c), syn::FnArg::Captured(c) => Some(c),
syn::FnArg::SelfValue(_) => { syn::FnArg::SelfValue(_) => {
panic!("by-value `self` not yet supported"); assert!(method_self.is_none());
method_self = Some(MethodSelf::ByValue);
None
} }
syn::FnArg::SelfRef(ref a) if allow_self => { syn::FnArg::SelfRef(ref a) if allow_self => {
assert!(mutable.is_none()); assert!(method_self.is_none());
mutable = Some(a.mutability.is_some()); if a.mutability.is_some() {
method_self = Some(MethodSelf::RefMutable);
} else {
method_self = Some(MethodSelf::RefShared);
}
None None
} }
_ => panic!("arguments cannot be `self` or ignored"), _ => panic!("arguments cannot be `self` or ignored"),
@ -572,7 +582,7 @@ impl Function {
rust_vis: vis, rust_vis: vis,
rust_attrs: attrs, rust_attrs: attrs,
}, },
mutable, method_self,
) )
} }
@ -618,9 +628,15 @@ impl Export {
} }
fn shared(&self) -> shared::Export { fn shared(&self) -> shared::Export {
let (method, consumed) = match self.method_self {
Some(MethodSelf::ByValue) => (true, true),
Some(_) => (true, false),
None => (false, false),
};
shared::Export { shared::Export {
class: self.class.as_ref().map(|s| s.to_string()), class: self.class.as_ref().map(|s| s.to_string()),
method: self.method, method,
consumed,
constructor: self.constructor.clone(), constructor: self.constructor.clone(),
function: self.function.shared(), function: self.function.shared(),
comments: self.comments.clone(), comments: self.comments.clone(),

View File

@ -309,16 +309,61 @@ impl ToTokens for ast::Export {
let ret = Ident::new("_ret", Span::call_site()); let ret = Ident::new("_ret", Span::call_site());
let mut offset = 0; let mut offset = 0;
if self.method { if self.method_self.is_some() {
let class = self.class.as_ref().unwrap(); args.push(quote! { me: u32 });
args.push(quote! { me: *mut ::wasm_bindgen::__rt::WasmRefCell<#class> });
arg_conversions.push(quote! {
::wasm_bindgen::__rt::assert_not_null(me);
let me = unsafe { &*me };
});
offset = 1; offset = 1;
} }
let name = &self.function.name;
let receiver = match self.method_self {
Some(ast::MethodSelf::ByValue) => {
let class = self.class.as_ref().unwrap();
arg_conversions.push(quote! {
let me = unsafe {
<#class as ::wasm_bindgen::convert::FromWasmAbi>::from_abi(
me,
&mut ::wasm_bindgen::convert::GlobalStack::new(),
)
};
});
quote! { me.#name }
}
Some(ast::MethodSelf::RefMutable) => {
let class = self.class.as_ref().unwrap();
arg_conversions.push(quote! {
let mut me = unsafe {
<#class as ::wasm_bindgen::convert::RefMutFromWasmAbi>
::ref_mut_from_abi(
me,
&mut ::wasm_bindgen::convert::GlobalStack::new(),
)
};
let me = &mut *me;
});
quote! { me.#name }
}
Some(ast::MethodSelf::RefShared) => {
let class = self.class.as_ref().unwrap();
arg_conversions.push(quote! {
let me = unsafe {
<#class as ::wasm_bindgen::convert::RefFromWasmAbi>
::ref_from_abi(
me,
&mut ::wasm_bindgen::convert::GlobalStack::new(),
)
};
let me = &*me;
});
quote! { me.#name }
}
None => {
match &self.class {
Some(class) => quote! { #class::#name },
None => quote! { #name }
}
}
};
for (i, syn::ArgCaptured { ty, .. }) in self.function.arguments.iter().enumerate() { for (i, syn::ArgCaptured { ty, .. }) in self.function.arguments.iter().enumerate() {
let i = i + offset; let i = i + offset;
let ident = Ident::new(&format!("arg{}", i), Span::call_site()); let ident = Ident::new(&format!("arg{}", i), Span::call_site());
@ -394,19 +439,6 @@ impl ToTokens for ast::Export {
} }
None => quote! { inform(0); }, None => quote! { inform(0); },
}; };
let name = &self.function.name;
let receiver = match &self.class {
Some(_) if self.method => {
if self.mutable {
quote! { me.borrow_mut().#name }
} else {
quote! { me.borrow().#name }
}
}
Some(class) => quote! { #class::#name },
None => quote!{ #name },
};
let descriptor_name = format!("__wbindgen_describe_{}", export_name); let descriptor_name = format!("__wbindgen_describe_{}", export_name);
let descriptor_name = Ident::new(&descriptor_name, Span::call_site()); let descriptor_name = Ident::new(&descriptor_name, Span::call_site());
let nargs = self.function.arguments.len() as u32; let nargs = self.function.arguments.len() as u32;

View File

@ -66,14 +66,22 @@ impl<'a, 'b> Js2Rust<'a, 'b> {
/// Flag this shim as a method call into Rust, so the first Rust argument /// Flag this shim as a method call into Rust, so the first Rust argument
/// passed should be `this.ptr`. /// passed should be `this.ptr`.
pub fn method(&mut self, method: bool) -> &mut Self { pub fn method(&mut self, method: bool, consumed: bool) -> &mut Self {
if method { if method {
self.prelude( self.prelude(
"if (this.ptr === 0) { "if (this.ptr === 0) {
throw new Error('Attempt to use a moved value'); throw new Error('Attempt to use a moved value');
}", }"
); );
self.rust_arguments.insert(0, "this.ptr".to_string()); if consumed {
self.prelude("\
const ptr = this.ptr;\n\
this.ptr = 0;\n\
");
self.rust_arguments.insert(0, "ptr".to_string());
} else {
self.rust_arguments.insert(0, "this.ptr".to_string());
}
} }
self self
} }

View File

@ -551,7 +551,7 @@ impl<'a> Context<'a> {
let set = { let set = {
let mut cx = Js2Rust::new(&field.name, self); let mut cx = Js2Rust::new(&field.name, self);
cx.method(true).argument(&descriptor)?.ret(&None)?; cx.method(true, false).argument(&descriptor)?.ret(&None)?;
ts_dst.push_str(&format!( ts_dst.push_str(&format!(
"{}{}: {}\n", "{}{}: {}\n",
if field.readonly { "readonly " } else { "" }, if field.readonly { "readonly " } else { "" },
@ -561,7 +561,7 @@ impl<'a> Context<'a> {
cx.finish("", &format!("wasm.{}", wasm_setter)).0 cx.finish("", &format!("wasm.{}", wasm_setter)).0
}; };
let (get, _ts) = Js2Rust::new(&field.name, self) let (get, _ts) = Js2Rust::new(&field.name, self)
.method(true) .method(true, false)
.ret(&Some(descriptor))? .ret(&Some(descriptor))?
.finish("", &format!("wasm.{}", wasm_getter)); .finish("", &format!("wasm.{}", wasm_getter));
if !dst.ends_with("\n") { if !dst.ends_with("\n") {
@ -1657,7 +1657,7 @@ impl<'a, 'b> SubContext<'a, 'b> {
}; };
let (js, ts) = Js2Rust::new(&export.function.name, self.cx) let (js, ts) = Js2Rust::new(&export.function.name, self.cx)
.method(export.method) .method(export.method, export.consumed)
.process(descriptor.unwrap_function())? .process(descriptor.unwrap_function())?
.finish("", &format!("wasm.{}", wasm_name)); .finish("", &format!("wasm.{}", wasm_name));
let class = self let class = self

View File

@ -1,7 +1,7 @@
#[macro_use] #[macro_use]
extern crate serde_derive; extern crate serde_derive;
pub const SCHEMA_VERSION: &str = "4"; pub const SCHEMA_VERSION: &str = "5";
#[derive(Deserialize)] #[derive(Deserialize)]
pub struct ProgramOnlySchema { pub struct ProgramOnlySchema {
@ -72,6 +72,7 @@ pub struct ImportType {}
pub struct Export { pub struct Export {
pub class: Option<String>, pub class: Option<String>,
pub method: bool, pub method: bool,
pub consumed: bool,
pub constructor: Option<String>, pub constructor: Option<String>,
pub function: Function, pub function: Function,
pub comments: Vec<String>, pub comments: Vec<String>,

View File

@ -32,6 +32,10 @@ fn simple() {
self.contents += amt; self.contents += amt;
self.contents self.contents
} }
pub fn consume(self) -> u32 {
self.contents
}
} }
"#, "#,
) )
@ -46,7 +50,9 @@ fn simple() {
assert.strictEqual(r.add(0), 0); assert.strictEqual(r.add(0), 0);
assert.strictEqual(r.add(1), 1); assert.strictEqual(r.add(1), 1);
assert.strictEqual(r.add(1), 2); assert.strictEqual(r.add(1), 2);
r.free(); r.add(2);
assert.strictEqual(r.consume(), 4);
assert.throws(() => r.free(), /null pointer passed to rust/);
const r2 = Foo.with_contents(10); const r2 = Foo.with_contents(10);
assert.strictEqual(r2.add(1), 11); assert.strictEqual(r2.add(1), 11);
@ -672,3 +678,40 @@ fn readonly_fields() {
) )
.test(); .test();
} }
#[test]
fn double_consume() {
project()
.file("src/lib.rs", r#"
#![feature(proc_macro, wasm_custom_section, wasm_import_module)]
extern crate wasm_bindgen;
use wasm_bindgen::prelude::*;
#[wasm_bindgen]
pub struct Foo { }
#[wasm_bindgen]
impl Foo {
#[wasm_bindgen(constructor)]
pub fn new() -> Foo {
Foo {}
}
pub fn consume(self, other: Foo) {
drop(other);
}
}
"#)
.file("test.ts", r#"
import * as assert from "assert";
import { Foo } from "./out";
export function test() {
const r = Foo.new();
assert.throws(() => r.consume(r), /Attempt to use a moved value/);
}
"#)
.test();
}