Add structured coroutines and explicit error handling

This commit is contained in:
pavel 2026-08-27 16:26:40 +02:00
commit fe62e81152
31 changed files with 1701 additions and 325 deletions

View file

@ -70,7 +70,7 @@ fun main() {
for _, want := range []string{
`"strings"`,
`rand "math/rand"`,
`upper := gotlinAutoThrow(strings.ToUpper("go"))`,
`upper := strings.ToUpper("go")`,
`fmt.Println(rand.Intn(3))`,
} {
if !strings.Contains(code, want) {
@ -294,7 +294,7 @@ fun main() {
`func (self *Greeter) greet() {`,
`fmt.Println("hello, " + self.name)`,
`fmt.Println(self.name)`,
`greeter := gotlinAutoThrow(NewGreeter("world"))`,
`greeter := NewGreeter("world")`,
} {
if !strings.Contains(code, want) {
t.Fatalf("generated Go missing %q:\n%s", want, code)
@ -388,7 +388,7 @@ fun main() {
`type EpicControllerImpl struct {`,
`func NewEpicControllerImpl() *EpicControllerImpl`,
`func (self *EpicControllerImpl) hello(w http.ResponseWriter, r *http.Request) {`,
`var epicController EpicController = gotlinAutoThrow(NewEpicControllerImpl())`,
`var epicController EpicController = NewEpicControllerImpl()`,
`http.HandleFunc("/", epicController.hello)`,
`http.ListenAndServe(":8080", http.DefaultServeMux)`,
} {
@ -546,7 +546,7 @@ fun main() {
`type gotlinResult struct {`,
`func gotlinRunCatching(fn func()) (result gotlinResult) {`,
`panic("boom")`,
`result := gotlinAutoThrow(gotlinRunCatching(func() {`,
`result := gotlinRunCatching(func() {`,
`if recovered := recover(); recovered != nil {`,
`e := recovered`,
} {
@ -556,7 +556,7 @@ fun main() {
}
}
func TestGenerateGoAutoThrowForGoErrorResults(t *testing.T) {
func TestGenerateGoExplicitErrorsForGoErrorResults(t *testing.T) {
src := `
package demo
@ -569,6 +569,7 @@ fun main() {
throw err
}
}
`
prog, err := Parse(src)
@ -583,9 +584,8 @@ fun main() {
code := string(out)
for _, want := range []string{
`func gotlinAutoThrow[T any](value T, rest ...any) T {`,
`db := gotlinAutoThrow(sql.Open("postgres", "postgres://localhost/postgres?sslmode=disable"))`,
`err := gotlinAutoThrow(db.Ping())`,
`db := sql.Open("postgres", "postgres://localhost/postgres?sslmode=disable")`,
`err := db.Ping()`,
`if err != nil {`,
`panic(err)`,
} {
@ -595,7 +595,32 @@ fun main() {
}
}
func TestGenerateGoAutoThrowForErrorExprStmt(t *testing.T) {
func TestGenerateGoMigrationInteropHelpers(t *testing.T) {
prog, err := Parse(`package demo
data class Versioned(var version: Long)
fun names(values: Map<String, Int>): List<String> { return keys(values) }
fun buffer(): ByteSlice { return ByteSlice(32) }
fun optionalError(): Error? { return null }`)
if err != nil {
t.Fatal(err)
}
out, err := GenerateGo(prog)
if err != nil {
t.Fatal(err)
}
for _, want := range []string{
"Version int64",
"for key := range values",
"return make([]byte, 32)",
"func optionalError() error",
} {
if !strings.Contains(string(out), want) {
t.Fatalf("generated Go missing %q:\n%s", want, out)
}
}
}
func TestGenerateGoExplicitErrorsForErrorExprStmt(t *testing.T) {
src := `
package demo
@ -619,7 +644,7 @@ fun main() {
code := string(out)
for _, want := range []string{
`db := gotlinAutoThrow(sql.Open("postgres", "postgres://localhost/postgres?sslmode=disable"))`,
`db := sql.Open("postgres", "postgres://localhost/postgres?sslmode=disable")`,
`db.Ping()`,
} {
if !strings.Contains(code, want) {
@ -654,7 +679,7 @@ fun main() {
for _, want := range []string{
`type User struct {`,
`func NewUser(Name string) *User`,
`user := gotlinAutoThrow(NewUser("alice"))`,
`user := NewUser("alice")`,
`fmt.Println(user.Name)`,
} {
if !strings.Contains(code, want) {
@ -723,8 +748,8 @@ fun main() {
code := string(out)
for _, want := range []string{
`var names []string = gotlinAutoThrow([]string{"alice", "bob"})`,
`var ages map[string]int = gotlinAutoThrow(map[string]int{"alice": 30, "bob": 25})`,
`var names []string = []string{"alice", "bob"}`,
`var ages map[string]int = map[string]int{"alice": 30, "bob": 25}`,
`fmt.Println(names)`,
`fmt.Println(ages)`,
} {
@ -758,8 +783,8 @@ fun main() {
code := string(out)
for _, want := range []string{
`test := gotlinAutoThrow([]int{})`,
`labels := gotlinAutoThrow(map[string]int{})`,
`test := []int{}`,
`labels := map[string]int{}`,
`fmt.Println(test)`,
`fmt.Println(labels)`,
} {
@ -791,7 +816,7 @@ fun main() {
code := string(out)
for _, want := range []string{
`_ = gotlinAutoThrow([]int{1, 7})`,
`_ = []int{1, 7}`,
`fmt.Println("ok")`,
} {
if !strings.Contains(code, want) {
@ -814,7 +839,7 @@ fun runWorker(name: String) {
}
fun main() {
go runWorker("alice")
runBlocking { launch { runWorker("alice") } }
}
`
@ -832,9 +857,9 @@ fun main() {
for _, want := range []string{
`func runWorker(name string)`,
`fmt.Println(name)`,
`go func() {`,
`gotlinScope.Launch`,
`runWorker("alice")`,
`println("async panic:", recovered)`,
`gotlinRunBlocking`,
} {
if !strings.Contains(code, want) {
t.Fatalf("generated Go missing %q:\n%s", want, code)
@ -854,16 +879,9 @@ worker Counter {
}
`
prog, err := Parse(src)
if err != nil {
t.Fatalf("parse failed: %v", err)
}
_, err = GenerateGo(prog)
_, err := Parse(src)
if err == nil {
t.Fatal("expected worker self-call generation error")
}
if !strings.Contains(err.Error(), "worker self-calls are forbidden") {
t.Fatalf("unexpected error: %v", err)
t.Fatal("expected removed worker syntax error")
}
}
@ -1054,25 +1072,9 @@ worker Counter {
}
}
`
prog, err := Parse(src)
if err != nil {
t.Fatalf("parse failed: %v", err)
}
out, err := GenerateGo(prog)
if err != nil {
t.Fatalf("go generation failed: %v", err)
}
code := string(out)
for _, want := range []string{
`panicCh := make(chan any, 1)`,
`if recovered := recover(); recovered != nil {`,
`panicCh <- recovered`,
`case recovered := <-panicCh:`,
`panic(recovered)`,
} {
if !strings.Contains(code, want) {
t.Fatalf("generated Go missing %q:\n%s", want, code)
}
_, err := Parse(src)
if err == nil {
t.Fatal("expected removed worker syntax error")
}
}
@ -1089,7 +1091,7 @@ fun main() {
if err == nil {
t.Fatal("expected parse error")
}
if !strings.Contains(err.Error(), "'go' expects a function call expression") {
if !strings.Contains(err.Error(), "bare go is removed") {
t.Fatalf("unexpected parse error: %v", err)
}
}
@ -1104,9 +1106,11 @@ fun writer(ch: Channel<Int>) {
fun main() {
val ch = Channel<Int>()
go writer(ch)
select {
ch -> println(it)
runBlocking {
launch { writer(ch) }
select {
ch -> println(it)
}
}
val v = ch.read()
println(v)
@ -1127,13 +1131,13 @@ fun main() {
for _, want := range []string{
`func writer(ch chan int)`,
`ch <- 7`,
`ch := gotlinAutoThrow(make(chan int))`,
`go func() {`,
`ch := make(chan int)`,
`gotlinScope.Launch`,
`writer(ch)`,
`select {`,
`case it := <-ch:`,
`fmt.Println(it)`,
`v := gotlinAutoThrow(<-ch)`,
`v := <-ch`,
`fmt.Println(v)`,
} {
if !strings.Contains(code, want) {
@ -1326,7 +1330,7 @@ fun main() {
}
}
func TestGenerateGoAutoThrowForBunStyleCalls(t *testing.T) {
func TestGenerateGoExplicitErrorsForBunStyleCalls(t *testing.T) {
src := `
package demo
@ -1356,7 +1360,7 @@ class Repo(val db: *bun.DB) {
code := string(out)
for _, want := range []string{
`self.db.NewSelect().ColumnExpr("1").Scan(ctx)`,
`total := gotlinAutoThrow(self.db.NewSelect().ColumnExpr("1").Count(ctx))`,
`total := self.db.NewSelect().ColumnExpr("1").Count(ctx)`,
`fmt.Println(total)`,
} {
if !strings.Contains(code, want) {