Skip to content

Commit

Permalink
Reflect optional struct fields in typescript (#1990)
Browse files Browse the repository at this point in the history
* reflect option struct fields in typescript

* optional fields: move type checker to getter

* infer optional fields from ts_args
  • Loading branch information
clearloop committed Feb 18, 2020
1 parent 156e1cb commit b619070
Show file tree
Hide file tree
Showing 6 changed files with 59 additions and 16 deletions.
23 changes: 20 additions & 3 deletions crates/cli-support/src/js/binding.rs
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@ pub struct JsFunction {
pub js_doc: String,
pub ts_arg_tys: Vec<String>,
pub ts_ret_ty: Option<String>,
pub might_be_optional_field: bool,
}

impl<'a, 'b> Builder<'a, 'b> {
Expand Down Expand Up @@ -215,16 +216,25 @@ impl<'a, 'b> Builder<'a, 'b> {
code.push_str(&call);
code.push_str("}");

let (ts_sig, ts_arg_tys, ts_ret_ty) =
self.typescript_signature(&function_args, &arg_tys, &adapter.results);
// Rust Structs' fields converted into Getter and Setter functions before
// we decode them from webassembly, finding if a function is a field
// should start from here. Struct fields(Getter) only have one arg, and
// this is the clue we can infer if a function might be a field.
let mut might_be_optional_field = false;
let (ts_sig, ts_arg_tys, ts_ret_ty) = self.typescript_signature(
&function_args,
&arg_tys,
&adapter.results,
&mut might_be_optional_field,
);
let js_doc = self.js_doc_comments(&function_args, &arg_tys, &ts_ret_ty);

Ok(JsFunction {
code,
ts_sig,
js_doc,
ts_arg_tys,
ts_ret_ty,
might_be_optional_field,
})
}

Expand All @@ -238,6 +248,7 @@ impl<'a, 'b> Builder<'a, 'b> {
arg_names: &[String],
arg_tys: &[&AdapterType],
result_tys: &[AdapterType],
might_be_optional_field: &mut bool,
) -> (String, Vec<String>, Option<String>) {
// Build up the typescript signature as well
let mut omittable = true;
Expand Down Expand Up @@ -270,6 +281,12 @@ impl<'a, 'b> Builder<'a, 'b> {
ts_arg_tys.reverse();
let mut ts = format!("({})", ts_args.join(", "));

// If this function is an optional field's setter, it should have only
// one arg, and omittable should be `true`.
if ts_args.len() == 1 && omittable {
*might_be_optional_field = true;
}

// Constructors have no listed return type in typescript
let mut ts_ret = None;
if self.constructor.is_none() {
Expand Down
40 changes: 27 additions & 13 deletions crates/cli-support/src/js/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,8 @@ pub struct ExportedClass {
/// All readable properties of the class
readable_properties: Vec<String>,
/// Map from field name to type as a string plus whether it has a setter
typescript_fields: HashMap<String, (String, bool)>,
/// and it is optional
typescript_fields: HashMap<String, (String, bool, bool)>,
}

const INITIAL_HEAP_VALUES: &[&str] = &["undefined", "null", "true", "false"];
Expand Down Expand Up @@ -703,14 +704,18 @@ impl<'a> Context<'a> {
let mut fields = class.typescript_fields.keys().collect::<Vec<_>>();
fields.sort(); // make sure we have deterministic output
for name in fields {
let (ty, has_setter) = &class.typescript_fields[name];
let (ty, has_setter, is_optional) = &class.typescript_fields[name];
ts_dst.push_str(" ");
if !has_setter {
ts_dst.push_str("readonly ");
}
ts_dst.push_str(name);
ts_dst.push_str(": ");
ts_dst.push_str(ty);
if *is_optional {
ts_dst.push_str("?: ");
} else {
ts_dst.push_str(": ");
}
ts_dst.push_str(&ty);
ts_dst.push_str(";\n");
}
dst.push_str("}\n");
Expand Down Expand Up @@ -781,9 +786,7 @@ impl<'a> Context<'a> {
if !self.should_write_global("not_defined") {
return;
}
self.global(
"function notDefined(what) { return () => { throw new Error(`${what} is not defined`); }; }"
);
self.global("function notDefined(what) { return () => { throw new Error(`${what} is not defined`); }; }");
}

fn expose_assert_num(&mut self) {
Expand Down Expand Up @@ -2045,6 +2048,7 @@ impl<'a> Context<'a> {
ts_ret_ty,
js_doc,
code,
might_be_optional_field,
} = builder
.process(&adapter, instrs, arg_names)
.with_context(|| match kind {
Expand Down Expand Up @@ -2089,7 +2093,7 @@ impl<'a> Context<'a> {
AuxExportKind::Setter { class, field } => {
let arg_ty = ts_arg_tys[0].clone();
let exported = require_class(&mut self.exported_classes, class);
exported.push_setter(&docs, field, &code, &arg_ty);
exported.push_setter(&docs, field, &code, &arg_ty, might_be_optional_field);
}
AuxExportKind::StaticFunction { class, name } => {
let exported = require_class(&mut self.exported_classes, class);
Expand Down Expand Up @@ -3097,9 +3101,17 @@ impl ExportedClass {

/// Used for adding a setter to a class, mainly to ensure that TypeScript
/// generation is handled specially.
fn push_setter(&mut self, docs: &str, field: &str, js: &str, ret_ty: &str) {
let has_setter = self.push_accessor(docs, field, js, "set ", ret_ty);
fn push_setter(
&mut self,
docs: &str,
field: &str,
js: &str,
ret_ty: &str,
might_be_optional_field: bool,
) {
let (has_setter, is_optional) = self.push_accessor(docs, field, js, "set ", ret_ty);
*has_setter = true;
*is_optional = might_be_optional_field;
}

fn push_accessor(
Expand All @@ -3109,18 +3121,20 @@ impl ExportedClass {
js: &str,
prefix: &str,
ret_ty: &str,
) -> &mut bool {
) -> (&mut bool, &mut bool) {
self.contents.push_str(docs);
self.contents.push_str(prefix);
self.contents.push_str(field);
self.contents.push_str(js);
self.contents.push_str("\n");
let (ty, has_setter) = self

let (ty, has_setter, is_optional) = self
.typescript_fields
.entry(field.to_string())
.or_insert_with(Default::default);

*ty = ret_ty.to_string();
has_setter
(has_setter, is_optional)
}
}

Expand Down
1 change: 1 addition & 0 deletions crates/typescript-tests/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
pub mod custom_section;
pub mod getters_setters;
pub mod opt_args_and_ret;
pub mod optional_fields;
pub mod simple_fn;
pub mod simple_struct;
7 changes: 7 additions & 0 deletions crates/typescript-tests/src/optional_fields.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
use wasm_bindgen::prelude::*;

#[wasm_bindgen]
pub struct Fields {
pub hallo: Option<bool>,
pub spaceboy: bool,
}
3 changes: 3 additions & 0 deletions crates/typescript-tests/src/optional_fields.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import * as wbg from '../pkg/typescript_tests';

const fields: wbg.Fields = { spaceboy: true, free: () => { } };
1 change: 1 addition & 0 deletions rustfmt.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
tab_spaces = 4

0 comments on commit b619070

Please sign in to comment.