Remove the need for a ConstructorToken

This commit removes the need for an injected `ConstructorToken` type and
also cleans up the story we have for generating constructors a bit.
After this commit a `constructor()` is omitted entirely if we're in
non-debug mode and there's no actual listed constructor. Additionally we
don't deal with splat arguments and rerouting constructors, Nick was
kind enough to enlighten me about `Object.create` which is creating an
instance without running the constructor!

Instances of an exported type are now created through one of two
methods:

* If `#[wasm_bindgen(constructor)]` is present, then a `constructor` is
  generated with the appropriate signature. If a constructor is not
  present and we're in debug mode, a throwing constructor is generated.
  If we're in release mode and there's no constructor, no constructor is
  generated.

* Otherwise if a binding returns an instance of a type (or otherwise
  needs to manfuacture an instance, then it will cause an internal
  `__wrap` function to be generated. This function will use
  `Object.create` to create an instance without running the constructor.

This should ideally clean up our generated JS for classes quite a bit,
making it much more lean-and-mean!
This commit is contained in:
Alex Crichton
2018-09-21 15:45:31 -07:00
parent 616b27457d
commit 3f85d7db9f
8 changed files with 114 additions and 115 deletions

View File

@ -37,6 +37,14 @@ pub struct Js2Rust<'a, 'b: 'a> {
/// Name of the JS shim/function that we're generating, primarily for
/// TypeScript right now.
js_name: String,
/// whether or not this generated function body will act like a constructor,
/// meaning it doesn't actually return something but rather assigns to
/// `this`
///
/// The string value here is the class that this should be a constructor
/// for.
constructor: Option<String>,
}
impl<'a, 'b> Js2Rust<'a, 'b> {
@ -51,6 +59,7 @@ impl<'a, 'b> Js2Rust<'a, 'b> {
arg_idx: 0,
ret_ty: String::new(),
ret_expr: String::new(),
constructor: None,
}
}
@ -64,6 +73,11 @@ impl<'a, 'b> Js2Rust<'a, 'b> {
Ok(self)
}
pub fn constructor(&mut self, class: Option<&str>) -> &mut Self {
self.constructor = class.map(|s| s.to_string());
self
}
/// 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, consumed: bool) -> &mut Self {
@ -393,6 +407,32 @@ impl<'a, 'b> Js2Rust<'a, 'b> {
}
pub fn ret(&mut self, ty: &Descriptor) -> Result<&mut Self, Error> {
if let Some(name) = ty.rust_struct() {
match &self.constructor {
Some(class) if class == name => {
self.ret_expr = format!("this.ptr = RET;");
if self.cx.config.weak_refs {
self.ret_expr.push_str(&format!("\
addCleanup(this, this.ptr, free{});
", name));
}
}
Some(class) => {
bail!("constructor for `{}` cannot return `{}`", class, name)
}
None => {
self.ret_ty = name.to_string();
self.cx.require_class_wrap(name);
self.ret_expr = format!("return {name}.__wrap(RET);", name = name);
}
}
return Ok(self);
}
if self.constructor.is_some() {
bail!("constructor functions must return a Rust structure")
}
if let Descriptor::Unit = ty {
self.ret_ty = "void".to_string();
self.ret_expr = format!("return RET;");
@ -551,7 +591,8 @@ impl<'a, 'b> Js2Rust<'a, 'b> {
if let Some(name) = ty.rust_struct() {
self.ret_ty = name.to_string();
self.ret_expr = format!("return {name}.__construct(RET);", name = name);
self.cx.require_class_wrap(name);
self.ret_expr = format!("return {name}.__wrap(RET);", name = name);
return Ok(self);
}