Skip to content

Commit b619070

Browse files
authored
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
1 parent 156e1cb commit b619070

File tree

6 files changed

+59
-16
lines changed

6 files changed

+59
-16
lines changed

crates/cli-support/src/js/binding.rs

+20-3
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,7 @@ pub struct JsFunction {
6464
pub js_doc: String,
6565
pub ts_arg_tys: Vec<String>,
6666
pub ts_ret_ty: Option<String>,
67+
pub might_be_optional_field: bool,
6768
}
6869

6970
impl<'a, 'b> Builder<'a, 'b> {
@@ -215,16 +216,25 @@ impl<'a, 'b> Builder<'a, 'b> {
215216
code.push_str(&call);
216217
code.push_str("}");
217218

218-
let (ts_sig, ts_arg_tys, ts_ret_ty) =
219-
self.typescript_signature(&function_args, &arg_tys, &adapter.results);
219+
// Rust Structs' fields converted into Getter and Setter functions before
220+
// we decode them from webassembly, finding if a function is a field
221+
// should start from here. Struct fields(Getter) only have one arg, and
222+
// this is the clue we can infer if a function might be a field.
223+
let mut might_be_optional_field = false;
224+
let (ts_sig, ts_arg_tys, ts_ret_ty) = self.typescript_signature(
225+
&function_args,
226+
&arg_tys,
227+
&adapter.results,
228+
&mut might_be_optional_field,
229+
);
220230
let js_doc = self.js_doc_comments(&function_args, &arg_tys, &ts_ret_ty);
221-
222231
Ok(JsFunction {
223232
code,
224233
ts_sig,
225234
js_doc,
226235
ts_arg_tys,
227236
ts_ret_ty,
237+
might_be_optional_field,
228238
})
229239
}
230240

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

284+
// If this function is an optional field's setter, it should have only
285+
// one arg, and omittable should be `true`.
286+
if ts_args.len() == 1 && omittable {
287+
*might_be_optional_field = true;
288+
}
289+
273290
// Constructors have no listed return type in typescript
274291
let mut ts_ret = None;
275292
if self.constructor.is_none() {

crates/cli-support/src/js/mod.rs

+27-13
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,8 @@ pub struct ExportedClass {
6767
/// All readable properties of the class
6868
readable_properties: Vec<String>,
6969
/// Map from field name to type as a string plus whether it has a setter
70-
typescript_fields: HashMap<String, (String, bool)>,
70+
/// and it is optional
71+
typescript_fields: HashMap<String, (String, bool, bool)>,
7172
}
7273

7374
const INITIAL_HEAP_VALUES: &[&str] = &["undefined", "null", "true", "false"];
@@ -703,14 +704,18 @@ impl<'a> Context<'a> {
703704
let mut fields = class.typescript_fields.keys().collect::<Vec<_>>();
704705
fields.sort(); // make sure we have deterministic output
705706
for name in fields {
706-
let (ty, has_setter) = &class.typescript_fields[name];
707+
let (ty, has_setter, is_optional) = &class.typescript_fields[name];
707708
ts_dst.push_str(" ");
708709
if !has_setter {
709710
ts_dst.push_str("readonly ");
710711
}
711712
ts_dst.push_str(name);
712-
ts_dst.push_str(": ");
713-
ts_dst.push_str(ty);
713+
if *is_optional {
714+
ts_dst.push_str("?: ");
715+
} else {
716+
ts_dst.push_str(": ");
717+
}
718+
ts_dst.push_str(&ty);
714719
ts_dst.push_str(";\n");
715720
}
716721
dst.push_str("}\n");
@@ -781,9 +786,7 @@ impl<'a> Context<'a> {
781786
if !self.should_write_global("not_defined") {
782787
return;
783788
}
784-
self.global(
785-
"function notDefined(what) { return () => { throw new Error(`${what} is not defined`); }; }"
786-
);
789+
self.global("function notDefined(what) { return () => { throw new Error(`${what} is not defined`); }; }");
787790
}
788791

789792
fn expose_assert_num(&mut self) {
@@ -2045,6 +2048,7 @@ impl<'a> Context<'a> {
20452048
ts_ret_ty,
20462049
js_doc,
20472050
code,
2051+
might_be_optional_field,
20482052
} = builder
20492053
.process(&adapter, instrs, arg_names)
20502054
.with_context(|| match kind {
@@ -2089,7 +2093,7 @@ impl<'a> Context<'a> {
20892093
AuxExportKind::Setter { class, field } => {
20902094
let arg_ty = ts_arg_tys[0].clone();
20912095
let exported = require_class(&mut self.exported_classes, class);
2092-
exported.push_setter(&docs, field, &code, &arg_ty);
2096+
exported.push_setter(&docs, field, &code, &arg_ty, might_be_optional_field);
20932097
}
20942098
AuxExportKind::StaticFunction { class, name } => {
20952099
let exported = require_class(&mut self.exported_classes, class);
@@ -3097,9 +3101,17 @@ impl ExportedClass {
30973101

30983102
/// Used for adding a setter to a class, mainly to ensure that TypeScript
30993103
/// generation is handled specially.
3100-
fn push_setter(&mut self, docs: &str, field: &str, js: &str, ret_ty: &str) {
3101-
let has_setter = self.push_accessor(docs, field, js, "set ", ret_ty);
3104+
fn push_setter(
3105+
&mut self,
3106+
docs: &str,
3107+
field: &str,
3108+
js: &str,
3109+
ret_ty: &str,
3110+
might_be_optional_field: bool,
3111+
) {
3112+
let (has_setter, is_optional) = self.push_accessor(docs, field, js, "set ", ret_ty);
31023113
*has_setter = true;
3114+
*is_optional = might_be_optional_field;
31033115
}
31043116

31053117
fn push_accessor(
@@ -3109,18 +3121,20 @@ impl ExportedClass {
31093121
js: &str,
31103122
prefix: &str,
31113123
ret_ty: &str,
3112-
) -> &mut bool {
3124+
) -> (&mut bool, &mut bool) {
31133125
self.contents.push_str(docs);
31143126
self.contents.push_str(prefix);
31153127
self.contents.push_str(field);
31163128
self.contents.push_str(js);
31173129
self.contents.push_str("\n");
3118-
let (ty, has_setter) = self
3130+
3131+
let (ty, has_setter, is_optional) = self
31193132
.typescript_fields
31203133
.entry(field.to_string())
31213134
.or_insert_with(Default::default);
3135+
31223136
*ty = ret_ty.to_string();
3123-
has_setter
3137+
(has_setter, is_optional)
31243138
}
31253139
}
31263140

crates/typescript-tests/src/lib.rs

+1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
pub mod custom_section;
22
pub mod getters_setters;
33
pub mod opt_args_and_ret;
4+
pub mod optional_fields;
45
pub mod simple_fn;
56
pub mod simple_struct;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
use wasm_bindgen::prelude::*;
2+
3+
#[wasm_bindgen]
4+
pub struct Fields {
5+
pub hallo: Option<bool>,
6+
pub spaceboy: bool,
7+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
import * as wbg from '../pkg/typescript_tests';
2+
3+
const fields: wbg.Fields = { spaceboy: true, free: () => { } };

rustfmt.toml

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
tab_spaces = 4

0 commit comments

Comments
 (0)