diff --git a/crates/backend/src/ast.rs b/crates/backend/src/ast.rs index 158e8210f..962df0f82 100644 --- a/crates/backend/src/ast.rs +++ b/crates/backend/src/ast.rs @@ -17,13 +17,19 @@ pub struct Program { #[cfg_attr(feature = "extra-traits", derive(Debug, PartialEq, Eq))] pub struct Export { pub class: Option, - pub method: bool, - pub mutable: bool, + pub method_self: Option, pub constructor: Option, pub function: Function, pub comments: Vec, } +#[cfg_attr(feature = "extra-traits", derive(Debug, PartialEq, Eq))] +pub enum MethodSelf { + ByValue, + RefMutable, + RefShared, +} + #[cfg_attr(feature = "extra-traits", derive(Debug, PartialEq, Eq))] pub struct Import { pub module: Option, @@ -170,8 +176,7 @@ impl Program { f.to_tokens(tokens); self.exports.push(Export { class: None, - method: false, - mutable: false, + method_self: None, constructor: None, function: Function::from(f, opts), comments, @@ -263,7 +268,7 @@ impl Program { None }; - let (function, mutable) = Function::from_decl( + let (function, method_self) = Function::from_decl( &method.sig.ident, Box::new(method.sig.decl.clone()), method.attrs.clone(), @@ -274,8 +279,7 @@ impl Program { self.exports.push(Export { class: Some(class.clone()), - method: mutable.is_some(), - mutable: mutable.unwrap_or(false), + method_self, constructor, function, comments, @@ -529,7 +533,7 @@ impl Function { opts: BindgenAttrs, vis: syn::Visibility, allow_self: bool, - ) -> (Function, Option) { + ) -> (Function, Option) { if decl.variadic.is_some() { panic!("can't bindgen variadic functions") } @@ -541,17 +545,23 @@ impl Function { let syn::FnDecl { inputs, output, .. } = { *decl }; - let mut mutable = None; + let mut method_self = None; let arguments = inputs .into_iter() .filter_map(|arg| match arg { syn::FnArg::Captured(c) => Some(c), 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 => { - assert!(mutable.is_none()); - mutable = Some(a.mutability.is_some()); + assert!(method_self.is_none()); + if a.mutability.is_some() { + method_self = Some(MethodSelf::RefMutable); + } else { + method_self = Some(MethodSelf::RefShared); + } None } _ => panic!("arguments cannot be `self` or ignored"), @@ -572,7 +582,7 @@ impl Function { rust_vis: vis, rust_attrs: attrs, }, - mutable, + method_self, ) } @@ -618,9 +628,15 @@ impl 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 { class: self.class.as_ref().map(|s| s.to_string()), - method: self.method, + method, + consumed, constructor: self.constructor.clone(), function: self.function.shared(), comments: self.comments.clone(), diff --git a/crates/backend/src/codegen.rs b/crates/backend/src/codegen.rs index 5e0b7414c..d19e04316 100644 --- a/crates/backend/src/codegen.rs +++ b/crates/backend/src/codegen.rs @@ -309,16 +309,61 @@ impl ToTokens for ast::Export { let ret = Ident::new("_ret", Span::call_site()); let mut offset = 0; - if self.method { - let class = self.class.as_ref().unwrap(); - 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 }; - }); + if self.method_self.is_some() { + args.push(quote! { me: u32 }); 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() { let i = i + offset; let ident = Ident::new(&format!("arg{}", i), Span::call_site()); @@ -394,19 +439,6 @@ impl ToTokens for ast::Export { } 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 = Ident::new(&descriptor_name, Span::call_site()); let nargs = self.function.arguments.len() as u32; diff --git a/crates/cli-support/src/js/js2rust.rs b/crates/cli-support/src/js/js2rust.rs index 104487476..b67352fb3 100644 --- a/crates/cli-support/src/js/js2rust.rs +++ b/crates/cli-support/src/js/js2rust.rs @@ -66,14 +66,22 @@ impl<'a, 'b> Js2Rust<'a, 'b> { /// Flag this shim as a method call into Rust, so the first Rust argument /// 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 { self.prelude( "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 } diff --git a/crates/cli-support/src/js/mod.rs b/crates/cli-support/src/js/mod.rs index 7ac84b6e2..e3f114668 100644 --- a/crates/cli-support/src/js/mod.rs +++ b/crates/cli-support/src/js/mod.rs @@ -551,7 +551,7 @@ impl<'a> Context<'a> { let set = { 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!( "{}{}: {}\n", if field.readonly { "readonly " } else { "" }, @@ -561,7 +561,7 @@ impl<'a> Context<'a> { cx.finish("", &format!("wasm.{}", wasm_setter)).0 }; let (get, _ts) = Js2Rust::new(&field.name, self) - .method(true) + .method(true, false) .ret(&Some(descriptor))? .finish("", &format!("wasm.{}", wasm_getter)); 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) - .method(export.method) + .method(export.method, export.consumed) .process(descriptor.unwrap_function())? .finish("", &format!("wasm.{}", wasm_name)); let class = self diff --git a/crates/shared/src/lib.rs b/crates/shared/src/lib.rs index 6d1da8540..66f624d74 100644 --- a/crates/shared/src/lib.rs +++ b/crates/shared/src/lib.rs @@ -1,7 +1,7 @@ #[macro_use] extern crate serde_derive; -pub const SCHEMA_VERSION: &str = "4"; +pub const SCHEMA_VERSION: &str = "5"; #[derive(Deserialize)] pub struct ProgramOnlySchema { @@ -72,6 +72,7 @@ pub struct ImportType {} pub struct Export { pub class: Option, pub method: bool, + pub consumed: bool, pub constructor: Option, pub function: Function, pub comments: Vec, diff --git a/tests/all/classes.rs b/tests/all/classes.rs index 5946af2d4..1abb6bf12 100644 --- a/tests/all/classes.rs +++ b/tests/all/classes.rs @@ -32,6 +32,10 @@ fn simple() { self.contents += amt; 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(1), 1); 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); assert.strictEqual(r2.add(1), 11); @@ -672,3 +678,40 @@ fn readonly_fields() { ) .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(); +}