mirror of
https://github.com/fluencelabs/wasm-bindgen
synced 2025-03-15 17:50:51 +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:
parent
156e1cb47f
commit
b6190700c9
@ -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() {
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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;
|
||||
|
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
|
Loading…
x
Reference in New Issue
Block a user