Reflect optional struct fields in typescript (#1990)

* reflect option struct fields in typescript

* optional fields: move type checker to getter

* infer optional fields from ts_args
This commit is contained in:
clearloop 2020-02-18 23:15:37 +08:00 committed by GitHub
parent 156e1cb47f
commit b6190700c9
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 59 additions and 16 deletions

View File

@ -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> {
@ -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,
})
}
@ -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;
@ -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() {

View File

@ -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"];
@ -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");
@ -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) {
@ -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 {
@ -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);
@ -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(
@ -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)
}
}

View File

@ -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;

View File

@ -0,0 +1,7 @@
use wasm_bindgen::prelude::*;
#[wasm_bindgen]
pub struct Fields {
pub hallo: Option<bool>,
pub spaceboy: bool,
}

View File

@ -0,0 +1,3 @@
import * as wbg from '../pkg/typescript_tests';
const fields: wbg.Fields = { spaceboy: true, free: () => { } };

1
rustfmt.toml Normal file
View File

@ -0,0 +1 @@
tab_spaces = 4