mirror of
https://github.com/fluencelabs/wasm-bindgen
synced 2025-06-14 05:21:24 +00:00
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:
@ -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() {
|
||||||
|
@ -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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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;
|
||||||
|
7
crates/typescript-tests/src/optional_fields.rs
Normal file
7
crates/typescript-tests/src/optional_fields.rs
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
use wasm_bindgen::prelude::*;
|
||||||
|
|
||||||
|
#[wasm_bindgen]
|
||||||
|
pub struct Fields {
|
||||||
|
pub hallo: Option<bool>,
|
||||||
|
pub spaceboy: bool,
|
||||||
|
}
|
3
crates/typescript-tests/src/optional_fields.ts
Normal file
3
crates/typescript-tests/src/optional_fields.ts
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
import * as wbg from '../pkg/typescript_tests';
|
||||||
|
|
||||||
|
const fields: wbg.Fields = { spaceboy: true, free: () => { } };
|
1
rustfmt.toml
Normal file
1
rustfmt.toml
Normal file
@ -0,0 +1 @@
|
|||||||
|
tab_spaces = 4
|
Reference in New Issue
Block a user