Skip to content

Commit

Permalink
Add option to emit TypeScript definitions for Wasm module exports. (#…
Browse files Browse the repository at this point in the history
…21279)

The new flag `--emit-tsd <filename>` will generate a TypeScript defintion
file for any Wasm module exports. If embind is also used the definitions
for those types will also be included in the same file.

This still doesn't give the full picture of Wasm module e.g. missing
HEAP<N> and the various helper functions defined in JS.
  • Loading branch information
brendandahl committed Feb 12, 2024
1 parent 7459cab commit 57ba793
Show file tree
Hide file tree
Showing 13 changed files with 329 additions and 21 deletions.
4 changes: 4 additions & 0 deletions emcc.py
Original file line number Diff line number Diff line change
Expand Up @@ -140,6 +140,7 @@ def __init__(self):
self.ignore_dynamic_linking = False
self.shell_path = None
self.source_map_base = ''
self.emit_tsd = ''
self.embind_emit_tsd = ''
self.emrun = False
self.cpu_profiler = False
Expand Down Expand Up @@ -1276,6 +1277,9 @@ def consume_arg_file():
options.source_map_base = consume_arg()
elif check_arg('--embind-emit-tsd'):
options.embind_emit_tsd = consume_arg()
elif check_arg('--emit-tsd'):
diagnostics.warning('experimental', '--emit-tsd is still experimental. Not all definitions are generated.')
options.emit_tsd = consume_arg()
elif check_flag('--no-entry'):
options.no_entry = True
elif check_arg('--js-library'):
Expand Down
2 changes: 1 addition & 1 deletion src/embind/embind_gen.js
Original file line number Diff line number Diff line change
Expand Up @@ -365,7 +365,7 @@ var LibraryEmbind = {
def.print(this.typeToJsName.bind(this), out);
}
// Print module definitions
out.push('export interface MainModule {\n');
out.push('interface EmbindModule {\n');
for (const def of this.definitions) {
if (!def.printModuleEntry) {
continue;
Expand Down
8 changes: 7 additions & 1 deletion test/other/embind_tsgen.d.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,8 @@
// TypeScript bindings for emscripten-generated code. Automatically generated at compile time.
interface WasmModule {
_main(_0: number, _1: number): number;
}

export interface Test {
x: number;
readonly y: number;
Expand Down Expand Up @@ -77,7 +82,7 @@ export interface DerivedClass extends BaseClass {

export type ValArr = [ number, number, number ];

export interface MainModule {
interface EmbindModule {
Test: {staticFunction(_0: number): number; staticFunctionWithParam(x: number): number; staticProperty: number};
class_returning_fn(): Test;
class_unique_ptr_returning_fn(): Test;
Expand All @@ -104,3 +109,4 @@ export interface MainModule {
string_test(_0: ArrayBuffer|Uint8Array|Uint8ClampedArray|Int8Array|string): string;
wstring_test(_0: string): string;
}
export type MainModule = WasmModule & EmbindModule;
7 changes: 6 additions & 1 deletion test/other/embind_tsgen_bigint.d.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,8 @@
export interface MainModule {
// TypeScript bindings for emscripten-generated code. Automatically generated at compile time.
interface WasmModule {
}

interface EmbindModule {
bigintFn(_0: bigint): bigint;
}
export type MainModule = WasmModule & EmbindModule;
110 changes: 110 additions & 0 deletions test/other/embind_tsgen_ignore_1.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
// TypeScript bindings for emscripten-generated code. Automatically generated at compile time.
interface WasmModule {
_pthread_self(): number;
_main(_0: number, _1: number): number;
__emscripten_tls_init(): number;
__emscripten_proxy_main(_0: number, _1: number): number;
__embind_initialize_bindings(): void;
__emscripten_thread_init(_0: number, _1: number, _2: number, _3: number, _4: number, _5: number): void;
__emscripten_thread_crashed(): void;
__emscripten_thread_exit(_0: number): void;
}

export interface Test {
x: number;
readonly y: number;
functionOne(_0: number, _1: number): number;
functionTwo(_0: number, _1: number): number;
functionFour(_0: boolean): number;
functionFive(x: number, y: number): number;
constFn(): number;
longFn(_0: number): number;
functionThree(_0: ArrayBuffer|Uint8Array|Uint8ClampedArray|Int8Array|string): number;
functionSix(str: ArrayBuffer|Uint8Array|Uint8ClampedArray|Int8Array|string): number;
delete(): void;
}

export interface BarValue<T extends number> {
value: T;
}
export type Bar = BarValue<0>|BarValue<1>|BarValue<2>;

export interface EmptyEnumValue<T extends number> {
value: T;
}
export type EmptyEnum = never/* Empty Enumerator */;

export type ValArrIx = [ Bar, Bar, Bar, Bar ];

export interface IntVec {
push_back(_0: number): void;
resize(_0: number, _1: number): void;
size(): number;
set(_0: number, _1: number): boolean;
get(_0: number): any;
delete(): void;
}

export interface Foo {
process(_0: Test): void;
delete(): void;
}

export type ValObj = {
foo: Foo,
bar: Bar
};

export interface ClassWithConstructor {
fn(_0: number): number;
delete(): void;
}

export interface ClassWithTwoConstructors {
delete(): void;
}

export interface ClassWithSmartPtrConstructor {
fn(_0: number): number;
delete(): void;
}

export interface BaseClass {
fn(_0: number): number;
delete(): void;
}

export interface DerivedClass extends BaseClass {
fn2(_0: number): number;
delete(): void;
}

export type ValArr = [ number, number, number ];

interface EmbindModule {
Test: {staticFunction(_0: number): number; staticFunctionWithParam(x: number): number; staticProperty: number};
class_returning_fn(): Test;
class_unique_ptr_returning_fn(): Test;
a_class_instance: Test;
an_enum: Bar;
Bar: {valueOne: BarValue<0>, valueTwo: BarValue<1>, valueThree: BarValue<2>};
EmptyEnum: {};
enum_returning_fn(): Bar;
IntVec: {new(): IntVec};
Foo: {};
ClassWithConstructor: {new(_0: number, _1: ValArr): ClassWithConstructor};
ClassWithTwoConstructors: {new(): ClassWithTwoConstructors; new(_0: number): ClassWithTwoConstructors};
ClassWithSmartPtrConstructor: {new(_0: number, _1: ValArr): ClassWithSmartPtrConstructor};
BaseClass: {};
DerivedClass: {};
a_bool: boolean;
an_int: number;
global_fn(_0: number, _1: number): number;
optional_test(_0: Foo | undefined): number | undefined;
smart_ptr_function(_0: ClassWithSmartPtrConstructor): number;
smart_ptr_function_with_params(foo: ClassWithSmartPtrConstructor): number;
function_with_callback_param(_0: (message: string) => void): number;
string_test(_0: ArrayBuffer|Uint8Array|Uint8ClampedArray|Int8Array|string): string;
wstring_test(_0: string): string;
}
export type MainModule = WasmModule & EmbindModule;
102 changes: 102 additions & 0 deletions test/other/embind_tsgen_ignore_2.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
// TypeScript bindings for emscripten-generated code. Automatically generated at compile time.
interface WasmModule {
}

export interface Test {
x: number;
readonly y: number;
functionOne(_0: number, _1: number): number;
functionTwo(_0: number, _1: number): number;
functionFour(_0: boolean): number;
functionFive(x: number, y: number): number;
constFn(): number;
longFn(_0: number): number;
functionThree(_0: ArrayBuffer|Uint8Array|Uint8ClampedArray|Int8Array|string): number;
functionSix(str: ArrayBuffer|Uint8Array|Uint8ClampedArray|Int8Array|string): number;
delete(): void;
}

export interface BarValue<T extends number> {
value: T;
}
export type Bar = BarValue<0>|BarValue<1>|BarValue<2>;

export interface EmptyEnumValue<T extends number> {
value: T;
}
export type EmptyEnum = never/* Empty Enumerator */;

export type ValArrIx = [ Bar, Bar, Bar, Bar ];

export interface IntVec {
push_back(_0: number): void;
resize(_0: number, _1: number): void;
size(): number;
set(_0: number, _1: number): boolean;
get(_0: number): any;
delete(): void;
}

export interface Foo {
process(_0: Test): void;
delete(): void;
}

export type ValObj = {
foo: Foo,
bar: Bar
};

export interface ClassWithConstructor {
fn(_0: number): number;
delete(): void;
}

export interface ClassWithTwoConstructors {
delete(): void;
}

export interface ClassWithSmartPtrConstructor {
fn(_0: number): number;
delete(): void;
}

export interface BaseClass {
fn(_0: number): number;
delete(): void;
}

export interface DerivedClass extends BaseClass {
fn2(_0: number): number;
delete(): void;
}

export type ValArr = [ number, number, number ];

interface EmbindModule {
Test: {staticFunction(_0: number): number; staticFunctionWithParam(x: number): number; staticProperty: number};
class_returning_fn(): Test;
class_unique_ptr_returning_fn(): Test;
a_class_instance: Test;
an_enum: Bar;
Bar: {valueOne: BarValue<0>, valueTwo: BarValue<1>, valueThree: BarValue<2>};
EmptyEnum: {};
enum_returning_fn(): Bar;
IntVec: {new(): IntVec};
Foo: {};
ClassWithConstructor: {new(_0: number, _1: ValArr): ClassWithConstructor};
ClassWithTwoConstructors: {new(): ClassWithTwoConstructors; new(_0: number): ClassWithTwoConstructors};
ClassWithSmartPtrConstructor: {new(_0: number, _1: ValArr): ClassWithSmartPtrConstructor};
BaseClass: {};
DerivedClass: {};
a_bool: boolean;
an_int: number;
global_fn(_0: number, _1: number): number;
optional_test(_0: Foo | undefined): number | undefined;
smart_ptr_function(_0: ClassWithSmartPtrConstructor): number;
smart_ptr_function_with_params(foo: ClassWithSmartPtrConstructor): number;
function_with_callback_param(_0: (message: string) => void): number;
string_test(_0: ArrayBuffer|Uint8Array|Uint8ClampedArray|Int8Array|string): string;
wstring_test(_0: string): string;
}
export type MainModule = WasmModule & EmbindModule;
7 changes: 6 additions & 1 deletion test/other/embind_tsgen_memory64.d.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,8 @@
export interface MainModule {
// TypeScript bindings for emscripten-generated code. Automatically generated at compile time.
interface WasmModule {
}

interface EmbindModule {
longFn(_0: bigint): bigint;
}
export type MainModule = WasmModule & EmbindModule;
10 changes: 10 additions & 0 deletions test/other/test_emit_tsd.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
#include <emscripten.h>

EMSCRIPTEN_KEEPALIVE void fooVoid() {}
EMSCRIPTEN_KEEPALIVE int fooInt(int a, int b) {
return 42;
}

int main() {

}
8 changes: 8 additions & 0 deletions test/other/test_emit_tsd.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
// TypeScript bindings for emscripten-generated code. Automatically generated at compile time.
interface WasmModule {
_fooVoid(): void;
_fooInt(_0: number, _1: number): number;
_main(_0: number, _1: number): number;
}

export type MainModule = WasmModule;
10 changes: 8 additions & 2 deletions test/test_other.py
Original file line number Diff line number Diff line change
Expand Up @@ -3136,14 +3136,14 @@ def test_embind_tsgen_ignore(self):
'-lembind', # Test duplicated link option.
]
self.emcc(test_file('other/embind_tsgen.cpp'), extra_args)
self.assertFileContents(test_file('other/embind_tsgen.d.ts'), read_file('embind_tsgen.d.ts'))
self.assertFileContents(test_file('other/embind_tsgen_ignore_1.d.ts'), read_file('embind_tsgen.d.ts'))
# Test these args separately since they conflict with arguments in the first test.
extra_args = ['-sMODULARIZE',
'--embed-file', 'fail.js',
'-sMINIMAL_RUNTIME=2',
'-sEXPORT_ES6=1']
self.emcc(test_file('other/embind_tsgen.cpp'), extra_args)
self.assertFileContents(test_file('other/embind_tsgen.d.ts'), read_file('embind_tsgen.d.ts'))
self.assertFileContents(test_file('other/embind_tsgen_ignore_2.d.ts'), read_file('embind_tsgen.d.ts'))

def test_embind_tsgen_test_embind(self):
self.run_process([EMXX, test_file('embind/embind_test.cpp'),
Expand Down Expand Up @@ -3189,6 +3189,12 @@ def test_embind_jsgen_method_pointer_stability(self):
# AOT JS generation still works correctly.
self.do_runf('other/embind_jsgen_method_pointer_stability.cpp', 'done')

def test_emit_tsd(self):
self.run_process([EMCC, test_file('other/test_emit_tsd.c'),
'--emit-tsd', 'test_emit_tsd.d.ts', '-Wno-experimental'] +
self.get_emcc_args())
self.assertFileContents(test_file('other/test_emit_tsd.d.ts'), read_file('test_emit_tsd.d.ts'))

def test_emconfig(self):
output = self.run_process([emconfig, 'LLVM_ROOT'], stdout=PIPE).stdout.strip()
self.assertEqual(output, config.LLVM_ROOT)
Expand Down
Loading

0 comments on commit 57ba793

Please sign in to comment.