From b9aab0c75d4a57310f32041608372bdf1d236e40 Mon Sep 17 00:00:00 2001 From: Tyler K Date: Fri, 13 Dec 2024 10:02:40 -0800 Subject: [PATCH] JS Functions can accept struct params as JS objects (#734) --- example/js/lib/api/FixedDecimalFormatter.d.ts | 3 +- example/js/lib/api/FixedDecimalFormatter.mjs | 2 +- .../lib/api/FixedDecimalFormatterOptions.d.ts | 4 +- .../lib/api/FixedDecimalFormatterOptions.mjs | 12 ++ feature_tests/c/include/CyclicStructC.h | 2 + feature_tests/cpp/include/CyclicStructC.d.hpp | 2 + feature_tests/cpp/include/CyclicStructC.hpp | 7 + .../dart/lib/src/CyclicStructC.g.dart | 11 ++ feature_tests/js/api/BigStructWithStuff.d.ts | 7 +- feature_tests/js/api/BigStructWithStuff.mjs | 18 ++- feature_tests/js/api/BorrowedFields.d.ts | 4 +- feature_tests/js/api/BorrowedFields.mjs | 12 ++ .../js/api/BorrowedFieldsReturning.d.ts | 4 +- .../js/api/BorrowedFieldsReturning.mjs | 12 ++ .../js/api/BorrowedFieldsWithBounds.d.ts | 4 +- .../js/api/BorrowedFieldsWithBounds.mjs | 12 ++ feature_tests/js/api/CyclicStructA.d.ts | 7 +- feature_tests/js/api/CyclicStructA.mjs | 18 ++- feature_tests/js/api/CyclicStructB.d.ts | 4 +- feature_tests/js/api/CyclicStructB.mjs | 12 ++ feature_tests/js/api/CyclicStructC.d.ts | 9 +- feature_tests/js/api/CyclicStructC.mjs | 32 +++- feature_tests/js/api/ErrorStruct.d.ts | 4 +- feature_tests/js/api/ErrorStruct.mjs | 12 ++ feature_tests/js/api/Foo.d.ts | 6 +- feature_tests/js/api/Foo.mjs | 4 +- feature_tests/js/api/ImportedStruct.d.ts | 4 +- feature_tests/js/api/ImportedStruct.mjs | 12 ++ feature_tests/js/api/MyStruct.d.ts | 4 +- feature_tests/js/api/MyStruct.mjs | 12 ++ feature_tests/js/api/MyZst.d.ts | 4 +- .../js/api/NestedBorrowedFields.d.ts | 12 +- feature_tests/js/api/NestedBorrowedFields.mjs | 26 +++- feature_tests/js/api/Opaque.d.ts | 3 +- feature_tests/js/api/Opaque.mjs | 2 +- feature_tests/js/api/OptionInputStruct.d.ts | 4 +- feature_tests/js/api/OptionInputStruct.mjs | 12 ++ feature_tests/js/api/OptionOpaque.mjs | 2 +- feature_tests/js/api/OptionStruct.mjs | 12 ++ .../js/api/ScalarPairWithPadding.d.ts | 4 +- .../js/api/ScalarPairWithPadding.mjs | 12 ++ feature_tests/js/test/struct-ts.mts | 36 ++++- .../dev/diplomattest/somelib/CyclicStructC.kt | 9 ++ feature_tests/src/structs.rs | 4 + tool/src/js/converter.rs | 20 ++- tool/src/js/formatter.rs | 8 +- tool/src/js/gen.rs | 143 ++++++++++++++++-- tool/src/js/mod.rs | 22 ++- tool/templates/js/struct.js.jinja | 20 ++- 49 files changed, 520 insertions(+), 91 deletions(-) diff --git a/example/js/lib/api/FixedDecimalFormatter.d.ts b/example/js/lib/api/FixedDecimalFormatter.d.ts index fe4366f1a..9561d4b46 100644 --- a/example/js/lib/api/FixedDecimalFormatter.d.ts +++ b/example/js/lib/api/FixedDecimalFormatter.d.ts @@ -2,6 +2,7 @@ import type { DataProvider } from "./DataProvider" import type { FixedDecimal } from "./FixedDecimal" import type { FixedDecimalFormatterOptions } from "./FixedDecimalFormatterOptions" +import type { FixedDecimalFormatterOptions_obj } from "./FixedDecimalFormatterOptions" import type { Locale } from "./Locale" import type { pointer, codepoint } from "./diplomat-runtime.d.ts"; @@ -15,7 +16,7 @@ export class FixedDecimalFormatter { get ffiValue(): pointer; - static tryNew(locale: Locale, provider: DataProvider, options: FixedDecimalFormatterOptions): FixedDecimalFormatter | null; + static tryNew(locale: Locale, provider: DataProvider, options: FixedDecimalFormatterOptions_obj): FixedDecimalFormatter | null; formatWrite(value: FixedDecimal): string; } \ No newline at end of file diff --git a/example/js/lib/api/FixedDecimalFormatter.mjs b/example/js/lib/api/FixedDecimalFormatter.mjs index c545d7a9e..c424ec42f 100644 --- a/example/js/lib/api/FixedDecimalFormatter.mjs +++ b/example/js/lib/api/FixedDecimalFormatter.mjs @@ -47,7 +47,7 @@ export class FixedDecimalFormatter { const diplomatReceive = new diplomatRuntime.DiplomatReceiveBuf(wasm, 5, 4, true); - const result = wasm.icu4x_FixedDecimalFormatter_try_new_mv1(diplomatReceive.buffer, locale.ffiValue, provider.ffiValue, ...options._intoFFI(functionCleanupArena, {})); + const result = wasm.icu4x_FixedDecimalFormatter_try_new_mv1(diplomatReceive.buffer, locale.ffiValue, provider.ffiValue, ...FixedDecimalFormatterOptions._fromSuppliedValue(diplomatRuntime.internalConstructor, options)._intoFFI(functionCleanupArena, {})); try { if (!diplomatReceive.resultFlag) { diff --git a/example/js/lib/api/FixedDecimalFormatterOptions.d.ts b/example/js/lib/api/FixedDecimalFormatterOptions.d.ts index 0055dac38..23db17b77 100644 --- a/example/js/lib/api/FixedDecimalFormatterOptions.d.ts +++ b/example/js/lib/api/FixedDecimalFormatterOptions.d.ts @@ -2,7 +2,7 @@ import type { FixedDecimalGroupingStrategy } from "./FixedDecimalGroupingStrategy" import type { pointer, codepoint } from "./diplomat-runtime.d.ts"; -type FixedDecimalFormatterOptions_Obj = { +type FixedDecimalFormatterOptions_obj = { groupingStrategy: FixedDecimalGroupingStrategy; someOtherConfig: boolean; }; @@ -14,7 +14,7 @@ export class FixedDecimalFormatterOptions { get someOtherConfig() : boolean; set someOtherConfig(value: boolean); - constructor(structObj : FixedDecimalFormatterOptions_Obj); + constructor(structObj : FixedDecimalFormatterOptions_obj); static default_(): FixedDecimalFormatterOptions; } \ No newline at end of file diff --git a/example/js/lib/api/FixedDecimalFormatterOptions.mjs b/example/js/lib/api/FixedDecimalFormatterOptions.mjs index 623784e9e..f601efb19 100644 --- a/example/js/lib/api/FixedDecimalFormatterOptions.mjs +++ b/example/js/lib/api/FixedDecimalFormatterOptions.mjs @@ -53,6 +53,18 @@ export class FixedDecimalFormatterOptions { return [this.#groupingStrategy.ffiValue, this.#someOtherConfig, ...diplomatRuntime.maybePaddingFields(forcePadding, 3 /* x i8 */)] } + static _fromSuppliedValue(internalConstructor, obj) { + if (internalConstructor !== diplomatRuntime.internalConstructor) { + throw new Error("_fromSuppliedValue cannot be called externally."); + } + + if (obj instanceof FixedDecimalFormatterOptions) { + return obj; + } + + return new FixedDecimalFormatterOptions(obj); + } + _writeToArrayBuffer( arrayBuffer, offset, diff --git a/feature_tests/c/include/CyclicStructC.h b/feature_tests/c/include/CyclicStructC.h index 757545db8..2513c96fe 100644 --- a/feature_tests/c/include/CyclicStructC.h +++ b/feature_tests/c/include/CyclicStructC.h @@ -15,6 +15,8 @@ +CyclicStructC CyclicStructC_takes_nested_parameters(CyclicStructC c); + void CyclicStructC_cyclic_out(CyclicStructC self, DiplomatWrite* write); diff --git a/feature_tests/cpp/include/CyclicStructC.d.hpp b/feature_tests/cpp/include/CyclicStructC.d.hpp index 01797ce71..f3f7876f1 100644 --- a/feature_tests/cpp/include/CyclicStructC.d.hpp +++ b/feature_tests/cpp/include/CyclicStructC.d.hpp @@ -27,6 +27,8 @@ namespace capi { struct CyclicStructC { CyclicStructA a; + inline static CyclicStructC takes_nested_parameters(CyclicStructC c); + inline std::string cyclic_out(); inline diplomat::capi::CyclicStructC AsFFI() const; diff --git a/feature_tests/cpp/include/CyclicStructC.hpp b/feature_tests/cpp/include/CyclicStructC.hpp index 1ca720126..d712687d8 100644 --- a/feature_tests/cpp/include/CyclicStructC.hpp +++ b/feature_tests/cpp/include/CyclicStructC.hpp @@ -17,6 +17,8 @@ namespace diplomat { namespace capi { extern "C" { + diplomat::capi::CyclicStructC CyclicStructC_takes_nested_parameters(diplomat::capi::CyclicStructC c); + void CyclicStructC_cyclic_out(diplomat::capi::CyclicStructC self, diplomat::capi::DiplomatWrite* write); @@ -24,6 +26,11 @@ namespace capi { } // namespace capi } // namespace +inline CyclicStructC CyclicStructC::takes_nested_parameters(CyclicStructC c) { + auto result = diplomat::capi::CyclicStructC_takes_nested_parameters(c.AsFFI()); + return CyclicStructC::FromFFI(result); +} + inline std::string CyclicStructC::cyclic_out() { std::string output; diplomat::capi::DiplomatWrite write = diplomat::WriteFromString(output); diff --git a/feature_tests/dart/lib/src/CyclicStructC.g.dart b/feature_tests/dart/lib/src/CyclicStructC.g.dart index d31a05e76..8866c1481 100644 --- a/feature_tests/dart/lib/src/CyclicStructC.g.dart +++ b/feature_tests/dart/lib/src/CyclicStructC.g.dart @@ -27,6 +27,12 @@ final class CyclicStructC { return struct; } + static CyclicStructC takesNestedParameters(CyclicStructC c) { + final temp = _FinalizedArena(); + final result = _CyclicStructC_takes_nested_parameters(c._toFfi(temp.arena)); + return CyclicStructC._fromFfi(result); + } + String cyclicOut() { final temp = _FinalizedArena(); final write = _Write(); @@ -45,6 +51,11 @@ final class CyclicStructC { ]); } +@meta.RecordUse() +@ffi.Native<_CyclicStructCFfi Function(_CyclicStructCFfi)>(isLeaf: true, symbol: 'CyclicStructC_takes_nested_parameters') +// ignore: non_constant_identifier_names +external _CyclicStructCFfi _CyclicStructC_takes_nested_parameters(_CyclicStructCFfi c); + @meta.RecordUse() @ffi.Native)>(isLeaf: true, symbol: 'CyclicStructC_cyclic_out') // ignore: non_constant_identifier_names diff --git a/feature_tests/js/api/BigStructWithStuff.d.ts b/feature_tests/js/api/BigStructWithStuff.d.ts index 7f8b66ffd..add1d37c5 100644 --- a/feature_tests/js/api/BigStructWithStuff.d.ts +++ b/feature_tests/js/api/BigStructWithStuff.d.ts @@ -1,15 +1,16 @@ // generated by diplomat-tool import type { ScalarPairWithPadding } from "./ScalarPairWithPadding" +import type { ScalarPairWithPadding_obj } from "./ScalarPairWithPadding" import type { pointer, codepoint } from "./diplomat-runtime.d.ts"; /** Testing JS-specific layout/padding behavior */ -type BigStructWithStuff_Obj = { +type BigStructWithStuff_obj = { first: number; second: number; third: number; - fourth: ScalarPairWithPadding; + fourth: ScalarPairWithPadding_obj; fifth: number; }; @@ -29,7 +30,7 @@ export class BigStructWithStuff { get fifth() : number; set fifth(value: number); - constructor(structObj : BigStructWithStuff_Obj); + constructor(structObj : BigStructWithStuff_obj); assertValue(extraVal: number): void; } \ No newline at end of file diff --git a/feature_tests/js/api/BigStructWithStuff.mjs b/feature_tests/js/api/BigStructWithStuff.mjs index fc8b080dc..1669c9eba 100644 --- a/feature_tests/js/api/BigStructWithStuff.mjs +++ b/feature_tests/js/api/BigStructWithStuff.mjs @@ -71,7 +71,7 @@ export class BigStructWithStuff { } if ("fourth" in structObj) { - this.#fourth = structObj.fourth; + this.#fourth = ScalarPairWithPadding._fromSuppliedValue(diplomatRuntime.internalConstructor, structObj.fourth); } else { throw new Error("Missing required field fourth."); } @@ -91,7 +91,19 @@ export class BigStructWithStuff { functionCleanupArena, appendArrayMap ) { - return [this.#first, /* [1 x i8] padding */ 0 /* end padding */, this.#second, this.#third, /* [1 x i16] padding */ 0 /* end padding */, ...this.#fourth._intoFFI(functionCleanupArena, {}, true), this.#fifth, /* [3 x i8] padding */ 0, 0, 0 /* end padding */] + return [this.#first, /* [1 x i8] padding */ 0 /* end padding */, this.#second, this.#third, /* [1 x i16] padding */ 0 /* end padding */, ...ScalarPairWithPadding._fromSuppliedValue(diplomatRuntime.internalConstructor, this.#fourth)._intoFFI(functionCleanupArena, {}, true), this.#fifth, /* [3 x i8] padding */ 0, 0, 0 /* end padding */] + } + + static _fromSuppliedValue(internalConstructor, obj) { + if (internalConstructor !== diplomatRuntime.internalConstructor) { + throw new Error("_fromSuppliedValue cannot be called externally."); + } + + if (obj instanceof BigStructWithStuff) { + return obj; + } + + return new BigStructWithStuff(obj); } _writeToArrayBuffer( @@ -103,7 +115,7 @@ export class BigStructWithStuff { diplomatRuntime.writeToArrayBuffer(arrayBuffer, offset + 0, this.#first, Uint8Array); diplomatRuntime.writeToArrayBuffer(arrayBuffer, offset + 2, this.#second, Uint16Array); diplomatRuntime.writeToArrayBuffer(arrayBuffer, offset + 4, this.#third, Uint16Array); - this.#fourth._writeToArrayBuffer(arrayBuffer, offset + 8, functionCleanupArena, {}); + ScalarPairWithPadding._fromSuppliedValue(diplomatRuntime.internalConstructor, this.#fourth)._writeToArrayBuffer(arrayBuffer, offset + 8, functionCleanupArena, {}); diplomatRuntime.writeToArrayBuffer(arrayBuffer, offset + 16, this.#fifth, Uint8Array); } diff --git a/feature_tests/js/api/BorrowedFields.d.ts b/feature_tests/js/api/BorrowedFields.d.ts index 5856c2ea6..13c15d576 100644 --- a/feature_tests/js/api/BorrowedFields.d.ts +++ b/feature_tests/js/api/BorrowedFields.d.ts @@ -2,7 +2,7 @@ import type { Bar } from "./Bar" import type { pointer, codepoint } from "./diplomat-runtime.d.ts"; -type BorrowedFields_Obj = { +type BorrowedFields_obj = { a: string; b: string; c: string; @@ -18,7 +18,7 @@ export class BorrowedFields { get c() : string; set c(value: string); - constructor(structObj : BorrowedFields_Obj); + constructor(structObj : BorrowedFields_obj); static fromBarAndStrings(bar: Bar, dstr16: string, utf8Str: string): BorrowedFields; } \ No newline at end of file diff --git a/feature_tests/js/api/BorrowedFields.mjs b/feature_tests/js/api/BorrowedFields.mjs index d8b4ba518..b3182c6b7 100644 --- a/feature_tests/js/api/BorrowedFields.mjs +++ b/feature_tests/js/api/BorrowedFields.mjs @@ -66,6 +66,18 @@ export class BorrowedFields { return [...diplomatRuntime.CleanupArena.maybeCreateWith(functionCleanupArena, ...appendArrayMap['aAppendArray']).alloc(diplomatRuntime.DiplomatBuf.str16(wasm, this.#a)).splat(), ...diplomatRuntime.CleanupArena.maybeCreateWith(functionCleanupArena, ...appendArrayMap['aAppendArray']).alloc(diplomatRuntime.DiplomatBuf.str8(wasm, this.#b)).splat(), ...diplomatRuntime.CleanupArena.maybeCreateWith(functionCleanupArena, ...appendArrayMap['aAppendArray']).alloc(diplomatRuntime.DiplomatBuf.str8(wasm, this.#c)).splat()] } + static _fromSuppliedValue(internalConstructor, obj) { + if (internalConstructor !== diplomatRuntime.internalConstructor) { + throw new Error("_fromSuppliedValue cannot be called externally."); + } + + if (obj instanceof BorrowedFields) { + return obj; + } + + return new BorrowedFields(obj); + } + _writeToArrayBuffer( arrayBuffer, offset, diff --git a/feature_tests/js/api/BorrowedFieldsReturning.d.ts b/feature_tests/js/api/BorrowedFieldsReturning.d.ts index 40f8aa9cf..609874175 100644 --- a/feature_tests/js/api/BorrowedFieldsReturning.d.ts +++ b/feature_tests/js/api/BorrowedFieldsReturning.d.ts @@ -1,7 +1,7 @@ // generated by diplomat-tool import type { pointer, codepoint } from "./diplomat-runtime.d.ts"; -type BorrowedFieldsReturning_Obj = { +type BorrowedFieldsReturning_obj = { bytes: string; }; @@ -9,5 +9,5 @@ export class BorrowedFieldsReturning { get bytes() : string; set bytes(value: string); - constructor(structObj : BorrowedFieldsReturning_Obj); + constructor(structObj : BorrowedFieldsReturning_obj); } \ No newline at end of file diff --git a/feature_tests/js/api/BorrowedFieldsReturning.mjs b/feature_tests/js/api/BorrowedFieldsReturning.mjs index b80f546ec..ef1cda7b6 100644 --- a/feature_tests/js/api/BorrowedFieldsReturning.mjs +++ b/feature_tests/js/api/BorrowedFieldsReturning.mjs @@ -37,6 +37,18 @@ export class BorrowedFieldsReturning { return [...diplomatRuntime.CleanupArena.maybeCreateWith(functionCleanupArena, ...appendArrayMap['aAppendArray']).alloc(diplomatRuntime.DiplomatBuf.str8(wasm, this.#bytes)).splat()] } + static _fromSuppliedValue(internalConstructor, obj) { + if (internalConstructor !== diplomatRuntime.internalConstructor) { + throw new Error("_fromSuppliedValue cannot be called externally."); + } + + if (obj instanceof BorrowedFieldsReturning) { + return obj; + } + + return new BorrowedFieldsReturning(obj); + } + _writeToArrayBuffer( arrayBuffer, offset, diff --git a/feature_tests/js/api/BorrowedFieldsWithBounds.d.ts b/feature_tests/js/api/BorrowedFieldsWithBounds.d.ts index a448fd141..875d7b41f 100644 --- a/feature_tests/js/api/BorrowedFieldsWithBounds.d.ts +++ b/feature_tests/js/api/BorrowedFieldsWithBounds.d.ts @@ -2,7 +2,7 @@ import type { Foo } from "./Foo" import type { pointer, codepoint } from "./diplomat-runtime.d.ts"; -type BorrowedFieldsWithBounds_Obj = { +type BorrowedFieldsWithBounds_obj = { fieldA: string; fieldB: string; fieldC: string; @@ -18,7 +18,7 @@ export class BorrowedFieldsWithBounds { get fieldC() : string; set fieldC(value: string); - constructor(structObj : BorrowedFieldsWithBounds_Obj); + constructor(structObj : BorrowedFieldsWithBounds_obj); static fromFooAndStrings(foo: Foo, dstr16X: string, utf8StrZ: string): BorrowedFieldsWithBounds; } \ No newline at end of file diff --git a/feature_tests/js/api/BorrowedFieldsWithBounds.mjs b/feature_tests/js/api/BorrowedFieldsWithBounds.mjs index e492f7bb0..91faddaaf 100644 --- a/feature_tests/js/api/BorrowedFieldsWithBounds.mjs +++ b/feature_tests/js/api/BorrowedFieldsWithBounds.mjs @@ -68,6 +68,18 @@ export class BorrowedFieldsWithBounds { return [...diplomatRuntime.CleanupArena.maybeCreateWith(functionCleanupArena, ...appendArrayMap['aAppendArray']).alloc(diplomatRuntime.DiplomatBuf.str16(wasm, this.#fieldA)).splat(), ...diplomatRuntime.CleanupArena.maybeCreateWith(functionCleanupArena, ...appendArrayMap['bAppendArray']).alloc(diplomatRuntime.DiplomatBuf.str8(wasm, this.#fieldB)).splat(), ...diplomatRuntime.CleanupArena.maybeCreateWith(functionCleanupArena, ...appendArrayMap['cAppendArray']).alloc(diplomatRuntime.DiplomatBuf.str8(wasm, this.#fieldC)).splat()] } + static _fromSuppliedValue(internalConstructor, obj) { + if (internalConstructor !== diplomatRuntime.internalConstructor) { + throw new Error("_fromSuppliedValue cannot be called externally."); + } + + if (obj instanceof BorrowedFieldsWithBounds) { + return obj; + } + + return new BorrowedFieldsWithBounds(obj); + } + _writeToArrayBuffer( arrayBuffer, offset, diff --git a/feature_tests/js/api/CyclicStructA.d.ts b/feature_tests/js/api/CyclicStructA.d.ts index 72fef6983..d4a4fde52 100644 --- a/feature_tests/js/api/CyclicStructA.d.ts +++ b/feature_tests/js/api/CyclicStructA.d.ts @@ -1,16 +1,17 @@ // generated by diplomat-tool import type { CyclicStructB } from "./CyclicStructB" +import type { CyclicStructB_obj } from "./CyclicStructB" import type { pointer, codepoint } from "./diplomat-runtime.d.ts"; -type CyclicStructA_Obj = { - a: CyclicStructB; +type CyclicStructA_obj = { + a: CyclicStructB_obj; }; export class CyclicStructA { get a() : CyclicStructB; set a(value: CyclicStructB); - constructor(structObj : CyclicStructA_Obj); + constructor(structObj : CyclicStructA_obj); static getB(): CyclicStructB; diff --git a/feature_tests/js/api/CyclicStructA.mjs b/feature_tests/js/api/CyclicStructA.mjs index 8c60d800f..d9cb148ab 100644 --- a/feature_tests/js/api/CyclicStructA.mjs +++ b/feature_tests/js/api/CyclicStructA.mjs @@ -18,7 +18,7 @@ export class CyclicStructA { } if ("a" in structObj) { - this.#a = structObj.a; + this.#a = CyclicStructB._fromSuppliedValue(diplomatRuntime.internalConstructor, structObj.a); } else { throw new Error("Missing required field a."); } @@ -32,7 +32,19 @@ export class CyclicStructA { functionCleanupArena, appendArrayMap ) { - return [...this.#a._intoFFI(functionCleanupArena, {})] + return [...CyclicStructB._fromSuppliedValue(diplomatRuntime.internalConstructor, this.#a)._intoFFI(functionCleanupArena, {})] + } + + static _fromSuppliedValue(internalConstructor, obj) { + if (internalConstructor !== diplomatRuntime.internalConstructor) { + throw new Error("_fromSuppliedValue cannot be called externally."); + } + + if (obj instanceof CyclicStructA) { + return obj; + } + + return new CyclicStructA(obj); } _writeToArrayBuffer( @@ -41,7 +53,7 @@ export class CyclicStructA { functionCleanupArena, appendArrayMap ) { - this.#a._writeToArrayBuffer(arrayBuffer, offset + 0, functionCleanupArena, {}); + CyclicStructB._fromSuppliedValue(diplomatRuntime.internalConstructor, this.#a)._writeToArrayBuffer(arrayBuffer, offset + 0, functionCleanupArena, {}); } // This struct contains borrowed fields, so this takes in a list of diff --git a/feature_tests/js/api/CyclicStructB.d.ts b/feature_tests/js/api/CyclicStructB.d.ts index 955c36ea1..74a06af26 100644 --- a/feature_tests/js/api/CyclicStructB.d.ts +++ b/feature_tests/js/api/CyclicStructB.d.ts @@ -2,7 +2,7 @@ import type { CyclicStructA } from "./CyclicStructA" import type { pointer, codepoint } from "./diplomat-runtime.d.ts"; -type CyclicStructB_Obj = { +type CyclicStructB_obj = { field: number; }; @@ -10,7 +10,7 @@ export class CyclicStructB { get field() : number; set field(value: number); - constructor(structObj : CyclicStructB_Obj); + constructor(structObj : CyclicStructB_obj); static getA(): CyclicStructA; diff --git a/feature_tests/js/api/CyclicStructB.mjs b/feature_tests/js/api/CyclicStructB.mjs index 2129fa5fd..6f87daf7f 100644 --- a/feature_tests/js/api/CyclicStructB.mjs +++ b/feature_tests/js/api/CyclicStructB.mjs @@ -35,6 +35,18 @@ export class CyclicStructB { return [this.#field] } + static _fromSuppliedValue(internalConstructor, obj) { + if (internalConstructor !== diplomatRuntime.internalConstructor) { + throw new Error("_fromSuppliedValue cannot be called externally."); + } + + if (obj instanceof CyclicStructB) { + return obj; + } + + return new CyclicStructB(obj); + } + _writeToArrayBuffer( arrayBuffer, offset, diff --git a/feature_tests/js/api/CyclicStructC.d.ts b/feature_tests/js/api/CyclicStructC.d.ts index a4797a038..0edd8f613 100644 --- a/feature_tests/js/api/CyclicStructC.d.ts +++ b/feature_tests/js/api/CyclicStructC.d.ts @@ -1,16 +1,19 @@ // generated by diplomat-tool import type { CyclicStructA } from "./CyclicStructA" +import type { CyclicStructA_obj } from "./CyclicStructA" import type { pointer, codepoint } from "./diplomat-runtime.d.ts"; -type CyclicStructC_Obj = { - a: CyclicStructA; +type CyclicStructC_obj = { + a: CyclicStructA_obj; }; export class CyclicStructC { get a() : CyclicStructA; set a(value: CyclicStructA); - constructor(structObj : CyclicStructC_Obj); + constructor(structObj : CyclicStructC_obj); + + static takesNestedParameters(c: CyclicStructC_obj): CyclicStructC; cyclicOut(): string; } \ No newline at end of file diff --git a/feature_tests/js/api/CyclicStructC.mjs b/feature_tests/js/api/CyclicStructC.mjs index 5107418d7..221beb115 100644 --- a/feature_tests/js/api/CyclicStructC.mjs +++ b/feature_tests/js/api/CyclicStructC.mjs @@ -18,7 +18,7 @@ export class CyclicStructC { } if ("a" in structObj) { - this.#a = structObj.a; + this.#a = CyclicStructA._fromSuppliedValue(diplomatRuntime.internalConstructor, structObj.a); } else { throw new Error("Missing required field a."); } @@ -32,7 +32,19 @@ export class CyclicStructC { functionCleanupArena, appendArrayMap ) { - return [...this.#a._intoFFI(functionCleanupArena, {})] + return [...CyclicStructA._fromSuppliedValue(diplomatRuntime.internalConstructor, this.#a)._intoFFI(functionCleanupArena, {})] + } + + static _fromSuppliedValue(internalConstructor, obj) { + if (internalConstructor !== diplomatRuntime.internalConstructor) { + throw new Error("_fromSuppliedValue cannot be called externally."); + } + + if (obj instanceof CyclicStructC) { + return obj; + } + + return new CyclicStructC(obj); } _writeToArrayBuffer( @@ -41,7 +53,7 @@ export class CyclicStructC { functionCleanupArena, appendArrayMap ) { - this.#a._writeToArrayBuffer(arrayBuffer, offset + 0, functionCleanupArena, {}); + CyclicStructA._fromSuppliedValue(diplomatRuntime.internalConstructor, this.#a)._writeToArrayBuffer(arrayBuffer, offset + 0, functionCleanupArena, {}); } // This struct contains borrowed fields, so this takes in a list of @@ -60,6 +72,20 @@ export class CyclicStructC { return new CyclicStructC(structObj, internalConstructor); } + static takesNestedParameters(c) { + let functionCleanupArena = new diplomatRuntime.CleanupArena(); + + const result = wasm.CyclicStructC_takes_nested_parameters(...CyclicStructC._fromSuppliedValue(diplomatRuntime.internalConstructor, c)._intoFFI(functionCleanupArena, {})); + + try { + return CyclicStructC._fromFFI(diplomatRuntime.internalConstructor, result); + } + + finally { + functionCleanupArena.free(); + } + } + cyclicOut() { let functionCleanupArena = new diplomatRuntime.CleanupArena(); diff --git a/feature_tests/js/api/ErrorStruct.d.ts b/feature_tests/js/api/ErrorStruct.d.ts index 16b02b617..d7b99d1dc 100644 --- a/feature_tests/js/api/ErrorStruct.d.ts +++ b/feature_tests/js/api/ErrorStruct.d.ts @@ -1,7 +1,7 @@ // generated by diplomat-tool import type { pointer, codepoint } from "./diplomat-runtime.d.ts"; -type ErrorStruct_Obj = { +type ErrorStruct_obj = { i: number; j: number; }; @@ -13,5 +13,5 @@ export class ErrorStruct { get j() : number; set j(value: number); - constructor(structObj : ErrorStruct_Obj); + constructor(structObj : ErrorStruct_obj); } \ No newline at end of file diff --git a/feature_tests/js/api/ErrorStruct.mjs b/feature_tests/js/api/ErrorStruct.mjs index 004ba1bbf..d4b05fdf7 100644 --- a/feature_tests/js/api/ErrorStruct.mjs +++ b/feature_tests/js/api/ErrorStruct.mjs @@ -48,6 +48,18 @@ export class ErrorStruct { return [this.#i, this.#j] } + static _fromSuppliedValue(internalConstructor, obj) { + if (internalConstructor !== diplomatRuntime.internalConstructor) { + throw new Error("_fromSuppliedValue cannot be called externally."); + } + + if (obj instanceof ErrorStruct) { + return obj; + } + + return new ErrorStruct(obj); + } + _writeToArrayBuffer( arrayBuffer, offset, diff --git a/feature_tests/js/api/Foo.d.ts b/feature_tests/js/api/Foo.d.ts index 9718ca4a1..a4f65e6bc 100644 --- a/feature_tests/js/api/Foo.d.ts +++ b/feature_tests/js/api/Foo.d.ts @@ -3,6 +3,8 @@ import type { Bar } from "./Bar" import type { BorrowedFields } from "./BorrowedFields" import type { BorrowedFieldsReturning } from "./BorrowedFieldsReturning" import type { BorrowedFieldsWithBounds } from "./BorrowedFieldsWithBounds" +import type { BorrowedFieldsWithBounds_obj } from "./BorrowedFieldsWithBounds" +import type { BorrowedFields_obj } from "./BorrowedFields" import type { pointer, codepoint } from "./diplomat-runtime.d.ts"; export class Foo { @@ -16,7 +18,7 @@ export class Foo { asReturning(): BorrowedFieldsReturning; - static extractFromFields(fields: BorrowedFields): Foo; + static extractFromFields(fields: BorrowedFields_obj): Foo; - static extractFromBounds(bounds: BorrowedFieldsWithBounds, anotherString: string): Foo; + static extractFromBounds(bounds: BorrowedFieldsWithBounds_obj, anotherString: string): Foo; } \ No newline at end of file diff --git a/feature_tests/js/api/Foo.mjs b/feature_tests/js/api/Foo.mjs index a8be17a9b..06572b431 100644 --- a/feature_tests/js/api/Foo.mjs +++ b/feature_tests/js/api/Foo.mjs @@ -98,7 +98,7 @@ export class Foo { // This lifetime edge depends on lifetimes 'a let aEdges = [...fields._fieldsForLifetimeA]; - const result = wasm.Foo_extract_from_fields(...fields._intoFFI(functionCleanupArena, {aAppendArray: [aEdges],})); + const result = wasm.Foo_extract_from_fields(...BorrowedFields._fromSuppliedValue(diplomatRuntime.internalConstructor, fields)._intoFFI(functionCleanupArena, {aAppendArray: [aEdges],})); try { return new Foo(diplomatRuntime.internalConstructor, result, [], aEdges); @@ -118,7 +118,7 @@ export class Foo { // This lifetime edge depends on lifetimes 'a, 'y, 'z let aEdges = [...bounds._fieldsForLifetimeB, ...bounds._fieldsForLifetimeC, anotherStringSlice]; - const result = wasm.Foo_extract_from_bounds(...bounds._intoFFI(functionCleanupArena, {bAppendArray: [aEdges],cAppendArray: [aEdges],}), ...anotherStringSlice.splat()); + const result = wasm.Foo_extract_from_bounds(...BorrowedFieldsWithBounds._fromSuppliedValue(diplomatRuntime.internalConstructor, bounds)._intoFFI(functionCleanupArena, {bAppendArray: [aEdges],cAppendArray: [aEdges],}), ...anotherStringSlice.splat()); try { return new Foo(diplomatRuntime.internalConstructor, result, [], aEdges); diff --git a/feature_tests/js/api/ImportedStruct.d.ts b/feature_tests/js/api/ImportedStruct.d.ts index 73b223e28..8e9d1bceb 100644 --- a/feature_tests/js/api/ImportedStruct.d.ts +++ b/feature_tests/js/api/ImportedStruct.d.ts @@ -2,7 +2,7 @@ import type { UnimportedEnum } from "./UnimportedEnum" import type { pointer, codepoint } from "./diplomat-runtime.d.ts"; -type ImportedStruct_Obj = { +type ImportedStruct_obj = { foo: UnimportedEnum; count: number; }; @@ -14,5 +14,5 @@ export class ImportedStruct { get count() : number; set count(value: number); - constructor(structObj : ImportedStruct_Obj); + constructor(structObj : ImportedStruct_obj); } \ No newline at end of file diff --git a/feature_tests/js/api/ImportedStruct.mjs b/feature_tests/js/api/ImportedStruct.mjs index 185847a85..1e1f535b7 100644 --- a/feature_tests/js/api/ImportedStruct.mjs +++ b/feature_tests/js/api/ImportedStruct.mjs @@ -53,6 +53,18 @@ export class ImportedStruct { return [this.#foo.ffiValue, this.#count, ...diplomatRuntime.maybePaddingFields(forcePadding, 3 /* x i8 */)] } + static _fromSuppliedValue(internalConstructor, obj) { + if (internalConstructor !== diplomatRuntime.internalConstructor) { + throw new Error("_fromSuppliedValue cannot be called externally."); + } + + if (obj instanceof ImportedStruct) { + return obj; + } + + return new ImportedStruct(obj); + } + _writeToArrayBuffer( arrayBuffer, offset, diff --git a/feature_tests/js/api/MyStruct.d.ts b/feature_tests/js/api/MyStruct.d.ts index 2440c8923..2f094a099 100644 --- a/feature_tests/js/api/MyStruct.d.ts +++ b/feature_tests/js/api/MyStruct.d.ts @@ -3,7 +3,7 @@ import type { MyEnum } from "./MyEnum" import type { MyZst } from "./MyZst" import type { pointer, codepoint } from "./diplomat-runtime.d.ts"; -type MyStruct_Obj = { +type MyStruct_obj = { a: number; b: boolean; c: number; @@ -35,7 +35,7 @@ export class MyStruct { get g() : MyEnum; set g(value: MyEnum); - constructor(structObj : MyStruct_Obj); + constructor(structObj : MyStruct_obj); static new_(): MyStruct; diff --git a/feature_tests/js/api/MyStruct.mjs b/feature_tests/js/api/MyStruct.mjs index da25317e7..9c2d2daa4 100644 --- a/feature_tests/js/api/MyStruct.mjs +++ b/feature_tests/js/api/MyStruct.mjs @@ -120,6 +120,18 @@ export class MyStruct { return [this.#a, this.#b, this.#c, /* [5 x i8] padding */ 0, 0, 0, 0, 0 /* end padding */, this.#d, this.#e, this.#f, this.#g.ffiValue, /* [1 x i32] padding */ 0 /* end padding */] } + static _fromSuppliedValue(internalConstructor, obj) { + if (internalConstructor !== diplomatRuntime.internalConstructor) { + throw new Error("_fromSuppliedValue cannot be called externally."); + } + + if (obj instanceof MyStruct) { + return obj; + } + + return new MyStruct(obj); + } + _writeToArrayBuffer( arrayBuffer, offset, diff --git a/feature_tests/js/api/MyZst.d.ts b/feature_tests/js/api/MyZst.d.ts index 3e98c26f9..83ac3785c 100644 --- a/feature_tests/js/api/MyZst.d.ts +++ b/feature_tests/js/api/MyZst.d.ts @@ -1,9 +1,9 @@ // generated by diplomat-tool import type { pointer, codepoint } from "./diplomat-runtime.d.ts"; -type MyZst_Obj = { +type MyZst_obj = { }; export class MyZst { - constructor(structObj : MyZst_Obj); + constructor(structObj : MyZst_obj); } \ No newline at end of file diff --git a/feature_tests/js/api/NestedBorrowedFields.d.ts b/feature_tests/js/api/NestedBorrowedFields.d.ts index 0a2746ad1..9e49766f9 100644 --- a/feature_tests/js/api/NestedBorrowedFields.d.ts +++ b/feature_tests/js/api/NestedBorrowedFields.d.ts @@ -2,13 +2,15 @@ import type { Bar } from "./Bar" import type { BorrowedFields } from "./BorrowedFields" import type { BorrowedFieldsWithBounds } from "./BorrowedFieldsWithBounds" +import type { BorrowedFieldsWithBounds_obj } from "./BorrowedFieldsWithBounds" +import type { BorrowedFields_obj } from "./BorrowedFields" import type { Foo } from "./Foo" import type { pointer, codepoint } from "./diplomat-runtime.d.ts"; -type NestedBorrowedFields_Obj = { - fields: BorrowedFields; - bounds: BorrowedFieldsWithBounds; - bounds2: BorrowedFieldsWithBounds; +type NestedBorrowedFields_obj = { + fields: BorrowedFields_obj; + bounds: BorrowedFieldsWithBounds_obj; + bounds2: BorrowedFieldsWithBounds_obj; }; export class NestedBorrowedFields { @@ -21,7 +23,7 @@ export class NestedBorrowedFields { get bounds2() : BorrowedFieldsWithBounds; set bounds2(value: BorrowedFieldsWithBounds); - constructor(structObj : NestedBorrowedFields_Obj); + constructor(structObj : NestedBorrowedFields_obj); static fromBarAndFooAndStrings(bar: Bar, foo: Foo, dstr16X: string, dstr16Z: string, utf8StrY: string, utf8StrZ: string): NestedBorrowedFields; } \ No newline at end of file diff --git a/feature_tests/js/api/NestedBorrowedFields.mjs b/feature_tests/js/api/NestedBorrowedFields.mjs index ceb6d3a80..2f225fabc 100644 --- a/feature_tests/js/api/NestedBorrowedFields.mjs +++ b/feature_tests/js/api/NestedBorrowedFields.mjs @@ -37,19 +37,19 @@ export class NestedBorrowedFields { } if ("fields" in structObj) { - this.#fields = structObj.fields; + this.#fields = BorrowedFields._fromSuppliedValue(diplomatRuntime.internalConstructor, structObj.fields); } else { throw new Error("Missing required field fields."); } if ("bounds" in structObj) { - this.#bounds = structObj.bounds; + this.#bounds = BorrowedFieldsWithBounds._fromSuppliedValue(diplomatRuntime.internalConstructor, structObj.bounds); } else { throw new Error("Missing required field bounds."); } if ("bounds2" in structObj) { - this.#bounds2 = structObj.bounds2; + this.#bounds2 = BorrowedFieldsWithBounds._fromSuppliedValue(diplomatRuntime.internalConstructor, structObj.bounds2); } else { throw new Error("Missing required field bounds2."); } @@ -68,7 +68,19 @@ export class NestedBorrowedFields { functionCleanupArena, appendArrayMap ) { - return [...this.#fields._intoFFI(functionCleanupArena, {aAppendArray: [...xAppendArray],}), ...this.#bounds._intoFFI(functionCleanupArena, {aAppendArray: [...xAppendArray],bAppendArray: [...yAppendArray],cAppendArray: [...yAppendArray],}), ...this.#bounds2._intoFFI(functionCleanupArena, {aAppendArray: [...zAppendArray],bAppendArray: [...zAppendArray],cAppendArray: [...zAppendArray],})] + return [...BorrowedFields._fromSuppliedValue(diplomatRuntime.internalConstructor, this.#fields)._intoFFI(functionCleanupArena, {aAppendArray: [...xAppendArray],}), ...BorrowedFieldsWithBounds._fromSuppliedValue(diplomatRuntime.internalConstructor, this.#bounds)._intoFFI(functionCleanupArena, {aAppendArray: [...xAppendArray],bAppendArray: [...yAppendArray],cAppendArray: [...yAppendArray],}), ...BorrowedFieldsWithBounds._fromSuppliedValue(diplomatRuntime.internalConstructor, this.#bounds2)._intoFFI(functionCleanupArena, {aAppendArray: [...zAppendArray],bAppendArray: [...zAppendArray],cAppendArray: [...zAppendArray],})] + } + + static _fromSuppliedValue(internalConstructor, obj) { + if (internalConstructor !== diplomatRuntime.internalConstructor) { + throw new Error("_fromSuppliedValue cannot be called externally."); + } + + if (obj instanceof NestedBorrowedFields) { + return obj; + } + + return new NestedBorrowedFields(obj); } _writeToArrayBuffer( @@ -77,9 +89,9 @@ export class NestedBorrowedFields { functionCleanupArena, appendArrayMap ) { - this.#fields._writeToArrayBuffer(arrayBuffer, offset + 0, functionCleanupArena, {aAppendArray: [...xAppendArray],}); - this.#bounds._writeToArrayBuffer(arrayBuffer, offset + 24, functionCleanupArena, {aAppendArray: [...xAppendArray],bAppendArray: [...yAppendArray],cAppendArray: [...yAppendArray],}); - this.#bounds2._writeToArrayBuffer(arrayBuffer, offset + 48, functionCleanupArena, {aAppendArray: [...zAppendArray],bAppendArray: [...zAppendArray],cAppendArray: [...zAppendArray],}); + BorrowedFields._fromSuppliedValue(diplomatRuntime.internalConstructor, this.#fields)._writeToArrayBuffer(arrayBuffer, offset + 0, functionCleanupArena, {aAppendArray: [...xAppendArray],}); + BorrowedFieldsWithBounds._fromSuppliedValue(diplomatRuntime.internalConstructor, this.#bounds)._writeToArrayBuffer(arrayBuffer, offset + 24, functionCleanupArena, {aAppendArray: [...xAppendArray],bAppendArray: [...yAppendArray],cAppendArray: [...yAppendArray],}); + BorrowedFieldsWithBounds._fromSuppliedValue(diplomatRuntime.internalConstructor, this.#bounds2)._writeToArrayBuffer(arrayBuffer, offset + 48, functionCleanupArena, {aAppendArray: [...zAppendArray],bAppendArray: [...zAppendArray],cAppendArray: [...zAppendArray],}); } static _fromFFI(internalConstructor, ptr, xEdges, yEdges, zEdges) { diff --git a/feature_tests/js/api/Opaque.d.ts b/feature_tests/js/api/Opaque.d.ts index ceb6cd37a..f9bcc0b7e 100644 --- a/feature_tests/js/api/Opaque.d.ts +++ b/feature_tests/js/api/Opaque.d.ts @@ -1,6 +1,7 @@ // generated by diplomat-tool import type { ImportedStruct } from "./ImportedStruct" import type { MyStruct } from "./MyStruct" +import type { MyStruct_obj } from "./MyStruct" import type { pointer, codepoint } from "./diplomat-runtime.d.ts"; export class Opaque { @@ -16,7 +17,7 @@ export class Opaque { getDebugStr(): string; - assertStruct(s: MyStruct): void; + assertStruct(s: MyStruct_obj): void; static returnsUsize(): number; diff --git a/feature_tests/js/api/Opaque.mjs b/feature_tests/js/api/Opaque.mjs index 663118c27..6518a3a91 100644 --- a/feature_tests/js/api/Opaque.mjs +++ b/feature_tests/js/api/Opaque.mjs @@ -92,7 +92,7 @@ export class Opaque { assertStruct(s) { let functionCleanupArena = new diplomatRuntime.CleanupArena(); - wasm.Opaque_assert_struct(this.ffiValue, ...s._intoFFI(functionCleanupArena, {})); + wasm.Opaque_assert_struct(this.ffiValue, ...MyStruct._fromSuppliedValue(diplomatRuntime.internalConstructor, s)._intoFFI(functionCleanupArena, {})); try {} diff --git a/feature_tests/js/api/OptionInputStruct.d.ts b/feature_tests/js/api/OptionInputStruct.d.ts index 5e7bf96f1..1548ad8b2 100644 --- a/feature_tests/js/api/OptionInputStruct.d.ts +++ b/feature_tests/js/api/OptionInputStruct.d.ts @@ -2,7 +2,7 @@ import type { OptionEnum } from "./OptionEnum" import type { pointer, codepoint } from "./diplomat-runtime.d.ts"; -type OptionInputStruct_Obj = { +type OptionInputStruct_obj = { a?: number | null; b?: codepoint | null; c?: OptionEnum | null; @@ -18,5 +18,5 @@ export class OptionInputStruct { get c() : OptionEnum | null; set c(value: OptionEnum | null); - constructor(structObj : OptionInputStruct_Obj); + constructor(structObj : OptionInputStruct_obj); } \ No newline at end of file diff --git a/feature_tests/js/api/OptionInputStruct.mjs b/feature_tests/js/api/OptionInputStruct.mjs index 14da5eb00..15012b579 100644 --- a/feature_tests/js/api/OptionInputStruct.mjs +++ b/feature_tests/js/api/OptionInputStruct.mjs @@ -63,6 +63,18 @@ export class OptionInputStruct { return [...diplomatRuntime.optionToArgsForCalling(this.#a, 1, 1, false, (arrayBuffer, offset, jsValue) => [diplomatRuntime.writeToArrayBuffer(arrayBuffer, offset + 0, jsValue, Uint8Array)]), /* [2 x i8] padding */ 0, 0 /* end padding */, ...diplomatRuntime.optionToArgsForCalling(this.#b, 4, 4, false, (arrayBuffer, offset, jsValue) => [diplomatRuntime.writeToArrayBuffer(arrayBuffer, offset + 0, jsValue, Uint32Array)]), ...diplomatRuntime.optionToArgsForCalling(this.#c, 4, 4, false, (arrayBuffer, offset, jsValue) => [diplomatRuntime.writeToArrayBuffer(arrayBuffer, offset + 0, jsValue.ffiValue, Int32Array)])] } + static _fromSuppliedValue(internalConstructor, obj) { + if (internalConstructor !== diplomatRuntime.internalConstructor) { + throw new Error("_fromSuppliedValue cannot be called externally."); + } + + if (obj instanceof OptionInputStruct) { + return obj; + } + + return new OptionInputStruct(obj); + } + _writeToArrayBuffer( arrayBuffer, offset, diff --git a/feature_tests/js/api/OptionOpaque.mjs b/feature_tests/js/api/OptionOpaque.mjs index f6065439f..2c24e05cb 100644 --- a/feature_tests/js/api/OptionOpaque.mjs +++ b/feature_tests/js/api/OptionOpaque.mjs @@ -225,7 +225,7 @@ export class OptionOpaque { const diplomatReceive = new diplomatRuntime.DiplomatReceiveBuf(wasm, 21, 4, true); - const result = wasm.OptionOpaque_accepts_option_input_struct(diplomatReceive.buffer, ...diplomatRuntime.optionToArgsForCalling(arg, 20, 4, false, (arrayBuffer, offset, jsValue) => [jsValue._writeToArrayBuffer(arrayBuffer, offset + 0, functionCleanupArena, {})])); + const result = wasm.OptionOpaque_accepts_option_input_struct(diplomatReceive.buffer, ...diplomatRuntime.optionToArgsForCalling(arg, 20, 4, false, (arrayBuffer, offset, jsValue) => [OptionInputStruct._fromSuppliedValue(diplomatRuntime.internalConstructor, jsValue)._writeToArrayBuffer(arrayBuffer, offset + 0, functionCleanupArena, {})])); try { if (!diplomatReceive.resultFlag) { diff --git a/feature_tests/js/api/OptionStruct.mjs b/feature_tests/js/api/OptionStruct.mjs index 3df863c6a..d66b22e66 100644 --- a/feature_tests/js/api/OptionStruct.mjs +++ b/feature_tests/js/api/OptionStruct.mjs @@ -73,6 +73,18 @@ export class OptionStruct { return [this.#a.ffiValue ?? 0, this.#b.ffiValue ?? 0, this.#c, this.#d.ffiValue ?? 0] } + static _fromSuppliedValue(internalConstructor, obj) { + if (internalConstructor !== diplomatRuntime.internalConstructor) { + throw new Error("_fromSuppliedValue cannot be called externally."); + } + + if (obj instanceof OptionStruct) { + return obj; + } + + return new OptionStruct(obj); + } + _writeToArrayBuffer( arrayBuffer, offset, diff --git a/feature_tests/js/api/ScalarPairWithPadding.d.ts b/feature_tests/js/api/ScalarPairWithPadding.d.ts index d7fa935b8..13964fc3c 100644 --- a/feature_tests/js/api/ScalarPairWithPadding.d.ts +++ b/feature_tests/js/api/ScalarPairWithPadding.d.ts @@ -4,7 +4,7 @@ import type { pointer, codepoint } from "./diplomat-runtime.d.ts"; /** Testing JS-specific layout/padding behavior */ -type ScalarPairWithPadding_Obj = { +type ScalarPairWithPadding_obj = { first: number; second: number; }; @@ -16,7 +16,7 @@ export class ScalarPairWithPadding { get second() : number; set second(value: number); - constructor(structObj : ScalarPairWithPadding_Obj); + constructor(structObj : ScalarPairWithPadding_obj); assertValue(): void; } \ No newline at end of file diff --git a/feature_tests/js/api/ScalarPairWithPadding.mjs b/feature_tests/js/api/ScalarPairWithPadding.mjs index a3302e717..0ee8b43b5 100644 --- a/feature_tests/js/api/ScalarPairWithPadding.mjs +++ b/feature_tests/js/api/ScalarPairWithPadding.mjs @@ -55,6 +55,18 @@ export class ScalarPairWithPadding { return [this.#first, ...diplomatRuntime.maybePaddingFields(forcePadding, 3 /* x i8 */), this.#second] } + static _fromSuppliedValue(internalConstructor, obj) { + if (internalConstructor !== diplomatRuntime.internalConstructor) { + throw new Error("_fromSuppliedValue cannot be called externally."); + } + + if (obj instanceof ScalarPairWithPadding) { + return obj; + } + + return new ScalarPairWithPadding(obj); + } + _writeToArrayBuffer( arrayBuffer, offset, diff --git a/feature_tests/js/test/struct-ts.mts b/feature_tests/js/test/struct-ts.mts index 878edb061..3df12ff6e 100644 --- a/feature_tests/js/test/struct-ts.mts +++ b/feature_tests/js/test/struct-ts.mts @@ -1,5 +1,5 @@ import test from 'ava'; -import { MyEnum, MyStruct, CyclicStructB, ScalarPairWithPadding, BigStructWithStuff } from "diplomat-wasm-js-feature-tests"; +import { MyEnum, MyStruct, CyclicStructB, CyclicStructC, ScalarPairWithPadding, BigStructWithStuff } from "diplomat-wasm-js-feature-tests"; test("Verify invariants of struct", t => { const s = MyStruct.new_(); @@ -26,6 +26,40 @@ test("Test struct creation", t => { t.is(s.intoA(), 17); }); +test("Function Takes Nested Struct Parameters", t => { + const nested = CyclicStructC.takesNestedParameters({ + a: { + a: { + field: 10 + } + } + }); + t.is(nested.cyclicOut(), "10"); +}); + +test("Nested Struct Construction", t => { + const nested = new CyclicStructC({ + a: { + a: { + field: 10 + } + } + }); + t.is(nested.cyclicOut(), "10"); + // Test that CyclicStructA is constructed from our object: + t.is(nested.a.cyclicOut(), "10"); +}); + +test("Nested Struct with pre-built Object", t => { + const existing = new CyclicStructB({ field: 15 }); + const nested = new CyclicStructC({ + a: { + a: existing + } + }); + t.is(nested.cyclicOut(), "15"); +}); + test("Test struct layout: scalar pair layout", t => { const s = new ScalarPairWithPadding({ first: 122, diff --git a/feature_tests/kotlin/somelib/src/main/kotlin/dev/diplomattest/somelib/CyclicStructC.kt b/feature_tests/kotlin/somelib/src/main/kotlin/dev/diplomattest/somelib/CyclicStructC.kt index b55f7c053..7af78ce3f 100644 --- a/feature_tests/kotlin/somelib/src/main/kotlin/dev/diplomattest/somelib/CyclicStructC.kt +++ b/feature_tests/kotlin/somelib/src/main/kotlin/dev/diplomattest/somelib/CyclicStructC.kt @@ -7,6 +7,7 @@ import com.sun.jna.Pointer import com.sun.jna.Structure internal interface CyclicStructCLib: Library { + fun CyclicStructC_takes_nested_parameters(c: CyclicStructCNative): CyclicStructCNative fun CyclicStructC_cyclic_out(nativeStruct: CyclicStructCNative, write: Pointer): Unit } @@ -28,6 +29,14 @@ class CyclicStructC internal constructor ( internal val libClass: Class = CyclicStructCLib::class.java internal val lib: CyclicStructCLib = Native.load("somelib", libClass) val NATIVESIZE: Long = Native.getNativeSize(CyclicStructCNative::class.java).toLong() + + fun takesNestedParameters(c: CyclicStructC): CyclicStructC { + + val returnVal = lib.CyclicStructC_takes_nested_parameters(c.nativeStruct); + + val returnStruct = CyclicStructC(returnVal) + return returnStruct + } } fun cyclicOut(): String { diff --git a/feature_tests/src/structs.rs b/feature_tests/src/structs.rs index 8e84ee9e8..a4c374494 100644 --- a/feature_tests/src/structs.rs +++ b/feature_tests/src/structs.rs @@ -268,6 +268,10 @@ pub mod ffi { } impl CyclicStructC { + pub fn takes_nested_parameters(c: CyclicStructC) -> CyclicStructC { + c + } + pub fn cyclic_out(self, out: &mut DiplomatWrite) { out.write_str(&self.a.a.field.to_string()).unwrap(); } diff --git a/tool/src/js/converter.rs b/tool/src/js/converter.rs index 39e0adae4..110223070 100644 --- a/tool/src/js/converter.rs +++ b/tool/src/js/converter.rs @@ -70,7 +70,7 @@ impl<'tcx> TyGenContext<'_, 'tcx> { let type_name = self.formatter.fmt_type_name(opaque_id); // Add to the import list: - self.add_import(type_name.clone().into()); + self.add_import(type_name.clone(), None, super::gen::ImportUsage::Both); if self.tcx.resolve_type(opaque_id).attrs().disable { self.errors @@ -88,7 +88,7 @@ impl<'tcx> TyGenContext<'_, 'tcx> { let type_name = self.formatter.fmt_type_name(id); // Add to the import list: - self.add_import(type_name.clone().into()); + self.add_import(type_name.clone(), None, super::gen::ImportUsage::Both); if self.tcx.resolve_type(id).attrs().disable { self.errors @@ -101,7 +101,7 @@ impl<'tcx> TyGenContext<'_, 'tcx> { let type_name = self.formatter.fmt_type_name(enum_id); // Add to the import list: - self.add_import(type_name.clone().into()); + self.add_import(type_name.clone(), None, super::gen::ImportUsage::Both); if self.tcx.resolve_type(enum_id).attrs().disable { self.errors @@ -454,7 +454,7 @@ impl<'tcx> TyGenContext<'_, 'tcx> { let (requires_buf, error_ret) = match return_type { ReturnType::Fallible(s, Some(e)) => { let type_name = self.formatter.fmt_type_name(e.id().unwrap()); - self.add_import(type_name.into()); + self.add_import(type_name, None, super::gen::ImportUsage::Both); let fields_empty = matches!(e, Type::Struct(s) if match s.resolve(self.tcx) { ReturnableStructDef::Struct(s) => s.fields.is_empty(), @@ -629,7 +629,8 @@ impl<'tcx> TyGenContext<'_, 'tcx> { gen_context, PrimitiveType::Int(IntType::I32), ), - Type::Struct(..) => self.gen_js_to_c_for_struct_type( + Type::Struct(ref s) => self.gen_js_to_c_for_struct_type( + self.formatter.fmt_type_name(s.id()), js_name, struct_borrow_info, alloc.unwrap(), @@ -721,6 +722,7 @@ impl<'tcx> TyGenContext<'_, 'tcx> { /// The end goal of this is to call `_intoFFI`, to convert a structure into a flattened list of values that WASM understands. pub(super) fn gen_js_to_c_for_struct_type( &self, + js_type: Cow<'tcx, str>, js_name: Cow<'tcx, str>, struct_borrow_info: Option<&StructBorrowContext<'tcx>>, allocator: &str, @@ -749,6 +751,10 @@ impl<'tcx> TyGenContext<'_, 'tcx> { write!(&mut params, "],").unwrap(); } } + + let js_call = + format!("{js_type}._fromSuppliedValue(diplomatRuntime.internalConstructor, {js_name})"); + match gen_context { JsToCConversionContext::List(force_padding) => { let force_padding = match force_padding { @@ -756,10 +762,10 @@ impl<'tcx> TyGenContext<'_, 'tcx> { ForcePaddingStatus::Force => ", true", ForcePaddingStatus::PassThrough => ", forcePadding", }; - format!("...{js_name}._intoFFI({allocator}, {{{params}}}{force_padding})").into() + format!("...{js_call}._intoFFI({allocator}, {{{params}}}{force_padding})").into() } JsToCConversionContext::WriteToBuffer(offset_var, offset) => format!( - "{js_name}._writeToArrayBuffer(arrayBuffer, {offset_var} + {offset}, {allocator}, {{{params}}})" + "{js_call}._writeToArrayBuffer(arrayBuffer, {offset_var} + {offset}, {allocator}, {{{params}}})" ) .into(), JsToCConversionContext::SlicePrealloc => { diff --git a/tool/src/js/formatter.rs b/tool/src/js/formatter.rs index 7a071e906..da9e244ae 100644 --- a/tool/src/js/formatter.rs +++ b/tool/src/js/formatter.rs @@ -110,8 +110,8 @@ impl<'tcx> JSFormatter<'tcx> { type_name: &str, typescript: bool, relative_path: String, + file_name: &str, ) -> String { - let file_name = self.fmt_file_name_extensionless(type_name); format!( r#"{{ {type_name} }} from "{relative_path}{file_name}{}"#, match typescript { @@ -127,6 +127,7 @@ impl<'tcx> JSFormatter<'tcx> { type_name: &str, typescript: bool, relative_path: String, + file_name: &str, ) -> String { format!( r#"import {}{}""#, @@ -134,7 +135,7 @@ impl<'tcx> JSFormatter<'tcx> { true => "type ", false => "", }, - self.fmt_module_statement(type_name, typescript, relative_path) + self.fmt_module_statement(type_name, typescript, relative_path, file_name) ) } @@ -158,10 +159,11 @@ impl<'tcx> JSFormatter<'tcx> { type_name: &str, typescript: bool, relative_path: String, + file_name: &str, ) -> String { format!( r#"export {}""#, - self.fmt_module_statement(type_name, typescript, relative_path) + self.fmt_module_statement(type_name, typescript, relative_path, file_name) ) } diff --git a/tool/src/js/gen.rs b/tool/src/js/gen.rs index 52bf7fd05..a49f9feed 100644 --- a/tool/src/js/gen.rs +++ b/tool/src/js/gen.rs @@ -21,6 +21,13 @@ use crate::ErrorStore; use super::converter::{ForcePaddingStatus, JsToCConversionContext, StructBorrowContext}; +/// Represents list of imports that our Type is going to use. +/// Resolved in [`TyGenContext::generate_base`] +pub(super) struct Imports<'tcx> { + pub js: BTreeSet>, + pub ts: BTreeSet>, +} + /// Represents context for generating a Javascript class. /// /// Given an enum, opaque, struct, etc. (anything from [`hir::TypeDef`] that JS supports), this handles creation of the associated `.mjs`` files. @@ -30,7 +37,7 @@ pub(super) struct TyGenContext<'ctx, 'tcx> { pub formatter: &'ctx JSFormatter<'tcx>, pub errors: &'ctx ErrorStore<'tcx, String>, /// Imports, stored as a type name. Imports are fully resolved in [`TyGenContext::generate_base`], with a call to [`JSFormatter::fmt_import_statement`]. - pub imports: RefCell>, + pub imports: RefCell>, } impl<'tcx> TyGenContext<'_, 'tcx> { @@ -46,12 +53,18 @@ impl<'tcx> TyGenContext<'_, 'tcx> { imports: Vec, } + let i = self.imports.borrow(); + let mut new_imports = Vec::new(); - for import in self.imports.borrow().iter() { - new_imports.push( - self.formatter - .fmt_import_statement(import, typescript, "./".into()), - ); + let imports = if typescript { i.ts.iter() } else { i.js.iter() }; + + for import in imports { + new_imports.push(self.formatter.fmt_import_statement( + &import.import_type, + typescript, + "./".into(), + &import.import_file, + )); } BaseTemplate { @@ -66,15 +79,49 @@ impl<'tcx> TyGenContext<'_, 'tcx> { /// A wrapper for `borrow_mut`ably inserting new imports. /// /// I do this to avoid borrow checking madness. - pub(super) fn add_import(&self, import_str: String) { - self.imports.borrow_mut().insert(import_str); + pub(super) fn add_import( + &self, + import_str: Cow<'tcx, str>, + import_file: Option>, + usage: ImportUsage, + ) { + let inf = ImportInfo { + import_type: import_str.clone(), + import_file: import_file.unwrap_or( + self.formatter + .fmt_file_name_extensionless(&import_str) + .into(), + ), + }; + if usage == ImportUsage::Module || usage == ImportUsage::Both { + self.imports.borrow_mut().js.insert(inf.clone()); + } + if usage == ImportUsage::Typescript || usage == ImportUsage::Both { + self.imports.borrow_mut().ts.insert(inf); + } } /// Exists for the same reason as [`Self::add_import`]. /// /// Right now, only used for removing any self imports. - pub(super) fn remove_import(&self, import_str: String) { - self.imports.borrow_mut().remove(&import_str); + pub(super) fn remove_import( + &self, + import_str: Cow<'tcx, str>, + import_file: Option>, + usage: ImportUsage, + ) { + let inf = ImportInfo { + import_type: import_str, + import_file: import_file.unwrap_or_default(), + }; + + if usage == ImportUsage::Module || usage == ImportUsage::Both { + self.imports.borrow_mut().js.remove(&inf); + } + + if usage == ImportUsage::Typescript || usage == ImportUsage::Both { + self.imports.borrow_mut().ts.remove(&inf); + } } /// Generate an enumerator type's body for a file from the given definition. @@ -181,6 +228,20 @@ impl<'tcx> TyGenContext<'_, 'tcx> { let js_type_name = self.gen_js_type_str(&field.ty); + if let Type::Struct(..) = &field.ty { + let obj_ty: Cow<'tcx, str> = format!("{js_type_name}_obj").into(); + + self.add_import( + obj_ty.clone(), + Some( + self.formatter + .fmt_file_name_extensionless(&js_type_name) + .into(), + ), + ImportUsage::Typescript, + ); + } + let is_option = matches!(&field.ty, hir::Type::DiplomatOption(..)); let c_to_js_deref = self.gen_c_to_js_deref_for_type(&field.ty, "ptr".into(), struct_field_info.fields[i].offset); @@ -422,9 +483,31 @@ impl<'tcx> TyGenContext<'_, 'tcx> { } for param in method.params.iter() { + let base_type = self.gen_js_type_str(¶m.ty); + let param_type_str = format!( + "{}", + // If we're a struct, we can substitute StructType_obj (since it's the only thing we need to pass to WASM) + if let Type::Struct(..) = ¶m.ty { + let obj_ty: Cow<'tcx, str> = format!("{base_type}_obj").into(); + self.add_import( + obj_ty.clone(), + Some( + self.formatter + .fmt_file_name_extensionless(&base_type) + .into(), + ), + ImportUsage::Typescript, + ); + obj_ty + } else { + base_type + } + ) + .into(); + let param_info = ParamInfo { name: self.formatter.fmt_param_name(param.name.as_str()), - ty: self.gen_js_type_str(¶m.ty), + ty: param_type_str, }; let param_borrow_kind = visitor.visit_param(¶m.ty, ¶m_info.name); @@ -643,6 +726,44 @@ pub(super) struct FieldInfo<'info, P: hir::TyPosition> { is_optional: bool, } +/// Where the imports are going to be used in. +#[derive(PartialEq, Clone)] +pub(super) enum ImportUsage { + /// .mjs files only + Module, + /// .d.ts files only + Typescript, + /// Both .mjs and .d.ts + Both, +} + +#[derive(Clone)] +pub(super) struct ImportInfo<'info> { + import_type: Cow<'info, str>, + import_file: Cow<'info, str>, +} + +/// Imports are only unique if they use a different type. We don't care about anything else. +impl Ord for ImportInfo<'_> { + fn cmp(&self, other: &Self) -> core::cmp::Ordering { + self.import_type.cmp(&other.import_type) + } +} + +impl PartialOrd for ImportInfo<'_> { + fn partial_cmp(&self, other: &Self) -> Option { + Some(self.import_type.cmp(&other.import_type)) + } +} + +impl PartialEq for ImportInfo<'_> { + fn eq(&self, other: &Self) -> bool { + self.import_type.eq(&other.import_type) + } +} + +impl Eq for ImportInfo<'_> {} + // Helpers used in templates (Askama has restrictions on Rust syntax) /// Used in `method.js.jinja`. Used to create JS friendly interpretations of lifetime edges, to be passed into newly created JS structures (see [`JSFormatter::fmt_lifetime_edge_array`] and see [`TyGenContext::gen_c_to_js_for_type`] for more.) diff --git a/tool/src/js/mod.rs b/tool/src/js/mod.rs index ec92c5206..c539dbae0 100644 --- a/tool/src/js/mod.rs +++ b/tool/src/js/mod.rs @@ -102,7 +102,10 @@ pub(crate) fn run<'tcx>( type_name, formatter: &formatter, errors: &errors, - imports: RefCell::new(BTreeSet::new()), + imports: RefCell::new(gen::Imports { + js: BTreeSet::new(), + ts: BTreeSet::new(), + }), }; let (m, special_method_presence, fields, fields_out) = match type_def { @@ -156,19 +159,30 @@ pub(crate) fn run<'tcx>( let file_name = formatter.fmt_file_name(&context.type_name, &file_type); // Remove our self reference: - context.remove_import(context.type_name.clone().into()); + context.remove_import(context.type_name.clone(), None, gen::ImportUsage::Both); + + // If we're a struct, remove importing our own StructType_obj definition if it exists. + if matches!(type_def, TypeDef::Struct(..)) { + context.remove_import( + format!("{}_obj", context.type_name).into(), + None, + gen::ImportUsage::Typescript, + ); + } files.add_file(file_name, context.generate_base(ts, contents)); } + let export_filename = formatter.fmt_file_name_extensionless(&context.type_name); + exports.push( formatter - .fmt_export_statement(&context.type_name, false, "./".into()) + .fmt_export_statement(&context.type_name, false, "./".into(), &export_filename) .into(), ); ts_exports.push( formatter - .fmt_export_statement(&context.type_name, true, "./".into()) + .fmt_export_statement(&context.type_name, true, "./".into(), &export_filename) .into(), ) } diff --git a/tool/templates/js/struct.js.jinja b/tool/templates/js/struct.js.jinja index 7bf313d53..c95a425fb 100644 --- a/tool/templates/js/struct.js.jinja +++ b/tool/templates/js/struct.js.jinja @@ -4,9 +4,9 @@ {% endif -%} {%- if typescript && !is_out -%} -type {{type_name}}_Obj = { +type {{type_name}}_obj = { {%- for field in fields %} - {{field.field_name}} {%- if let Type::DiplomatOption(o) = field.field_type %}?{% endif %}: {{field.js_type_name}}; + {{field.field_name}} {%- if let Type::DiplomatOption(o) = field.field_type %}?{% endif %}: {%- if let Type::Struct(s) = field.field_type %} {{field.js_type_name}}_obj {%- else %} {{field.js_type_name}} {%- endif %}; {%- endfor %} }; @@ -31,7 +31,7 @@ export class {{type_name}} { {%- if !(typescript && is_out) %} constructor(structObj - {%- if typescript %} : {{type_name}}_Obj {%- endif -%} + {%- if typescript %} : {{type_name}}_obj {%- endif -%} {%- if is_out && !typescript %}, internalConstructor{% endif -%} ) {%- if typescript %};{% else %} { if (typeof structObj !== "object") { @@ -46,7 +46,7 @@ export class {{type_name}} { {%- for field in fields %} if ("{{field.field_name}}" in structObj) { - this.#{{ field.field_name }} = structObj.{{field.field_name}}; + this.#{{ field.field_name }} = {%- if let Type::Struct(s) = field.field_type %} {{field.js_type_name}}._fromSuppliedValue(diplomatRuntime.internalConstructor, structObj.{{field.field_name}}) {%- else %} structObj.{{field.field_name}} {%- endif %}; } else { {%- if field.is_optional %} this.#{{ field.field_name }} = null; @@ -92,6 +92,18 @@ export class {{type_name}} { ] } + static _fromSuppliedValue(internalConstructor, obj) { + if (internalConstructor !== diplomatRuntime.internalConstructor) { + throw new Error("_fromSuppliedValue cannot be called externally."); + } + + if (obj instanceof {{type_name}}) { + return obj; + } + + return new {{type_name}}(obj); + } + _writeToArrayBuffer( arrayBuffer, offset,