package lang import ( "strings" "testing" ) func TestGenerateGoHello(t *testing.T) { src := ` package demo fun greet(name: String): String { return "hello, " + name } fun main() { val message = greet("world") println(message) } ` 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{ `package demo`, `func greet(name string) string`, `return "hello, " + name`, `fmt.Println(message)`, } { if !strings.Contains(code, want) { t.Fatalf("generated Go missing %q:\n%s", want, code) } } } func TestGenerateGoImports(t *testing.T) { src := ` package demo import strings import rand math.rand fun main() { val upper = strings.ToUpper("go") println(upper) println(rand.Intn(3)) } ` 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{ `"strings"`, `rand "math/rand"`, `upper := gotlinAutoThrow(strings.ToUpper("go"))`, `fmt.Println(rand.Intn(3))`, } { if !strings.Contains(code, want) { t.Fatalf("generated Go missing %q:\n%s", want, code) } } } func TestGenerateGoQualifiedTypes(t *testing.T) { src := ` package demo.web import fmt import net.http fun helloHandler(w: http.ResponseWriter, r: *http.Request) { fmt.Fprintln(w, "hello") } fun main() { http.HandleFunc("/", helloHandler) } ` 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{ `package web`, `func helloHandler(w http.ResponseWriter, r *http.Request)`, `fmt.Fprintln(w, "hello")`, `http.HandleFunc("/", helloHandler)`, } { if !strings.Contains(code, want) { t.Fatalf("generated Go missing %q:\n%s", want, code) } } } func TestGenerateGoTypedLambda(t *testing.T) { src := ` package demo import fmt import net.http fun main() { http.HandleFunc("/", { w: http.ResponseWriter, r: *http.Request -> fmt.Fprintln(w, "hello from lambda") }) } ` 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{ `http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {`, `fmt.Fprintln(w, "hello from lambda")`, } { if !strings.Contains(code, want) { t.Fatalf("generated Go missing %q:\n%s", want, code) } } } func TestGenerateGoTrailingLambda(t *testing.T) { src := ` package demo fun apply(value: String, fn: (String) -> Unit) { fn(value) } fun main() { apply("world") { value -> println(value) } } ` 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{ `apply("world", func(value string) {`, `fmt.Println(value)`, } { if !strings.Contains(code, want) { t.Fatalf("generated Go missing %q:\n%s", want, code) } } } func TestGenerateGoHandleFuncKotlinSyntax(t *testing.T) { src := ` package demo import fmt import net.http fun helloHandler(w: http.ResponseWriter, r: *http.Request) { fmt.Fprintln(w, "hello") } fun main() { http.HandleFunc("/") { w, r -> helloHandler(w, r) } } ` 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{ `http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {`, `helloHandler(w, r)`, } { if !strings.Contains(code, want) { t.Fatalf("generated Go missing %q:\n%s", want, code) } } } func TestGenerateGoImplicitItLambda(t *testing.T) { src := ` package demo fun apply(value: String, fn: (String) -> Unit) { fn(value) } fun main() { apply("world", { println(it) }) } ` 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{ `func apply(value string, fn func(string))`, `apply("world", func(it string) {`, `fmt.Println(it)`, } { if !strings.Contains(code, want) { t.Fatalf("generated Go missing %q:\n%s", want, code) } } } func TestGenerateGoClass(t *testing.T) { src := ` package demo class Greeter(val name: String) { fun greet() { println("hello, " + name) println(this.name) } } fun main() { val greeter = Greeter("world") greeter.greet() } ` 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{ `type Greeter struct {`, `name string`, `func NewGreeter(name string) *Greeter`, `func (self *Greeter) greet() {`, `fmt.Println("hello, " + self.name)`, `fmt.Println(self.name)`, `greeter := gotlinAutoThrow(NewGreeter("world"))`, } { if !strings.Contains(code, want) { t.Fatalf("generated Go missing %q:\n%s", want, code) } } } func TestGenerateGoInterface(t *testing.T) { src := ` package demo interface Greeter { fun greet(name: String): String } class ConsoleGreeter(val prefix: String) { fun greet(name: String): String { return prefix + name } } fun greetAll(greeter: Greeter) { println(greeter.greet("world")) } ` 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{ `type Greeter interface {`, `greet(name string) string`, `type ConsoleGreeter struct {`, `func (self *ConsoleGreeter) greet(name string) string {`, `return self.prefix + name`, `func greetAll(greeter Greeter)`, } { if !strings.Contains(code, want) { t.Fatalf("generated Go missing %q:\n%s", want, code) } } } func TestGenerateGoHTTPServerWithInterfaceMethodHandler(t *testing.T) { src := ` package examples.http import fmt import net.http interface EpicController { fun hello(w: http.ResponseWriter, r: *http.Request) } class EpicControllerImpl: EpicController { override fun hello(w: http.ResponseWriter, r: *http.Request) { fmt.Fprintln(w, "hello from gotlin") } } fun main() { val epicController: EpicController = EpicControllerImpl() fmt.Println("serving http://localhost:8080") http.HandleFunc("/", epicController.hello) http.ListenAndServe(":8080", http.DefaultServeMux) } ` 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{ `type EpicController interface {`, `hello(w http.ResponseWriter, r *http.Request)`, `type EpicControllerImpl struct {`, `func NewEpicControllerImpl() *EpicControllerImpl`, `func (self *EpicControllerImpl) hello(w http.ResponseWriter, r *http.Request) {`, `var epicController EpicController = gotlinAutoThrow(NewEpicControllerImpl())`, `http.HandleFunc("/", epicController.hello)`, `http.ListenAndServe(":8080", http.DefaultServeMux)`, } { if !strings.Contains(code, want) { t.Fatalf("generated Go missing %q:\n%s", want, code) } } } func TestGenerateGoAliasedStringImport(t *testing.T) { src := ` package demo import _ github.com.lib.pq import database.sql fun main() { println(sql.Drivers()) } ` 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{ `_ "github.com/lib/pq"`, `"database/sql"`, `fmt.Println(sql.Drivers())`, } { if !strings.Contains(code, want) { t.Fatalf("generated Go missing %q:\n%s", want, code) } } } func TestGenerateGoMultiAssignAndNull(t *testing.T) { src := ` package demo import database.sql fun main() { val db, err = sql.Open("postgres", "postgres://localhost/postgres?sslmode=disable") if (err != null) { return } db.Close() } ` 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{ `db, err := sql.Open("postgres", "postgres://localhost/postgres?sslmode=disable")`, `if err != nil {`, `db.Close()`, } { if !strings.Contains(code, want) { t.Fatalf("generated Go missing %q:\n%s", want, code) } } } func TestGenerateGoAutoBlankImportForSideEffects(t *testing.T) { src := ` package demo import github.com.lib.pq import database.sql fun main() { val db, err = sql.Open("postgres", "postgres://localhost/postgres?sslmode=disable") if (err != null) { return } db.Close() } ` 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{ `_ "github.com/lib/pq"`, `"database/sql"`, `db, err := sql.Open("postgres", "postgres://localhost/postgres?sslmode=disable")`, } { if !strings.Contains(code, want) { t.Fatalf("generated Go missing %q:\n%s", want, code) } } } func TestGenerateGoExceptionsAndRunCatching(t *testing.T) { src := ` package demo fun mayFail() { throw "boom" } fun main() { val result = runCatching({ mayFail() }) if (result.isSuccess()) { println("ok") } else { println(result.exceptionOrNull()) } try { mayFail() } catch (e: String) { println(e) } } ` 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{ `type gotlinResult struct {`, `func gotlinRunCatching(fn func()) (result gotlinResult) {`, `panic("boom")`, `result := gotlinAutoThrow(gotlinRunCatching(func() {`, `if recovered := recover(); recovered != nil {`, `e := recovered`, } { if !strings.Contains(code, want) { t.Fatalf("generated Go missing %q:\n%s", want, code) } } } func TestGenerateGoAutoThrowForGoErrorResults(t *testing.T) { src := ` package demo import database.sql fun main() { val db = sql.Open("postgres", "postgres://localhost/postgres?sslmode=disable") val err = db.Ping() if (err != null) { throw err } } ` 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{ `func gotlinAutoThrow[T any](value T, rest ...any) T {`, `db := gotlinAutoThrow(sql.Open("postgres", "postgres://localhost/postgres?sslmode=disable"))`, `err := gotlinAutoThrow(db.Ping())`, `if err != nil {`, `panic(err)`, } { if !strings.Contains(code, want) { t.Fatalf("generated Go missing %q:\n%s", want, code) } } } func TestGenerateGoAutoThrowForErrorExprStmt(t *testing.T) { src := ` package demo import database.sql fun main() { val db = sql.Open("postgres", "postgres://localhost/postgres?sslmode=disable") db.Ping() } ` 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{ `db := gotlinAutoThrow(sql.Open("postgres", "postgres://localhost/postgres?sslmode=disable"))`, `db.Ping()`, } { if !strings.Contains(code, want) { t.Fatalf("generated Go missing %q:\n%s", want, code) } } } func TestGenerateGoClassWithoutBodyBraces(t *testing.T) { src := ` package demo class User(val Name: String) fun main() { val user = User("alice") println(user.Name) } ` 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{ `type User struct {`, `func NewUser(Name string) *User`, `user := gotlinAutoThrow(NewUser("alice"))`, `fmt.Println(user.Name)`, } { if !strings.Contains(code, want) { t.Fatalf("generated Go missing %q:\n%s", want, code) } } } func TestGenerateGoGenericListAndMapTypes(t *testing.T) { src := ` package demo class Store(val names: List, val counts: Map) fun apply(values: List, f: (Map) -> Unit) { f(null) println(values) } ` 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{ `type Store struct {`, `names []string`, `counts map[string]int`, `func NewStore(names []string, counts map[string]int) *Store`, `func apply(values []string, f func(map[string]int))`, } { if !strings.Contains(code, want) { t.Fatalf("generated Go missing %q:\n%s", want, code) } } } func TestGenerateGoListAndMapLiterals(t *testing.T) { src := ` package demo fun main() { val names: List = listOf("alice", "bob") val ages: Map = mapOf("alice", 30, "bob", 25) println(names) println(ages) } ` 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{ `var names []string = gotlinAutoThrow([]string{"alice", "bob"})`, `var ages map[string]int = gotlinAutoThrow(map[string]int{"alice": 30, "bob": 25})`, `fmt.Println(names)`, `fmt.Println(ages)`, } { if !strings.Contains(code, want) { t.Fatalf("generated Go missing %q:\n%s", want, code) } } } func TestGenerateGoListAndMapLiteralsWithCallTypeArgs(t *testing.T) { src := ` package demo fun main() { val test = listOf() val labels = mapOf() println(test) println(labels) } ` 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{ `test := gotlinAutoThrow([]int{})`, `labels := gotlinAutoThrow(map[string]int{})`, `fmt.Println(test)`, `fmt.Println(labels)`, } { if !strings.Contains(code, want) { t.Fatalf("generated Go missing %q:\n%s", want, code) } } } func TestGenerateGoUnusedLocalDoesNotEmitNamedVariable(t *testing.T) { src := ` package demo fun main() { val test = listOf(1, 7) println("ok") } ` 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{ `_ = gotlinAutoThrow([]int{1, 7})`, `fmt.Println("ok")`, } { if !strings.Contains(code, want) { t.Fatalf("generated Go missing %q:\n%s", want, code) } } if strings.Contains(code, `test :=`) || strings.Contains(code, `var test `) { t.Fatalf("generated Go should not contain named unused local:\n%s", code) } } func TestGenerateGoGoStatement(t *testing.T) { src := ` package demo import fmt fun runWorker(name: String) { fmt.Println(name) } fun main() { go runWorker("alice") } ` 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{ `func runWorker(name string)`, `fmt.Println(name)`, `go func() {`, `runWorker("alice")`, `println("async panic:", recovered)`, } { if !strings.Contains(code, want) { t.Fatalf("generated Go missing %q:\n%s", want, code) } } } func TestGenerateGoForbidsWorkerSelfCalls(t *testing.T) { src := ` package demo worker Counter { val n = 0 fun inc() { inc() } } ` prog, err := Parse(src) if err != nil { t.Fatalf("parse failed: %v", err) } _, err = GenerateGo(prog) 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) } } func TestGenerateGoWorkerSyncPanicPropagationScaffolding(t *testing.T) { src := ` package demo worker Counter { fun boom() { throw "x" } } ` 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) } } } func TestParseGoStatementRequiresCall(t *testing.T) { src := ` package demo fun main() { go 123 } ` _, err := Parse(src) if err == nil { t.Fatal("expected parse error") } if !strings.Contains(err.Error(), "'go' expects a function call expression") { t.Fatalf("unexpected parse error: %v", err) } } func TestGenerateGoChannelsAndSelect(t *testing.T) { src := ` package demo fun writer(ch: Channel) { ch.send(7) } fun main() { val ch = Channel() go writer(ch) select { ch -> println(it) } val v = ch.read() println(v) } ` 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{ `func writer(ch chan int)`, `ch <- 7`, `ch := gotlinAutoThrow(make(chan int))`, `go func() {`, `writer(ch)`, `select {`, `case it := <-ch:`, `fmt.Println(it)`, `v := gotlinAutoThrow(<-ch)`, `fmt.Println(v)`, } { if !strings.Contains(code, want) { t.Fatalf("generated Go missing %q:\n%s", want, code) } } } func TestGenerateGoSelectAfterAndEvery(t *testing.T) { src := ` package demo fun main() { select { after(1000) -> println(it) every(250) -> println("tick") } } ` 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{ `"time"`, `func gotlinEveryMs(ms int) <-chan time.Time {`, `case it := <-time.After(time.Duration(1000) * time.Millisecond):`, `fmt.Println(it)`, `case <-gotlinEveryMs(250):`, `fmt.Println("tick")`, } { if !strings.Contains(code, want) { t.Fatalf("generated Go missing %q:\n%s", want, code) } } } func TestGenerateGoEveryRejectsNonPositiveMilliseconds(t *testing.T) { cases := []string{ `every(0)`, `every(-1)`, } for _, timerCall := range cases { src := ` package demo fun main() { select { ` + timerCall + ` -> println("x") } } ` prog, err := Parse(src) if err != nil { t.Fatalf("parse failed: %v", err) } _, err = GenerateGo(prog) if err == nil { t.Fatalf("expected go generation error for %s", timerCall) } if !strings.Contains(err.Error(), "every(ms) requires ms > 0") { t.Fatalf("unexpected error for %s: %v", timerCall, err) } } } func TestGenerateGoAfterEveryArityValidation(t *testing.T) { cases := []struct { call string want string }{ {`after()`, "after(ms) expects exactly one Int argument"}, {`after(1, 2)`, "after(ms) expects exactly one Int argument"}, {`every()`, "every(ms) expects exactly one Int argument"}, {`every(1, 2)`, "every(ms) expects exactly one Int argument"}, } for _, tc := range cases { src := ` package demo fun main() { select { ` + tc.call + ` -> println("x") } } ` prog, err := Parse(src) if err != nil { t.Fatalf("parse failed: %v", err) } _, err = GenerateGo(prog) if err == nil { t.Fatalf("expected go generation error for %s", tc.call) } if !strings.Contains(err.Error(), tc.want) { t.Fatalf("unexpected error for %s: %v", tc.call, err) } } } func TestGenerateGoAfterEveryIntValidation(t *testing.T) { cases := []struct { call string want string }{ {`after("1s")`, "after(ms) expects an Int argument"}, {`every(true)`, "every(ms) expects an Int argument"}, } for _, tc := range cases { src := ` package demo fun main() { select { ` + tc.call + ` -> println("x") } } ` prog, err := Parse(src) if err != nil { t.Fatalf("parse failed: %v", err) } _, err = GenerateGo(prog) if err == nil { t.Fatalf("expected go generation error for %s", tc.call) } if !strings.Contains(err.Error(), tc.want) { t.Fatalf("unexpected error for %s: %v", tc.call, err) } } } func TestGenerateGoNoTimeImportWhenUnused(t *testing.T) { src := ` package demo fun main() { println("ok") } ` 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) if strings.Contains(code, `"time"`) { t.Fatalf("unexpected time import in generated code:\n%s", code) } } func TestGenerateGoNoTickerShorthandRewrite(t *testing.T) { src := ` package demo import time fun main() { val tick = time.NewTicker(time.Second) select { tick -> println("x") } } ` 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) if strings.Contains(code, "<-tick.C") { t.Fatalf("ticker shorthand rewrite should be removed:\n%s", code) } if !strings.Contains(code, "case <-tick:") { t.Fatalf("expected raw source receive in generated code:\n%s", code) } } func TestGenerateGoAutoThrowForBunStyleCalls(t *testing.T) { src := ` package demo import context import github.com.uptrace.bun class Repo(val db: *bun.DB) { fun run() { val ctx = context.Background() db.NewSelect().ColumnExpr("1").Scan(ctx) val total = db.NewSelect().ColumnExpr("1").Count(ctx) println(total) } } ` 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{ `self.db.NewSelect().ColumnExpr("1").Scan(ctx)`, `total := gotlinAutoThrow(self.db.NewSelect().ColumnExpr("1").Count(ctx))`, `fmt.Println(total)`, } { if !strings.Contains(code, want) { t.Fatalf("generated Go missing %q:\n%s", want, code) } } }