From 1a0aba0c0c7664b2d7d3cad1aa622a606dadd6dc Mon Sep 17 00:00:00 2001 From: cs01 Date: Sat, 28 Feb 2026 19:33:37 -0800 Subject: [PATCH 1/3] fix json --- src/codegen/stdlib/json.ts | 27 ++++++++++++------- .../builtins/json-stringify-mixed-vars.ts | 8 ++++++ .../builtins/json-stringify-type-assertion.ts | 8 ++++++ 3 files changed, 34 insertions(+), 9 deletions(-) create mode 100644 tests/fixtures/builtins/json-stringify-mixed-vars.ts create mode 100644 tests/fixtures/builtins/json-stringify-type-assertion.ts diff --git a/src/codegen/stdlib/json.ts b/src/codegen/stdlib/json.ts index 4101fff3..87c321a6 100644 --- a/src/codegen/stdlib/json.ts +++ b/src/codegen/stdlib/json.ts @@ -1,4 +1,4 @@ -import { Expression, MethodCallNode, ObjectNode } from "../../ast/types.js"; +import { Expression, MethodCallNode, ObjectNode, TypeAssertionNode } from "../../ast/types.js"; interface ExprBase { type: string; @@ -500,9 +500,13 @@ export class JsonGenerator { return this.ctx.emitError("JSON.stringify() requires 1 argument", expr.loc); } - const arg = expr.args[0]; + let arg = expr.args[0]; const spaces = this.getSpaces(expr); + if ((arg as { type: string }).type === "type_assertion") { + arg = (arg as unknown as TypeAssertionNode).expression; + } + if (this.ctx.isStringExpression(arg)) { return this.stringifyString(arg, params); } @@ -759,29 +763,34 @@ export class JsonGenerator { const prop = obj.properties[i]; const nameConst = this.ctx.createStringConstant(prop.key); - if (prop.value.type === "object") { + let propValue = prop.value; + if ((propValue as { type: string }).type === "type_assertion") { + propValue = (propValue as unknown as TypeAssertionNode).expression; + } + + if (propValue.type === "object") { const childObj = this.ctx.emitCall( "i8*", "@csyyjson_obj_add_obj", `i8* ${jsonDoc}, i8* ${jsonObj}, i8* ${nameConst}`, ); - this.buildJsonProperties(prop.value as unknown as ObjectNode, params, jsonDoc, childObj); - } else if (prop.value.type === "boolean") { - const val = this.ctx.generateExpression(prop.value, params); + this.buildJsonProperties(propValue as unknown as ObjectNode, params, jsonDoc, childObj); + } else if (propValue.type === "boolean") { + const val = this.ctx.generateExpression(propValue, params); const boolI32 = this.ctx.nextTemp(); this.ctx.emit(`${boolI32} = trunc i64 ${val} to i32`); this.ctx.emitCallVoid( "@csyyjson_obj_add_bool", `i8* ${jsonDoc}, i8* ${jsonObj}, i8* ${nameConst}, i32 ${boolI32}`, ); - } else if (this.ctx.isStringExpression(prop.value)) { - const val = this.ctx.generateExpression(prop.value, params); + } else if (this.ctx.isStringExpression(propValue)) { + const val = this.ctx.generateExpression(propValue, params); this.ctx.emitCallVoid( "@csyyjson_obj_add_str", `i8* ${jsonDoc}, i8* ${jsonObj}, i8* ${nameConst}, i8* ${val}`, ); } else { - const val = this.ctx.generateExpression(prop.value, params); + const val = this.ctx.generateExpression(propValue, params); const vt = this.ctx.getVariableType(val); if (vt === "i8*") { this.ctx.emitCallVoid( diff --git a/tests/fixtures/builtins/json-stringify-mixed-vars.ts b/tests/fixtures/builtins/json-stringify-mixed-vars.ts new file mode 100644 index 00000000..ba8d7388 --- /dev/null +++ b/tests/fixtures/builtins/json-stringify-mixed-vars.ts @@ -0,0 +1,8 @@ +// @test-description: json stringify inline object with mixed string and number variable values +const name = "hello"; +const count = 42; +const ts = 1234567890; +const result = JSON.stringify({ name: name, count: count, ts: ts }); +if (result.includes("hello") && result.includes("42") && result.includes("1234567890")) { + console.log("TEST_PASSED"); +} diff --git a/tests/fixtures/builtins/json-stringify-type-assertion.ts b/tests/fixtures/builtins/json-stringify-type-assertion.ts new file mode 100644 index 00000000..94b1a1ad --- /dev/null +++ b/tests/fixtures/builtins/json-stringify-type-assertion.ts @@ -0,0 +1,8 @@ +// @test-description: json stringify inline object with type assertion does not crash +const name = "hello"; +const count = 42; +const ts = 1234567890; +const result = JSON.stringify({ name: name, count: count, ts: ts } as any); +if (result.includes("hello") && result.includes("42") && result.includes("1234567890")) { + console.log("TEST_PASSED"); +} From 7898c8e0a34d6a703642e4cb1e749997490e8067 Mon Sep 17 00:00:00 2001 From: cs01 Date: Sat, 28 Feb 2026 19:43:08 -0800 Subject: [PATCH 2/3] fix tests --- src/codegen/stdlib/json.ts | 31 +++++++++++++------------------ 1 file changed, 13 insertions(+), 18 deletions(-) diff --git a/src/codegen/stdlib/json.ts b/src/codegen/stdlib/json.ts index 87c321a6..821e3254 100644 --- a/src/codegen/stdlib/json.ts +++ b/src/codegen/stdlib/json.ts @@ -1,4 +1,4 @@ -import { Expression, MethodCallNode, ObjectNode, TypeAssertionNode } from "../../ast/types.js"; +import { Expression, MethodCallNode, ObjectNode } from "../../ast/types.js"; interface ExprBase { type: string; @@ -500,13 +500,13 @@ export class JsonGenerator { return this.ctx.emitError("JSON.stringify() requires 1 argument", expr.loc); } - let arg = expr.args[0]; + const rawArg = expr.args[0]; + const arg = + (rawArg as { type: string }).type === "type_assertion" + ? (rawArg as { type: string; expression: Expression }).expression + : rawArg; const spaces = this.getSpaces(expr); - if ((arg as { type: string }).type === "type_assertion") { - arg = (arg as unknown as TypeAssertionNode).expression; - } - if (this.ctx.isStringExpression(arg)) { return this.stringifyString(arg, params); } @@ -763,34 +763,29 @@ export class JsonGenerator { const prop = obj.properties[i]; const nameConst = this.ctx.createStringConstant(prop.key); - let propValue = prop.value; - if ((propValue as { type: string }).type === "type_assertion") { - propValue = (propValue as unknown as TypeAssertionNode).expression; - } - - if (propValue.type === "object") { + if (prop.value.type === "object") { const childObj = this.ctx.emitCall( "i8*", "@csyyjson_obj_add_obj", `i8* ${jsonDoc}, i8* ${jsonObj}, i8* ${nameConst}`, ); - this.buildJsonProperties(propValue as unknown as ObjectNode, params, jsonDoc, childObj); - } else if (propValue.type === "boolean") { - const val = this.ctx.generateExpression(propValue, params); + this.buildJsonProperties(prop.value as unknown as ObjectNode, params, jsonDoc, childObj); + } else if (prop.value.type === "boolean") { + const val = this.ctx.generateExpression(prop.value, params); const boolI32 = this.ctx.nextTemp(); this.ctx.emit(`${boolI32} = trunc i64 ${val} to i32`); this.ctx.emitCallVoid( "@csyyjson_obj_add_bool", `i8* ${jsonDoc}, i8* ${jsonObj}, i8* ${nameConst}, i32 ${boolI32}`, ); - } else if (this.ctx.isStringExpression(propValue)) { - const val = this.ctx.generateExpression(propValue, params); + } else if (this.ctx.isStringExpression(prop.value)) { + const val = this.ctx.generateExpression(prop.value, params); this.ctx.emitCallVoid( "@csyyjson_obj_add_str", `i8* ${jsonDoc}, i8* ${jsonObj}, i8* ${nameConst}, i8* ${val}`, ); } else { - const val = this.ctx.generateExpression(propValue, params); + const val = this.ctx.generateExpression(prop.value, params); const vt = this.ctx.getVariableType(val); if (vt === "i8*") { this.ctx.emitCallVoid( From 0eed4146a38c6e240b197f7d9a78c7136d6ef540 Mon Sep 17 00:00:00 2001 From: cs01 Date: Sat, 28 Feb 2026 20:32:56 -0800 Subject: [PATCH 3/3] fix json stringify type assertion: use named TypeAssertionNode cast and extract helper method to avoid phi node metadata loss --- src/codegen/stdlib/json.ts | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/src/codegen/stdlib/json.ts b/src/codegen/stdlib/json.ts index 821e3254..992e0f11 100644 --- a/src/codegen/stdlib/json.ts +++ b/src/codegen/stdlib/json.ts @@ -1,4 +1,4 @@ -import { Expression, MethodCallNode, ObjectNode } from "../../ast/types.js"; +import { Expression, MethodCallNode, ObjectNode, TypeAssertionNode } from "../../ast/types.js"; interface ExprBase { type: string; @@ -500,11 +500,17 @@ export class JsonGenerator { return this.ctx.emitError("JSON.stringify() requires 1 argument", expr.loc); } - const rawArg = expr.args[0]; - const arg = - (rawArg as { type: string }).type === "type_assertion" - ? (rawArg as { type: string; expression: Expression }).expression - : rawArg; + if (expr.args[0].type === "type_assertion") { + return this.generateStringifyArg( + (expr.args[0] as unknown as TypeAssertionNode).expression, + expr, + params, + ); + } + return this.generateStringifyArg(expr.args[0], expr, params); + } + + private generateStringifyArg(arg: Expression, expr: MethodCallNode, params: string[]): string { const spaces = this.getSpaces(expr); if (this.ctx.isStringExpression(arg)) {