Skip to content

Commit

Permalink
don't evict global declarations (#72)
Browse files Browse the repository at this point in the history
Fixes #54 

As part of this PR, I revisited how we manage build tags in order to
inject the `!durable` tag on input files that we generate the durable
coroutine code from.
  • Loading branch information
achille-roussel authored Sep 25, 2023
2 parents 3f2e3c1 + 8087b8a commit 8be7c63
Show file tree
Hide file tree
Showing 12 changed files with 308 additions and 69 deletions.
4 changes: 3 additions & 1 deletion compiler/Makefile
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
GO ?= go

TARGET = testdata/coroutine_durable.go testdata/coroutine_functypes.go
TARGET = \
testdata/coroutine_generated.go \
testdata/testdata_generated.go

test: clean generate
$(GO) test ./...
Expand Down
11 changes: 1 addition & 10 deletions compiler/cmd/coroc/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,6 @@ USAGE:
OPTIONS:
--output <FILENAME> Name of the Go file to generate in each package
--tags <TAGS...> Build tags to set on generated files
-h, --help Show this help information
`

Expand All @@ -30,8 +28,6 @@ func main() {
}

func run() error {
buildTags := flag.String("tags", "", "")

flag.Usage = func() { println(usage[1:]) }
flag.Parse()

Expand All @@ -49,10 +45,5 @@ func run() error {
}
}

var options []compiler.Option
if *buildTags != "" {
options = append(options, compiler.WithBuildTags(*buildTags))
}

return compiler.Compile(path, options...)
return compiler.Compile(path)
}
13 changes: 12 additions & 1 deletion compiler/comment.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
package compiler

import "go/ast"
import (
"go/ast"
)

func appendCommentGroup(comments []*ast.Comment, group *ast.CommentGroup) []*ast.Comment {
if group != nil && len(group.List) > 0 {
Expand All @@ -19,3 +21,12 @@ func appendComment(comments []*ast.Comment, text string) []*ast.Comment {
Text: text,
})
}

func commentGroupsOf(file *ast.File) []*ast.CommentGroup {
groups := make([]*ast.CommentGroup, 0, 1+len(file.Comments))
groups = append(groups, file.Comments...)
if file.Doc != nil {
groups = append(groups, file.Doc)
}
return groups
}
97 changes: 52 additions & 45 deletions compiler/compile.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package compiler
import (
"fmt"
"go/ast"
"go/build/constraint"
"go/format"
"go/token"
"go/types"
Expand Down Expand Up @@ -44,15 +45,7 @@ func Compile(path string, options ...Option) error {
// Option configures the compiler.
type Option func(*compiler)

// WithBuildTags instructs the compiler to attach the specified build
// tags to generated files.
func WithBuildTags(buildTags string) Option {
return func(c *compiler) { c.buildTags = buildTags }
}

type compiler struct {
buildTags string

fset *token.FileSet
}

Expand Down Expand Up @@ -192,22 +185,29 @@ func (c *compiler) compile(path string) error {
return nil
}

func (c *compiler) writeFile(path string, file *ast.File) error {
f, err := os.Create(path)
func (c *compiler) writeFile(path string, file *ast.File, changeBuildTags func(constraint.Expr) constraint.Expr) error {
buildTags, err := parseBuildTags(file)
if err != nil {
return err
}
defer f.Close()
buildTags = changeBuildTags(buildTags)
stripBuildTagsOf(file, path)

// Comments are awkward to attach to the tree (they rely on token.Pos, which
// is coupled to a token.FileSet). Instead, just write out the raw strings.
var b strings.Builder
b.WriteString(`// Code generated by coroc. DO NOT EDIT`)
b.WriteString("\n\n")
if c.buildTags != "" {
if buildTags != nil {
b.WriteString(`//go:build `)
b.WriteString(c.buildTags)
b.WriteString(buildTags.String())
b.WriteString("\n\n")
}

f, err := os.Create(path)
if err != nil {
return err
}
defer f.Close()

if _, err := f.WriteString(b.String()); err != nil {
return err
}
Expand All @@ -233,53 +233,60 @@ func (c *compiler) compilePackage(p *packages.Package, colors functionColors) er
colorsByFunc[decl] = color
}

buildTag := &constraint.TagExpr{
Tag: "durable",
}

for i, f := range p.Syntax {
if err := c.writeFile(p.GoFiles[i], f, func(expr constraint.Expr) constraint.Expr {
return withoutBuildTag(expr, buildTag)
}); err != nil {
return err
}

// Generate the coroutine AST.
gen := &ast.File{
Name: ast.NewIdent(p.Name),
}

for _, anydecl := range f.Decls {
decl, ok := anydecl.(*ast.FuncDecl)
if !ok {
continue
}
color, ok := colorsByFunc[decl]
if !ok {
gen.Decls = append(gen.Decls, decl)
continue
}
// Reject certain language features for now.
if err := unsupported(decl, p.TypesInfo); err != nil {
return err
}
switch decl := anydecl.(type) {
case *ast.GenDecl:
// Imports get re-added by addImports below, so no need to carry
// them from declarations in the input file.
if decl.Tok != token.IMPORT {
gen.Decls = append(gen.Decls, decl)
continue
}

scope := &scope{colors: colorsByFunc}
// If the function has a single expression it does not contain a
// deferred closure; it won't be added to the list of colored
// functions so generateFunctypes does not mistakenly increment the
// local symbol counter when generating closure names.
gen.Decls = append(gen.Decls, scope.compileFuncDecl(p, decl, color))
}
case *ast.FuncDecl:
color, ok := colorsByFunc[decl]
if !ok {
gen.Decls = append(gen.Decls, decl)
continue
}
// Reject certain language features for now.
if err := unsupported(decl, p.TypesInfo); err != nil {
return err
}

if len(gen.Decls) == 0 {
continue
scope := &scope{colors: colorsByFunc}
gen.Decls = append(gen.Decls, scope.compileFuncDecl(p, decl, color))
}
}

clearPos(gen)

generateFunctypes(p, gen, colorsByFunc)

// Find all the required imports for this file.
gen = addImports(p, gen)

outputPath, _ := strings.CutSuffix(p.GoFiles[i], ".go")
if c.buildTags != "" {
outputPath += "_" + strings.ReplaceAll(c.buildTags, ",", "_") + ".go"
} else {
outputPath += "_generated.go"
}
if err := c.writeFile(outputPath, gen); err != nil {
outputPath := strings.TrimSuffix(p.GoFiles[i], ".go")
outputPath += "_generated.go"

if err := c.writeFile(outputPath, gen, func(expr constraint.Expr) constraint.Expr {
return withBuildTag(expr, buildTag)
}); err != nil {
return err
}
}
Expand Down
79 changes: 79 additions & 0 deletions compiler/constraint.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
package compiler

import (
"go/ast"
"go/build/constraint"
"reflect"
"slices"
)

func containsExpr(expr, contains constraint.Expr) bool {
switch x := expr.(type) {
case *constraint.AndExpr:
return containsExpr(x.X, contains) || containsExpr(x.Y, contains)
case *constraint.OrExpr:
return containsExpr(x.X, contains) && containsExpr(x.Y, contains)
default:
return reflect.DeepEqual(expr, contains)
}
}

func withBuildTag(expr constraint.Expr, buildTag *constraint.TagExpr) constraint.Expr {
if buildTag == nil || containsExpr(expr, buildTag) {
return expr
} else if expr == nil {
return buildTag
} else {
return &constraint.AndExpr{X: expr, Y: buildTag}
}
}

func withoutBuildTag(expr constraint.Expr, buildTag *constraint.TagExpr) constraint.Expr {
notBuildTag := &constraint.NotExpr{X: buildTag}
if buildTag == nil || containsExpr(expr, notBuildTag) {
return expr
} else if expr == nil {
return notBuildTag
} else {
return &constraint.AndExpr{X: expr, Y: notBuildTag}
}
}

func parseBuildTags(file *ast.File) (constraint.Expr, error) {
groups := commentGroupsOf(file)

for _, group := range groups {
for _, c := range group.List {
if constraint.IsGoBuild(c.Text) {
return constraint.Parse(c.Text)
}
}
}

var plusBuildLines constraint.Expr
for _, group := range groups {
for _, c := range group.List {
if constraint.IsPlusBuild(c.Text) {
x, err := constraint.Parse(c.Text)
if err != nil {
return nil, err
}
if plusBuildLines == nil {
plusBuildLines = x
} else {
plusBuildLines = &constraint.AndExpr{X: plusBuildLines, Y: x}
}
}
}
}

return plusBuildLines, nil
}

func stripBuildTagsOf(file *ast.File, path string) {
for _, group := range commentGroupsOf(file) {
group.List = slices.DeleteFunc(group.List, func(c *ast.Comment) bool {
return constraint.IsGoBuild(c.Text) || constraint.IsPlusBuild(c.Text)
})
}
}
18 changes: 10 additions & 8 deletions compiler/function.go
Original file line number Diff line number Diff line change
Expand Up @@ -240,14 +240,16 @@ func generateFunctypes(p *packages.Package, f *ast.File, colors map[ast.Node]*ty
})
}

astutil.AddNamedImport(nil, f, "_types", "github.com/stealthrocket/coroutine/types")

f.Decls = append(f.Decls,
&ast.FuncDecl{
Name: ast.NewIdent("init"),
Type: &ast.FuncType{Params: new(ast.FieldList)},
Body: init,
})
if len(init.List) > 0 {
astutil.AddNamedImport(nil, f, "_types", "github.com/stealthrocket/coroutine/types")

f.Decls = append(f.Decls,
&ast.FuncDecl{
Name: ast.NewIdent("init"),
Type: &ast.FuncType{Params: new(ast.FieldList)},
Body: init,
})
}
}

// This function computes the name that the linker gives to anonymous functions,
Expand Down
2 changes: 1 addition & 1 deletion compiler/testdata/coroutine.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import (
"github.com/stealthrocket/coroutine"
)

//go:generate coroc --tags durable
//go:generate coroc

func SomeFunctionThatShouldExistInTheCompiledFile() {
}
Expand Down
Loading

0 comments on commit 8be7c63

Please sign in to comment.