diff --git a/src/codegen/expressions/calls.ts b/src/codegen/expressions/calls.ts index 8b9ecb56..36dd6ff8 100644 --- a/src/codegen/expressions/calls.ts +++ b/src/codegen/expressions/calls.ts @@ -1170,6 +1170,20 @@ export class CallExpressionGenerator { return coerced; } + // FFI null-string coercion: `declare function f(): string` returning NULL + // from C should round-trip to TS as the empty string so `result === ""` + // works reliably. Only applied to user-declared extern functions (not + // internal runtime calls, which rely on NULL sentinels internally). + if (returnType === "i8*" && func && func.declare) { + const emptyStr = this.ctx.stringGen.doCreateStringConstant(""); + const isNull = this.ctx.nextTemp(); + this.ctx.emit(`${isNull} = icmp eq i8* ${temp}, null`); + const coerced = this.ctx.nextTemp(); + this.ctx.emit(`${coerced} = select i1 ${isNull}, i8* ${emptyStr}, i8* ${temp}`); + this.ctx.setVariableType(coerced, "i8*"); + return coerced; + } + return temp; } diff --git a/src/codegen/infrastructure/assignment-generator.ts b/src/codegen/infrastructure/assignment-generator.ts index cbb494aa..c56a9c66 100644 --- a/src/codegen/infrastructure/assignment-generator.ts +++ b/src/codegen/infrastructure/assignment-generator.ts @@ -419,6 +419,9 @@ export class AssignmentGenerator { value: string, memberAccessValue: MemberAccessAssignmentNode, ): void { + if (value.startsWith("__lambda_")) { + value = `@${value}`; + } if (fiTsType) { const enumResult = this.isEnumType(fiTsType); if (enumResult) { diff --git a/src/parser-ts/handlers/expressions.ts b/src/parser-ts/handlers/expressions.ts index c3e75f93..e3879f27 100644 --- a/src/parser-ts/handlers/expressions.ts +++ b/src/parser-ts/handlers/expressions.ts @@ -435,7 +435,8 @@ function transformCallExpression( }; } else if ( ts.isCallExpression(node.expression) || - ts.isParenthesizedExpression(node.expression) + ts.isParenthesizedExpression(node.expression) || + ts.isElementAccessExpression(node.expression) ) { const callee = transformExpression(node.expression, checker); return { diff --git a/tests/fixtures/classes/class-arrow-field-in-ctor.ts b/tests/fixtures/classes/class-arrow-field-in-ctor.ts new file mode 100644 index 00000000..60b5d299 --- /dev/null +++ b/tests/fixtures/classes/class-arrow-field-in-ctor.ts @@ -0,0 +1,12 @@ +// @test-description: issue #587 — arrow literal assigned to class field inside ctor body +class Foo { + cb: (x: number) => number; + constructor() { + this.cb = (x) => x * 2; + } +} + +const f = new Foo(); +if (f.cb(21) === 42) { + console.log("TEST_PASSED"); +} diff --git a/tests/fixtures/edge-cases/call-element-access-callee.ts b/tests/fixtures/edge-cases/call-element-access-callee.ts new file mode 100644 index 00000000..947f6762 --- /dev/null +++ b/tests/fixtures/edge-cases/call-element-access-callee.ts @@ -0,0 +1,12 @@ +// @test-description: issue #589 — parser accepts arr[i](args) as a call expression (ElementAccessExpression callee) +// @test-compile-error: Immediately invoked function expressions +function doubler(x: number): number { + return x * 2; +} +function plusOne(x: number): number { + return x + 1; +} + +const fns = [doubler, plusOne]; +const a = fns[0](10); +console.log(a); diff --git a/tests/fixtures/edge-cases/ffi-null-string-to-empty.ts b/tests/fixtures/edge-cases/ffi-null-string-to-empty.ts new file mode 100644 index 00000000..56d81ff3 --- /dev/null +++ b/tests/fixtures/edge-cases/ffi-null-string-to-empty.ts @@ -0,0 +1,9 @@ +// @test-description: issue #591 — NULL char* from FFI round-trips to TS as empty string +declare function getenv(name: string): string; + +const v = getenv("__CHADSCRIPT_TEST_VAR_THAT_DOES_NOT_EXIST_91237__"); +if (v === "") { + console.log("TEST_PASSED"); +} else { + console.log("FAIL: got " + v); +}