Skip to content

Commit

Permalink
FEATURE: add go interface-like trait system (#7)
Browse files Browse the repository at this point in the history
  • Loading branch information
taylorchu authored Dec 22, 2016
1 parent 1ff161a commit c648c3f
Show file tree
Hide file tree
Showing 4 changed files with 92 additions and 50 deletions.
5 changes: 3 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -145,7 +145,7 @@ See [Internal packages](https://golang.org/doc/go1.4#internalpackages).
`generic` does the followings if you put the following comments in your go code:

```go
// THINK: generate from a generic package and save result as a new package,
// generate from a generic package and save result as a new package,
// with a list of rewrite rules!

//go:generate generic github.com/go/sort int Type->int
Expand All @@ -155,10 +155,11 @@ See [Internal packages](https://golang.org/doc/go1.4#internalpackages).
- If the package exists locally, go-get will not be called.
2. Gather `.go` files (skip `_test.go`) in github.com/go/sort
3. Apply AST rewrite to replace Type in those `.go` files to `int`.
- Only type that starts with __Type__ can be converted. This enables variable naming like __TypeKey__ or __TypeValue__
- Only type that starts with __Type__ and is [non-composite](https://golang.org/ref/spec#Types) can be converted. This enables variable naming like __TypeKey__ or __TypeValue__
that closely expresses meaning while there is still a namespace for type placeholder.
- Many rewrite rules are possible: `TypeKey->string TypeValue->int`.
- We can rewrite non-builtin types with `:`: `Type->github.com/go/types:types.Box`.
- If a type placeholder has methods defined, the replaced type will need to implement those methods.
4. Type-check results.
5. Save the results as a new package called `int` in `$PWD`.
- If there is already a dir called `int`, it will first be removed.
Expand Down
4 changes: 2 additions & 2 deletions context.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ type Context struct {
FromPkgPath string
PkgPath string
PkgName string
SameDir bool
Local bool
TypeMap map[string]Target
}

Expand All @@ -22,7 +22,7 @@ func NewContext(pkgPath, newPkgPath string, rules ...string) (*Context, error) {
FromPkgPath: pkgPath,
}
if strings.HasPrefix(newPkgPath, ".") {
ctx.SameDir = true
ctx.Local = true
ctx.PkgPath = strings.TrimPrefix(newPkgPath, ".")
ctx.PkgName = os.Getenv("GOPACKAGE")
if ctx.PkgName == "" {
Expand Down
4 changes: 4 additions & 0 deletions fixture/method/file.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,7 @@ package method

func (Type2 Type) Type2(_ Type2, _ Type2) {
}

func (t Type2) func1() {}
func (_ Type2) func2() {}
func (Type2) func3() {}
129 changes: 83 additions & 46 deletions rewrite.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ func RewritePackage(ctx *Context) error {
for _, rewriteFunc := range []func(*Context, map[string]*ast.File, *token.FileSet) error{
parsePackage,
rewritePkgName,
removeTypeDecl,
removePlaceholder,
rewriteIdent,
rewriteTopLevelIdent,
refreshAST,
Expand Down Expand Up @@ -65,7 +65,7 @@ func writePackage(ctx *Context, files map[string]*ast.File, fset *token.FileSet)
return nil
}

if ctx.SameDir {
if ctx.Local {
return writeOutput()
}

Expand All @@ -88,49 +88,58 @@ func writePackage(ctx *Context, files map[string]*ast.File, fset *token.FileSet)
}

func outPath(ctx *Context, path string) string {
if ctx.SameDir {
if ctx.Local {
return fmt.Sprintf("%s_%s", ctx.PkgPath, filepath.Base(path))
}
return filepath.Join(ctx.PkgPath, filepath.Base(path))
}

func localGoFiles(ctx *Context, files map[string]*ast.File, fset *token.FileSet) ([]*ast.File, error) {
buildP, err := build.Import(".", ".", 0)
if err != nil {
if _, ok := err.(*build.NoGoError); !ok {
return nil, err
}
}
generated := func(path string) bool {
for p := range files {
if outPath(ctx, p) == path {
return true
}
}
return false
}
var localFiles []*ast.File
for _, file := range buildP.GoFiles {
path := filepath.Join(buildP.Dir, file)
if generated(path) {
// Allow updating existing generated files.
continue
}
f, err := parser.ParseFile(fset, path, nil, 0)
if err != nil {
return nil, err
}
localFiles = append(localFiles, f)
}
return localFiles, nil
}

func typeCheck(ctx *Context, files map[string]*ast.File, fset *token.FileSet) error {
var allFiles []*ast.File
for _, f := range files {
allFiles = append(allFiles, f)
}

// Type-check.
if ctx.SameDir {
if ctx.Local {
// Also include same-dir files.
// However, it is silly to add the entire file,
// because that file might have identifiers from another generic package.
buildP, err := build.Import(".", ".", 0)
localFiles, err := localGoFiles(ctx, files, fset)
if err != nil {
if _, ok := err.(*build.NoGoError); !ok {
return err
}
}
generated := func(path string) bool {
for p := range files {
if outPath(ctx, p) == path {
return true
}
}
return false
}
for _, file := range buildP.GoFiles {
path := filepath.Join(buildP.Dir, file)
if generated(path) {
// Allow updating existing generated files.
continue
}
f, err := parser.ParseFile(fset, path, nil, 0)
if err != nil {
return err
}
allFiles = append(allFiles, f)
return err
}
allFiles = append(allFiles, localFiles...)
}

var errType []error
Expand Down Expand Up @@ -244,32 +253,60 @@ func rewriteIdent(ctx *Context, nodes map[string]*ast.File, fset *token.FileSet)
return nil
}

// removeTypeDecl removes type declarations defined in typeMap.
func removeTypeDecl(ctx *Context, nodes map[string]*ast.File, fset *token.FileSet) error {
for _, node := range nodes {
// removePlaceholder removes type declarations defined in typeMap.
func removePlaceholder(ctx *Context, files map[string]*ast.File, fset *token.FileSet) error {
declMap := make(map[interface{}]struct{})
for _, node := range files {
for i := len(node.Decls) - 1; i >= 0; i-- {
genDecl, ok := node.Decls[i].(*ast.GenDecl)
if !ok {
continue
var remove bool
switch decl := node.Decls[i].(type) {
case *ast.GenDecl:
for _, spec := range decl.Specs {
switch spec := spec.(type) {
case *ast.TypeSpec:
_, ok := ctx.TypeMap[spec.Name.Name]
if !ok {
continue
}
_, ok = spec.Type.(*ast.Ident)
if !ok {
continue
}
remove = true
declMap[spec] = struct{}{}
}
}
}
if genDecl.Tok != token.TYPE {
continue
if remove {
node.Decls = append(node.Decls[:i], node.Decls[i+1:]...)
}
}
}
// If a type placeholder is removed, its linked methods should be removed too.
// This works like go interface because now the replaced types need to implement these methods.
for _, node := range files {
for i := len(node.Decls) - 1; i >= 0; i-- {
var remove bool
for _, spec := range genDecl.Specs {
typeSpec := spec.(*ast.TypeSpec)

_, ok = ctx.TypeMap[typeSpec.Name.Name]
if !ok {
switch decl := node.Decls[i].(type) {
case *ast.FuncDecl:
if decl.Recv == nil {
continue
}

_, ok = typeSpec.Type.(*ast.Ident)
var obj *ast.Object
switch expr := decl.Recv.List[0].Type.(type) {
case *ast.StarExpr:
obj = expr.X.(*ast.Ident).Obj
case *ast.Ident:
obj = expr.Obj
}
if obj == nil || obj.Decl == nil {
continue
}
_, ok := declMap[obj.Decl]
if !ok {
continue
}
remove = true
break
}
if remove {
node.Decls = append(node.Decls[:i], node.Decls[i+1:]...)
Expand All @@ -283,7 +320,7 @@ func removeTypeDecl(ctx *Context, nodes map[string]*ast.File, fset *token.FileSe
//
// This prevents name conflicts when a package is rewritten to $PWD.
func rewriteTopLevelIdent(ctx *Context, nodes map[string]*ast.File, fset *token.FileSet) error {
if !ctx.SameDir {
if !ctx.Local {
return nil
}

Expand Down

0 comments on commit c648c3f

Please sign in to comment.