Skip to content

Commit c5a0b87

Browse files
Merge pull request #371 from swiftwasm/yt/add-import-ts-runtime-tests
BridgeJS: Enhance importing TS classes
2 parents aa15657 + aa44c42 commit c5a0b87

File tree

15 files changed

+261
-62
lines changed

15 files changed

+261
-62
lines changed

Plugins/BridgeJS/Sources/BridgeJSLink/BridgeJSLink.swift

Lines changed: 117 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -47,10 +47,8 @@ struct BridgeJSLink {
4747

4848
func link() throws -> (outputJs: String, outputDts: String) {
4949
var exportsLines: [String] = []
50-
var importedLines: [String] = []
5150
var classLines: [String] = []
5251
var dtsExportLines: [String] = []
53-
var dtsImportLines: [String] = []
5452
var dtsClassLines: [String] = []
5553

5654
if exportedSkeletons.contains(where: { $0.classes.count > 0 }) {
@@ -84,57 +82,18 @@ struct BridgeJSLink {
8482
}
8583
}
8684

85+
var importObjectBuilders: [ImportObjectBuilder] = []
8786
for skeletonSet in importedSkeletons {
88-
importedLines.append("const \(skeletonSet.moduleName) = importObject[\"\(skeletonSet.moduleName)\"] = {};")
89-
func assignToImportObject(name: String, function: [String]) {
90-
var js = function
91-
js[0] = "\(skeletonSet.moduleName)[\"\(name)\"] = " + js[0]
92-
importedLines.append(contentsOf: js)
93-
}
87+
let importObjectBuilder = ImportObjectBuilder(moduleName: skeletonSet.moduleName)
9488
for fileSkeleton in skeletonSet.children {
9589
for function in fileSkeleton.functions {
96-
let (js, dts) = try renderImportedFunction(function: function)
97-
assignToImportObject(name: function.abiName(context: nil), function: js)
98-
dtsImportLines.append(contentsOf: dts)
90+
try renderImportedFunction(importObjectBuilder: importObjectBuilder, function: function)
9991
}
10092
for type in fileSkeleton.types {
101-
for property in type.properties {
102-
let getterAbiName = property.getterAbiName(context: type)
103-
let (js, dts) = try renderImportedProperty(
104-
property: property,
105-
abiName: getterAbiName,
106-
emitCall: { thunkBuilder in
107-
thunkBuilder.callPropertyGetter(name: property.name, returnType: property.type)
108-
return try thunkBuilder.lowerReturnValue(returnType: property.type)
109-
}
110-
)
111-
assignToImportObject(name: getterAbiName, function: js)
112-
dtsImportLines.append(contentsOf: dts)
113-
114-
if !property.isReadonly {
115-
let setterAbiName = property.setterAbiName(context: type)
116-
let (js, dts) = try renderImportedProperty(
117-
property: property,
118-
abiName: setterAbiName,
119-
emitCall: { thunkBuilder in
120-
thunkBuilder.liftParameter(
121-
param: Parameter(label: nil, name: "newValue", type: property.type)
122-
)
123-
thunkBuilder.callPropertySetter(name: property.name, returnType: property.type)
124-
return nil
125-
}
126-
)
127-
assignToImportObject(name: setterAbiName, function: js)
128-
dtsImportLines.append(contentsOf: dts)
129-
}
130-
}
131-
for method in type.methods {
132-
let (js, dts) = try renderImportedMethod(context: type, method: method)
133-
assignToImportObject(name: method.abiName(context: type), function: js)
134-
dtsImportLines.append(contentsOf: dts)
135-
}
93+
try renderImportedType(importObjectBuilder: importObjectBuilder, type: type)
13694
}
13795
}
96+
importObjectBuilders.append(importObjectBuilder)
13897
}
13998

14099
let outputJs = """
@@ -175,7 +134,7 @@ struct BridgeJSLink {
175134
target.set(tmpRetBytes);
176135
tmpRetBytes = undefined;
177136
}
178-
\(importedLines.map { $0.indent(count: 12) }.joined(separator: "\n"))
137+
\(importObjectBuilders.flatMap { $0.importedLines }.map { $0.indent(count: 12) }.joined(separator: "\n"))
179138
},
180139
setInstance: (i) => {
181140
instance = i;
@@ -198,7 +157,7 @@ struct BridgeJSLink {
198157
dtsLines.append(contentsOf: dtsExportLines.map { $0.indent(count: 4) })
199158
dtsLines.append("}")
200159
dtsLines.append("export type Imports = {")
201-
dtsLines.append(contentsOf: dtsImportLines.map { $0.indent(count: 4) })
160+
dtsLines.append(contentsOf: importObjectBuilders.flatMap { $0.dtsImportLines }.map { $0.indent(count: 4) })
202161
dtsLines.append("}")
203162
let outputDts = """
204163
// NOTICE: This is auto-generated code by BridgeJS from JavaScriptKit,
@@ -437,6 +396,11 @@ struct BridgeJSLink {
437396
}
438397
}
439398

399+
func callConstructor(name: String) {
400+
let call = "new options.imports.\(name)(\(parameterForwardings.joined(separator: ", ")))"
401+
bodyLines.append("let ret = \(call);")
402+
}
403+
440404
func callMethod(name: String, returnType: BridgeType) {
441405
let call = "swift.memory.getObject(self).\(name)(\(parameterForwardings.joined(separator: ", ")))"
442406
if returnType == .void {
@@ -475,7 +439,31 @@ struct BridgeJSLink {
475439
}
476440
}
477441

478-
func renderImportedFunction(function: ImportedFunctionSkeleton) throws -> (js: [String], dts: [String]) {
442+
class ImportObjectBuilder {
443+
var moduleName: String
444+
var importedLines: [String] = []
445+
var dtsImportLines: [String] = []
446+
447+
init(moduleName: String) {
448+
self.moduleName = moduleName
449+
importedLines.append("const \(moduleName) = importObject[\"\(moduleName)\"] = {};")
450+
}
451+
452+
func assignToImportObject(name: String, function: [String]) {
453+
var js = function
454+
js[0] = "\(moduleName)[\"\(name)\"] = " + js[0]
455+
importedLines.append(contentsOf: js)
456+
}
457+
458+
func appendDts(_ lines: [String]) {
459+
dtsImportLines.append(contentsOf: lines)
460+
}
461+
}
462+
463+
func renderImportedFunction(
464+
importObjectBuilder: ImportObjectBuilder,
465+
function: ImportedFunctionSkeleton
466+
) throws {
479467
let thunkBuilder = ImportedThunkBuilder()
480468
for param in function.parameters {
481469
thunkBuilder.liftParameter(param: param)
@@ -486,11 +474,85 @@ struct BridgeJSLink {
486474
name: function.abiName(context: nil),
487475
returnExpr: returnExpr
488476
)
489-
var dtsLines: [String] = []
490-
dtsLines.append(
491-
"\(function.name)\(renderTSSignature(parameters: function.parameters, returnType: function.returnType));"
477+
importObjectBuilder.appendDts(
478+
[
479+
"\(function.name)\(renderTSSignature(parameters: function.parameters, returnType: function.returnType));"
480+
]
492481
)
493-
return (funcLines, dtsLines)
482+
importObjectBuilder.assignToImportObject(name: function.abiName(context: nil), function: funcLines)
483+
}
484+
485+
func renderImportedType(
486+
importObjectBuilder: ImportObjectBuilder,
487+
type: ImportedTypeSkeleton
488+
) throws {
489+
if let constructor = type.constructor {
490+
try renderImportedConstructor(
491+
importObjectBuilder: importObjectBuilder,
492+
type: type,
493+
constructor: constructor
494+
)
495+
}
496+
for property in type.properties {
497+
let getterAbiName = property.getterAbiName(context: type)
498+
let (js, dts) = try renderImportedProperty(
499+
property: property,
500+
abiName: getterAbiName,
501+
emitCall: { thunkBuilder in
502+
thunkBuilder.callPropertyGetter(name: property.name, returnType: property.type)
503+
return try thunkBuilder.lowerReturnValue(returnType: property.type)
504+
}
505+
)
506+
importObjectBuilder.assignToImportObject(name: getterAbiName, function: js)
507+
importObjectBuilder.appendDts(dts)
508+
509+
if !property.isReadonly {
510+
let setterAbiName = property.setterAbiName(context: type)
511+
let (js, dts) = try renderImportedProperty(
512+
property: property,
513+
abiName: setterAbiName,
514+
emitCall: { thunkBuilder in
515+
thunkBuilder.liftParameter(
516+
param: Parameter(label: nil, name: "newValue", type: property.type)
517+
)
518+
thunkBuilder.callPropertySetter(name: property.name, returnType: property.type)
519+
return nil
520+
}
521+
)
522+
importObjectBuilder.assignToImportObject(name: setterAbiName, function: js)
523+
importObjectBuilder.appendDts(dts)
524+
}
525+
}
526+
for method in type.methods {
527+
let (js, dts) = try renderImportedMethod(context: type, method: method)
528+
importObjectBuilder.assignToImportObject(name: method.abiName(context: type), function: js)
529+
importObjectBuilder.appendDts(dts)
530+
}
531+
}
532+
533+
func renderImportedConstructor(
534+
importObjectBuilder: ImportObjectBuilder,
535+
type: ImportedTypeSkeleton,
536+
constructor: ImportedConstructorSkeleton
537+
) throws {
538+
let thunkBuilder = ImportedThunkBuilder()
539+
for param in constructor.parameters {
540+
thunkBuilder.liftParameter(param: param)
541+
}
542+
let returnType = BridgeType.jsObject(type.name)
543+
thunkBuilder.callConstructor(name: type.name)
544+
let returnExpr = try thunkBuilder.lowerReturnValue(returnType: returnType)
545+
let abiName = constructor.abiName(context: type)
546+
let funcLines = thunkBuilder.renderFunction(
547+
name: abiName,
548+
returnExpr: returnExpr
549+
)
550+
importObjectBuilder.assignToImportObject(name: abiName, function: funcLines)
551+
importObjectBuilder.appendDts([
552+
"\(type.name): {",
553+
"new\(renderTSSignature(parameters: constructor.parameters, returnType: returnType));".indent(count: 4),
554+
"}",
555+
])
494556
}
495557

496558
func renderImportedProperty(
@@ -552,8 +614,8 @@ extension BridgeType {
552614
return "number"
553615
case .bool:
554616
return "boolean"
555-
case .jsObject:
556-
return "any"
617+
case .jsObject(let name):
618+
return name ?? "any"
557619
case .swiftHeapObject(let name):
558620
return name
559621
}

Plugins/BridgeJS/Sources/BridgeJSTool/ImportTS.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -237,7 +237,7 @@ struct ImportTS {
237237
preconditionFailure("assignThis can only be called with a jsObject return type")
238238
}
239239
abiReturnType = .i32
240-
body.append("self.this = ret")
240+
body.append("self.this = JSObject(id: UInt32(bitPattern: ret))")
241241
}
242242

243243
func renderImportDecl() -> DeclSyntax {

Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/Interface.Import.d.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
export type Exports = {
88
}
99
export type Imports = {
10-
returnAnimatable(): any;
10+
returnAnimatable(): Animatable;
1111
}
1212
export function createInstantiator(options: {
1313
imports: Imports;

Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/TypeScriptClass.Import.d.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,9 @@
77
export type Exports = {
88
}
99
export type Imports = {
10+
Greeter: {
11+
new(name: string): Greeter;
12+
}
1013
}
1114
export function createInstantiator(options: {
1215
imports: Imports;

Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/TypeScriptClass.Import.js

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,12 @@ export async function createInstantiator(options, swift) {
3636
tmpRetBytes = undefined;
3737
}
3838
const TestModule = importObject["TestModule"] = {};
39+
TestModule["bjs_Greeter_init"] = function bjs_Greeter_init(name) {
40+
const nameObject = swift.memory.getObject(name);
41+
swift.memory.release(name);
42+
let ret = new options.imports.Greeter(nameObject);
43+
return swift.memory.retain(ret);
44+
}
3945
TestModule["bjs_Greeter_greet"] = function bjs_Greeter_greet(self) {
4046
let ret = swift.memory.getObject(self).greet();
4147
tmpRetBytes = textEncoder.encode(ret);

Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ImportTSTests/TypeScriptClass.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ struct Greeter {
3434
_make_jsstring(b.baseAddress.unsafelyUnwrapped, Int32(b.count))
3535
}
3636
let ret = bjs_Greeter_init(nameId)
37-
self.this = ret
37+
self.this = JSObject(id: UInt32(bitPattern: ret))
3838
}
3939

4040
func greet() -> String {

Plugins/PackageToJS/Templates/bin/test.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,7 @@ const harnesses = {
6565
if (preludeScript) {
6666
const prelude = await import(preludeScript)
6767
if (prelude.setupOptions) {
68-
options = prelude.setupOptions(options, { isMainThread: true })
68+
options = await prelude.setupOptions(options, { isMainThread: true })
6969
}
7070
}
7171
process.on("beforeExit", () => {

Plugins/PackageToJS/Templates/platforms/node.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,7 @@ export function createDefaultWorkerFactory(preludeScript) {
5959
if (preludeScript) {
6060
const prelude = await import(preludeScript);
6161
if (prelude.setupOptions) {
62-
options = prelude.setupOptions(options, { isMainThread: false })
62+
options = await prelude.setupOptions(options, { isMainThread: false })
6363
}
6464
}
6565
await instantiateForThread(tid, startArg, {

Plugins/PackageToJS/Templates/test.d.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,12 @@
11
import type { InstantiateOptions, instantiate } from "./instantiate";
22

3+
export type SetupOptionsFn = (
4+
options: InstantiateOptions,
5+
context: {
6+
isMainThread: boolean,
7+
}
8+
) => Promise<InstantiateOptions>
9+
310
export function testBrowser(
411
options: {
512
preludeScript?: string,

Plugins/PackageToJS/Templates/test.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -157,6 +157,7 @@ export async function testBrowserInPage(options, processInfo) {
157157
});
158158

159159
const { instantiate } = await import("./instantiate.js");
160+
/** @type {import('./test.d.ts').SetupOptionsFn} */
160161
let setupOptions = (options, _) => { return options };
161162
if (processInfo.preludeScript) {
162163
const prelude = await import(processInfo.preludeScript);

Tests/BridgeJSRuntimeTests/Generated/ImportTS.swift

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,4 +47,48 @@ func jsRoundTripString(_ v: String) -> String {
4747
_init_memory_with_result(b.baseAddress.unsafelyUnwrapped, Int32(ret))
4848
return Int(ret)
4949
}
50+
}
51+
52+
struct JsGreeter {
53+
let this: JSObject
54+
55+
init(this: JSObject) {
56+
self.this = this
57+
}
58+
59+
init(takingThis this: Int32) {
60+
self.this = JSObject(id: UInt32(bitPattern: this))
61+
}
62+
63+
init(_ name: String) {
64+
@_extern(wasm, module: "BridgeJSRuntimeTests", name: "bjs_JsGreeter_init")
65+
func bjs_JsGreeter_init(_ name: Int32) -> Int32
66+
var name = name
67+
let nameId = name.withUTF8 { b in
68+
_make_jsstring(b.baseAddress.unsafelyUnwrapped, Int32(b.count))
69+
}
70+
let ret = bjs_JsGreeter_init(nameId)
71+
self.this = JSObject(id: UInt32(bitPattern: ret))
72+
}
73+
74+
func greet() -> String {
75+
@_extern(wasm, module: "BridgeJSRuntimeTests", name: "bjs_JsGreeter_greet")
76+
func bjs_JsGreeter_greet(_ self: Int32) -> Int32
77+
let ret = bjs_JsGreeter_greet(Int32(bitPattern: self.this.id))
78+
return String(unsafeUninitializedCapacity: Int(ret)) { b in
79+
_init_memory_with_result(b.baseAddress.unsafelyUnwrapped, Int32(ret))
80+
return Int(ret)
81+
}
82+
}
83+
84+
func changeName(_ name: String) -> Void {
85+
@_extern(wasm, module: "BridgeJSRuntimeTests", name: "bjs_JsGreeter_changeName")
86+
func bjs_JsGreeter_changeName(_ self: Int32, _ name: Int32) -> Void
87+
var name = name
88+
let nameId = name.withUTF8 { b in
89+
_make_jsstring(b.baseAddress.unsafelyUnwrapped, Int32(b.count))
90+
}
91+
bjs_JsGreeter_changeName(Int32(bitPattern: self.this.id), nameId)
92+
}
93+
5094
}

0 commit comments

Comments
 (0)