]> Cypherpunks repositories - gostls13.git/commitdiff
cmd/goapi: expand embedded interfaces
authorBrad Fitzpatrick <bradfitz@golang.org>
Mon, 30 Jan 2012 05:04:13 +0000 (21:04 -0800)
committerBrad Fitzpatrick <bradfitz@golang.org>
Mon, 30 Jan 2012 05:04:13 +0000 (21:04 -0800)
Fixes #2801

R=rsc
CC=golang-dev
https://golang.org/cl/5576068

src/cmd/goapi/goapi.go
src/cmd/goapi/goapi_test.go
src/cmd/goapi/testdata/src/pkg/p1/golden.txt [moved from src/cmd/goapi/testdata/p1/golden.txt with 83% similarity]
src/cmd/goapi/testdata/src/pkg/p1/p1.go [moved from src/cmd/goapi/testdata/p1/p1.go with 92% similarity]
src/cmd/goapi/testdata/src/pkg/p2/golden.txt [new file with mode: 0644]
src/cmd/goapi/testdata/src/pkg/p2/p2.go [new file with mode: 0644]

index a64edcae7d9f54e819707cb526b20c453c8bfad9..a9fbbeac3d39068580162bbf5f31ef5c3c3ad50f 100644 (file)
@@ -21,8 +21,10 @@ import (
        "log"
        "os"
        "os/exec"
+       "path"
        "path/filepath"
        "sort"
+       "strconv"
        "strings"
 )
 
@@ -51,6 +53,11 @@ func main() {
        if err != nil {
                log.Fatalf("failed to find tree: %v", err)
        }
+       w.tree = tree
+
+       for _, pkg := range pkgs {
+               w.wantedPkg[pkg] = true
+       }
 
        for _, pkg := range pkgs {
                if strings.HasPrefix(pkg, "cmd/") ||
@@ -61,8 +68,7 @@ func main() {
                if !tree.HasSrc(pkg) {
                        log.Fatalf("no source in tree for package %q", pkg)
                }
-               pkgSrcDir := filepath.Join(tree.SrcDir(), filepath.FromSlash(pkg))
-               w.WalkPackage(pkg, pkgSrcDir)
+               w.WalkPackage(pkg)
        }
 
        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 {
-       fset           *token.FileSet
-       scope          []string
-       features       map[string]bool // set
-       lastConstType  string
-       curPackageName string
-       curPackage     *ast.Package
-       prevConstType  map[string]string // identifer -> "ideal-int"
+       tree            *build.Tree
+       fset            *token.FileSet
+       scope           []string
+       features        map[string]bool // set
+       lastConstType   string
+       curPackageName  string
+       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 {
        return &Walker{
-               fset:     token.NewFileSet(),
-               features: make(map[string]bool),
+               fset:            token.NewFileSet(),
+               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.
 // Rather than litter the code with unnecessary type annotations, we'll hard-code
 // the cases we can't handle yet.
@@ -162,10 +192,34 @@ func (w *Walker) Features() (fs []string) {
        return
 }
 
-func (w *Walker) WalkPackage(name, dir string) {
-       log.Printf("package %s", name)
-       pop := w.pushScope("pkg " + name)
-       defer pop()
+// fileDeps returns the imports in a file.
+func fileDeps(f *ast.File) (pkgs []string) {
+       for _, is := range f.Imports {
+               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)
        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)
                }
                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.curPackage = apkg
        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
@@ -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.
 
        for _, di := range file.Decls {
@@ -237,7 +324,18 @@ func (w *Walker) walkFile(name string, file *ast.File) {
                case *ast.GenDecl:
                        switch d.Tok {
                        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:
                                for _, sp := range d.Specs {
                                        w.walkConst(sp.(*ast.ValueSpec))
@@ -527,12 +625,15 @@ func (w *Walker) nodeDebug(node interface{}) 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) {
        name := ts.Name.Name
        if !ast.IsExported(name) {
                return
        }
-
        switch t := ts.Type.(type) {
        case *ast.StructType:
                w.walkStructType(name, t)
@@ -540,7 +641,6 @@ func (w *Walker) walkTypeSpec(ts *ast.TypeSpec) {
                w.walkInterfaceType(name, t)
        default:
                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) {
-       methods := []string{}
+// method is a method of an interface.
+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 {
                typ := f.Type
-               for _, name := range f.Names {
-                       if ast.IsExported(name.Name) {
-                               ft := typ.(*ast.FuncType)
-                               w.emitFeature(fmt.Sprintf("%s%s", name, w.funcSigString(ft)))
-                               methods = append(methods, name.Name)
+               switch tv := typ.(type) {
+               case *ast.FuncType:
+                       for _, mname := range f.Names {
+                               if ast.IsExported(mname.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()
 
-       sort.Strings(methods)
-       if len(methods) == 0 {
+       sort.Strings(methNames)
+       if len(methNames) == 0 {
                w.emitFeature(fmt.Sprintf("type %s interface {}", name))
        } 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) {
+       if !w.wantedPkg[w.curPackageName] {
+               return
+       }
        f := strings.Join(w.scope, ", ") + ", " + feature
        if _, dup := w.features[f]; dup {
                panic("duplicate feature inserted: " + f)
index 1f23b1d68abda2968c67bb6271d5f89fd3449333..dbbec46b0d34d64ddcb874ec0e9d54a409ab863b 100644 (file)
@@ -7,6 +7,7 @@ package main
 import (
        "flag"
        "fmt"
+       "go/build"
        "io/ioutil"
        "os"
        "path/filepath"
@@ -20,7 +21,7 @@ var (
 )
 
 func TestGolden(t *testing.T) {
-       td, err := os.Open("testdata")
+       td, err := os.Open("testdata/src/pkg")
        if err != nil {
                t.Fatal(err)
        }
@@ -33,9 +34,11 @@ func TestGolden(t *testing.T) {
                        continue
                }
                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 {
                        os.Remove(goldenFile)
similarity index 83%
rename from src/cmd/goapi/testdata/p1/golden.txt
rename to src/cmd/goapi/testdata/src/pkg/p1/golden.txt
index 5b2aff54852f4d215876165645acd28142e99590..2693240a795ec5def0789fa4f3a2b55049783720 100644 (file)
@@ -31,11 +31,18 @@ pkg p1, type EmbedSelector struct, embedded time.Time
 pkg p1, type EmbedURLPtr struct
 pkg p1, type EmbedURLPtr struct, embedded *url.URL
 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, 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 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, Public *int
 pkg p1, type S struct, PublicTime time.Time
similarity index 92%
rename from src/cmd/goapi/testdata/p1/p1.go
rename to src/cmd/goapi/testdata/src/pkg/p1/p1.go
index 67a0ed9a4f32b8d99c57a7ca9f25e42f865c5339..3829686e6b23887dc1a1c2b1e81237c533dc3803 100644 (file)
@@ -1,8 +1,7 @@
-package foo
+package p1
 
 import (
-       "time"
-       "url"
+       ptwo "p2"
 )
 
 const (
@@ -44,16 +43,27 @@ var X int64
 
 var (
        Y int
-       X I // todo: resolve this to foo.I? probably doesn't matter.
+       X I
 )
 
+type Namer interface {
+       Name() string
+}
+
 type I interface {
+       Namer
+       ptwo.Twoer
        Set(name string, balance int64)
        Get(string) int64
        GetNamed(string) (balance int64)
        private()
 }
 
+type Error interface {
+       error
+       Temporary() bool
+}
+
 func (myInt) privateTypeMethod()           {}
 func (myInt) CapitalMethodUnexportedType() {}
 
diff --git a/src/cmd/goapi/testdata/src/pkg/p2/golden.txt b/src/cmd/goapi/testdata/src/pkg/p2/golden.txt
new file mode 100644 (file)
index 0000000..111427b
--- /dev/null
@@ -0,0 +1,2 @@
+pkg p2, type Twoer interface { PackageTwoMeth }
+pkg p2, type Twoer interface, PackageTwoMeth()
diff --git a/src/cmd/goapi/testdata/src/pkg/p2/p2.go b/src/cmd/goapi/testdata/src/pkg/p2/p2.go
new file mode 100644 (file)
index 0000000..695d08d
--- /dev/null
@@ -0,0 +1,5 @@
+package p2
+
+type Twoer interface {
+       PackageTwoMeth()
+}