Skip to content

Commit f730716

Browse files
authored
Allow go statements in certain circumstances (#150)
We don't currently allow `go` statements when compiling functions because there's no structured concurrency in Go; goroutines are executed independently (there's no hierarchy; no concept of ownership), and we are unable to suspend execution of related goroutines when another goroutine explicitly yields. The compiler only needs to compile a subset of functions in the program; those that yield, those that may be on the call stack of a coroutine that yields, or those that contain a function literal that yields or may end up on the call stack of a coroutine that yields. All other functions are allowed to use `go`. This PR relaxes the restrictions a bit so that certain functions are now allowed to use `go`. The major use case we unlock is to allow patterns like this, which seems to come up a lot: ```go func outer() { coroutine := func(...) { /* e.g. explicitly yields here */ } go func() { /* do something unrelated */ }() } ``` If the compiler determines that the `coroutine` function literal yields explicitly, or may end up on the call stack of a coroutine that yields explicitly, it needs to compile both the inner function literal and the outer function (since #135). We currently reject the `go` statement in the outer function, even though it may not interact with any coroutines. This PR relaxes the restriction; we allow `go` statements in this one instance. The goroutine could still mutate state from the outer function which is then read/mutated within the inner function literal. Given that we do not support serializing synchronization primitives at this time (e.g. channels, mutexes), the assumption is that the user isn't doing anything unsafe and that it's fine to allow the `go` statement. It prints a warning just in case.
2 parents 9fefde4 + af67c21 commit f730716

File tree

2 files changed

+22
-4
lines changed

2 files changed

+22
-4
lines changed

compiler/compile.go

+2-2
Original file line numberDiff line numberDiff line change
@@ -379,7 +379,7 @@ func (c *compiler) compilePackage(p *packages.Package, colors functionColors) er
379379
compiled := false
380380
if color != nil || containsColoredFuncLit(decl, colorsByFunc) {
381381
// Reject certain language features for now.
382-
if err := unsupported(decl, p.TypesInfo); err != nil {
382+
if err := c.unsupported(decl, p.TypesInfo, colorsByFunc); err != nil {
383383
return err
384384
}
385385
scope := &scope{compiler: c, colors: colorsByFunc}
@@ -433,7 +433,7 @@ func (c *compiler) compilePackage(p *packages.Package, colors functionColors) er
433433
return nil
434434
}
435435

436-
func containsColoredFuncLit(decl *ast.FuncDecl, colorsByFunc map[ast.Node]*types.Signature) (yes bool) {
436+
func containsColoredFuncLit(decl ast.Node, colorsByFunc map[ast.Node]*types.Signature) (yes bool) {
437437
ast.Inspect(decl, func(n ast.Node) bool {
438438
if lit, ok := n.(*ast.FuncLit); ok {
439439
if _, ok := colorsByFunc[lit]; ok {

compiler/unsupported.go

+20-2
Original file line numberDiff line numberDiff line change
@@ -5,17 +5,35 @@ import (
55
"go/ast"
66
"go/token"
77
"go/types"
8+
"log"
89
)
910

1011
// unsupported checks a function for unsupported language features.
11-
func unsupported(decl ast.Node, info *types.Info) (err error) {
12+
func (c *compiler) unsupported(decl ast.Node, info *types.Info, colorsByFunc map[ast.Node]*types.Signature) (err error) {
1213
ast.Inspect(decl, func(node ast.Node) bool {
1314
switch nn := node.(type) {
1415
case ast.Stmt:
1516
switch n := nn.(type) {
1617
// Not yet supported:
1718
case *ast.GoStmt:
18-
err = fmt.Errorf("not implemented: go")
19+
if _, inColoredFunc := colorsByFunc[decl]; inColoredFunc {
20+
err = fmt.Errorf("not implemented: go")
21+
} else {
22+
// Allow go statement in certain limited circumstances.
23+
_, goColoredFunc := colorsByFunc[n.Call.Fun]
24+
if !goColoredFunc {
25+
switch fn := n.Call.Fun.(type) {
26+
case *ast.FuncLit:
27+
goColoredFunc = containsColoredFuncLit(fn, colorsByFunc)
28+
}
29+
}
30+
if goColoredFunc {
31+
err = fmt.Errorf("not implemented: go")
32+
} else {
33+
pos := c.fset.Position(n.Pos())
34+
log.Printf("warning: goroutine mutations at %s may not be durable", pos)
35+
}
36+
}
1937

2038
// Partially supported:
2139
case *ast.BranchStmt:

0 commit comments

Comments
 (0)