From b6190700c94634bbc5b61ec4dd16c3ffb4f4cca8 Mon Sep 17 00:00:00 2001 From: clearloop Date: Tue, 18 Feb 2020 23:15:37 +0800 Subject: [PATCH] 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 --- crates/cli-support/src/js/binding.rs | 23 +++++++++-- crates/cli-support/src/js/mod.rs | 40 +++++++++++++------ crates/typescript-tests/src/lib.rs | 1 + .../typescript-tests/src/optional_fields.rs | 7 ++++ .../typescript-tests/src/optional_fields.ts | 3 ++ rustfmt.toml | 1 + 6 files changed, 59 insertions(+), 16 deletions(-) create mode 100644 crates/typescript-tests/src/optional_fields.rs create mode 100644 crates/typescript-tests/src/optional_fields.ts create mode 100644 rustfmt.toml diff --git a/crates/cli-support/src/js/binding.rs b/crates/cli-support/src/js/binding.rs index f9c09398dc5..d5b47b2ee01 100644 --- a/crates/cli-support/src/js/binding.rs +++ b/crates/cli-support/src/js/binding.rs @@ -64,6 +64,7 @@ pub struct JsFunction { pub js_doc: String, pub ts_arg_tys: Vec, pub ts_ret_ty: Option, + 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, Option) { // 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() { diff --git a/crates/cli-support/src/js/mod.rs b/crates/cli-support/src/js/mod.rs index b6e9f80dd30..c57f9b1dd11 100644 --- a/crates/cli-support/src/js/mod.rs +++ b/crates/cli-support/src/js/mod.rs @@ -67,7 +67,8 @@ pub struct ExportedClass { /// All readable properties of the class readable_properties: Vec, /// Map from field name to type as a string plus whether it has a setter - typescript_fields: HashMap, + /// and it is optional + typescript_fields: HashMap, } 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::>(); 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) } } diff --git a/crates/typescript-tests/src/lib.rs b/crates/typescript-tests/src/lib.rs index 211dfff6bdd..3198524a8e2 100644 --- a/crates/typescript-tests/src/lib.rs +++ b/crates/typescript-tests/src/lib.rs @@ -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; diff --git a/crates/typescript-tests/src/optional_fields.rs b/crates/typescript-tests/src/optional_fields.rs new file mode 100644 index 00000000000..bdf373f17b8 --- /dev/null +++ b/crates/typescript-tests/src/optional_fields.rs @@ -0,0 +1,7 @@ +use wasm_bindgen::prelude::*; + +#[wasm_bindgen] +pub struct Fields { + pub hallo: Option, + pub spaceboy: bool, +} diff --git a/crates/typescript-tests/src/optional_fields.ts b/crates/typescript-tests/src/optional_fields.ts new file mode 100644 index 00000000000..61e1fc05b1b --- /dev/null +++ b/crates/typescript-tests/src/optional_fields.ts @@ -0,0 +1,3 @@ +import * as wbg from '../pkg/typescript_tests'; + +const fields: wbg.Fields = { spaceboy: true, free: () => { } }; diff --git a/rustfmt.toml b/rustfmt.toml new file mode 100644 index 00000000000..b6f799d6557 --- /dev/null +++ b/rustfmt.toml @@ -0,0 +1 @@ +tab_spaces = 4