Skip to content

Commit 2dae877

Browse files
committed
fix: await on function calls returning async functions
Signed-off-by: Christian Stewart <christian@aperture.us>
1 parent 5d8d81b commit 2dae877

File tree

18 files changed

+249
-3
lines changed

18 files changed

+249
-3
lines changed

AGENTS.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,8 @@ GoScript is an experimental Go to TypeScript transpiler that enables developers
2424

2525
**This is an experimental project** - we do not maintain backwards compatibility and prioritize simplicity and correctness over legacy support. You may sometimes encounter a problem that requires a complete re-design or re-think or re-architecting of an aspect of goscript, which is perfectly okay, in this case write a design to `compliance/WIP.md` and think it through extensively before performing your refactor. It's perfectly OK to delete large swaths of code as needed. Focus on correctness.
2626

27+
If you want to overwrite WIP.md you must `rm` it first.
28+
2729
The GoScript runtime, located in `gs/builtin/builtin.ts`, provides necessary helper functions and is imported in generated code using the `@goscript/builtin` alias.
2830

2931
**Output Style**: Generated TypeScript should not use semicolons and should always focus on code clarity and correctness.

compiler/expr-call.go

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -117,14 +117,26 @@ func (c *GoToTSCompiler) WriteCallExpr(exp *ast.CallExpr) error {
117117
}
118118
c.tsw.WriteLiterally(")")
119119
} else {
120+
// Check if this is a function call that returns a function (e.g., simpleIterator(m)())
121+
// and if the inner call is async, wrap it in parentheses
122+
innerCallExpr, isCallExpr := expFun.(*ast.CallExpr)
123+
needsParens := isCallExpr && c.isCallExprAsync(innerCallExpr)
124+
125+
if needsParens {
126+
c.tsw.WriteLiterally("(")
127+
}
128+
120129
// Not an identifier (e.g., method call on a value, function call result)
121130
if err := c.WriteValueExpr(expFun); err != nil {
122131
return fmt.Errorf("failed to write method expression in call: %w", err)
123132
}
124133

125-
// Check if this is a function call that returns a function (e.g., simpleIterator(m)())
134+
if needsParens {
135+
c.tsw.WriteLiterally(")")
136+
}
137+
126138
// Add non-null assertion since the returned function could be null
127-
if _, isCallExpr := expFun.(*ast.CallExpr); isCallExpr {
139+
if isCallExpr {
128140
c.tsw.WriteLiterally("!")
129141
} else {
130142
c.addNonNullAssertion(expFun)

compliance/deps/encoding/json/encode.gs.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -382,7 +382,7 @@ export class encodeState {
382382

383383
public async reflectValue(v: reflect.Value, opts: encOpts): Promise<void> {
384384
const e = this
385-
await valueEncoder(v)!(e, v, opts)
385+
(await valueEncoder(v))!(e, v, opts)
386386
}
387387

388388
// Register this type with the runtime type system
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
package main
2+
3+
import "sync"
4+
5+
var cache sync.Map
6+
7+
// Async function that returns a function
8+
func getCallback() func(string) {
9+
cache.Load(1)
10+
return func(msg string) {
11+
println("Callback:", msg)
12+
}
13+
}
14+
15+
func main() {
16+
// Call the function returned by an async function
17+
// This should generate: (await getCallback())!("hello")
18+
// NOT: await getCallback()!("hello")
19+
getCallback()("hello")
20+
}
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
// Generated file based on call_async_returned_function.go
2+
// Updated when compliance tests are re-run, DO NOT EDIT!
3+
4+
import * as $ from "@goscript/builtin/index.js"
5+
6+
import * as sync from "@goscript/sync/index.js"
7+
8+
let cache: $.VarRef<sync.Map> = $.varRef(new sync.Map())
9+
10+
// Async function that returns a function
11+
export async function getCallback(): Promise<((p0: string) => void) | null> {
12+
await cache!.value.Load(1)
13+
return (msg: string): void => {
14+
console.log("Callback:", msg)
15+
}
16+
}
17+
18+
export async function main(): Promise<void> {
19+
// Call the function returned by an async function
20+
// This should generate: (await getCallback())!("hello")
21+
// NOT: await getCallback()!("hello")
22+
(await getCallback())!("hello")
23+
}
24+
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Callback: hello

compliance/tests/call_async_returned_function/index.ts

Whitespace-only changes.
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
{
2+
"compilerOptions": {
3+
"allowImportingTsExtensions": true,
4+
"lib": [
5+
"es2022",
6+
"esnext.disposable",
7+
"dom"
8+
],
9+
"module": "nodenext",
10+
"moduleResolution": "nodenext",
11+
"noEmit": true,
12+
"paths": {
13+
"*": [
14+
"./*"
15+
],
16+
"@goscript/*": [
17+
"../../../gs/*",
18+
"../../../compliance/deps/*"
19+
],
20+
"@goscript/github.com/aperturerobotics/goscript/compliance/tests/call_async_returned_function/*": [
21+
"./*"
22+
]
23+
},
24+
"sourceMap": true,
25+
"target": "es2022"
26+
},
27+
"extends": "../../../tsconfig.json",
28+
"include": [
29+
"call_async_returned_function.gs.ts",
30+
"index.ts"
31+
]
32+
}
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
package main
2+
3+
func getAdder(x int) func(int) int {
4+
return func(y int) int {
5+
return x + y
6+
}
7+
}
8+
9+
func asyncAdd(a, b int) int {
10+
return a + b
11+
}
12+
13+
func getAsyncAdder(x int) func(int) int {
14+
return func(y int) int {
15+
return asyncAdd(x, y)
16+
}
17+
}
18+
19+
func main() {
20+
// Direct call of returned function - not async
21+
result1 := getAdder(5)(3)
22+
println("Result 1:", result1)
23+
24+
// Direct call of returned function - with async call inside
25+
result2 := getAsyncAdder(10)(7)
26+
println("Result 2:", result2)
27+
}
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
// Generated file based on call_returned_function_async.go
2+
// Updated when compliance tests are re-run, DO NOT EDIT!
3+
4+
import * as $ from "@goscript/builtin/index.js"
5+
6+
export function getAdder(x: number): ((p0: number) => number) | null {
7+
return (y: number): number => {
8+
return x + y
9+
}
10+
}
11+
12+
export function asyncAdd(a: number, b: number): number {
13+
return a + b
14+
}
15+
16+
export function getAsyncAdder(x: number): ((p0: number) => number) | null {
17+
return (y: number): number => {
18+
return asyncAdd(x, y)
19+
}
20+
}
21+
22+
export async function main(): Promise<void> {
23+
// Direct call of returned function - not async
24+
let result1 = getAdder(5)!(3)
25+
console.log("Result 1:", result1)
26+
27+
// Direct call of returned function - with async call inside
28+
let result2 = getAsyncAdder(10)!(7)
29+
console.log("Result 2:", result2)
30+
}
31+

0 commit comments

Comments
 (0)