Infer coroutine effects automatically

This commit is contained in:
pavel 2026-08-27 21:53:34 +02:00
commit 5b30e49486
15 changed files with 339 additions and 103 deletions

View file

@ -251,7 +251,11 @@ func (g *goGenerator) function(fn FunctionDecl) error {
g.currentClass = nil
g.currentFunc = fn
previousScope := g.currentCoroutineScope
if fn.Suspend {
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 }()
@ -284,11 +288,7 @@ func (g *goGenerator) interfaceDecl(decl InterfaceDecl) {
g.line("type " + decl.Name + " interface {")
g.indentLevel++
for _, method := range decl.Methods {
prefix := ""
if method.Suspend {
prefix = "gotlinScope *GotlinCoroutineScope"
}
g.line(method.Name + g.renderGoParamsWithPrefix(method.Params, prefix) + g.renderGoReturnSuffix(method.ReturnType))
g.line(method.Name + g.renderGoParamsWithPrefix(method.Params, "") + g.renderGoReturnSuffix(method.ReturnType))
}
g.indentLevel--
g.line("}")
@ -416,7 +416,7 @@ func (g *goGenerator) method(class ClassDecl, fn FunctionDecl) error {
g.currentClass = &class
g.currentFunc = fn
previousScope := g.currentCoroutineScope
if fn.Suspend {
if g.hasCoroutineEffect(fn) {
g.currentCoroutineScope = "gotlinScope"
}
defer func() { g.currentCoroutineScope = previousScope }()
@ -954,6 +954,11 @@ func (g *goGenerator) expr(expr Expr, expectedType string) (string, error) {
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 {
@ -1197,27 +1202,14 @@ func (g *goGenerator) expr(expr Expr, expectedType string) (string, error) {
return fmt.Sprintf("New%s%s(%s)", ident.Name, g.renderCallTypeArguments(e.TypeArgs), strings.Join(args, ", ")), nil
}
}
if ident, ok := e.Callee.(IdentExpr); ok {
if fn, found := g.semantic.Functions[ident.Name]; found && fn.Suspend {
if resolvedCall != nil {
if call, ok := resolvedCall.Node.(HIRGotlinCall); ok && call.Target != nil && call.Target.Effects.Has(CoroutineEffect) {
if g.currentCoroutineScope == "" {
return "", fmt.Errorf("suspend function %s requires a coroutine scope", ident.Name)
return "", fmt.Errorf("contextual function %s requires a coroutine scope", call.Target.Name)
}
args = append([]string{g.currentCoroutineScope}, args...)
}
}
if selector, ok := e.Callee.(SelectorExpr); ok {
if class, found := g.classForType(g.exprType(selector.Receiver)); found {
for _, method := range class.Methods {
if method.Name == selector.Name && method.Suspend {
if g.currentCoroutineScope == "" {
return "", fmt.Errorf("suspend method %s requires a coroutine scope", selector.Name)
}
args = append([]string{g.currentCoroutineScope}, args...)
break
}
}
}
}
callee, err := g.expr(e.Callee, "")
if err != nil {
return "", err
@ -2491,12 +2483,20 @@ func (g *goGenerator) cloneScopes() []map[string]bool {
func (g *goGenerator) renderGoFunctionParams(function FunctionDecl) string {
prefix := ""
if function.Suspend {
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 renderGoTypeParameters(params []string) string {
if len(params) == 0 {
return ""