Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

don't evict global declarations #72

Merged
merged 5 commits into from
Sep 25, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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