Skip to content

Commit cba1e70

Browse files
committed
Fix TypeScript output for fields
1 parent ff0a50e commit cba1e70

File tree

2 files changed

+45
-4
lines changed

2 files changed

+45
-4
lines changed

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@ pub struct Js2Rust<'a, 'b: 'a> {
5151

5252
/// Typescript expression representing the type of the return value of this
5353
/// function.
54-
ret_ty: String,
54+
pub ret_ty: String,
5555

5656
/// Expression used to generate the return value. The string "RET" in this
5757
/// expression is replaced with the actual wasm invocation eventually.

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

Lines changed: 44 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,8 @@ pub struct ExportedClass {
5858
typescript: String,
5959
has_constructor: bool,
6060
wrap_needed: bool,
61+
/// Map from field name to type as a string plus whether it has a setter
62+
typescript_fields: HashMap<String, (String, bool)>,
6163
}
6264

6365
const INITIAL_HEAP_VALUES: &[&str] = &["undefined", "null", "true", "false"];
@@ -638,6 +640,19 @@ impl<'a> Context<'a> {
638640
ts_dst.push_str(" free(): void;");
639641
dst.push_str(&class.contents);
640642
ts_dst.push_str(&class.typescript);
643+
644+
let mut fields = class.typescript_fields.keys().collect::<Vec<_>>();
645+
fields.sort(); // make sure we have deterministic output
646+
for name in fields {
647+
let (ty, readonly) = &class.typescript_fields[name];
648+
if *readonly {
649+
ts_dst.push_str("readonly ");
650+
}
651+
ts_dst.push_str(name);
652+
ts_dst.push_str(": ");
653+
ts_dst.push_str(ty);
654+
ts_dst.push_str(";\n");
655+
}
641656
dst.push_str("}\n");
642657
ts_dst.push_str("}\n");
643658

@@ -1913,14 +1928,15 @@ impl<'a> Context<'a> {
19131928
&format!("wasm.{}", wasm_name),
19141929
ExportedShim::Named(&wasm_name),
19151930
);
1931+
let ret_ty = j2r.ret_ty.clone();
19161932
let exported = require_class(&mut self.exported_classes, class);
19171933
let docs = format_doc_comments(&export.comments, Some(raw_docs));
19181934
match export.kind {
19191935
AuxExportKind::Getter { .. } => {
1920-
exported.push(&docs, name, "get ", &js, &ts);
1936+
exported.push_field(name, &js, Some(&ret_ty), true);
19211937
}
19221938
AuxExportKind::Setter { .. } => {
1923-
exported.push(&docs, name, "set ", &js, &ts);
1939+
exported.push_field(name, &js, None, false);
19241940
}
19251941
AuxExportKind::StaticFunction { .. } => {
19261942
exported.push(&docs, name, "static ", &js, &ts);
@@ -1983,7 +1999,7 @@ impl<'a> Context<'a> {
19831999
bail!(
19842000
"NPM dependencies have been specified in `{}` but \
19852001
this is only compatible with the `bundler` and `nodejs` targets",
1986-
path.display(),
2002+
path.display(),
19872003
);
19882004
}
19892005

@@ -2164,6 +2180,31 @@ impl ExportedClass {
21642180
self.typescript.push_str(ts);
21652181
self.typescript.push_str("\n");
21662182
}
2183+
2184+
/// Used for adding a field to a class, mainly to ensure that TypeScript
2185+
/// generation is handled specially.
2186+
///
2187+
/// Note that the `ts` is optional and it's expected to just be the field
2188+
/// type, not the full signature. It's currently only available on getters,
2189+
/// but there currently has to always be at least a getter.
2190+
fn push_field(&mut self, field: &str, js: &str, ts: Option<&str>, getter: bool) {
2191+
if getter {
2192+
self.contents.push_str("get ");
2193+
} else {
2194+
self.contents.push_str("set ");
2195+
}
2196+
self.contents.push_str(field);
2197+
self.contents.push_str(js);
2198+
self.contents.push_str("\n");
2199+
let (ty, has_setter) = self
2200+
.typescript_fields
2201+
.entry(field.to_string())
2202+
.or_insert_with(Default::default);
2203+
if let Some(ts) = ts {
2204+
*ty = ts.to_string();
2205+
}
2206+
*has_setter = *has_setter || !getter;
2207+
}
21672208
}
21682209

21692210
#[test]

0 commit comments

Comments
 (0)