From 6540f167c4a9b4003fc3d22b0733f507cfd2c7a4 Mon Sep 17 00:00:00 2001 From: cs01 Date: Fri, 6 Mar 2026 09:28:06 -0800 Subject: [PATCH] json stringify: nested interfaces, llvm-type resolution, member_access dispatch --- src/codegen/stdlib/json.ts | 48 +++++++++++++++++-- .../json-stringify-nested-interface.ts | 24 ++++++++++ .../json-stringify-parse-roundtrip.ts | 18 +++++++ 3 files changed, 87 insertions(+), 3 deletions(-) create mode 100644 tests/fixtures/builtins/json-stringify-nested-interface.ts create mode 100644 tests/fixtures/builtins/json-stringify-parse-roundtrip.ts diff --git a/src/codegen/stdlib/json.ts b/src/codegen/stdlib/json.ts index 7a811a0e..7b757ac8 100644 --- a/src/codegen/stdlib/json.ts +++ b/src/codegen/stdlib/json.ts @@ -595,14 +595,27 @@ export class JsonGenerator { ); } + private extractInterfaceFromLlvmType(llvmType: string): string | null { + if (llvmType.startsWith("%") && llvmType.endsWith("*")) { + return llvmType.slice(1, -1); + } + return null; + } + private resolveInterfaceType(arg: Expression): string | null { if (arg.type === "variable") { const varNode = arg as { type: string; name: string }; - return ( + const fromSymbol = this.ctx.symbolTable.getInterfaceType(varNode.name) || this.ctx.symbolTable.getRawInterfaceType(varNode.name) || - null - ); + null; + if (fromSymbol) return fromSymbol; + const llvmType = this.ctx.getVariableType(varNode.name); + if (llvmType) { + const extracted = this.extractInterfaceFromLlvmType(llvmType); + if (extracted && this.ctx.interfaceStructGenHasInterface(extracted)) return extracted; + } + return null; } if (arg.type === "index_access") { const indexAccess = arg as { type: string; object: Expression; index: Expression }; @@ -619,6 +632,24 @@ export class JsonGenerator { return null; } } + if (arg.type === "member_access") { + const memberAccess = arg as { type: string; object: Expression; property: string }; + const objType = this.resolveInterfaceType(memberAccess.object); + if (objType && this.ctx.interfaceStructGenHasInterface(objType)) { + const fieldCount = this.ctx.interfaceStructGenGetFieldCount(objType); + for (let i = 0; i < fieldCount; i++) { + const rawName = this.ctx.interfaceStructGenGetFieldName(objType, i); + const fName = + rawName.charAt(rawName.length - 1) === "?" + ? rawName.substring(0, rawName.length - 1) + : rawName; + if (fName === memberAccess.property) { + const fTsType = this.ctx.interfaceStructGenGetFieldTsType(objType, i); + if (this.ctx.interfaceStructGenHasInterface(fTsType)) return fTsType; + } + } + } + } return null; } @@ -697,6 +728,17 @@ export class JsonGenerator { "@csyyjson_obj_add_bool", `i8* ${jsonDoc}, i8* ${jsonObj}, i8* ${nameConst}, i32 ${boolInt}`, ); + } else if (this.ctx.interfaceStructGenHasInterface(fieldTsType)) { + const nestedPtr = this.ctx.emitLoad("i8*", fieldPtr); + const nestedFieldCount = this.ctx.interfaceStructGenGetFieldCount(fieldTsType); + const nestedStructType = this.buildStructType(fieldTsType, nestedFieldCount); + const nestedTyped = this.ctx.emitBitcast(nestedPtr, "i8*", `${nestedStructType}*`); + const subObj = this.ctx.emitCall( + "i8*", + "@csyyjson_obj_add_obj", + `i8* ${jsonDoc}, i8* ${jsonObj}, i8* ${nameConst}`, + ); + this.emitAddFieldsToJsonObj(nestedTyped, nestedStructType, fieldTsType, jsonDoc, subObj); } else { const val = this.ctx.emitLoad("double", fieldPtr); this.ctx.emitCallVoid( diff --git a/tests/fixtures/builtins/json-stringify-nested-interface.ts b/tests/fixtures/builtins/json-stringify-nested-interface.ts new file mode 100644 index 00000000..c46d9326 --- /dev/null +++ b/tests/fixtures/builtins/json-stringify-nested-interface.ts @@ -0,0 +1,24 @@ +// @test-description: json stringify nested interface fields + +interface Address { + street: string; + zip: string; +} + +interface Person { + name: string; + age: number; + address: Address; +} + +const addr: Address = { street: "123 Main St", zip: "90210" }; +const person: Person = { name: "Alice", age: 30, address: addr }; + +const json = JSON.stringify(person); +const parsed = JSON.parse(json); + +if (parsed.name === "Alice" && parsed.age === 30) { + console.log("TEST_PASSED"); +} else { + console.log("FAIL"); +} diff --git a/tests/fixtures/builtins/json-stringify-parse-roundtrip.ts b/tests/fixtures/builtins/json-stringify-parse-roundtrip.ts new file mode 100644 index 00000000..f876c92d --- /dev/null +++ b/tests/fixtures/builtins/json-stringify-parse-roundtrip.ts @@ -0,0 +1,18 @@ +// @test-description: json stringify of a json parse result (llvm type tracking) + +interface Config { + host: string; + port: number; + debug: boolean; +} + +const raw = '{"host":"localhost","port":8080,"debug":1}'; +const cfg = JSON.parse(raw); +const out = JSON.stringify(cfg); +const cfg2 = JSON.parse(out); + +if (cfg2.host === "localhost" && cfg2.port === 8080) { + console.log("TEST_PASSED"); +} else { + console.log("FAIL: " + cfg2.host + " " + cfg2.port); +}