1
0
mirror of https://github.com/golang/go synced 2024-11-21 23:24:41 -07:00

cmd/goapi: expand embedded interfaces

Fixes #2801

R=rsc
CC=golang-dev
https://golang.org/cl/5576068
This commit is contained in:
Brad Fitzpatrick 2012-01-29 21:04:13 -08:00
parent 0f2659a323
commit a94bd4d7c3
6 changed files with 221 additions and 40 deletions

View File

@ -21,8 +21,10 @@ import (
"log" "log"
"os" "os"
"os/exec" "os/exec"
"path"
"path/filepath" "path/filepath"
"sort" "sort"
"strconv"
"strings" "strings"
) )
@ -51,6 +53,11 @@ func main() {
if err != nil { if err != nil {
log.Fatalf("failed to find tree: %v", err) log.Fatalf("failed to find tree: %v", err)
} }
w.tree = tree
for _, pkg := range pkgs {
w.wantedPkg[pkg] = true
}
for _, pkg := range pkgs { for _, pkg := range pkgs {
if strings.HasPrefix(pkg, "cmd/") || if strings.HasPrefix(pkg, "cmd/") ||
@ -61,8 +68,7 @@ func main() {
if !tree.HasSrc(pkg) { if !tree.HasSrc(pkg) {
log.Fatalf("no source in tree for package %q", pkg) log.Fatalf("no source in tree for package %q", pkg)
} }
pkgSrcDir := filepath.Join(tree.SrcDir(), filepath.FromSlash(pkg)) w.WalkPackage(pkg)
w.WalkPackage(pkg, pkgSrcDir)
} }
bw := bufio.NewWriter(os.Stdout) bw := bufio.NewWriter(os.Stdout)
@ -99,23 +105,47 @@ func main() {
} }
} }
// pkgSymbol represents a symbol in a package
type pkgSymbol struct {
pkg string // "net/http"
symbol string // "RoundTripper"
}
type Walker struct { type Walker struct {
fset *token.FileSet tree *build.Tree
scope []string fset *token.FileSet
features map[string]bool // set scope []string
lastConstType string features map[string]bool // set
curPackageName string lastConstType string
curPackage *ast.Package curPackageName string
prevConstType map[string]string // identifer -> "ideal-int" curPackage *ast.Package
prevConstType map[string]string // identifer -> "ideal-int"
packageState map[string]loadState
interfaces map[pkgSymbol]*ast.InterfaceType
selectorFullPkg map[string]string // "http" => "net/http", updated by imports
wantedPkg map[string]bool // packages requested on the command line
} }
func NewWalker() *Walker { func NewWalker() *Walker {
return &Walker{ return &Walker{
fset: token.NewFileSet(), fset: token.NewFileSet(),
features: make(map[string]bool), features: make(map[string]bool),
packageState: make(map[string]loadState),
interfaces: make(map[pkgSymbol]*ast.InterfaceType),
selectorFullPkg: make(map[string]string),
wantedPkg: make(map[string]bool),
} }
} }
// loadState is the state of a package's parsing.
type loadState int
const (
notLoaded loadState = iota
loading
loaded
)
// hardCodedConstantType is a hack until the type checker is sufficient for our needs. // hardCodedConstantType is a hack until the type checker is sufficient for our needs.
// Rather than litter the code with unnecessary type annotations, we'll hard-code // Rather than litter the code with unnecessary type annotations, we'll hard-code
// the cases we can't handle yet. // the cases we can't handle yet.
@ -162,10 +192,34 @@ func (w *Walker) Features() (fs []string) {
return return
} }
func (w *Walker) WalkPackage(name, dir string) { // fileDeps returns the imports in a file.
log.Printf("package %s", name) func fileDeps(f *ast.File) (pkgs []string) {
pop := w.pushScope("pkg " + name) for _, is := range f.Imports {
defer pop() fpkg, err := strconv.Unquote(is.Path.Value)
if err != nil {
log.Fatalf("error unquoting import string %q: %v", is.Path.Value, err)
}
if fpkg != "C" {
pkgs = append(pkgs, fpkg)
}
}
return
}
// WalkPackage walks all files in package `name'.
// WalkPackage does nothing if the package has already been loaded.
func (w *Walker) WalkPackage(name string) {
switch w.packageState[name] {
case loading:
log.Fatalf("import cycle loading package %q?", name)
case loaded:
return
}
w.packageState[name] = loading
defer func() {
w.packageState[name] = loaded
}()
dir := filepath.Join(w.tree.SrcDir(), filepath.FromSlash(name))
info, err := build.ScanDir(dir) info, err := build.ScanDir(dir)
if err != nil { if err != nil {
@ -183,13 +237,26 @@ func (w *Walker) WalkPackage(name, dir string) {
log.Fatalf("error parsing package %s, file %s: %v", name, file, err) log.Fatalf("error parsing package %s, file %s: %v", name, file, err)
} }
apkg.Files[file] = f apkg.Files[file] = f
for _, dep := range fileDeps(f) {
w.WalkPackage(dep)
}
} }
log.Printf("package %s", name)
pop := w.pushScope("pkg " + name)
defer pop()
w.curPackageName = name w.curPackageName = name
w.curPackage = apkg w.curPackage = apkg
w.prevConstType = map[string]string{} w.prevConstType = map[string]string{}
for name, afile := range apkg.Files {
w.walkFile(filepath.Join(dir, name), afile) for _, afile := range apkg.Files {
w.recordTypes(afile)
}
for _, afile := range apkg.Files {
w.walkFile(afile)
} }
// Now that we're done walking types, vars and consts // Now that we're done walking types, vars and consts
@ -229,7 +296,27 @@ func (w *Walker) pushScope(name string) (popFunc func()) {
} }
} }
func (w *Walker) walkFile(name string, file *ast.File) { func (w *Walker) recordTypes(file *ast.File) {
for _, di := range file.Decls {
switch d := di.(type) {
case *ast.GenDecl:
switch d.Tok {
case token.TYPE:
for _, sp := range d.Specs {
ts := sp.(*ast.TypeSpec)
name := ts.Name.Name
if ast.IsExported(name) {
if it, ok := ts.Type.(*ast.InterfaceType); ok {
w.noteInterface(name, it)
}
}
}
}
}
}
}
func (w *Walker) walkFile(file *ast.File) {
// Not entering a scope here; file boundaries aren't interesting. // Not entering a scope here; file boundaries aren't interesting.
for _, di := range file.Decls { for _, di := range file.Decls {
@ -237,7 +324,18 @@ func (w *Walker) walkFile(name string, file *ast.File) {
case *ast.GenDecl: case *ast.GenDecl:
switch d.Tok { switch d.Tok {
case token.IMPORT: case token.IMPORT:
continue for _, sp := range d.Specs {
is := sp.(*ast.ImportSpec)
fpath, err := strconv.Unquote(is.Path.Value)
if err != nil {
log.Fatal(err)
}
name := path.Base(fpath)
if is.Name != nil {
name = is.Name.Name
}
w.selectorFullPkg[name] = fpath
}
case token.CONST: case token.CONST:
for _, sp := range d.Specs { for _, sp := range d.Specs {
w.walkConst(sp.(*ast.ValueSpec)) w.walkConst(sp.(*ast.ValueSpec))
@ -527,12 +625,15 @@ func (w *Walker) nodeDebug(node interface{}) string {
return b.String() return b.String()
} }
func (w *Walker) noteInterface(name string, it *ast.InterfaceType) {
w.interfaces[pkgSymbol{w.curPackageName, name}] = it
}
func (w *Walker) walkTypeSpec(ts *ast.TypeSpec) { func (w *Walker) walkTypeSpec(ts *ast.TypeSpec) {
name := ts.Name.Name name := ts.Name.Name
if !ast.IsExported(name) { if !ast.IsExported(name) {
return return
} }
switch t := ts.Type.(type) { switch t := ts.Type.(type) {
case *ast.StructType: case *ast.StructType:
w.walkStructType(name, t) w.walkStructType(name, t)
@ -540,7 +641,6 @@ func (w *Walker) walkTypeSpec(ts *ast.TypeSpec) {
w.walkInterfaceType(name, t) w.walkInterfaceType(name, t)
default: default:
w.emitFeature(fmt.Sprintf("type %s %s", name, w.nodeString(ts.Type))) w.emitFeature(fmt.Sprintf("type %s %s", name, w.nodeString(ts.Type)))
//log.Fatalf("unknown typespec %T", ts.Type)
} }
} }
@ -582,27 +682,78 @@ func (w *Walker) walkStructType(name string, t *ast.StructType) {
} }
} }
func (w *Walker) walkInterfaceType(name string, t *ast.InterfaceType) { // method is a method of an interface.
methods := []string{} type method struct {
name string // "Read"
sig string // "([]byte) (int, error)", from funcSigString
}
// interfaceMethods returns the expanded list of methods for an interface.
// pkg is the complete package name ("net/http")
// iname is the interface name.
func (w *Walker) interfaceMethods(pkg, iname string) (methods []method) {
t, ok := w.interfaces[pkgSymbol{pkg, iname}]
if !ok {
log.Fatalf("failed to find interface %s.%s", pkg, iname)
}
pop := w.pushScope("type " + name + " interface")
for _, f := range t.Methods.List { for _, f := range t.Methods.List {
typ := f.Type typ := f.Type
for _, name := range f.Names { switch tv := typ.(type) {
if ast.IsExported(name.Name) { case *ast.FuncType:
ft := typ.(*ast.FuncType) for _, mname := range f.Names {
w.emitFeature(fmt.Sprintf("%s%s", name, w.funcSigString(ft))) if ast.IsExported(mname.Name) {
methods = append(methods, name.Name) ft := typ.(*ast.FuncType)
methods = append(methods, method{
name: mname.Name,
sig: w.funcSigString(ft),
})
}
} }
case *ast.Ident:
embedded := typ.(*ast.Ident).Name
if embedded == "error" {
methods = append(methods, method{
name: "Error",
sig: "() string",
})
continue
}
if !ast.IsExported(embedded) {
log.Fatalf("unexported embedded interface %q in exported interface %s.%s; confused",
embedded, pkg, iname)
}
methods = append(methods, w.interfaceMethods(pkg, embedded)...)
case *ast.SelectorExpr:
lhs := w.nodeString(tv.X)
rhs := w.nodeString(tv.Sel)
fpkg, ok := w.selectorFullPkg[lhs]
if !ok {
log.Fatalf("can't resolve selector %q in interface %s.%s", lhs, pkg, iname)
}
methods = append(methods, w.interfaceMethods(fpkg, rhs)...)
default:
log.Fatalf("unknown type %T in interface field", typ)
} }
} }
return
}
func (w *Walker) walkInterfaceType(name string, t *ast.InterfaceType) {
methNames := []string{}
pop := w.pushScope("type " + name + " interface")
for _, m := range w.interfaceMethods(w.curPackageName, name) {
methNames = append(methNames, m.name)
w.emitFeature(fmt.Sprintf("%s%s", m.name, m.sig))
}
pop() pop()
sort.Strings(methods) sort.Strings(methNames)
if len(methods) == 0 { if len(methNames) == 0 {
w.emitFeature(fmt.Sprintf("type %s interface {}", name)) w.emitFeature(fmt.Sprintf("type %s interface {}", name))
} else { } else {
w.emitFeature(fmt.Sprintf("type %s interface { %s }", name, strings.Join(methods, ", "))) w.emitFeature(fmt.Sprintf("type %s interface { %s }", name, strings.Join(methNames, ", ")))
} }
} }
@ -691,6 +842,9 @@ func (w *Walker) namelessField(f *ast.Field) *ast.Field {
} }
func (w *Walker) emitFeature(feature string) { func (w *Walker) emitFeature(feature string) {
if !w.wantedPkg[w.curPackageName] {
return
}
f := strings.Join(w.scope, ", ") + ", " + feature f := strings.Join(w.scope, ", ") + ", " + feature
if _, dup := w.features[f]; dup { if _, dup := w.features[f]; dup {
panic("duplicate feature inserted: " + f) panic("duplicate feature inserted: " + f)

View File

@ -7,6 +7,7 @@ package main
import ( import (
"flag" "flag"
"fmt" "fmt"
"go/build"
"io/ioutil" "io/ioutil"
"os" "os"
"path/filepath" "path/filepath"
@ -20,7 +21,7 @@ var (
) )
func TestGolden(t *testing.T) { func TestGolden(t *testing.T) {
td, err := os.Open("testdata") td, err := os.Open("testdata/src/pkg")
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
@ -33,9 +34,11 @@ func TestGolden(t *testing.T) {
continue continue
} }
w := NewWalker() w := NewWalker()
goldenFile := filepath.Join("testdata", fi.Name(), "golden.txt") w.wantedPkg[fi.Name()] = true
w.WalkPackage(fi.Name(), filepath.Join("testdata", fi.Name())) w.tree = &build.Tree{Path: "testdata", Goroot: true}
goldenFile := filepath.Join("testdata", "src", "pkg", fi.Name(), "golden.txt")
w.WalkPackage(fi.Name())
if *updateGolden { if *updateGolden {
os.Remove(goldenFile) os.Remove(goldenFile)

View File

@ -31,11 +31,18 @@ pkg p1, type EmbedSelector struct, embedded time.Time
pkg p1, type EmbedURLPtr struct pkg p1, type EmbedURLPtr struct
pkg p1, type EmbedURLPtr struct, embedded *url.URL pkg p1, type EmbedURLPtr struct, embedded *url.URL
pkg p1, type Embedded struct pkg p1, type Embedded struct
pkg p1, type I interface { Get, GetNamed, Set } pkg p1, type Error interface { Error, Temporary }
pkg p1, type Error interface, Error() string
pkg p1, type Error interface, Temporary() bool
pkg p1, type I interface { Get, GetNamed, Name, PackageTwoMeth, Set }
pkg p1, type I interface, Get(string) int64 pkg p1, type I interface, Get(string) int64
pkg p1, type I interface, GetNamed(string) int64 pkg p1, type I interface, GetNamed(string) int64
pkg p1, type I interface, Name() string
pkg p1, type I interface, PackageTwoMeth()
pkg p1, type I interface, Set(string, int64) pkg p1, type I interface, Set(string, int64)
pkg p1, type MyInt int pkg p1, type MyInt int
pkg p1, type Namer interface { Name }
pkg p1, type Namer interface, Name() string
pkg p1, type S struct pkg p1, type S struct
pkg p1, type S struct, Public *int pkg p1, type S struct, Public *int
pkg p1, type S struct, PublicTime time.Time pkg p1, type S struct, PublicTime time.Time

View File

@ -1,8 +1,7 @@
package foo package p1
import ( import (
"time" ptwo "p2"
"url"
) )
const ( const (
@ -44,16 +43,27 @@ var X int64
var ( var (
Y int Y int
X I // todo: resolve this to foo.I? probably doesn't matter. X I
) )
type Namer interface {
Name() string
}
type I interface { type I interface {
Namer
ptwo.Twoer
Set(name string, balance int64) Set(name string, balance int64)
Get(string) int64 Get(string) int64
GetNamed(string) (balance int64) GetNamed(string) (balance int64)
private() private()
} }
type Error interface {
error
Temporary() bool
}
func (myInt) privateTypeMethod() {} func (myInt) privateTypeMethod() {}
func (myInt) CapitalMethodUnexportedType() {} func (myInt) CapitalMethodUnexportedType() {}

View File

@ -0,0 +1,2 @@
pkg p2, type Twoer interface { PackageTwoMeth }
pkg p2, type Twoer interface, PackageTwoMeth()

View File

@ -0,0 +1,5 @@
package p2
type Twoer interface {
PackageTwoMeth()
}