mirror of
https://github.com/rustwasm/wasm-bindgen.git
synced 2024-12-25 11:02:11 +03:00
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:
parent
9a3ff77ea9
commit
e55af85edc
@ -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(),
|
||||||
|
@ -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;
|
||||||
|
@ -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
|
||||||
}
|
}
|
||||||
|
@ -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
|
||||||
|
@ -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>,
|
||||||
|
@ -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();
|
||||||
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user