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
6 changed files with 59 additions and 16 deletions

View File

@ -64,6 +64,7 @@ pub struct JsFunction {
pub js_doc: String, pub js_doc: String,
pub ts_arg_tys: Vec<String>, pub ts_arg_tys: Vec<String>,
pub ts_ret_ty: Option<String>, pub ts_ret_ty: Option<String>,
pub might_be_optional_field: bool,
} }
impl<'a, 'b> Builder<'a, 'b> { impl<'a, 'b> Builder<'a, 'b> {
@ -215,16 +216,25 @@ impl<'a, 'b> Builder<'a, 'b> {
code.push_str(&call); code.push_str(&call);
code.push_str("}"); code.push_str("}");
let (ts_sig, ts_arg_tys, ts_ret_ty) = // Rust Structs' fields converted into Getter and Setter functions before
self.typescript_signature(&function_args, &arg_tys, &adapter.results); // 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); let js_doc = self.js_doc_comments(&function_args, &arg_tys, &ts_ret_ty);
Ok(JsFunction { Ok(JsFunction {
code, code,
ts_sig, ts_sig,
js_doc, js_doc,
ts_arg_tys, ts_arg_tys,
ts_ret_ty, ts_ret_ty,
might_be_optional_field,
}) })
} }
@ -238,6 +248,7 @@ impl<'a, 'b> Builder<'a, 'b> {
arg_names: &[String], arg_names: &[String],
arg_tys: &[&AdapterType], arg_tys: &[&AdapterType],
result_tys: &[AdapterType], result_tys: &[AdapterType],
might_be_optional_field: &mut bool,
) -> (String, Vec<String>, Option<String>) { ) -> (String, Vec<String>, Option<String>) {
// Build up the typescript signature as well // Build up the typescript signature as well
let mut omittable = true; let mut omittable = true;
@ -270,6 +281,12 @@ impl<'a, 'b> Builder<'a, 'b> {
ts_arg_tys.reverse(); ts_arg_tys.reverse();
let mut ts = format!("({})", ts_args.join(", ")); 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 // Constructors have no listed return type in typescript
let mut ts_ret = None; let mut ts_ret = None;
if self.constructor.is_none() { if self.constructor.is_none() {

View File

@ -67,7 +67,8 @@ pub struct ExportedClass {
/// All readable properties of the class /// All readable properties of the class
readable_properties: Vec<String>, readable_properties: Vec<String>,
/// Map from field name to type as a string plus whether it has a setter /// 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"]; 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<_>>(); let mut fields = class.typescript_fields.keys().collect::<Vec<_>>();
fields.sort(); // make sure we have deterministic output fields.sort(); // make sure we have deterministic output
for name in fields { 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(" "); ts_dst.push_str(" ");
if !has_setter { if !has_setter {
ts_dst.push_str("readonly "); ts_dst.push_str("readonly ");
} }
ts_dst.push_str(name); ts_dst.push_str(name);
ts_dst.push_str(": "); if *is_optional {
ts_dst.push_str(ty); ts_dst.push_str("?: ");
} else {
ts_dst.push_str(": ");
}
ts_dst.push_str(&ty);
ts_dst.push_str(";\n"); ts_dst.push_str(";\n");
} }
dst.push_str("}\n"); dst.push_str("}\n");
@ -781,9 +786,7 @@ impl<'a> Context<'a> {
if !self.should_write_global("not_defined") { if !self.should_write_global("not_defined") {
return; return;
} }
self.global( self.global("function notDefined(what) { return () => { throw new Error(`${what} is not defined`); }; }");
"function notDefined(what) { return () => { throw new Error(`${what} is not defined`); }; }"
);
} }
fn expose_assert_num(&mut self) { fn expose_assert_num(&mut self) {
@ -2045,6 +2048,7 @@ impl<'a> Context<'a> {
ts_ret_ty, ts_ret_ty,
js_doc, js_doc,
code, code,
might_be_optional_field,
} = builder } = builder
.process(&adapter, instrs, arg_names) .process(&adapter, instrs, arg_names)
.with_context(|| match kind { .with_context(|| match kind {
@ -2089,7 +2093,7 @@ impl<'a> Context<'a> {
AuxExportKind::Setter { class, field } => { AuxExportKind::Setter { class, field } => {
let arg_ty = ts_arg_tys[0].clone(); let arg_ty = ts_arg_tys[0].clone();
let exported = require_class(&mut self.exported_classes, class); 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 } => { AuxExportKind::StaticFunction { class, name } => {
let exported = require_class(&mut self.exported_classes, class); 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 /// Used for adding a setter to a class, mainly to ensure that TypeScript
/// generation is handled specially. /// generation is handled specially.
fn push_setter(&mut self, docs: &str, field: &str, js: &str, ret_ty: &str) { fn push_setter(
let has_setter = self.push_accessor(docs, field, js, "set ", ret_ty); &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; *has_setter = true;
*is_optional = might_be_optional_field;
} }
fn push_accessor( fn push_accessor(
@ -3109,18 +3121,20 @@ impl ExportedClass {
js: &str, js: &str,
prefix: &str, prefix: &str,
ret_ty: &str, ret_ty: &str,
) -> &mut bool { ) -> (&mut bool, &mut bool) {
self.contents.push_str(docs); self.contents.push_str(docs);
self.contents.push_str(prefix); self.contents.push_str(prefix);
self.contents.push_str(field); self.contents.push_str(field);
self.contents.push_str(js); self.contents.push_str(js);
self.contents.push_str("\n"); self.contents.push_str("\n");
let (ty, has_setter) = self
let (ty, has_setter, is_optional) = self
.typescript_fields .typescript_fields
.entry(field.to_string()) .entry(field.to_string())
.or_insert_with(Default::default); .or_insert_with(Default::default);
*ty = ret_ty.to_string(); *ty = ret_ty.to_string();
has_setter (has_setter, is_optional)
} }
} }

View File

@ -1,5 +1,6 @@
pub mod custom_section; pub mod custom_section;
pub mod getters_setters; pub mod getters_setters;
pub mod opt_args_and_ret; pub mod opt_args_and_ret;
pub mod optional_fields;
pub mod simple_fn; pub mod simple_fn;
pub mod simple_struct; 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