package lang import ( "bytes" "fmt" "go/format" "regexp" "sort" "strconv" "strings" ) func GenerateGo(program *Program) ([]byte, error) { return generateGo(program, "", nil) } func GenerateGoMain(program *Program) ([]byte, error) { return generateGo(program, "main", nil) } func GenerateGoWithMetadata(program *Program, metadata []*PackageMetadata) ([]byte, error) { return generateGo(program, "", metadata) } func GenerateGoMainWithMetadata(program *Program, metadata []*PackageMetadata) ([]byte, error) { return generateGo(program, "main", metadata) } func generateGo(program *Program, packageOverride string, metadata []*PackageMetadata) ([]byte, error) { semantic, diagnostics := AnalyzeWithMetadata(program, metadata) if len(diagnostics) > 0 { return nil, diagnostics[0] } g := goGenerator{semantic: semantic} if err := g.program(program, packageOverride); err != nil { return nil, err } src := g.buf.Bytes() formatted, err := format.Source(src) if err != nil { return src, fmt.Errorf("generated invalid Go: %w", err) } return formatted, nil } type goGenerator struct { semantic *SemanticProgram buf bytes.Buffer indentLevel int needsFmt bool needsRunCatch bool needsGoUnwrap bool needsTime bool needsJSONDecode bool needsCoroutines bool needsSQLBulk bool sqlContextAlias string sqlPGXAlias string currentFunc FunctionDecl currentClass *ClassDecl currentCoroutineScope string scopes []map[string]bool semanticScope *Scope matchCounter int resultCounter int } func (g *goGenerator) program(program *Program, packageOverride string) error { containsSQL := programContainsSQL(program) containsSQLExecution := programContainsSQLExecution(program) g.needsSQLBulk = programContainsSQLBulk(program) g.needsCoroutines = programUsesCoroutines(program) if g.needsCoroutines { g.needsTime = true } if containsSQLExecution { g.needsGoUnwrap = true g.sqlContextAlias = runtimeImportAlias(program, "context", "gotlincontext") g.sqlPGXAlias = runtimeImportAlias(program, "github.com/jackc/pgx/v5", "gotlinpgx") } packageName := goPackageName(program.PackagePath) if packageOverride != "" { packageName = packageOverride } g.line("package " + packageName) g.line("") for _, fn := range program.Functions { if usesPrintln(fn.Body) || (fn.ExpressionBody != nil && exprUsesPrintln(fn.ExpressionBody)) { g.needsFmt = true break } } if !g.needsFmt { for _, class := range program.Classes { for _, method := range class.Methods { if usesPrintln(method.Body) || (method.ExpressionBody != nil && exprUsesPrintln(method.ExpressionBody)) { g.needsFmt = true break } } if g.needsFmt { break } } } if !g.needsRunCatch { for _, fn := range program.Functions { if usesRunCatching(fn.Body) || (fn.ExpressionBody != nil && exprUsesRunCatching(fn.ExpressionBody)) { g.needsRunCatch = true break } } } if !g.needsGoUnwrap { for _, fn := range program.Functions { if usesGoUnwrap(fn.Body) || fn.ExpressionBody != nil { g.needsGoUnwrap = true break } } } if !g.needsGoUnwrap { for _, class := range program.Classes { for _, method := range class.Methods { if usesGoUnwrap(method.Body) || method.ExpressionBody != nil { g.needsGoUnwrap = true break } } if g.needsGoUnwrap { break } } } if !g.needsRunCatch { for _, class := range program.Classes { for _, method := range class.Methods { if usesRunCatching(method.Body) || (method.ExpressionBody != nil && exprUsesRunCatching(method.ExpressionBody)) { g.needsRunCatch = true break } } if g.needsRunCatch { break } } } runtimeImports := map[string]string{} if g.needsSQLBulk { runtimeImports["strings"] = "strings" runtimeImports["strconv"] = "strconv" } if containsSQLExecution { runtimeImports["context"] = g.sqlContextAlias runtimeImports["github.com/jackc/pgx/v5"] = g.sqlPGXAlias } if g.needsCoroutines { runtimeImports["context"] = "context" runtimeImports["sync"] = "sync" } imports := collectImports(program, g.needsFmt, g.needsTime, runtimeImports) if len(program.Embeds) > 0 { hasEmbed := false for _, imp := range program.Imports { if imp.Path == "embed" { hasEmbed = true break } } if !hasEmbed { imports = append(imports, `_ "embed"`) sort.Strings(imports) } } if len(imports) == 1 { g.line("import " + imports[0]) g.line("") } else if len(imports) > 1 { g.line("import (") g.indentLevel++ for _, imp := range imports { g.line(imp) } g.indentLevel-- g.line(")") g.line("") } if g.needsRunCatch { g.emitRunCatchingSupport() g.line("") } if g.needsGoUnwrap { g.emitGoUnwrapSupport() g.line("") } if g.needsCoroutines { g.emitCoroutineSupport() g.line("") } g.line("type GotlinResult[T any] struct { Value T; Err error }") g.line("func gotlinResultUnwrap[T any](result GotlinResult[T]) T { if result.Err != nil { panic(result.Err) }; return result.Value }") g.line("func gotlinResultUnwrapOr[T any](result GotlinResult[T], fallback T) T { if result.Err != nil { return fallback }; return result.Value }") g.line("") for _, embedded := range program.Embeds { g.line("//go:embed " + embedded.Path) g.line("var " + embedded.Name + " " + g.goType(embedded.Type)) g.line("") } emitted := false if containsSQL { g.emitSQLSupport(program, containsSQLExecution) emitted = true } for _, decl := range program.Interfaces { if emitted { g.line("") } g.interfaceDecl(decl) emitted = true } for _, enum := range program.Enums { if emitted { g.line("") } g.enumDecl(enum) emitted = true } for _, class := range program.Classes { if emitted { g.line("") } if err := g.classDecl(class); err != nil { return err } emitted = true } for _, fn := range program.Functions { if emitted { g.line("") } if err := g.function(fn); err != nil { return err } emitted = true } for i := 0; i < len(g.semantic.Mappings.pairs); i++ { g.line("") if err := g.emitMapping(g.semantic.Mappings.pairs[i]); err != nil { return err } } if g.needsJSONDecode { if emitted { g.line("") } g.emitJSONDecodeSupport() } return nil } func (g *goGenerator) function(fn FunctionDecl) error { g.currentClass = nil g.currentFunc = fn previousScope := g.currentCoroutineScope hasCoroutineEffect := g.hasCoroutineEffect(fn) if hasCoroutineEffect && fn.Name == "main" { return fmt.Errorf("contextual function main requires a runBlocking boundary") } if hasCoroutineEffect { g.currentCoroutineScope = "gotlinScope" } defer func() { g.currentCoroutineScope = previousScope }() g.scopes = nil g.semanticScope = nil g.pushScope() for _, param := range fn.Params { g.defineType(param.Name, param.Type) } g.write("func ") g.write(fn.Name) g.write(renderGoTypeParameters(fn.TypeParams)) g.write(g.renderGoFunctionParams(fn)) returnType := g.functionReturnType(fn) if ret := g.goReturnType(returnType); ret != "" { g.write(" ") g.write(ret) } g.write(" {\n") g.indentLevel++ if fn.ExpressionBody != nil { value, err := g.expr(fn.ExpressionBody, returnType) if err != nil { return fmt.Errorf("function %s: %w", fn.Name, err) } if g.goReturnType(returnType) == "" { g.line(value) } else { g.line("return " + value) } } else if err := g.block(fn.Body); err != nil { return fmt.Errorf("function %s: %w", fn.Name, err) } g.indentLevel-- g.line("}") g.popScope() return nil } func (g *goGenerator) interfaceDecl(decl InterfaceDecl) { g.line("type " + decl.Name + " interface {") g.indentLevel++ for _, method := range decl.Methods { g.line(method.Name + g.renderGoParamsWithPrefix(method.Params, "") + g.renderGoReturnSuffix(method.ReturnType)) } g.indentLevel-- g.line("}") } func (g *goGenerator) enumDecl(decl EnumDecl) { if enumIsString(decl) { g.line("type " + decl.Name + " string") g.line("const (") g.indentLevel++ for _, variant := range decl.Variants { g.line(decl.Name + variant.Name + " " + decl.Name + " = " + strconv.Quote(enumStringValue(variant))) } g.indentLevel-- g.line(")") return } g.line("type " + decl.Name + " interface { is" + decl.Name + "() }") for _, variant := range decl.Variants { name := decl.Name + variant.Name g.line("") g.line("type " + name + " struct {") g.indentLevel++ for i, typ := range variant.PayloadTypes { g.line(fmt.Sprintf("Value%d %s", i, g.goType(typ))) } g.indentLevel-- g.line("}") g.line("func (*" + name + ") is" + decl.Name + "() {}") } } func enumIsString(decl EnumDecl) bool { for _, variant := range decl.Variants { if len(variant.PayloadTypes) > 0 { return false } } return true } func enumStringValue(variant EnumVariant) string { if variant.StringValue != "" { return variant.StringValue } return variant.Name } func enumVariant(decl EnumDecl, name string) *EnumVariant { for i := range decl.Variants { if decl.Variants[i].Name == name { return &decl.Variants[i] } } return nil } func (g *goGenerator) classDecl(class ClassDecl) error { g.line("type " + class.Name + renderGoTypeParameters(class.TypeParams) + " struct {") g.indentLevel++ for _, field := range class.Fields { name := field.Name tag := "" if class.Data { if field.Private { tag = " `json:\"-\"`" } else { name = exportedGoName(name) tag = fmt.Sprintf(" `json:\"%s\"`", jsonFieldName(field.Name, class.JSONNaming)) } } g.line(name + " " + g.goType(field.Type) + tag) } g.indentLevel-- g.line("}") g.line("") g.write("func New") g.write(class.Name) g.write(renderGoTypeParameters(class.TypeParams)) g.write("(") for i, field := range class.Fields { if i > 0 { g.write(", ") } g.write(field.Name) g.write(" ") g.write(g.goType(field.Type)) } g.write(") *") g.write(class.Name) g.write(renderGoTypeArguments(class.TypeParams)) g.write(" {\n") g.indentLevel++ g.writeIndent() g.write("return &") g.write(class.Name) g.write(renderGoTypeArguments(class.TypeParams)) g.write("{") for i, field := range class.Fields { if i > 0 { g.write(", ") } if class.Data && !field.Private { g.write(exportedGoName(field.Name)) } else { g.write(field.Name) } g.write(": ") g.write(field.Name) } g.write("}\n") g.indentLevel-- g.line("}") for _, method := range class.Methods { g.line("") if err := g.method(class, method); err != nil { return err } } return nil } func (g *goGenerator) method(class ClassDecl, fn FunctionDecl) error { g.currentClass = &class g.currentFunc = fn previousScope := g.currentCoroutineScope if g.hasCoroutineEffect(fn) { g.currentCoroutineScope = "gotlinScope" } defer func() { g.currentCoroutineScope = previousScope }() g.scopes = nil g.semanticScope = nil g.pushScope() g.define("self") g.define("this") for _, param := range fn.Params { g.defineType(param.Name, param.Type) } g.write("func (self *") g.write(class.Name) g.write(renderGoTypeArguments(class.TypeParams)) g.write(") ") g.write(fn.Name) g.write(g.renderGoFunctionParams(fn)) returnType := g.functionReturnType(fn) if ret := g.goReturnType(returnType); ret != "" { g.write(" ") g.write(ret) } g.write(" {\n") g.indentLevel++ if fn.ExpressionBody != nil { value, err := g.expr(fn.ExpressionBody, returnType) if err != nil { return fmt.Errorf("method %s.%s: %w", class.Name, fn.Name, err) } if g.goReturnType(returnType) == "" { g.line(value) } else { g.line("return " + value) } } else if err := g.block(fn.Body); err != nil { return fmt.Errorf("method %s.%s: %w", class.Name, fn.Name, err) } g.indentLevel-- g.line("}") g.popScope() g.currentClass = nil return nil } func (g *goGenerator) block(stmts []Stmt) error { for i, stmt := range stmts { if err := g.stmt(stmt, stmts[i+1:]); err != nil { return fmt.Errorf("%T: %w", stmt, err) } } return nil } func (g *goGenerator) tryVarDecl(decl VarDecl, attempt TryExpr, tail []Stmt) error { base, resultArgs, ok := parseGenericType(g.currentFunc.ReturnType) if !ok || base != "Result" || len(resultArgs) != 2 { return fmt.Errorf("? can only be used inside a function returning Result") } g.resultCounter++ errorName := fmt.Sprintf("gotlinError%d", g.resultCounter) valueName := decl.Name if !nameUsedInStmts(decl.Name, tail) { valueName = "_" } innerType := g.exprType(attempt.Value) resolvedAttempt := exprMeta(attempt.Value) rawGoCall := resolvedAttempt != nil && resolvedAttempt.Meaning == GoCallExpr if innerBase, innerArgs, isResult := parseGenericType(innerType); isResult && innerBase == "Result" && len(innerArgs) == 2 && !rawGoCall { resultName := fmt.Sprintf("gotlinResult%d", g.resultCounter) value, err := g.expr(attempt.Value, innerType) if err != nil { return err } g.line(resultName + " := " + value) g.line("if " + resultName + ".Err != nil { return GotlinResult[" + g.goType(resultArgs[0]) + "]{Err: " + resultName + ".Err} }") g.line(valueName + " := " + resultName + ".Value") if decl.Type == "" { decl.Type = innerArgs[0] } } else { value, err := g.expr(attempt.Value, "") if err != nil { return err } g.line(valueName + ", " + errorName + " := " + value) g.line("if " + errorName + " != nil { return GotlinResult[" + g.goType(resultArgs[0]) + "]{Err: " + errorName + "} }") } if valueName != "_" { g.defineType(decl.Name, decl.Type) } return nil } func (g *goGenerator) stmt(stmt Stmt, tail []Stmt) error { switch s := stmt.(type) { case VarDecl: if _, isNull := s.Value.(NullExpr); isNull && s.Type == "" { return fmt.Errorf("null requires an explicit nullable type") } if attempt, ok := s.Value.(TryExpr); ok { return g.tryVarDecl(s, attempt, tail) } value, err := g.expr(s.Value, s.Type) if err != nil { return err } value = g.passthroughValue(s.Value, value) if !nameUsedInStmts(s.Name, tail) { g.line(fmt.Sprintf("_ = %s", value)) return nil } if s.Type == "" { g.line(fmt.Sprintf("%s := %s", s.Name, value)) } else { g.line(fmt.Sprintf("var %s %s = %s", s.Name, g.goType(s.Type), value)) } typ := s.Type if typ == "" { typ = g.exprType(s.Value) } g.defineType(s.Name, typ) case MultiVarDecl: value, err := g.expr(s.Value, "") if err != nil { return err } assigned := make([]string, len(s.Names)) usedCount := 0 for i, name := range s.Names { if nameUsedInStmts(name, tail) { assigned[i] = name usedCount++ } else { assigned[i] = "_" } } if usedCount == 0 { g.line(fmt.Sprintf("%s = %s", strings.Join(assigned, ", "), value)) return nil } g.line(fmt.Sprintf("%s := %s", strings.Join(assigned, ", "), value)) for i, name := range s.Names { if assigned[i] != "_" { g.define(name) } } case AssignStmt: value, err := g.expr(s.Value, g.lookupType(s.Name)) if err != nil { return err } value = g.passthroughValue(s.Value, value) g.line(fmt.Sprintf("%s = %s", g.assignTarget(s.Name), value)) case AddAssignStmt: value, err := g.expr(s.Value, "") if err != nil { return err } value = g.passthroughValue(s.Value, value) g.line(fmt.Sprintf("%s += %s", g.assignTarget(s.Name), value)) case MultiAssignStmt: value, err := g.expr(s.Value, "") if err != nil { return err } names := make([]string, 0, len(s.Names)) for _, name := range s.Names { names = append(names, g.assignTarget(name)) } g.line(fmt.Sprintf("%s = %s", strings.Join(names, ", "), value)) case FieldAssignStmt: target, err := g.expr(s.Target, "") if err != nil { return err } value, err := g.expr(s.Value, g.exprType(s.Target)) if err != nil { return err } g.line(target + " = " + value) case ReturnStmt: if s.Value == nil { g.line("return") } else { value, err := g.expr(s.Value, g.currentFunc.ReturnType) if err != nil { return fmt.Errorf("returning %s: %w", g.currentFunc.ReturnType, err) } value = g.passthroughValue(s.Value, value) g.line(fmt.Sprintf("return %s", value)) } case ThrowStmt: value, err := g.expr(s.Value, "") if err != nil { return err } g.line(fmt.Sprintf("panic(%s)", value)) case DeferStmt: value, err := g.expr(s.Value, "") if err != nil { return err } g.line("defer " + value) case ExprStmt: value, err := g.expr(s.Value, "") if err != nil { return err } g.line(value) case IfStmt: cond, err := g.expr(s.Cond, "") if err != nil { return err } g.writeIndent() g.write(fmt.Sprintf("if %s {\n", cond)) g.indentLevel++ g.pushScope() if name, thenNonNull, _ := nullableCondition(s.Cond); thenNonNull { if typ := g.lookupType(name); strings.HasSuffix(typ, "?") { g.defineType(name, strings.TrimSuffix(typ, "?")) } } if err := g.block(s.Then); err != nil { return err } g.popScope() g.indentLevel-- g.writeIndent() g.write("}") if len(s.Else) > 0 { g.write(" else {\n") g.indentLevel++ g.pushScope() if name, _, elseNonNull := nullableCondition(s.Cond); elseNonNull { if typ := g.lookupType(name); strings.HasSuffix(typ, "?") { g.defineType(name, strings.TrimSuffix(typ, "?")) } } if err := g.block(s.Else); err != nil { return err } g.popScope() g.indentLevel-- g.writeIndent() g.write("}") } g.write("\n") if len(s.Else) == 0 && blockAlwaysTerminates(s.Then) { if name, _, elseNonNull := nullableCondition(s.Cond); elseNonNull { if typ := g.lookupType(name); strings.HasSuffix(typ, "?") { g.defineType(name, strings.TrimSuffix(typ, "?")) } } } case WhileStmt: cond, err := g.expr(s.Cond, "") if err != nil { return err } g.writeIndent() g.write(fmt.Sprintf("for %s {\n", cond)) g.indentLevel++ g.pushScope() if err := g.block(s.Body); err != nil { return err } g.popScope() g.indentLevel-- g.line("}") case ForEachStmt: source, err := g.expr(s.Source, "") if err != nil { return err } g.line(fmt.Sprintf("for _, %s := range %s {", s.Name, source)) g.indentLevel++ g.pushScope() g.define(s.Name) if err := g.block(s.Body); err != nil { return err } g.popScope() g.indentLevel-- g.line("}") case MatchStmt: enumName := strings.TrimPrefix(g.exprType(s.Value), "*") if enumName == "" && len(s.Cases) > 0 { enumName = s.Cases[0].EnumName } decl := g.semantic.Enums[enumName] value, err := g.expr(s.Value, enumName) if err != nil { return err } g.matchCounter++ matchName := fmt.Sprintf("gotlinMatch%d", g.matchCounter) if enumIsString(decl) { g.line("switch " + value + " {") g.indentLevel++ for _, c := range s.Cases { g.line("case " + enumName + c.VariantName + ":") g.indentLevel++ g.pushScope() if err := g.block(c.Body); err != nil { return err } g.popScope() g.indentLevel-- } g.indentLevel-- g.line("}") break } hasBindings := false for _, c := range s.Cases { if len(c.Bindings) > 0 { hasBindings = true break } } if hasBindings { g.line(fmt.Sprintf("switch %s := %s.(type) {", matchName, value)) } else { g.line(fmt.Sprintf("switch %s.(type) {", value)) } g.indentLevel++ for _, c := range s.Cases { variant := enumVariant(decl, c.VariantName) g.line("case *" + enumName + c.VariantName + ":") g.indentLevel++ g.pushScope() for i, binding := range c.Bindings { g.line(fmt.Sprintf("%s := %s.Value%d", binding, matchName, i)) g.defineType(binding, variant.PayloadTypes[i]) } if err := g.block(c.Body); err != nil { return err } g.popScope() g.indentLevel-- } g.indentLevel-- g.line("}") case TryCatchStmt: g.writeIndent() g.write("func() {\n") g.indentLevel++ g.writeIndent() g.write("defer func() {\n") g.indentLevel++ g.writeIndent() g.write("if recovered := recover(); recovered != nil {\n") g.indentLevel++ g.pushScope() g.define(s.CatchName) g.line(fmt.Sprintf("%s := recovered", s.CatchName)) if err := g.block(s.CatchBody); err != nil { return err } g.popScope() g.indentLevel-- g.line("}") g.indentLevel-- g.line("}()") g.pushScope() if err := g.block(s.TryBody); err != nil { return err } g.popScope() g.indentLevel-- g.line("}()") default: return fmt.Errorf("unsupported statement %T", stmt) } return nil } func (g *goGenerator) expr(expr Expr, expectedType string) (string, error) { resolved := exprMeta(expr) resolvedSQL := false if resolved != nil { _, resolvedSQL = resolved.Node.(HIRSQL) } if resolved == nil || resolvedSQL { if lowered, handled, err := g.lowerSQLQuery(expr); handled || err != nil { return lowered, err } } if expectedType != "" && expectedType != "Any" { if actualType := g.exprType(expr); strings.HasSuffix(actualType, "?") && !strings.HasSuffix(expectedType, "?") { return "", fmt.Errorf("nullable value of type %s cannot be used as non-nullable %s; use ?. or !!", actualType, expectedType) } } switch e := expr.(type) { case IdentExpr: if g.currentClass != nil { if e.Name == "this" { return "self", nil } if field, found := classFieldByName(*g.currentClass, e.Name); found && !g.isDefined(e.Name) { return "self." + mappingFieldName(*g.currentClass, field), nil } } return e.Name, nil case IntExpr: return e.Value, nil case FloatExpr: return e.Value, nil case StringExpr: return e.Value, nil case BoolExpr: if e.Value { return "true", nil } return "false", nil case NullExpr: if expectedType != "" && !strings.HasSuffix(expectedType, "?") { return "", fmt.Errorf("null is not allowed for non-nullable type %s", expectedType) } return "nil", nil case NonNullExpr: sourceType := g.exprType(e.Value) if !strings.HasSuffix(sourceType, "?") { return "", fmt.Errorf("non-null assertion requires a nullable value, got %s", sourceType) } innerType := strings.TrimSuffix(sourceType, "?") value, err := g.expr(e.Value, sourceType) if err != nil { return "", err } _, innerIsClass := g.classForType(innerType) if innerIsClass || strings.HasPrefix(innerType, "*") { return fmt.Sprintf("func(value %s) %s { if value == nil { panic(\"non-null assertion failed\") }; return value }(%s)", g.goType(sourceType), g.goType(innerType), value), nil } return fmt.Sprintf("func(value %s) %s { if value == nil { panic(\"non-null assertion failed\") }; return *value }(%s)", g.goType(sourceType), g.goType(innerType), value), nil case TryExpr: return "", fmt.Errorf("? propagation is only supported on variable declarations") case UnaryExpr: value, err := g.wrapExpr(e.Value, "") if err != nil { return "", err } return e.Op + value, nil case BinaryExpr: if e.Op == "==" || e.Op == "!=" { if _, ok := e.Left.(NullExpr); ok { if typ := g.exprType(e.Right); typ != "" && !strings.HasSuffix(typ, "?") { return "", fmt.Errorf("null comparison requires a nullable value, got %s", typ) } } if _, ok := e.Right.(NullExpr); ok { if typ := g.exprType(e.Left); typ != "" && !strings.HasSuffix(typ, "?") { return "", fmt.Errorf("null comparison requires a nullable value, got %s", typ) } } } left, err := g.wrapExpr(e.Left, "") if err != nil { return "", err } right, err := g.wrapExpr(e.Right, "") if err != nil { return "", err } return fmt.Sprintf("%s %s %s", left, e.Op, right), nil case CallExpr: resolvedCall := exprMeta(e) if resolvedCall == nil || resolvedCall.Meaning == EnumConstructionExpr { selector, ok := e.Callee.(SelectorExpr) if ok { if receiver, ok := selector.Receiver.(IdentExpr); ok { if receiver.Name == "Result" { return g.expr(EnumVariantExpr{EnumName: receiver.Name, VariantName: selector.Name, Values: e.Args}, expectedType) } if _, ok := g.semantic.Enums[receiver.Name]; ok { return g.expr(EnumVariantExpr{EnumName: receiver.Name, VariantName: selector.Name, Values: e.Args}, expectedType) } } } } if ident, ok := e.Callee.(IdentExpr); ok && coroutineBuiltins[ident.Name] { switch ident.Name { case "runBlocking": lambda, err := coroutineLambdaArg(e, "runBlocking") if err != nil { return "", err } block, err := g.coroutineLambda(lambda, "Unit") if err != nil { return "", err } return "gotlinRunBlocking(" + block + ")", nil case "withContext": if len(e.Args) != 2 { return "", fmt.Errorf("withContext expects a Go context and a lambda") } ctx, err := g.expr(e.Args[0], "context.Context") if err != nil { return "", err } lambda, ok := e.Args[1].(LambdaExpr) if !ok { return "", fmt.Errorf("withContext expects a lambda") } block, err := g.coroutineLambda(lambda, "Unit") if err != nil { return "", err } if g.currentCoroutineScope == "" { return "gotlinRunWithContext(" + ctx + ", " + block + ")", nil } return g.currentCoroutineScope + ".WithContext(" + ctx + ", " + block + ")", nil case "coroutineScope", "launch": if g.currentCoroutineScope == "" { return "", fmt.Errorf("%s requires a coroutine scope", ident.Name) } lambda, err := coroutineLambdaArg(e, ident.Name) if err != nil { return "", err } block, err := g.coroutineLambda(lambda, "Unit") if err != nil { return "", err } method := "Scope" if ident.Name == "launch" { method = "Launch" } return g.currentCoroutineScope + "." + method + "(" + block + ")", nil case "async": if g.currentCoroutineScope == "" { return "", fmt.Errorf("async requires a coroutine scope") } if len(e.TypeArgs) != 1 { return "", fmt.Errorf("async expects one result type") } lambda, err := coroutineLambdaArg(e, "async") if err != nil { return "", err } block, err := g.coroutineLambda(lambda, e.TypeArgs[0]) if err != nil { return "", err } return "gotlinAsync[" + g.goType(e.TypeArgs[0]) + "](" + g.currentCoroutineScope + ", " + block + ")", nil case "delay": if g.currentCoroutineScope == "" || len(e.Args) != 1 { return "", fmt.Errorf("delay(ms) requires a coroutine scope") } ms, err := g.expr(e.Args[0], "Int") if err != nil { return "", err } return g.currentCoroutineScope + ".Delay(" + ms + ")", nil case "withTimeout": if g.currentCoroutineScope == "" || len(e.Args) != 2 { return "", fmt.Errorf("withTimeout expects milliseconds and a lambda") } ms, err := g.expr(e.Args[0], "Int") if err != nil { return "", err } lambda, ok := e.Args[1].(LambdaExpr) if !ok { return "", fmt.Errorf("withTimeout expects a lambda") } block, err := g.coroutineLambda(lambda, "Unit") if err != nil { return "", err } return g.currentCoroutineScope + ".WithTimeout(" + ms + ", " + block + ")", nil case "isActive": if g.currentCoroutineScope == "" { return "", fmt.Errorf("isActive requires a coroutine scope") } return g.currentCoroutineScope + ".IsActive()", nil case "coroutineContext": if g.currentCoroutineScope == "" { return "", fmt.Errorf("coroutineContext requires a coroutine scope") } return g.currentCoroutineScope + ".Context()", nil } } if selector, ok := e.Callee.(SelectorExpr); ok && selector.Name == "await" && len(e.Args) == 0 { receiver, err := g.expr(selector.Receiver, "") if err != nil { return "", err } return receiver + ".await()", nil } if selector, ok := e.Callee.(SelectorExpr); ok && selector.Name == "unwrap" && len(e.Args) == 0 { receiverType := g.exprType(selector.Receiver) resolvedReceiver := exprMeta(selector.Receiver) receiverExpected := receiverType if resolvedReceiver != nil && resolvedReceiver.Meaning == GoCallExpr { receiverExpected = "" } receiver, err := g.expr(selector.Receiver, receiverExpected) if err != nil { return "", err } if base, _, ok := parseGenericType(receiverType); ok && base == "Result" && (resolvedReceiver == nil || resolvedReceiver.Meaning != GoCallExpr) { return "gotlinResultUnwrap(" + receiver + ")", nil } return "gotlinUnwrapGo(" + receiver + ")", nil } if selector, ok := e.Callee.(SelectorExpr); ok && selector.Name == "unwrapOr" && len(e.Args) == 1 { receiverType := g.exprType(selector.Receiver) base, args, ok := parseGenericType(receiverType) if !ok || base != "Result" || len(args) != 2 { return "", fmt.Errorf("unwrapOr requires a Result value") } receiver, err := g.expr(selector.Receiver, receiverType) if err != nil { return "", err } fallback, err := g.expr(e.Args[0], args[0]) if err != nil { return "", err } return "gotlinResultUnwrapOr(" + receiver + ", " + fallback + ")", nil } if safe, ok := e.Callee.(SafeSelectorExpr); ok { receiverType := g.exprType(safe.Receiver) if !strings.HasSuffix(receiverType, "?") { return "", fmt.Errorf("safe call requires a nullable receiver, got %s", receiverType) } innerType := strings.TrimSuffix(receiverType, "?") class, ok := g.classForType(innerType) if !ok { return "", fmt.Errorf("safe call receiver %s is not a Gotlin class", receiverType) } var method *FunctionDecl for i := range class.Methods { if class.Methods[i].Name == safe.Name { method = &class.Methods[i] break } } if method == nil { return "", fmt.Errorf("unknown safe-call method %s.%s", class.Name, safe.Name) } if len(e.Args) != len(method.Params) { return "", fmt.Errorf("%s.%s expects %d arguments", class.Name, safe.Name, len(method.Params)) } receiver, err := g.expr(safe.Receiver, receiverType) if err != nil { return "", err } args := make([]string, 0, len(e.Args)) for i, arg := range e.Args { value, err := g.expr(arg, method.Params[i].Type) if err != nil { return "", err } args = append(args, value) } call := "value." + safe.Name + "(" + strings.Join(args, ", ") + ")" if method.ReturnType == "Unit" { return fmt.Sprintf("func(value %s) { if value != nil { %s } }(%s)", g.goType(receiverType), call, receiver), nil } _, returnsClass := g.classForType(method.ReturnType) if returnsClass || strings.HasPrefix(method.ReturnType, "*") || strings.HasSuffix(method.ReturnType, "?") { return fmt.Sprintf("func(value %s) %s { if value == nil { return nil }; return %s }(%s)", g.goType(receiverType), g.goType(nullableType(method.ReturnType)), call, receiver), nil } return fmt.Sprintf("func(value %s) %s { if value == nil { return nil }; result := %s; return &result }(%s)", g.goType(receiverType), g.goType(nullableType(method.ReturnType)), call, receiver), nil } if selector, ok := e.Callee.(SelectorExpr); ok && selector.Name == "mapTo" && (resolvedCall == nil || resolvedCall.Meaning == MappingExpression) { if len(e.TypeArgs) > 1 || len(e.Args) != 0 || len(e.NamedArgs) != 0 { return "", fmt.Errorf("mapTo expects at most one type argument and no value arguments") } sourceType := g.exprType(selector.Receiver) if sourceType == "" { return "", fmt.Errorf("mapTo source requires a known type") } targetType := expectedType if len(e.TypeArgs) == 1 { targetType = g.mappingTopLevelTarget(e.TypeArgs[0]) } if targetType == "" { return "", fmt.Errorf("mapTo target type cannot be inferred; use mapTo() or provide a typed context") } function, err := g.ensureMapping(sourceType, targetType, strings.TrimPrefix(sourceType, "*")) if err != nil { return "", err } source, err := g.expr(selector.Receiver, sourceType) if err != nil { return "", err } return function + "(" + source + ")", nil } if len(e.NamedArgs) > 0 { callee, err := g.expr(e.Callee, "") if err != nil { return "", err } fields := make([]string, 0, len(e.NamedArgs)) for _, arg := range e.NamedArgs { value, err := g.expr(arg.Value, "") if err != nil { return "", err } fields = append(fields, exportedGoFieldName(arg.Name)+": "+value) } return "&" + callee + "{" + strings.Join(fields, ", ") + "}", nil } if selector, ok := e.Callee.(SelectorExpr); ok { if receiver, ok := selector.Receiver.(IdentExpr); ok && receiver.Name == "json" && selector.Name == "decode" { if len(e.TypeArgs) != 1 || len(e.Args) != 1 { return "", fmt.Errorf("json.decode expects one type argument and one body argument") } body, err := g.expr(e.Args[0], "") if err != nil { return "", err } g.needsJSONDecode = true return fmt.Sprintf("gotlinJSONDecode[%s](%s)", g.goType(e.TypeArgs[0]), body), nil } } if ident, ok := e.Callee.(IdentExpr); ok { switch ident.Name { case "goAssert": if len(e.TypeArgs) != 1 || len(e.Args) != 1 { return "", fmt.Errorf("goAssert(value) expects one type and one value") } value, err := g.expr(e.Args[0], "") if err != nil { return "", err } return "(" + value + ").(" + g.goType(e.TypeArgs[0]) + ")", nil case "listOf", "mutableListOf": return g.listLiteral(e.Args, e.TypeArgs, expectedType) case "mapOf", "mutableMapOf": return g.mapLiteral(e.Args, e.TypeArgs, expectedType) case "Channel": return g.channelMakeExpr(e.Args, e.TypeArgs, expectedType) case "keys": if len(e.Args) != 1 { return "", fmt.Errorf("keys(map) expects exactly one argument") } _, mapTypes, ok := parseGenericType(g.exprType(e.Args[0])) if !ok || len(mapTypes) != 2 { return "", fmt.Errorf("keys(map) requires a typed map") } value, err := g.expr(e.Args[0], "") if err != nil { return "", err } keyType := g.goType(mapTypes[0]) mapType := g.goType(g.exprType(e.Args[0])) return fmt.Sprintf("func(values %s) []%s { result := make([]%s, 0, len(values)); for key := range values { result = append(result, key) }; return result }(%s)", mapType, keyType, keyType, value), nil } } if selector, ok := e.Callee.(SelectorExpr); ok { switch selector.Name { case "read": if len(e.Args) != 0 { return "", fmt.Errorf("read() expects no arguments") } receiver, err := g.expr(selector.Receiver, "") if err != nil { return "", err } return "<-" + receiver, nil case "send": if len(e.Args) != 1 { return "", fmt.Errorf("send(value) expects exactly one argument") } receiver, err := g.expr(selector.Receiver, "") if err != nil { return "", err } arg, err := g.expr(e.Args[0], "") if err != nil { return "", err } return receiver + " <- " + arg, nil } } args := make([]string, 0, len(e.Args)) argTypes := g.callArgTypes(e) for i, arg := range e.Args { argType := "" if i < len(argTypes) { argType = argTypes[i] } if _, isNull := arg.(NullExpr); isNull && g.goCallArgumentIsNilable(e, i) { argType = "" } value, err := g.expr(arg, argType) if err != nil { return "", err } if spread, ok := arg.(UnaryExpr); ok && spread.Op == "*" { inner, err := g.expr(spread.Value, "") if err != nil { return "", err } value = inner + "..." } args = append(args, value) } if ident, ok := e.Callee.(IdentExpr); ok && ident.Name == "println" { return fmt.Sprintf("fmt.Println(%s)", strings.Join(args, ", ")), nil } if ident, ok := e.Callee.(IdentExpr); ok && ident.Name == "ByteSlice" { if len(args) != 1 { return "", fmt.Errorf("ByteSlice expects exactly one argument") } if g.exprType(e.Args[0]) == "Int" { return "make([]byte, " + args[0] + ")", nil } return "[]byte(" + args[0] + ")", nil } if ident, ok := e.Callee.(IdentExpr); ok && ident.Name == "runCatching" { if len(args) != 1 { return "", fmt.Errorf("runCatching expects exactly one argument") } return fmt.Sprintf("gotlinRunCatching(%s)", args[0]), nil } if ident, ok := e.Callee.(IdentExpr); ok { if _, ok := g.semantic.Classes[ident.Name]; ok && (resolvedCall == nil || resolvedCall.Meaning == ClassConstructionExpr) { return fmt.Sprintf("New%s%s(%s)", ident.Name, g.renderCallTypeArguments(e.TypeArgs), strings.Join(args, ", ")), nil } } if resolvedCall != nil { if call, ok := resolvedCall.Node.(HIRGotlinCall); ok && call.Target != nil && call.Target.Effects.Has(CoroutineEffect) { if g.currentCoroutineScope == "" { return "", fmt.Errorf("contextual function %s requires a coroutine scope", call.Target.Name) } args = append([]string{g.currentCoroutineScope}, args...) } if call, ok := resolvedCall.Node.(HIRGoCall); ok && call.InjectContext { if g.currentCoroutineScope == "" { return "", fmt.Errorf("Go function requires an ambient coroutine context") } args = append([]string{g.currentCoroutineScope + ".Context()"}, args...) } } callee, err := g.expr(e.Callee, "") if err != nil { return "", err } if len(e.TypeArgs) > 0 { callee += g.renderCallTypeArguments(e.TypeArgs) } call := fmt.Sprintf("%s(%s)", callee, strings.Join(args, ", ")) if result, ok := g.goErrorResult(e, call, expectedType); ok { return result, nil } return call, nil case SelectorExpr: resolvedSelector := exprMeta(e) if resolvedSelector == nil || resolvedSelector.Meaning == EnumReferenceExpr { if receiver, ok := e.Receiver.(IdentExpr); ok { if decl, ok := g.semantic.Enums[receiver.Name]; ok { variant := enumVariant(decl, e.Name) if variant == nil { return "", fmt.Errorf("unknown variant %s.%s", receiver.Name, e.Name) } if len(variant.PayloadTypes) != 0 { return "", fmt.Errorf("variant %s.%s requires %d values", receiver.Name, e.Name, len(variant.PayloadTypes)) } return g.expr(EnumVariantExpr{EnumName: receiver.Name, VariantName: e.Name}, expectedType) } } } if receiverType := g.exprType(e.Receiver); strings.HasSuffix(receiverType, "?") { return "", fmt.Errorf("nullable receiver %s requires ?. or !! before .%s", receiverType, e.Name) } receiver, err := g.expr(e.Receiver, "") if err != nil { return "", err } if call, ok := e.Receiver.(CallExpr); ok && len(call.NamedArgs) > 0 { receiver = "(" + receiver + ")" } name := e.Name if g.exprType(e.Receiver) == "GotlinSQLQuery" { if name == "sql" { name = "SQL" } if name == "args" { name = "Args" } } if ident, ok := e.Receiver.(IdentExpr); ok && g.semantic.Imports[ident.Name] { name = exportedGoName(name) } else if class, ok := g.classForType(g.exprType(e.Receiver)); ok { resolvedType, _ := g.semantic.ResolveType(g.exprType(e.Receiver)) if isImportedClassType(resolvedType) { name = exportedGoName(name) } else { for _, field := range class.Fields { if field.Name == name && class.Data && !field.Private { name = exportedGoName(name) break } } } } else if ident, ok := e.Receiver.(IdentExpr); ok && (ident.Name == "this" || ident.Name == "self") && g.currentClass != nil { for _, field := range g.currentClass.Fields { if field.Name == name && g.currentClass.Data && !field.Private { name = exportedGoName(name) break } } } else if typ := g.exprType(e.Receiver); typ == "" || strings.Contains(typ, ".") { name = exportedGoName(name) } return fmt.Sprintf("%s.%s", receiver, name), nil case SafeSelectorExpr: receiverType := g.exprType(e.Receiver) if !strings.HasSuffix(receiverType, "?") { return "", fmt.Errorf("safe access requires a nullable receiver, got %s", receiverType) } innerType := strings.TrimSuffix(receiverType, "?") class, ok := g.classForType(innerType) if !ok { return "", fmt.Errorf("safe access receiver %s is not a Gotlin class", receiverType) } field, ok := classFieldByName(class, e.Name) if !ok { return "", fmt.Errorf("unknown field %s.%s", class.Name, e.Name) } receiver, err := g.expr(e.Receiver, receiverType) if err != nil { return "", err } fieldName := mappingFieldName(class, field) _, fieldIsClass := g.classForType(field.Type) if fieldIsClass || strings.HasPrefix(field.Type, "*") || strings.HasSuffix(field.Type, "?") { return fmt.Sprintf("func(value %s) %s { if value == nil { return nil }; return value.%s }(%s)", g.goType(receiverType), g.goType(nullableType(field.Type)), fieldName, receiver), nil } return fmt.Sprintf("func(value %s) %s { if value == nil { return nil }; result := value.%s; return &result }(%s)", g.goType(receiverType), g.goType(nullableType(field.Type)), fieldName, receiver), nil case IndexExpr: receiver, err := g.expr(e.Receiver, "") if err != nil { return "", err } index, err := g.expr(e.Index, "") if err != nil { return "", err } return receiver + "[" + index + "]", nil case MatchExpr: return g.valueMatch(e, expectedType) case EnumVariantExpr: if e.EnumName == "Result" { base, args, ok := parseGenericType(expectedType) if !ok || base != "Result" || len(args) != 2 { return "", fmt.Errorf("Result.%s requires an expected Result type", e.VariantName) } if e.VariantName == "Ok" { if len(e.Values) != 1 { return "", fmt.Errorf("Result.Ok expects one value") } value, err := g.expr(e.Values[0], args[0]) if err != nil { return "", err } return fmt.Sprintf("GotlinResult[%s]{Value: %s}", g.goType(args[0]), value), nil } if e.VariantName == "Err" { if len(e.Values) != 1 { return "", fmt.Errorf("Result.Err expects one error") } value, err := g.expr(e.Values[0], "Error") if err != nil { return "", err } return fmt.Sprintf("GotlinResult[%s]{Err: %s}", g.goType(args[0]), value), nil } return "", fmt.Errorf("unknown Result variant %s", e.VariantName) } decl, ok := g.semantic.Enums[e.EnumName] if !ok { return "", fmt.Errorf("unknown enum %s", e.EnumName) } var variant *EnumVariant for i := range decl.Variants { if decl.Variants[i].Name == e.VariantName { variant = &decl.Variants[i] break } } if variant == nil { return "", fmt.Errorf("unknown variant %s.%s", e.EnumName, e.VariantName) } if len(e.Values) != len(variant.PayloadTypes) { return "", fmt.Errorf("variant %s.%s expects %d values", e.EnumName, e.VariantName, len(variant.PayloadTypes)) } if enumIsString(decl) { return e.EnumName + e.VariantName, nil } fields := make([]string, 0, len(e.Values)) for i, valueExpr := range e.Values { value, err := g.expr(valueExpr, variant.PayloadTypes[i]) if err != nil { return "", err } fields = append(fields, fmt.Sprintf("Value%d: %s", i, value)) } return "&" + e.EnumName + e.VariantName + "{" + strings.Join(fields, ", ") + "}", nil case LambdaExpr: return g.lambda(e, expectedType) default: return "", fmt.Errorf("unsupported expression %T", expr) } } func (g *goGenerator) valueMatch(match MatchExpr, expectedType string) (string, error) { enumName := strings.TrimPrefix(g.exprType(match.Value), "*") if enumName == "" && len(match.Cases) > 0 { enumName = match.Cases[0].EnumName } decl := g.semantic.Enums[enumName] resultType := expectedType if resultType == "" || resultType == "Any" { resultType = g.exprType(match) } value, err := g.expr(match.Value, enumName) if err != nil { return "", err } g.matchCounter++ matchName := fmt.Sprintf("gotlinMatch%d", g.matchCounter) var out strings.Builder out.WriteString("func() ") out.WriteString(g.goType(resultType)) out.WriteString(" { ") if enumIsString(decl) { out.WriteString("switch ") out.WriteString(value) out.WriteString(" { ") for _, c := range match.Cases { arm, err := g.expr(c.Value, resultType) if err != nil { return "", err } out.WriteString("case ") out.WriteString(enumName + c.VariantName) out.WriteString(": return ") out.WriteString(arm) out.WriteString("; ") } out.WriteString("}; ") } else { out.WriteString("switch ") out.WriteString(matchName) out.WriteString(" := ") out.WriteString(value) out.WriteString(".(type) { ") for _, c := range match.Cases { variant := enumVariant(decl, c.VariantName) g.pushScope() out.WriteString("case *") out.WriteString(enumName + c.VariantName) out.WriteString(": ") for i, binding := range c.Bindings { g.defineType(binding, variant.PayloadTypes[i]) out.WriteString(binding) out.WriteString(" := ") out.WriteString(matchName) out.WriteString(fmt.Sprintf(".Value%d; ", i)) } arm, err := g.expr(c.Value, resultType) g.popScope() if err != nil { return "", err } out.WriteString("return ") out.WriteString(arm) out.WriteString("; ") } out.WriteString("}; ") } out.WriteString(`panic("unreachable exhaustive match") }()`) return out.String(), nil } func (g *goGenerator) emitJSONDecodeSupport() { g.line("func gotlinJSONDecode[T any](body []byte) GotlinResult[T] {") g.indentLevel++ g.line("var value T") g.line("if err := json.Unmarshal(body, &value); err != nil { return GotlinResult[T]{Err: err} }") g.line("return GotlinResult[T]{Value: value}") g.indentLevel-- g.line("}") } func (g *goGenerator) emitSQLSupport(program *Program, execution bool) { g.line("type GotlinSQLQuery struct {") g.indentLevel++ g.line("SQL string") g.line("Args []any") g.indentLevel-- g.line("}") if g.needsSQLBulk { g.line("") g.line("func gotlinSQLBulkInsert[T any](rows []*T, table string, columns []string, values func(*T) []any) GotlinSQLQuery {") g.indentLevel++ g.line("if len(rows) == 0 { panic(\"bulk insert requires at least one row\") }") g.line("var query strings.Builder") g.line("query.WriteString(\"INSERT INTO \" + table + \" (\" + strings.Join(columns, \", \") + \") VALUES \")") g.line("args := make([]any, 0, len(rows)*len(columns))") g.line("placeholder := 1") g.line("for rowIndex, row := range rows {") g.indentLevel++ g.line("if rowIndex > 0 { query.WriteString(\", \") }") g.line("query.WriteString(\"(\")") g.line("for columnIndex := range columns { if columnIndex > 0 { query.WriteString(\", \") }; query.WriteString(\"$\" + strconv.Itoa(placeholder)); placeholder++ }") g.line("query.WriteString(\")\")") g.line("args = append(args, values(row)...)") g.indentLevel-- g.line("}") g.line("return GotlinSQLQuery{SQL: query.String(), Args: args}") g.indentLevel-- g.line("}") } if !execution { return } g.line("") g.line("type gotlinSQLQuerier interface {") g.indentLevel++ g.line("Query(" + g.sqlContextAlias + ".Context, string, ...any) (" + g.sqlPGXAlias + ".Rows, error)") g.indentLevel-- g.line("}") g.line("") g.line("type gotlinSQLRow interface {") g.indentLevel++ g.line("Scan(...any) error") g.indentLevel-- g.line("}") g.line("") g.line("type gotlinSQLScanner[T any] func(gotlinSQLRow) (*T, error)") g.line("") g.line("type gotlinSQLError string") g.line("") g.line("func (err gotlinSQLError) Error() string {") g.indentLevel++ g.line("return string(err)") g.indentLevel-- g.line("}") g.line("") g.line("func gotlinSQLFetch[T any](pool gotlinSQLQuerier, ctx " + g.sqlContextAlias + ".Context, query GotlinSQLQuery, scan gotlinSQLScanner[T]) GotlinResult[[]*T] {") g.indentLevel++ g.line("rows, err := pool.Query(ctx, query.SQL, query.Args...)") g.line("if err != nil { return GotlinResult[[]*T]{Err: err} }") g.line("defer rows.Close()") g.line("values := make([]*T, 0)") g.line("for rows.Next() {") g.indentLevel++ g.line("value, err := scan(rows)") g.line("if err != nil { return GotlinResult[[]*T]{Err: err} }") g.line("values = append(values, value)") g.indentLevel-- g.line("}") g.line("if err := rows.Err(); err != nil { return GotlinResult[[]*T]{Err: err} }") g.line("return GotlinResult[[]*T]{Value: values}") g.indentLevel-- g.line("}") g.line("") g.line("func gotlinSQLSingle[T any](pool gotlinSQLQuerier, ctx " + g.sqlContextAlias + ".Context, query GotlinSQLQuery, scan gotlinSQLScanner[T]) GotlinResult[*T] {") g.indentLevel++ g.line("rows, err := pool.Query(ctx, query.SQL, query.Args...)") g.line("if err != nil { return GotlinResult[*T]{Err: err} }") g.line("defer rows.Close()") g.line("if !rows.Next() {") g.indentLevel++ g.line("if err := rows.Err(); err != nil { return GotlinResult[*T]{Err: err} }") g.line(`return GotlinResult[*T]{Err: gotlinSQLError("SQL single() expected exactly one row, got zero")}`) g.indentLevel-- g.line("}") g.line("value, err := scan(rows)") g.line("if err != nil { return GotlinResult[*T]{Err: err} }") g.line("if rows.Next() {") g.indentLevel++ g.line(`return GotlinResult[*T]{Err: gotlinSQLError("SQL single() expected exactly one row, got more than one")}`) g.indentLevel-- g.line("}") g.line("if err := rows.Err(); err != nil { return GotlinResult[*T]{Err: err} }") g.line("return GotlinResult[*T]{Value: value}") g.indentLevel-- g.line("}") g.line("") g.line("type GotlinSQLIterator[T any] struct {") g.indentLevel++ g.line("rows " + g.sqlPGXAlias + ".Rows") g.line("scan gotlinSQLScanner[T]") g.line("current *T") g.line("streamErr error") g.indentLevel-- g.line("}") g.line("") g.line("func gotlinSQLIterate[T any](pool gotlinSQLQuerier, ctx " + g.sqlContextAlias + ".Context, query GotlinSQLQuery, scan gotlinSQLScanner[T]) GotlinResult[*GotlinSQLIterator[T]] {") g.indentLevel++ g.line("rows, err := pool.Query(ctx, query.SQL, query.Args...)") g.line("if err != nil { return GotlinResult[*GotlinSQLIterator[T]]{Err: err} }") g.line("return GotlinResult[*GotlinSQLIterator[T]]{Value: &GotlinSQLIterator[T]{rows: rows, scan: scan}}") g.indentLevel-- g.line("}") g.line("") g.line("func (iterator *GotlinSQLIterator[T]) next() bool {") g.indentLevel++ g.line("iterator.current = nil") g.line("if iterator.rows == nil || !iterator.rows.Next() {") g.indentLevel++ g.line("if iterator.rows != nil {") g.indentLevel++ g.line("iterator.streamErr = iterator.rows.Err()") g.indentLevel-- g.line("}") g.line("return false") g.indentLevel-- g.line("}") g.line("value, err := iterator.scan(iterator.rows)") g.line("if err != nil {") g.indentLevel++ g.line("iterator.streamErr = err") g.line("return false") g.indentLevel-- g.line("}") g.line("iterator.current = value") g.line("return true") g.indentLevel-- g.line("}") g.line("") g.line("func (iterator *GotlinSQLIterator[T]) value() *T {") g.indentLevel++ g.line("if iterator.current == nil {") g.indentLevel++ g.line(`panic("SQL iterator value() requires a successful next()")`) g.indentLevel-- g.line("}") g.line("return iterator.current") g.indentLevel-- g.line("}") g.line("") g.line("func (iterator *GotlinSQLIterator[T]) close() {") g.indentLevel++ g.line("if iterator.rows != nil {") g.indentLevel++ g.line("iterator.rows.Close()") g.indentLevel-- g.line("}") g.indentLevel-- g.line("}") g.line("") g.line("func (iterator *GotlinSQLIterator[T]) err() error {") g.indentLevel++ g.line("if iterator.streamErr != nil {") g.indentLevel++ g.line("return iterator.streamErr") g.indentLevel-- g.line("}") g.line("if iterator.rows != nil {") g.indentLevel++ g.line("return iterator.rows.Err()") g.indentLevel-- g.line("}") g.line("return nil") g.indentLevel-- g.line("}") for _, class := range program.Classes { if !class.Data { continue } g.line("") g.line("func gotlinSQLScan" + class.Name + "(row gotlinSQLRow) (*" + class.Name + ", error) {") g.indentLevel++ g.line("value := new(" + class.Name + ")") destinations := make([]string, 0, len(class.Fields)) for _, field := range class.Fields { name := field.Name if !field.Private { name = exportedGoName(name) } destinations = append(destinations, "&value."+name) } g.line("err := row.Scan(" + strings.Join(destinations, ", ") + ")") g.line("return value, err") g.indentLevel-- g.line("}") } } func exportedGoName(name string) string { if name == "" || name[0] < 'a' || name[0] > 'z' { return name } return strings.ToUpper(name[:1]) + name[1:] } func exportedGoFieldName(name string) string { name = exportedGoName(name) for _, pair := range [][2]string{{"Jwks", "JWKS"}, {"Jwt", "JWT"}, {"Url", "URL"}, {"Api", "API"}, {"Sql", "SQL"}, {"Id", "ID"}} { name = strings.ReplaceAll(name, pair[0], pair[1]) } return name } func snakeCase(name string) string { var b strings.Builder for i, r := range name { if i > 0 && r >= 'A' && r <= 'Z' { b.WriteByte('_') } b.WriteRune(rune(strings.ToLower(string(r))[0])) } return b.String() } func jsonFieldName(name, policy string) string { switch policy { case "", "snakeCase": return snakeCase(name) case "camelCase": if name == "" { return name } return strings.ToLower(name[:1]) + name[1:] case "pascalCase": return exportedGoName(name) case "kebabCase": return strings.ReplaceAll(snakeCase(name), "_", "-") default: return snakeCase(name) } } func (g *goGenerator) listLiteral(args []Expr, typeArgs []string, expectedType string) (string, error) { elemType := "" if len(typeArgs) > 0 { if len(typeArgs) != 1 { return "", fmt.Errorf("listOf expects exactly one type argument") } elemType = typeArgs[0] } if base, genericArgs, ok := parseGenericType(expectedType); ok && (base == "List" || base == "MutableList") && len(genericArgs) == 1 { if elemType == "" { elemType = genericArgs[0] } } goElemType := "any" if elemType != "" { goElemType = g.goType(elemType) } values := make([]string, 0, len(args)) for _, arg := range args { value, err := g.expr(arg, elemType) if err != nil { return "", err } values = append(values, value) } return "[]" + goElemType + "{" + strings.Join(values, ", ") + "}", nil } func (g *goGenerator) mapLiteral(args []Expr, typeArgs []string, expectedType string) (string, error) { if len(args)%2 != 0 { return "", fmt.Errorf("mapOf expects an even number of arguments (key/value pairs)") } keyType := "" valType := "" if len(typeArgs) > 0 { if len(typeArgs) != 2 { return "", fmt.Errorf("mapOf expects exactly two type arguments") } keyType = typeArgs[0] valType = typeArgs[1] } if base, genericArgs, ok := parseGenericType(expectedType); ok && (base == "Map" || base == "MutableMap") && len(genericArgs) == 2 { if keyType == "" { keyType = genericArgs[0] } if valType == "" { valType = genericArgs[1] } } goKeyType := "any" goValType := "any" if keyType != "" { goKeyType = g.goType(keyType) } if valType != "" { goValType = g.goType(valType) } var entries []string for i := 0; i < len(args); i += 2 { key, err := g.expr(args[i], keyType) if err != nil { return "", err } val, err := g.expr(args[i+1], valType) if err != nil { return "", err } entries = append(entries, key+": "+val) } return "map[" + goKeyType + "]" + goValType + "{" + strings.Join(entries, ", ") + "}", nil } func (g *goGenerator) channelMakeExpr(args []Expr, typeArgs []string, expectedType string) (string, error) { elemType := "" if len(typeArgs) > 0 { if len(typeArgs) != 1 { return "", fmt.Errorf("Channel expects exactly one type argument") } elemType = typeArgs[0] } if elemType == "" { if base, genericArgs, ok := parseGenericType(expectedType); ok && base == "Channel" && len(genericArgs) == 1 { elemType = genericArgs[0] } } goElemType := "any" if elemType != "" { goElemType = g.goType(elemType) } if len(args) > 1 { return "", fmt.Errorf("Channel() expects at most one capacity argument") } if len(args) == 1 { capacity, err := g.expr(args[0], "Int") if err != nil { return "", err } return "make(chan " + goElemType + ", " + capacity + ")", nil } return "make(chan " + goElemType + ")", nil } func (g *goGenerator) wrapExpr(expr Expr, expectedType string) (string, error) { switch expr.(type) { case BinaryExpr: value, err := g.expr(expr, expectedType) if err != nil { return "", err } return "(" + value + ")", nil default: return g.expr(expr, expectedType) } } func (g *goGenerator) line(text string) { g.writeIndent() g.write(text) g.write("\n") } func (g *goGenerator) writeIndent() { g.write(strings.Repeat("\t", g.indentLevel)) } func (g *goGenerator) write(text string) { g.buf.WriteString(text) } func (g *goGenerator) goType(name string) string { return g.semantic.GoType(name) } func nullableType(name string) string { if strings.HasSuffix(name, "?") { return name } return name + "?" } func nullableCondition(expr Expr) (name string, thenNonNull bool, elseNonNull bool) { binary, ok := expr.(BinaryExpr) if !ok || (binary.Op != "==" && binary.Op != "!=") { return "", false, false } ident, leftIdent := binary.Left.(IdentExpr) _, rightNull := binary.Right.(NullExpr) if !leftIdent || !rightNull { ident, leftIdent = binary.Right.(IdentExpr) _, rightNull = binary.Left.(NullExpr) } if !leftIdent || !rightNull { return "", false, false } if binary.Op == "!=" { return ident.Name, true, false } return ident.Name, false, true } func blockAlwaysTerminates(stmts []Stmt) bool { if len(stmts) == 0 { return false } switch last := stmts[len(stmts)-1].(type) { case ReturnStmt, ThrowStmt: return true case IfStmt: return len(last.Else) > 0 && blockAlwaysTerminates(last.Then) && blockAlwaysTerminates(last.Else) default: return false } } func (g *goGenerator) goReturnType(name string) string { return g.goType(name) } func usesPrintln(stmts []Stmt) bool { for _, stmt := range stmts { switch s := stmt.(type) { case ExprStmt: if call, ok := s.Value.(CallExpr); ok && isBuiltinPrintln(call.Callee) { return true } if exprUsesPrintln(s.Value) { return true } case VarDecl: if exprUsesPrintln(s.Value) { return true } case AssignStmt: if exprUsesPrintln(s.Value) { return true } case AddAssignStmt: if exprUsesPrintln(s.Value) { return true } case MultiAssignStmt: if exprUsesPrintln(s.Value) { return true } case MultiVarDecl: if exprUsesPrintln(s.Value) { return true } case ReturnStmt: if s.Value != nil && exprUsesPrintln(s.Value) { return true } case ThrowStmt: if exprUsesPrintln(s.Value) { return true } case DeferStmt: if exprUsesPrintln(s.Value) { return true } case IfStmt: if exprUsesPrintln(s.Cond) || usesPrintln(s.Then) || usesPrintln(s.Else) { return true } case WhileStmt: if exprUsesPrintln(s.Cond) || usesPrintln(s.Body) { return true } case ForEachStmt: if exprUsesPrintln(s.Source) || usesPrintln(s.Body) { return true } case MatchStmt: if exprUsesPrintln(s.Value) { return true } for _, c := range s.Cases { if usesPrintln(c.Body) { return true } } case TryCatchStmt: if usesPrintln(s.TryBody) || usesPrintln(s.CatchBody) { return true } } } return false } func usesRunCatching(stmts []Stmt) bool { for _, stmt := range stmts { switch s := stmt.(type) { case VarDecl: if exprUsesRunCatching(s.Value) { return true } case MultiVarDecl: if exprUsesRunCatching(s.Value) { return true } case AssignStmt: if exprUsesRunCatching(s.Value) { return true } case AddAssignStmt: if exprUsesRunCatching(s.Value) { return true } case MultiAssignStmt: if exprUsesRunCatching(s.Value) { return true } case ReturnStmt: if s.Value != nil && exprUsesRunCatching(s.Value) { return true } case ThrowStmt: if exprUsesRunCatching(s.Value) { return true } case ExprStmt: if exprUsesRunCatching(s.Value) { return true } case IfStmt: if exprUsesRunCatching(s.Cond) || usesRunCatching(s.Then) || usesRunCatching(s.Else) { return true } case WhileStmt: if exprUsesRunCatching(s.Cond) || usesRunCatching(s.Body) { return true } case TryCatchStmt: if usesRunCatching(s.TryBody) || usesRunCatching(s.CatchBody) { return true } } } return false } func usesGoUnwrap(stmts []Stmt) bool { for _, stmt := range stmts { switch s := stmt.(type) { case VarDecl: if _, ok := s.Value.(CallExpr); ok { return true } case AssignStmt: if _, ok := s.Value.(CallExpr); ok { return true } case AddAssignStmt: if _, ok := s.Value.(CallExpr); ok { return true } case ReturnStmt: if s.Value != nil { if _, ok := s.Value.(CallExpr); ok { return true } } case IfStmt: if usesGoUnwrap(s.Then) || usesGoUnwrap(s.Else) { return true } case WhileStmt: if usesGoUnwrap(s.Body) { return true } case TryCatchStmt: if usesGoUnwrap(s.TryBody) || usesGoUnwrap(s.CatchBody) { return true } } } return false } func (g *goGenerator) lambda(lambda LambdaExpr, expectedType string) (string, error) { params := lambda.Params returnType := "" paramTypes := []string(nil) if lambda.ImplicitIt { expectedReturnType := "" ok := false paramTypes, expectedReturnType, ok = parseFunctionType(expectedType) if !ok { return "", fmt.Errorf("lambda with implicit it requires a function type context") } if len(paramTypes) == 1 { params = []Param{{Name: "it", Type: paramTypes[0]}} } else if len(paramTypes) == 0 { params = nil } else { params = make([]Param, 0, len(paramTypes)) for _, typ := range paramTypes { params = append(params, Param{Name: "_", Type: typ}) } } returnType = expectedReturnType } else if parsedParamTypes, expectedReturnType, ok := parseFunctionType(expectedType); ok && len(parsedParamTypes) == len(params) { paramTypes = parsedParamTypes returnType = expectedReturnType } if returnType == "" && lambdaHasValueReturn(lambda.Body) { return "", fmt.Errorf("lambda with value return requires a function type context") } for i := range params { if params[i].Type == "" { if i >= len(paramTypes) || paramTypes[i] == "" { return "", fmt.Errorf("lambda parameter %q requires a type or typed call context", params[i].Name) } params[i].Type = paramTypes[i] } } var b strings.Builder b.WriteString("func(") for i, param := range params { if i > 0 { b.WriteString(", ") } b.WriteString(param.Name) b.WriteString(" ") b.WriteString(g.goType(param.Type)) } b.WriteString(")") if goRet := g.goType(returnType); goRet != "" { b.WriteString(" ") b.WriteString(goRet) } b.WriteString(" {\n") sub := goGenerator{ indentLevel: 1, needsFmt: g.needsFmt, semantic: g.semantic, currentFunc: FunctionDecl{ReturnType: returnType}, currentClass: g.currentClass, } if g.currentClass != nil { classCopy := *g.currentClass sub.currentClass = &classCopy } sub.scopes = g.cloneScopes() sub.semanticScope = g.semanticScope sub.pushScope() if lambda.ImplicitIt { if len(params) == 1 { sub.defineType("it", params[0].Type) } else { sub.define("it") } } for _, param := range params { sub.defineType(param.Name, param.Type) } if returnType != "" && returnType != "Unit" && !lambdaHasValueReturn(lambda.Body) { if len(lambda.Body) == 0 { return "", fmt.Errorf("value lambda requires a final expression") } last, ok := lambda.Body[len(lambda.Body)-1].(ExprStmt) if !ok { return "", fmt.Errorf("value lambda requires a final expression or explicit return") } if err := sub.block(lambda.Body[:len(lambda.Body)-1]); err != nil { return "", err } value, err := sub.expr(last.Value, returnType) if err != nil { return "", err } sub.line("return " + value) } else if err := sub.block(lambda.Body); err != nil { return "", err } b.Write(sub.buf.Bytes()) b.WriteString("}") return b.String(), nil } func (g *goGenerator) callArgTypes(call CallExpr) []string { argCount := len(call.Args) if resolved := exprMeta(call); resolved != nil { switch node := resolved.Node.(type) { case HIRGoCall: if len(node.Params) > 0 { params := node.Params if node.InjectContext { params = params[1:] } return semanticParamStrings(params, argCount, node.Variadic) } case HIRGotlinCall: if node.Target != nil { if function, ok := node.Target.Type.(FunctionType); ok { bindings := map[string]Type{} for index, argument := range call.TypeArgs { if index < len(function.TypeParams) { typ, _ := g.semantic.ResolveType(argument) bindings[function.TypeParams[index]] = typ } } if selector, ok := call.Callee.(SelectorExpr); ok { if _, receiverBindings := classInstance(ResolvedType(selector.Receiver)); receiverBindings != nil { for name, typ := range receiverBindings { bindings[name] = typ } } } for index, argument := range call.Args { if index < len(function.Params) { inferTypeBindings(function.Params[index], ResolvedType(argument), bindings) } } params := make([]Type, len(function.Params)) for index, param := range function.Params { params[index] = substituteType(param, bindings) } return semanticParamStrings(params, argCount, function.Variadic) } } } } callee := call.Callee ident, ok := callee.(IdentExpr) if ok { if ident.Name == "runCatching" { return []string{"() -> Unit"} } fn, ok := g.semantic.Functions[ident.Name] if ok { argTypes := make([]string, 0, min(argCount, len(fn.Params))) for i := 0; i < argCount && i < len(fn.Params); i++ { argTypes = append(argTypes, fn.Params[i].Type) } return argTypes } class, ok := g.semantic.Classes[ident.Name] if ok { argTypes := make([]string, 0, min(argCount, len(class.Fields))) for i := 0; i < argCount && i < len(class.Fields); i++ { argTypes = append(argTypes, class.Fields[i].Type) } return argTypes } } selector, ok := selectorPath(callee) if !ok { return nil } switch selector { case "http.HandleFunc": return []string{"String", "(http.ResponseWriter, *http.Request) -> Unit"} case "sort.Slice", "sort.slice": return []string{"Any", "(Int, Int) -> Boolean"} default: return nil } } func semanticParamStrings(params []Type, argCount int, variadic bool) []string { result := make([]string, 0, argCount) for index := 0; index < argCount; index++ { paramIndex := index if paramIndex >= len(params) { if !variadic || len(params) == 0 { break } paramIndex = len(params) - 1 } param := params[paramIndex] if variadic && paramIndex == len(params)-1 { if list, ok := param.(GenericType); ok && (list.Base.String() == "List" || list.Base.String() == "MutableList") && len(list.Args) == 1 { param = list.Args[0] } } result = append(result, param.String()) } return result } func (g *goGenerator) goCallArgumentIsNilable(call CallExpr, index int) bool { resolved := exprMeta(call) if resolved == nil { return false } goCall, ok := resolved.Node.(HIRGoCall) if !ok { return false } params := goCall.Params if goCall.InjectContext && len(params) > 0 { params = params[1:] } if len(params) == 0 { return false } paramIndex := index if paramIndex >= len(params) { if !goCall.Variadic { return false } paramIndex = len(params) - 1 } param := params[paramIndex] if goCall.Variadic && paramIndex == len(params)-1 { if list, ok := param.(GenericType); ok && len(list.Args) == 1 { param = list.Args[0] } } switch param.(type) { case GoInterfaceType, GoPointerType, NullableType: return true } return false } func nameUsedInStmts(name string, stmts []Stmt) bool { return nameUsedInStmtsWithShadow(name, stmts, false) } func nameUsedInStmtsWithShadow(name string, stmts []Stmt, shadowed bool) bool { localShadowed := shadowed for _, stmt := range stmts { switch s := stmt.(type) { case VarDecl: if exprUsesName(s.Value, name, localShadowed) { return true } if s.Name == name { localShadowed = true } case MultiVarDecl: if exprUsesName(s.Value, name, localShadowed) { return true } for _, declared := range s.Names { if declared == name { localShadowed = true break } } case AssignStmt: if !localShadowed && s.Name == name { return true } if exprUsesName(s.Value, name, localShadowed) { return true } case AddAssignStmt: if !localShadowed && s.Name == name { return true } if exprUsesName(s.Value, name, localShadowed) { return true } case MultiAssignStmt: if !localShadowed { for _, assigned := range s.Names { if assigned == name { return true } } } if exprUsesName(s.Value, name, localShadowed) { return true } case ReturnStmt: if s.Value != nil && exprUsesName(s.Value, name, localShadowed) { return true } case ThrowStmt: if exprUsesName(s.Value, name, localShadowed) { return true } case DeferStmt: if exprUsesName(s.Value, name, localShadowed) { return true } case ExprStmt: if exprUsesName(s.Value, name, localShadowed) { return true } case IfStmt: if exprUsesName(s.Cond, name, localShadowed) { return true } if nameUsedInStmtsWithShadow(name, s.Then, localShadowed) { return true } if nameUsedInStmtsWithShadow(name, s.Else, localShadowed) { return true } case WhileStmt: if exprUsesName(s.Cond, name, localShadowed) { return true } if nameUsedInStmtsWithShadow(name, s.Body, localShadowed) { return true } case ForEachStmt: if exprUsesName(s.Source, name, localShadowed) { return true } if nameUsedInStmtsWithShadow(name, s.Body, localShadowed || s.Name == name) { return true } case MatchStmt: if exprUsesName(s.Value, name, localShadowed) { return true } for _, c := range s.Cases { caseShadowed := localShadowed for _, binding := range c.Bindings { if binding == name { caseShadowed = true } } if nameUsedInStmtsWithShadow(name, c.Body, caseShadowed) { return true } } case TryCatchStmt: if nameUsedInStmtsWithShadow(name, s.TryBody, localShadowed) { return true } catchShadowed := localShadowed || s.CatchName == name if nameUsedInStmtsWithShadow(name, s.CatchBody, catchShadowed) { return true } } } return false } func exprUsesName(expr Expr, name string, shadowed bool) bool { switch e := expr.(type) { case IdentExpr: return !shadowed && e.Name == name case UnaryExpr: return exprUsesName(e.Value, name, shadowed) case NonNullExpr: return exprUsesName(e.Value, name, shadowed) case TryExpr: return exprUsesName(e.Value, name, shadowed) case BinaryExpr: return exprUsesName(e.Left, name, shadowed) || exprUsesName(e.Right, name, shadowed) case CallExpr: if exprUsesName(e.Callee, name, shadowed) { return true } for _, arg := range e.Args { if exprUsesName(arg, name, shadowed) { return true } } return false case SelectorExpr: return exprUsesName(e.Receiver, name, shadowed) case IndexExpr: return exprUsesName(e.Receiver, name, shadowed) || exprUsesName(e.Index, name, shadowed) case EnumVariantExpr: for _, value := range e.Values { if exprUsesName(value, name, shadowed) { return true } } return false case MatchExpr: if exprUsesName(e.Value, name, shadowed) { return true } for _, matchCase := range e.Cases { caseShadowed := shadowed for _, binding := range matchCase.Bindings { if binding == name { caseShadowed = true } } if exprUsesName(matchCase.Value, name, caseShadowed) { return true } } return false case LambdaExpr: lambdaShadowed := shadowed if e.ImplicitIt && name == "it" { lambdaShadowed = true } for _, param := range e.Params { if param.Name == name { lambdaShadowed = true break } } return nameUsedInStmtsWithShadow(name, e.Body, lambdaShadowed) default: return false } } func (g *goGenerator) pushScope() { g.scopes = append(g.scopes, map[string]bool{}) g.semanticScope = NewScope(g.semanticScope) } func (g *goGenerator) popScope() { if len(g.scopes) == 0 { return } g.scopes = g.scopes[:len(g.scopes)-1] g.semanticScope = g.semanticScope.Parent } func (g *goGenerator) define(name string) { g.defineType(name, "") } func (g *goGenerator) defineType(name, typ string) { if len(g.scopes) == 0 { g.pushScope() } g.scopes[len(g.scopes)-1][name] = true resolved, err := g.semantic.ResolveType(typ) if err != nil { resolved = UnknownType{} } g.semanticScope.Symbols[name] = &Symbol{Name: name, Kind: VariableSymbol, Type: resolved} } func (g *goGenerator) lookupType(name string) string { if g.semanticScope != nil { if symbol, ok := g.semanticScope.Lookup(name); ok { if isUnknownType(symbol.Type) { return "" } return symbol.Type.String() } } return "" } func (g *goGenerator) exprType(expr Expr) string { if ident, ok := expr.(IdentExpr); ok { if typ := g.lookupType(ident.Name); typ != "" { return typ } } if resolved := exprMeta(expr); resolved != nil && !isUnknownType(resolved.Type) { return resolved.Type.String() } // Generator-created expressions do not pass through semantic analysis. typ := g.semantic.TypeOf(expr, TypeEnvironment{Scope: g.semanticScope, Class: g.currentClassSymbol()}) if isUnknownType(typ) { return "" } return typ.String() } func (g *goGenerator) currentClassSymbol() *ClassSymbol { if g.currentClass == nil { return nil } return g.semantic.ClassInfo[g.currentClass.Name] } func (g *goGenerator) goErrorResult(call CallExpr, rendered, expectedType string) (string, bool) { base, args, ok := parseGenericType(expectedType) resolved := exprMeta(call) external := false if resolved != nil { _, external = resolved.Node.(HIRGoCall) } if !external { external = g.isExternalGoCall(call.Callee) } if !ok || base != "Result" || len(args) != 2 || args[1] != "Error" || !external { return "", false } if actualBase, _, actualIsResult := parseGenericType(g.exprType(call)); actualIsResult && actualBase == "Result" && (resolved == nil || resolved.Meaning != GoCallExpr) { return "", false } valueType := g.goType(args[0]) if args[0] == "Unit" { valueType = "struct{}" return fmt.Sprintf("func() GotlinResult[%s] { err := %s; return GotlinResult[%s]{Err: err} }()", valueType, rendered, valueType), true } return fmt.Sprintf("func() GotlinResult[%s] { value, err := %s; return GotlinResult[%s]{Value: value, Err: err} }()", valueType, rendered, valueType), true } func (g *goGenerator) isExternalGoCall(callee Expr) bool { switch value := callee.(type) { case IdentExpr: if _, ok := g.semantic.Functions[value.Name]; ok { return false } if _, ok := g.semantic.Classes[value.Name]; ok { return false } return true case SelectorExpr: if receiver, ok := value.Receiver.(IdentExpr); ok && g.semantic.Imports[receiver.Name] { return true } if _, ok := g.classForType(g.exprType(value.Receiver)); ok { return false } return true default: return false } } func (g *goGenerator) classForType(typ string) (ClassDecl, bool) { resolved, err := g.semantic.ResolveType(typ) if err != nil { return ClassDecl{}, false } class, _ := classInstance(resolved) if class == nil || class.Decl == nil { return ClassDecl{}, false } return *class.Decl, true } func (g *goGenerator) isDefined(name string) bool { for i := len(g.scopes) - 1; i >= 0; i-- { if g.scopes[i][name] { return true } } return false } func (g *goGenerator) assignTarget(name string) string { if g.currentClass != nil && !g.isDefined(name) { if field, found := classFieldByName(*g.currentClass, name); found { return "self." + mappingFieldName(*g.currentClass, field) } } return name } func (g *goGenerator) cloneScopes() []map[string]bool { dup := make([]map[string]bool, 0, len(g.scopes)) for _, scope := range g.scopes { copyScope := make(map[string]bool, len(scope)) for k, v := range scope { copyScope[k] = v } dup = append(dup, copyScope) } return dup } func (g *goGenerator) renderGoFunctionParams(function FunctionDecl) string { prefix := "" if g.hasCoroutineEffect(function) { prefix = "gotlinScope *GotlinCoroutineScope" } return g.renderGoParamsWithPrefix(function.Params, prefix) } func (g *goGenerator) hasCoroutineEffect(function FunctionDecl) bool { key := function.Name if g.currentClass != nil { key = g.currentClass.Name + "." + function.Name } return g.semantic.FunctionEffects[key].Has(CoroutineEffect) } func (g *goGenerator) functionReturnType(function FunctionDecl) string { if !function.InferReturn { return function.ReturnType } var symbol *Symbol if g.currentClass != nil { symbol = g.semantic.ClassInfo[g.currentClass.Name].Method(function.Name) } else { symbol, _ = g.semantic.Global.Lookup(function.Name) } if symbol != nil { if signature, ok := symbol.Type.(FunctionType); ok { return signature.Result.String() } } return function.ReturnType } func renderGoTypeParameters(params []string) string { if len(params) == 0 { return "" } values := make([]string, len(params)) for index, param := range params { values[index] = param + " any" } return "[" + strings.Join(values, ", ") + "]" } func renderGoTypeArguments(params []string) string { if len(params) == 0 { return "" } return "[" + strings.Join(params, ", ") + "]" } func (g *goGenerator) renderCallTypeArguments(args []string) string { if len(args) == 0 { return "" } values := make([]string, len(args)) for index, argument := range args { values[index] = g.goType(argument) } return "[" + strings.Join(values, ", ") + "]" } func (g *goGenerator) renderGoParamsWithPrefix(params []Param, prefix string) string { var b strings.Builder b.WriteString("(") if prefix != "" { b.WriteString(prefix) if len(params) > 0 { b.WriteString(", ") } } for i, param := range params { if i > 0 { b.WriteString(", ") } b.WriteString(param.Name) b.WriteString(" ") b.WriteString(g.goType(param.Type)) } b.WriteString(")") return b.String() } func (g *goGenerator) renderGoReturnSuffix(returnType string) string { if ret := g.goReturnType(returnType); ret != "" { return " " + ret } return "" } func exprUsesPrintln(expr Expr) bool { switch e := expr.(type) { case CallExpr: if isBuiltinPrintln(e.Callee) { return true } for _, arg := range e.Args { if exprUsesPrintln(arg) { return true } } case UnaryExpr: return exprUsesPrintln(e.Value) case BinaryExpr: return exprUsesPrintln(e.Left) || exprUsesPrintln(e.Right) case SelectorExpr: return exprUsesPrintln(e.Receiver) case MatchExpr: if exprUsesPrintln(e.Value) { return true } for _, matchCase := range e.Cases { if exprUsesPrintln(matchCase.Value) { return true } } case LambdaExpr: return usesPrintln(e.Body) } return false } func exprUsesRunCatching(expr Expr) bool { switch e := expr.(type) { case CallExpr: if ident, ok := e.Callee.(IdentExpr); ok && ident.Name == "runCatching" { return true } if exprUsesRunCatching(e.Callee) { return true } for _, arg := range e.Args { if exprUsesRunCatching(arg) { return true } } case UnaryExpr: return exprUsesRunCatching(e.Value) case BinaryExpr: return exprUsesRunCatching(e.Left) || exprUsesRunCatching(e.Right) case SelectorExpr: return exprUsesRunCatching(e.Receiver) case MatchExpr: if exprUsesRunCatching(e.Value) { return true } for _, matchCase := range e.Cases { if exprUsesRunCatching(matchCase.Value) { return true } } case LambdaExpr: return usesRunCatching(e.Body) } return false } func isBuiltinPrintln(expr Expr) bool { ident, ok := expr.(IdentExpr) return ok && ident.Name == "println" } func collectImports(program *Program, needsFmt bool, needsTime bool, runtimeImports map[string]string) []string { seen := map[string]bool{} runtimePaths := map[string]bool{} var imports []string usedAliases := usedImportAliases(program) if needsFmt { seen[`"fmt"`] = true imports = append(imports, `"fmt"`) } if needsTime { seen[`"time"`] = true imports = append(imports, `"time"`) } for path, alias := range runtimeImports { rendered := alias + ` "` + path + `"` seen[rendered] = true runtimePaths[path] = true imports = append(imports, rendered) } for _, imp := range program.Imports { path := imp.Path goPath := path if !strings.HasPrefix(path, `"`) { goPath = importPathToGoPath(path) path = `"` + goPath + `"` } if runtimePaths[goPath] { continue } rendered := path if imp.Alias != "" { rendered = imp.Alias + " " + path } else if !usedAliases[defaultImportAlias(imp)] { rendered = "_ " + path } if seen[rendered] { continue } seen[rendered] = true imports = append(imports, rendered) } return imports } func runtimeImportAlias(program *Program, goPath, fallback string) string { for _, imp := range program.Imports { path := strings.Trim(imp.Path, `"`) if !strings.Contains(path, "/") { path = importPathToGoPath(path) } if path != goPath { continue } if imp.Alias != "" && imp.Alias != "_" { return imp.Alias } parts := strings.Split(goPath, "/") return parts[len(parts)-1] } return fallback } func goPackageName(packagePath string) string { if packagePath == "" { return "main" } parts := strings.Split(packagePath, ".") return parts[len(parts)-1] } func usedImportAliases(program *Program) map[string]bool { used := map[string]bool{} markType := func(typ string) { for _, alias := range typeImportAliases(typ) { used[alias] = true } } var walkExpr func(expr Expr) var walkStmt func(stmt Stmt) walkExpr = func(expr Expr) { switch e := expr.(type) { case IdentExpr: used[e.Name] = true case UnaryExpr: walkExpr(e.Value) case NonNullExpr: walkExpr(e.Value) case TryExpr: walkExpr(e.Value) case BinaryExpr: walkExpr(e.Left) walkExpr(e.Right) case CallExpr: walkExpr(e.Callee) for _, arg := range e.Args { walkExpr(arg) } for _, arg := range e.NamedArgs { walkExpr(arg.Value) } case SelectorExpr: if alias, ok := selectorRootAlias(e); ok { used[alias] = true } walkExpr(e.Receiver) case SafeSelectorExpr: walkExpr(e.Receiver) case IndexExpr: walkExpr(e.Receiver) walkExpr(e.Index) case EnumVariantExpr: for _, value := range e.Values { walkExpr(value) } case MatchExpr: walkExpr(e.Value) for _, matchCase := range e.Cases { walkExpr(matchCase.Value) } case LambdaExpr: for _, param := range e.Params { markType(param.Type) } for _, stmt := range e.Body { walkStmt(stmt) } } } walkStmt = func(stmt Stmt) { switch s := stmt.(type) { case VarDecl: markType(s.Type) walkExpr(s.Value) case MultiVarDecl: walkExpr(s.Value) case AssignStmt: walkExpr(s.Value) case AddAssignStmt: walkExpr(s.Value) case MultiAssignStmt: walkExpr(s.Value) case ReturnStmt: if s.Value != nil { walkExpr(s.Value) } case ThrowStmt: walkExpr(s.Value) case DeferStmt: walkExpr(s.Value) case ExprStmt: walkExpr(s.Value) case IfStmt: walkExpr(s.Cond) for _, inner := range s.Then { walkStmt(inner) } for _, inner := range s.Else { walkStmt(inner) } case WhileStmt: walkExpr(s.Cond) for _, inner := range s.Body { walkStmt(inner) } case ForEachStmt: walkExpr(s.Source) for _, inner := range s.Body { walkStmt(inner) } case MatchStmt: walkExpr(s.Value) for _, c := range s.Cases { for _, inner := range c.Body { walkStmt(inner) } } case TryCatchStmt: for _, inner := range s.TryBody { walkStmt(inner) } for _, inner := range s.CatchBody { walkStmt(inner) } } } for _, decl := range program.Interfaces { for _, method := range decl.Methods { for _, param := range method.Params { markType(param.Type) } markType(method.ReturnType) } } for _, decl := range program.Classes { for _, field := range decl.Fields { markType(field.Type) } for _, method := range decl.Methods { for _, param := range method.Params { markType(param.Type) } markType(method.ReturnType) if method.ExpressionBody != nil { walkExpr(method.ExpressionBody) } for _, stmt := range method.Body { walkStmt(stmt) } } } for _, fn := range program.Functions { for _, param := range fn.Params { markType(param.Type) } markType(fn.ReturnType) if fn.ExpressionBody != nil { walkExpr(fn.ExpressionBody) } for _, stmt := range fn.Body { walkStmt(stmt) } } for _, embedded := range program.Embeds { markType(embedded.Type) } return used } func selectorRootAlias(expr SelectorExpr) (string, bool) { switch r := expr.Receiver.(type) { case IdentExpr: return r.Name, true case SelectorExpr: return selectorRootAlias(r) default: return "", false } } func typeImportAliases(typ string) []string { if typ == "" { return nil } matches := regexp.MustCompile(`([A-Za-z_][A-Za-z0-9_]*)\.`).FindAllStringSubmatch(typ, -1) out := make([]string, 0, len(matches)) for _, m := range matches { if len(m) == 2 { out = append(out, m[1]) } } return out } func defaultImportAlias(imp ImportDecl) string { if imp.Alias != "" { return imp.Alias } path := imp.Path if strings.HasPrefix(path, `"`) { path = strings.Trim(path, `"`) parts := strings.Split(path, "/") return parts[len(parts)-1] } parts := strings.Split(path, ".") return parts[len(parts)-1] } func importPathToGoPath(path string) string { trimmed := strings.TrimPrefix(path, "go.") parts := strings.Split(trimmed, ".") if len(parts) >= 3 && isDomainTLD(parts[1]) { return parts[0] + "." + parts[1] + "/" + strings.Join(parts[2:], "/") } return strings.ReplaceAll(trimmed, ".", "/") } func isDomainTLD(segment string) bool { switch segment { case "com", "org", "net", "io", "dev", "app", "ai": return true default: return false } } func selectorPath(expr Expr) (string, bool) { switch e := expr.(type) { case IdentExpr: return e.Name, true case SelectorExpr: left, ok := selectorPath(e.Receiver) if !ok { return "", false } return left + "." + e.Name, true default: return "", false } } func (g *goGenerator) emitRunCatchingSupport() { g.line("type gotlinResult struct {") g.indentLevel++ g.line("exception any") g.indentLevel-- g.line("}") g.line("") g.line("func gotlinRunCatching(fn func()) (result gotlinResult) {") g.indentLevel++ g.line("defer func() {") g.indentLevel++ g.line("if recovered := recover(); recovered != nil {") g.indentLevel++ g.line("result.exception = recovered") g.indentLevel-- g.line("}") g.indentLevel-- g.line("}()") g.line("fn()") g.line("return") g.indentLevel-- g.line("}") g.line("") g.line("func (r gotlinResult) isSuccess() bool {") g.indentLevel++ g.line("return r.exception == nil") g.indentLevel-- g.line("}") g.line("") g.line("func (r gotlinResult) exceptionOrNull() any {") g.indentLevel++ g.line("return r.exception") g.indentLevel-- g.line("}") } func (g *goGenerator) emitGoUnwrapSupport() { g.line("func gotlinUnwrapGo[T any](value T, rest ...any) T {") g.indentLevel++ g.line("if len(rest) == 1 {") g.indentLevel++ g.line("if rest[0] == nil {") g.indentLevel++ g.line("return value") g.indentLevel-- g.line("}") g.line("if err, ok := rest[0].(error); ok {") g.indentLevel++ g.line("if err != nil {") g.indentLevel++ g.line("panic(err)") g.indentLevel-- g.line("}") g.line("return value") g.indentLevel-- g.line("}") g.indentLevel-- g.line("}") g.line("if len(rest) == 0 {") g.indentLevel++ g.line("if any(value) == nil {") g.indentLevel++ g.line("return value") g.indentLevel-- g.line("}") g.line("if err, ok := any(value).(error); ok {") g.indentLevel++ g.line("if err != nil {") g.indentLevel++ g.line("panic(err)") g.indentLevel-- g.line("}") g.line("var zero T") g.line("return zero") g.indentLevel-- g.line("}") g.line("return value") g.indentLevel-- g.line("}") g.line(`panic("multi-value Go call requires explicit destructuring unless second value is error")`) g.indentLevel-- g.line("}") } func (g *goGenerator) passthroughValue(original Expr, rendered string) string { return rendered } func parseFunctionType(text string) ([]string, string, bool) { text = strings.TrimSpace(text) if !strings.HasPrefix(text, "(") { return nil, "", false } depth := 0 end := -1 for i, r := range text { switch r { case '(': depth++ case ')': depth-- if depth == 0 { end = i goto done } } } done: if end == -1 { return nil, "", false } rest := strings.TrimSpace(text[end+1:]) if !strings.HasPrefix(rest, "->") { return nil, "", false } paramsText := strings.TrimSpace(text[1:end]) retText := strings.TrimSpace(strings.TrimPrefix(rest, "->")) var params []string if paramsText != "" { params = splitTopLevel(paramsText, ',') } return params, retText, true } func splitTopLevel(text string, sep rune) []string { var parts []string depthParen := 0 depthAngle := 0 start := 0 for i, r := range text { switch r { case '(': depthParen++ case ')': depthParen-- case '<': depthAngle++ case '>': if depthAngle > 0 { depthAngle-- } default: if r == sep && depthParen == 0 && depthAngle == 0 { parts = append(parts, strings.TrimSpace(text[start:i])) start = i + 1 } } } parts = append(parts, strings.TrimSpace(text[start:])) return parts } func parseGenericType(text string) (string, []string, bool) { text = strings.TrimSpace(text) start := strings.Index(text, "<") if start <= 0 || !strings.HasSuffix(text, ">") { return "", nil, false } depth := 0 end := -1 for i, r := range text { switch r { case '<': depth++ case '>': depth-- if depth == 0 { end = i } } } if end != len(text)-1 || depth != 0 { return "", nil, false } base := strings.TrimSpace(text[:start]) argsText := strings.TrimSpace(text[start+1 : end]) if base == "" || argsText == "" { return "", nil, false } return base, splitTopLevel(argsText, ','), true } func lambdaHasValueReturn(stmts []Stmt) bool { for _, stmt := range stmts { switch s := stmt.(type) { case ReturnStmt: if s.Value != nil { return true } case IfStmt: if lambdaHasValueReturn(s.Then) || lambdaHasValueReturn(s.Else) { return true } case WhileStmt: if lambdaHasValueReturn(s.Body) { return true } } } return false } func min(a, b int) int { if a < b { return a } return b }