blob: 92721cce1a7b638b7a101ea8987954d1890587b7 [file] [log] [blame]
// Copyright 2011 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// Api computes the exported API of a set of Go packages.
//modify 2013-2014 visualfc
package goapi
import (
"bufio"
"bytes"
"fmt"
"go/ast"
"go/build"
"go/doc"
"go/parser"
"go/printer"
"go/token"
"io"
"io/ioutil"
"log"
"os"
"os/exec"
"path"
"path/filepath"
"regexp"
"sort"
"strconv"
"strings"
"time"
"github.com/visualfc/gotools/command"
)
var Command = &command.Command{
Run: runApi,
UsageLine: "goapi",
Short: "golang api util",
Long: `golang api util`,
}
var apiVerbose bool
var apiAllmethods bool
var apiAlldecls bool
var apiShowpos bool
var apiSeparate string
var apiImportParser bool
var apiDefaultCtx bool
var apiCustomCtx string
var apiLookupInfo string
var apiLookupStdin bool
var apiOutput string
func init() {
Command.Flag.BoolVar(&apiVerbose, "v", false, "verbose debugging")
Command.Flag.BoolVar(&apiAllmethods, "e", true, "extract for all embedded methods")
Command.Flag.BoolVar(&apiAlldecls, "a", false, "extract for all declarations")
Command.Flag.BoolVar(&apiShowpos, "pos", false, "addition token position")
Command.Flag.StringVar(&apiSeparate, "sep", ", ", "setup separators")
Command.Flag.BoolVar(&apiImportParser, "dep", true, "parser package imports")
Command.Flag.BoolVar(&apiDefaultCtx, "default_ctx", true, "extract for default context")
Command.Flag.StringVar(&apiCustomCtx, "custom_ctx", "", "optional comma-separated list of <goos>-<goarch>[-cgo] to override default contexts.")
Command.Flag.StringVar(&apiLookupInfo, "cursor_info", "", "lookup cursor node info\"file.go:pos\"")
Command.Flag.BoolVar(&apiLookupStdin, "cursor_std", false, "cursor_info use stdin")
Command.Flag.StringVar(&apiOutput, "o", "", "output file")
}
func runApi(cmd *command.Command, args []string) error {
if len(args) == 0 && apiLookupInfo == "" {
cmd.Usage()
return os.ErrInvalid
}
if apiVerbose {
now := time.Now()
defer func() {
log.Println("time", time.Now().Sub(now))
}()
}
var pkgs []string
if len(args) > 0 {
if args[0] == "std" || args[0] == "all" {
out, err := exec.Command("go", "list", "-e", args[0]).Output()
if err != nil {
log.Fatal(err)
}
pkgs = strings.Fields(string(out))
} else {
pkgs = args
}
}
var curinfo CursorInfo
if apiLookupInfo != "" {
pos := strings.Index(apiLookupInfo, ":")
if pos != -1 {
curinfo.file = (apiLookupInfo)[:pos]
if i, err := strconv.Atoi((apiLookupInfo)[pos+1:]); err == nil {
curinfo.pos = token.Pos(i)
}
}
}
if len(pkgs) == 1 && curinfo.pos != token.NoPos {
curinfo.pkg = pkgs[0]
}
if apiLookupStdin {
src, err := ioutil.ReadAll(os.Stdin)
if err == nil {
curinfo.src = src
curinfo.std = true
}
}
if apiCustomCtx != "" {
apiDefaultCtx = false
setCustomContexts()
}
var features []string
w := NewWalker()
if curinfo.pkg != "" {
w.cursorInfo = &curinfo
}
w.sep = apiSeparate
if apiDefaultCtx {
w.context = &build.Default
for _, pkg := range pkgs {
w.wantedPkg[pkg] = true
}
for _, pkg := range pkgs {
w.WalkPackage(pkg)
}
if w.cursorInfo != nil {
goto lookup
} else {
var file io.Writer
if apiOutput != "" {
var err error
file, err = os.Create(apiOutput)
if err != nil {
log.Fatal(err)
}
} else {
file = os.Stdout
}
bw := bufio.NewWriter(file)
defer bw.Flush()
for _, p := range w.packageMap {
if w.wantedPkg[p.name] {
for _, f := range p.Features() {
fmt.Fprintf(bw, "%s\n", f)
}
}
}
return nil
}
features = w.Features("")
} else {
for _, c := range contexts {
c.Compiler = build.Default.Compiler
}
for _, pkg := range pkgs {
w.wantedPkg[pkg] = true
}
var featureCtx = make(map[string]map[string]bool) // feature -> context name -> true
for _, context := range contexts {
w.context = context
w.ctxName = contextName(w.context) + ":"
for _, pkg := range pkgs {
w.WalkPackage(pkg)
}
if w.cursorInfo != nil && w.cursorInfo.info != nil {
goto lookup
}
}
for pkg, p := range w.packageMap {
if w.wantedPkg[p.name] {
pos := strings.Index(pkg, ":")
if pos == -1 {
continue
}
ctxName := pkg[:pos]
for _, f := range p.Features() {
if featureCtx[f] == nil {
featureCtx[f] = make(map[string]bool)
}
featureCtx[f][ctxName] = true
}
}
}
for f, cmap := range featureCtx {
if len(cmap) == len(contexts) {
features = append(features, f)
continue
}
comma := strings.Index(f, ",")
for cname := range cmap {
f2 := fmt.Sprintf("%s (%s)%s", f[:comma], cname, f[comma:])
features = append(features, f2)
}
}
sort.Strings(features)
}
lookup:
if w.cursorInfo != nil {
info := w.cursorInfo.info
if info == nil {
os.Exit(1)
return os.ErrInvalid
}
// fmt.Println("kind,", info.Kind)
// fmt.Println("name,", info.Name)
// if info.Type != "" {
// fmt.Println("type,", strings.TrimLeft(info.Type, "*"))
// }
if info.Name == info.Type || info.Type == "" {
fmt.Printf("info, %s, %s\n", info.Kind, info.Name)
} else {
fmt.Printf("info, %s, %s, %s\n", info.Kind, info.Name, info.Type)
}
if info.Kind == KindImport || info.Kind == KindPackage {
if p := w.findPackage(info.Name); p != nil {
fmt.Println("help,", p.name)
}
}
if info.T != nil {
for _, text := range []string{info.Name, info.Type} {
typ := strings.TrimLeft(text, "*")
pos := strings.Index(typ, ".")
if pos != -1 {
if p := w.findPackage(typ[:pos]); p != nil {
fmt.Println("help,", p.name+typ[pos:])
break
}
}
}
fmt.Println("pos,", w.fset.Position(info.T.Pos()))
}
return nil
}
fail := false
defer func() {
if fail {
os.Exit(1)
}
}()
bw := bufio.NewWriter(os.Stdout)
defer bw.Flush()
for _, f := range features {
fmt.Fprintf(bw, "%s\n", f)
}
return nil
}
type CursorInfo struct {
pkg string
file string
pos token.Pos
src []byte
std bool
info *TypeInfo
}
// contexts are the default contexts which are scanned, unless
// overridden by the -contexts flag.
var contexts = []*build.Context{
{GOOS: "linux", GOARCH: "386", CgoEnabled: true},
{GOOS: "linux", GOARCH: "386"},
{GOOS: "linux", GOARCH: "amd64", CgoEnabled: true},
{GOOS: "linux", GOARCH: "amd64"},
{GOOS: "linux", GOARCH: "arm"},
{GOOS: "darwin", GOARCH: "386", CgoEnabled: true},
{GOOS: "darwin", GOARCH: "386"},
{GOOS: "darwin", GOARCH: "amd64", CgoEnabled: true},
{GOOS: "darwin", GOARCH: "amd64"},
{GOOS: "windows", GOARCH: "amd64"},
{GOOS: "windows", GOARCH: "386"},
{GOOS: "freebsd", GOARCH: "amd64"},
{GOOS: "freebsd", GOARCH: "386"},
}
func contextName(c *build.Context) string {
s := c.GOOS + "-" + c.GOARCH
if c.CgoEnabled {
return s + "-cgo"
}
return s
}
func osArchName(c *build.Context) string {
return c.GOOS + "-" + c.GOARCH
}
func parseContext(c string) *build.Context {
parts := strings.Split(c, "-")
if len(parts) < 2 {
log.Fatalf("bad context: %q", c)
}
bc := &build.Context{
GOOS: parts[0],
GOARCH: parts[1],
}
if len(parts) == 3 {
if parts[2] == "cgo" {
bc.CgoEnabled = true
} else {
log.Fatalf("bad context: %q", c)
}
}
return bc
}
func setCustomContexts() {
contexts = []*build.Context{}
for _, c := range strings.Split(apiCustomCtx, ",") {
contexts = append(contexts, parseContext(c))
}
}
func set(items []string) map[string]bool {
s := make(map[string]bool)
for _, v := range items {
s[v] = true
}
return s
}
var spaceParensRx = regexp.MustCompile(` \(\S+?\)`)
func featureWithoutContext(f string) string {
if !strings.Contains(f, "(") {
return f
}
return spaceParensRx.ReplaceAllString(f, "")
}
func compareAPI(w io.Writer, features, required, optional, exception []string, allowNew bool) (ok bool) {
ok = true
optionalSet := set(optional)
exceptionSet := set(exception)
featureSet := set(features)
sort.Strings(features)
sort.Strings(required)
take := func(sl *[]string) string {
s := (*sl)[0]
*sl = (*sl)[1:]
return s
}
for len(required) > 0 || len(features) > 0 {
switch {
case len(features) == 0 || (len(required) > 0 && required[0] < features[0]):
feature := take(&required)
if exceptionSet[feature] {
fmt.Fprintf(w, "~%s\n", feature)
} else if featureSet[featureWithoutContext(feature)] {
// okay.
} else {
fmt.Fprintf(w, "-%s\n", feature)
ok = false // broke compatibility
}
case len(required) == 0 || (len(features) > 0 && required[0] > features[0]):
newFeature := take(&features)
if optionalSet[newFeature] {
// Known added feature to the upcoming release.
// Delete it from the map so we can detect any upcoming features
// which were never seen. (so we can clean up the nextFile)
delete(optionalSet, newFeature)
} else {
fmt.Fprintf(w, "+%s\n", newFeature)
if !allowNew {
ok = false // we're in lock-down mode for next release
}
}
default:
take(&required)
take(&features)
}
}
// In next file, but not in API.
var missing []string
for feature := range optionalSet {
missing = append(missing, feature)
}
sort.Strings(missing)
for _, feature := range missing {
fmt.Fprintf(w, "±%s\n", feature)
}
return
}
func fileFeatures(filename string) []string {
bs, err := ioutil.ReadFile(filename)
if err != nil {
log.Fatalf("Error reading file %s: %v", filename, err)
}
text := strings.TrimSpace(string(bs))
if text == "" {
return nil
}
return strings.Split(text, "\n")
}
func isExtract(name string) bool {
if apiAlldecls {
return true
}
return ast.IsExported(name)
}
// pkgSymbol represents a symbol in a package
type pkgSymbol struct {
pkg string // "net/http"
symbol string // "RoundTripper"
}
//expression kind
type Kind int
const (
KindBuiltin Kind = iota
KindPackage
KindImport
KindVar
KindConst
KindInterface
KindParam
KindStruct
KindMethod
KindField
KindType
KindFunc
KindChan
KindArray
KindMap
KindSlice
KindLabel
KindBranch
)
func (k Kind) String() string {
switch k {
case KindBuiltin:
return "builtin"
case KindPackage:
return "package"
case KindImport:
return "import"
case KindVar:
return "var"
case KindConst:
return "const"
case KindParam:
return "param"
case KindInterface:
return "interface"
case KindStruct:
return "struct"
case KindMethod:
return "method"
case KindField:
return "field"
case KindType:
return "type"
case KindFunc:
return "func"
case KindChan:
return "chan"
case KindMap:
return "map"
case KindArray:
return "array"
case KindSlice:
return "slice"
case KindLabel:
return "label"
case KindBranch:
return "branch"
}
return fmt.Sprint("unknown-kind")
}
//expression type
type TypeInfo struct {
Kind Kind
Name string
Type string
X ast.Expr
T ast.Expr
}
type ExprType struct {
X ast.Expr
T string
}
type Package struct {
dpkg *doc.Package
apkg *ast.Package
interfaceMethods map[string]([]typeMethod)
interfaces map[string]*ast.InterfaceType //interface
structs map[string]*ast.StructType //struct
types map[string]ast.Expr //type
functions map[string]typeMethod //function
consts map[string]*ExprType //const => type
vars map[string]*ExprType //var => type
name string
dir string
sep string
deps []string
features map[string](token.Pos) // set
}
func NewPackage() *Package {
return &Package{
interfaceMethods: make(map[string]([]typeMethod)),
interfaces: make(map[string]*ast.InterfaceType),
structs: make(map[string]*ast.StructType),
types: make(map[string]ast.Expr),
functions: make(map[string]typeMethod),
consts: make(map[string]*ExprType),
vars: make(map[string]*ExprType),
features: make(map[string](token.Pos)),
sep: ", ",
}
}
func (p *Package) Features() (fs []string) {
for f, ps := range p.features {
if apiShowpos {
fs = append(fs, f+p.sep+strconv.Itoa(int(ps)))
} else {
fs = append(fs, f)
}
}
sort.Strings(fs)
return
}
func (p *Package) findType(name string) ast.Expr {
for k, v := range p.interfaces {
if k == name {
return v
}
}
for k, v := range p.structs {
if k == name {
return v
}
}
for k, v := range p.types {
if k == name {
return v
}
}
return nil
}
func funcRetType(ft *ast.FuncType, index int) ast.Expr {
if ft.Results != nil {
pos := 0
for _, fi := range ft.Results.List {
if fi.Names == nil {
if pos == index {
return fi.Type
}
pos++
} else {
for _ = range fi.Names {
if pos == index {
return fi.Type
}
pos++
}
}
}
}
return nil
}
func findFunction(funcs []*doc.Func, name string) (*ast.Ident, *ast.FuncType) {
for _, f := range funcs {
if f.Name == name {
return &ast.Ident{Name: name, NamePos: f.Decl.Pos()}, f.Decl.Type
}
}
return nil, nil
}
func (p *Package) findSelectorType(name string) ast.Expr {
if t, ok := p.vars[name]; ok {
return &ast.Ident{
NamePos: t.X.Pos(),
Name: t.T,
}
}
if t, ok := p.consts[name]; ok {
return &ast.Ident{
NamePos: t.X.Pos(),
Name: t.T,
}
}
if t, ok := p.functions[name]; ok {
return t.ft
}
for k, v := range p.structs {
if k == name {
return &ast.Ident{
NamePos: v.Pos(),
Name: name,
}
}
}
for k, v := range p.interfaces {
if k == name {
return &ast.Ident{
NamePos: v.Pos(),
Name: name,
}
}
}
for k, v := range p.types {
if k == name {
return v
}
}
return nil
}
func (p *Package) findCallFunc(name string) ast.Expr {
if fn, ok := p.functions[name]; ok {
return fn.ft
}
if s, ok := p.structs[name]; ok {
return s
}
if t, ok := p.types[name]; ok {
return t
}
if v, ok := p.vars[name]; ok {
if strings.HasPrefix(v.T, "func(") {
e, err := parser.ParseExpr(v.T + "{}")
if err == nil {
return e
}
}
}
return nil
}
func (p *Package) findCallType(name string, index int) ast.Expr {
if fn, ok := p.functions[name]; ok {
return funcRetType(fn.ft, index)
}
if s, ok := p.structs[name]; ok {
return &ast.Ident{
NamePos: s.Pos(),
Name: name,
}
}
if t, ok := p.types[name]; ok {
return &ast.Ident{
NamePos: t.Pos(),
Name: name,
}
}
return nil
}
func (p *Package) findMethod(typ, name string) (*ast.Ident, *ast.FuncType) {
if t, ok := p.interfaces[typ]; ok && t.Methods != nil {
for _, fd := range t.Methods.List {
switch ft := fd.Type.(type) {
case *ast.FuncType:
for _, ident := range fd.Names {
if ident.Name == name {
return ident, ft
}
}
}
}
}
for k, v := range p.interfaceMethods {
if k == typ {
for _, m := range v {
if m.name == name {
return &ast.Ident{Name: name, NamePos: m.pos}, m.ft
}
}
}
}
if p.dpkg == nil {
return nil, nil
}
for _, t := range p.dpkg.Types {
if t.Name == typ {
return findFunction(t.Methods, name)
}
}
return nil, nil
}
type Walker struct {
context *build.Context
fset *token.FileSet
scope []string
// features map[string](token.Pos) // set
lastConstType string
curPackageName string
sep string
ctxName string
curPackage *Package
constDep map[string]*ExprType // key's const identifier has type of future value const identifier
packageState map[string]loadState
packageMap map[string]*Package
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
cursorInfo *CursorInfo
localvar map[string]*ExprType
}
func NewWalker() *Walker {
return &Walker{
fset: token.NewFileSet(),
// features: make(map[string]token.Pos),
packageState: make(map[string]loadState),
interfaces: make(map[pkgSymbol]*ast.InterfaceType),
packageMap: make(map[string]*Package),
selectorFullPkg: make(map[string]string),
wantedPkg: make(map[string]bool),
localvar: make(map[string]*ExprType),
sep: ", ",
}
}
// loadState is the state of a package's parsing.
type loadState int
const (
notLoaded loadState = iota
loading
loaded
)
func (w *Walker) Features(ctx string) (fs []string) {
for pkg, p := range w.packageMap {
if w.wantedPkg[p.name] {
if ctx == "" || strings.HasPrefix(pkg, ctx) {
fs = append(fs, p.Features()...)
}
}
}
sort.Strings(fs)
return
}
// 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
}
func (w *Walker) findPackage(pkg string) *Package {
if full, ok := w.selectorFullPkg[pkg]; ok {
if w.ctxName != "" {
ctxName := w.ctxName + full
for k, v := range w.packageMap {
if k == ctxName {
return v
}
}
}
for k, v := range w.packageMap {
if k == full {
return v
}
}
}
return nil
}
func (w *Walker) findPackageOSArch(pkg string) *Package {
if full, ok := w.selectorFullPkg[pkg]; ok {
ctxName := osArchName(w.context) + ":" + full
for k, v := range w.packageMap {
if k == ctxName {
return v
}
}
}
return nil
}
// WalkPackage walks all files in package `name'.
// WalkPackage does nothing if the package has already been loaded.
func (w *Walker) WalkPackage(pkg string) {
if build.IsLocalImport(pkg) {
wd, err := os.Getwd()
if err != nil {
if apiVerbose {
log.Println(err)
}
return
}
dir := filepath.Clean(filepath.Join(wd, pkg))
bp, err := w.context.ImportDir(dir, 0)
if err != nil {
if apiVerbose {
log.Println(err)
}
return
}
if w.wantedPkg[pkg] == true {
w.wantedPkg[bp.Name] = true
delete(w.wantedPkg, pkg)
}
if w.cursorInfo != nil && w.cursorInfo.pkg == pkg {
w.cursorInfo.pkg = bp.Name
}
w.WalkPackageDir(bp.Name, bp.Dir, bp)
} else if filepath.IsAbs(pkg) {
bp, err := build.ImportDir(pkg, 0)
if err != nil {
if apiVerbose {
log.Println(err)
}
}
if w.wantedPkg[pkg] == true {
w.wantedPkg[bp.Name] = true
delete(w.wantedPkg, pkg)
}
if w.cursorInfo != nil && w.cursorInfo.pkg == pkg {
w.cursorInfo.pkg = bp.Name
}
w.WalkPackageDir(bp.Name, bp.Dir, bp)
} else {
bp, err := build.Import(pkg, "", build.FindOnly)
if err != nil {
if apiVerbose {
log.Println(err)
}
return
}
w.WalkPackageDir(pkg, bp.Dir, nil)
}
}
func (w *Walker) WalkPackageDir(name string, dir string, bp *build.Package) {
ctxName := w.ctxName + name
curName := name
switch w.packageState[ctxName] {
case loading:
log.Fatalf("import cycle loading package %q?", name)
return
case loaded:
return
}
w.packageState[ctxName] = loading
w.selectorFullPkg[name] = name
defer func() {
w.packageState[ctxName] = loaded
}()
sname := name[strings.LastIndexAny(name, ".-/\\")+1:]
apkg := &ast.Package{
Files: make(map[string]*ast.File),
}
if bp == nil {
bp, _ = w.context.ImportDir(dir, 0)
}
if bp == nil {
return
}
if w.ctxName != "" {
isCgo := (len(bp.CgoFiles) > 0) && w.context.CgoEnabled
if isCgo {
curName = ctxName
} else {
isOSArch := false
for _, file := range bp.GoFiles {
if isOSArchFile(w.context, file) {
isOSArch = true
break
}
}
var p *Package
if isOSArch {
curName = osArchName(w.context) + ":" + name
p = w.findPackageOSArch(name)
} else {
curName = name
p = w.findPackage(name)
}
if p != nil {
if apiImportParser {
for _, dep := range p.deps {
if _, ok := w.packageState[dep]; ok {
continue
}
w.WalkPackage(dep)
}
}
w.packageMap[ctxName] = p
return
}
}
}
files := append(append([]string{}, bp.GoFiles...), bp.CgoFiles...)
if w.cursorInfo != nil && w.cursorInfo.pkg == name {
files = append(files, bp.TestGoFiles...)
for _, v := range bp.XTestGoFiles {
if v == w.cursorInfo.file {
var xbp build.Package
xbp.Name = name + "_test"
xbp.GoFiles = append(xbp.GoFiles, bp.XTestGoFiles...)
w.cursorInfo.pkg = xbp.Name
w.WalkPackageDir(xbp.Name, dir, &xbp)
break
}
}
}
if len(files) == 0 {
if apiVerbose {
log.Println("no Go source files in", bp.Dir)
}
return
}
var deps []string
for _, file := range files {
var src interface{} = nil
if w.cursorInfo != nil &&
w.cursorInfo.pkg == name &&
w.cursorInfo.file == file &&
w.cursorInfo.std {
src = w.cursorInfo.src
}
f, err := parser.ParseFile(w.fset, filepath.Join(dir, file), src, 0)
if err != nil {
if apiVerbose {
log.Printf("error parsing package %s, file %s: %v", name, file, err)
}
}
if sname != f.Name.Name {
continue
}
apkg.Files[file] = f
if apiImportParser {
deps = fileDeps(f)
for _, dep := range deps {
if _, ok := w.packageState[dep]; ok {
continue
}
w.WalkPackage(dep)
}
}
if apiShowpos && w.wantedPkg[name] {
tf := w.fset.File(f.Pos())
if tf != nil {
fmt.Printf("pos %s%s%s%s%d%s%d\n", name, w.sep, filepath.Join(dir, file), w.sep, tf.Base(), w.sep, tf.Size())
}
}
}
/* else {
fdir, err := os.Open(dir)
if err != nil {
log.Fatalln(err)
}
infos, err := fdir.Readdir(-1)
fdir.Close()
if err != nil {
log.Fatalln(err)
}
for _, info := range infos {
if info.IsDir() {
continue
}
file := info.Name()
if strings.HasPrefix(file, "_") || strings.HasSuffix(file, "_test.go") {
continue
}
if strings.HasSuffix(file, ".go") {
f, err := parser.ParseFile(w.fset, filepath.Join(dir, file), nil, 0)
if err != nil {
if apiVerbose {
log.Printf("error parsing package %s, file %s: %v", name, file, err)
}
continue
}
if f.Name.Name != sname {
continue
}
apkg.Files[file] = f
if apiImportParser {
for _, dep := range fileDeps(f) {
w.WalkPackage(dep)
}
}
if apiShowpos && w.wantedPkg[name] {
tf := w.fset.File(f.Pos())
if tf != nil {
fmt.Printf("pos %s%s%s%s%d:%d\n", name, w.sep, filepath.Join(dir, file), w.sep, tf.Base(), tf.Base()+tf.Size())
}
}
}
}
}*/
if curName != ctxName {
w.packageState[curName] = loading
defer func() {
w.packageState[curName] = loaded
}()
}
if apiVerbose {
log.Printf("package %s => %s, %v", ctxName, curName, w.wantedPkg[curName])
}
pop := w.pushScope("pkg " + name)
defer pop()
w.curPackageName = curName
w.constDep = map[string]*ExprType{}
w.curPackage = NewPackage()
w.curPackage.apkg = apkg
w.curPackage.name = name
w.curPackage.dir = dir
w.curPackage.deps = deps
w.curPackage.sep = w.sep
w.packageMap[curName] = w.curPackage
w.packageMap[ctxName] = w.curPackage
for _, afile := range apkg.Files {
w.recordTypes(afile)
}
// Register all function declarations first.
for _, afile := range apkg.Files {
for _, di := range afile.Decls {
if d, ok := di.(*ast.FuncDecl); ok {
if !w.isExtract(d.Name.Name) {
continue
}
w.peekFuncDecl(d)
}
}
}
for _, afile := range apkg.Files {
w.walkFile(afile)
}
w.resolveConstantDeps()
if w.cursorInfo != nil && w.cursorInfo.pkg == name {
for k, v := range apkg.Files {
if k == w.cursorInfo.file {
f := w.fset.File(v.Pos())
if f == nil {
log.Fatalf("error fset postion %v", v.Pos())
}
info, err := w.lookupFile(v, token.Pos(f.Base())+w.cursorInfo.pos-1)
if err != nil {
log.Fatalln("lookup error,", err)
} else {
if info != nil && info.Kind == KindImport {
for _, is := range v.Imports {
fpath, err := strconv.Unquote(is.Path.Value)
if err == nil {
if info.Name == path.Base(fpath) {
info.T = is.Path
}
}
}
}
w.cursorInfo.info = info
}
break
}
}
return
}
// Now that we're done walking types, vars and consts
// in the *ast.Package, use go/doc to do the rest
// (functions and methods). This is done here because
// go/doc is destructive. We can't use the
// *ast.Package after this.
var mode doc.Mode
if apiAllmethods {
mode |= doc.AllMethods
}
if apiAlldecls && w.wantedPkg[w.ctxName] {
mode |= doc.AllDecls
}
dpkg := doc.New(apkg, name, mode)
w.curPackage.dpkg = dpkg
if w.wantedPkg[name] != true {
return
}
for _, t := range dpkg.Types {
// Move funcs up to the top-level, not hiding in the Types.
dpkg.Funcs = append(dpkg.Funcs, t.Funcs...)
for _, m := range t.Methods {
w.walkFuncDecl(m.Decl)
}
}
for _, f := range dpkg.Funcs {
w.walkFuncDecl(f.Decl)
}
}
// pushScope enters a new scope (walking a package, type, node, etc)
// and returns a function that will leave the scope (with sanity checking
// for mismatched pushes & pops)
func (w *Walker) pushScope(name string) (popFunc func()) {
w.scope = append(w.scope, name)
return func() {
if len(w.scope) == 0 {
log.Fatalf("attempt to leave scope %q with empty scope list", name)
}
if w.scope[len(w.scope)-1] != name {
log.Fatalf("attempt to leave scope %q, but scope is currently %#v", name, w.scope)
}
w.scope = w.scope[:len(w.scope)-1]
}
}
func (w *Walker) recordTypes(file *ast.File) {
cur := w.curPackage
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
switch t := ts.Type.(type) {
case *ast.InterfaceType:
if isExtract(name) {
w.noteInterface(name, t)
}
cur.interfaces[name] = t
case *ast.StructType:
cur.structs[name] = t
default:
cur.types[name] = ts.Type
}
}
}
}
}
}
func inRange(node ast.Node, p token.Pos) bool {
if node == nil {
return false
}
return p >= node.Pos() && p <= node.End()
}
func (w *Walker) lookupLabel(body *ast.BlockStmt, name string) (*TypeInfo, error) {
for _, stmt := range body.List {
switch v := stmt.(type) {
case *ast.BlockStmt:
return w.lookupLabel(v, name)
case *ast.LabeledStmt:
return &TypeInfo{Kind: KindLabel, Name: v.Label.Name, Type: "branch", T: v.Label}, nil
}
}
return nil, nil
}
func (w *Walker) lookupFile(file *ast.File, p token.Pos) (*TypeInfo, error) {
if inRange(file.Name, p) {
return &TypeInfo{Kind: KindPackage, X: file.Name, Name: file.Name.Name, Type: file.Name.Name, T: file.Name}, nil
}
for _, di := range file.Decls {
switch d := di.(type) {
case *ast.GenDecl:
if inRange(d, p) {
return w.lookupDecl(d, p, false)
}
case *ast.FuncDecl:
if inRange(d, p) {
info, err := w.lookupDecl(d, p, false)
if info != nil && info.Kind == KindBranch {
return w.lookupLabel(d.Body, info.Name)
}
return info, err
}
if d.Body != nil && inRange(d.Body, p) {
return w.lookupStmt(d.Body, p)
}
default:
return nil, fmt.Errorf("un parser decl %T", di)
}
}
return nil, fmt.Errorf("un find cursor %v", w.fset.Position(p))
}
func (w *Walker) isExtract(name string) bool {
if w.wantedPkg[w.curPackageName] || apiAlldecls {
return true
}
return ast.IsExported(name)
}
func (w *Walker) isType(typ string) *ExprType {
pos := strings.Index(typ, ".")
if pos != -1 {
pkg := typ[:pos]
typ = typ[pos+1:]
if p := w.findPackage(pkg); p != nil {
if t, ok := p.types[typ]; ok {
if r := w.isType(typ); r != nil {
return r
}
return &ExprType{X: t, T: w.pkgRetType(pkg, w.nodeString(t))}
}
}
return nil
}
if t, ok := w.curPackage.types[typ]; ok {
if r := w.isType(w.nodeString(t)); r != nil {
return r
}
return &ExprType{X: t, T: w.nodeString(t)}
}
return nil
}
func (w *Walker) lookupStmt(vi ast.Stmt, p token.Pos) (*TypeInfo, error) {
if vi == nil {
return nil, nil
}
switch v := vi.(type) {
case *ast.BadStmt:
//
case *ast.EmptyStmt:
//
case *ast.LabeledStmt:
if inRange(v.Label, p) {
return &TypeInfo{Kind: KindLabel, Name: v.Label.Name}, nil
}
return w.lookupStmt(v.Stmt, p)
//
case *ast.DeclStmt:
return w.lookupDecl(v.Decl, p, true)
case *ast.AssignStmt:
if len(v.Lhs) == len(v.Rhs) {
for i := 0; i < len(v.Lhs); i++ {
switch lt := v.Lhs[i].(type) {
case *ast.Ident:
typ, err := w.varValueType(v.Rhs[i], 0)
if err == nil && v.Tok == token.DEFINE {
w.localvar[lt.Name] = &ExprType{T: typ, X: lt}
} else if apiVerbose {
log.Println(err)
}
}
if inRange(v.Lhs[i], p) {
return w.lookupExprInfo(v.Lhs[i], p)
} else if inRange(v.Rhs[i], p) {
return w.lookupExprInfo(v.Rhs[i], p)
}
if fl, ok := v.Rhs[i].(*ast.FuncLit); ok {
if inRange(fl, p) {
return w.lookupStmt(fl.Body, p)
}
}
}
} else if len(v.Rhs) == 1 {
for i := 0; i < len(v.Lhs); i++ {
switch lt := v.Lhs[i].(type) {
case *ast.Ident:
typ, err := w.varValueType(v.Rhs[0], i)
if err == nil && v.Tok == token.DEFINE {
w.localvar[lt.Name] = &ExprType{T: typ, X: lt}
} else if apiVerbose {
log.Println(err)
}
}
if inRange(v.Lhs[i], p) {
return w.lookupExprInfo(v.Lhs[i], p)
} else if inRange(v.Rhs[0], p) {
return w.lookupExprInfo(v.Rhs[0], p)
}
if fl, ok := v.Rhs[0].(*ast.FuncLit); ok {
if inRange(fl, p) {
return w.lookupStmt(fl.Body, p)
}
}
}
}
return nil, nil
case *ast.ExprStmt:
return w.lookupExprInfo(v.X, p)
case *ast.BlockStmt:
for _, st := range v.List {
if inRange(st, p) {
return w.lookupStmt(st, p)
}
_, err := w.lookupStmt(st, p)
if err != nil {
log.Println(err)
}
}
case *ast.IfStmt:
if inRange(v.Init, p) {
return w.lookupStmt(v.Init, p)
} else {
w.lookupStmt(v.Init, p)
}
if inRange(v.Cond, p) {
return w.lookupExprInfo(v.Cond, p)
} else if inRange(v.Body, p) {
return w.lookupStmt(v.Body, p)
} else if inRange(v.Else, p) {
return w.lookupStmt(v.Else, p)
}
case *ast.SendStmt:
if inRange(v.Chan, p) {
return w.lookupExprInfo(v.Chan, p)
} else if inRange(v.Value, p) {
return w.lookupExprInfo(v.Value, p)
}
case *ast.IncDecStmt:
return w.lookupExprInfo(v.X, p)
case *ast.GoStmt:
return w.lookupExprInfo(v.Call, p)
case *ast.DeferStmt:
return w.lookupExprInfo(v.Call, p)
case *ast.ReturnStmt:
for _, r := range v.Results {
if inRange(r, p) {
return w.lookupExprInfo(r, p)
}
}
case *ast.BranchStmt:
if inRange(v.Label, p) {
return &TypeInfo{Kind: KindBranch, Name: v.Label.Name, Type: "label", T: v.Label}, nil
}
//
case *ast.CaseClause:
for _, r := range v.List {
if inRange(r, p) {
return w.lookupExprInfo(r, p)
}
}
for _, body := range v.Body {
if inRange(body, p) {
return w.lookupStmt(body, p)
} else {
w.lookupStmt(body, p)
}
}
case *ast.SwitchStmt:
if inRange(v.Init, p) {
return w.lookupStmt(v.Init, p)
} else {
w.lookupStmt(v.Init, p)
}
if inRange(v.Tag, p) {
return w.lookupExprInfo(v.Tag, p)
} else if inRange(v.Body, p) {
return w.lookupStmt(v.Body, p)
}
case *ast.TypeSwitchStmt:
if inRange(v.Assign, p) {
return w.lookupStmt(v.Assign, p)
} else {
w.lookupStmt(v.Assign, p)
}
if inRange(v.Init, p) {
return w.lookupStmt(v.Init, p)
} else {
w.lookupStmt(v.Init, p)
}
var vs string
if as, ok := v.Assign.(*ast.AssignStmt); ok {
if len(as.Lhs) == 1 {
vs = w.nodeString(as.Lhs[0])
}
}
if inRange(v.Body, p) {
for _, s := range v.Body.List {
if inRange(s, p) {
switch cs := s.(type) {
case *ast.CaseClause:
for _, r := range cs.List {
if inRange(r, p) {
return w.lookupExprInfo(r, p)
} else if vs != "" {
typ, err := w.varValueType(r, 0)
if err == nil {
w.localvar[vs] = &ExprType{T: typ, X: r}
}
}
}
for _, body := range cs.Body {
if inRange(body, p) {
return w.lookupStmt(body, p)
} else {
w.lookupStmt(body, p)
}
}
default:
return w.lookupStmt(cs, p)
}
}
}
}
case *ast.CommClause:
if inRange(v.Comm, p) {
return w.lookupStmt(v.Comm, p)
}
for _, body := range v.Body {
if inRange(body, p) {
return w.lookupStmt(body, p)
}
}
case *ast.SelectStmt:
if inRange(v.Body, p) {
return w.lookupStmt(v.Body, p)
}
case *ast.ForStmt:
if inRange(v.Init, p) {
return w.lookupStmt(v.Init, p)
} else {
w.lookupStmt(v.Init, p)
}
if inRange(v.Cond, p) {
return w.lookupExprInfo(v.Cond, p)
} else if inRange(v.Body, p) {
return w.lookupStmt(v.Body, p)
} else if inRange(v.Post, p) {
return w.lookupStmt(v.Post, p)
}
case *ast.RangeStmt:
if inRange(v.X, p) {
return w.lookupExprInfo(v.X, p)
} else if inRange(v.Key, p) {
return &TypeInfo{Kind: KindBuiltin, Name: w.nodeString(v.Key), Type: "int"}, nil
} else if inRange(v.Value, p) {
typ, err := w.lookupExprInfo(v.X, p)
if typ != nil {
typ.Name = w.nodeString(v.Value)
return typ, err
}
} else {
typ, err := w.varValueType(v.X, 0)
//check is type
if t := w.isType(typ); t != nil {
typ = t.T
}
if err == nil {
var kt, vt string
if strings.HasPrefix(typ, "[]") {
kt = "int"
vt = typ[2:]
} else if strings.HasPrefix(typ, "map[") {
node, err := parser.ParseExpr(typ + "{}")
if err == nil {
if cl, ok := node.(*ast.CompositeLit); ok {
if m, ok := cl.Type.(*ast.MapType); ok {
kt = w.nodeString(w.namelessType(m.Key))
vt = w.nodeString(w.namelessType(m.Value))
}
}
}
}
if inRange(v.Key, p) {
return &TypeInfo{Kind: KindVar, X: v.Key, Name: w.nodeString(v.Key), T: v.X, Type: kt}, nil
} else if inRange(v.Value, p) {
return &TypeInfo{Kind: KindVar, X: v.Value, Name: w.nodeString(v.Value), T: v.X, Type: vt}, nil
}
if key, ok := v.Key.(*ast.Ident); ok {
w.localvar[key.Name] = &ExprType{T: kt, X: v.Key}
}
if value, ok := v.Value.(*ast.Ident); ok {
w.localvar[value.Name] = &ExprType{T: vt, X: v.Value}
}
}
}
if inRange(v.Body, p) {
return w.lookupStmt(v.Body, p)
}
}
return nil, nil //fmt.Errorf("not lookup stmt %v %T", vi, vi)
}
func (w *Walker) lookupVar(vs *ast.ValueSpec, p token.Pos, local bool) (*TypeInfo, error) {
if inRange(vs.Type, p) {
return w.lookupExprInfo(vs.Type, p)
}
for _, v := range vs.Values {
if inRange(v, p) {
return w.lookupExprInfo(v, p)
}
}
if vs.Type != nil {
typ := w.nodeString(vs.Type)
for _, ident := range vs.Names {
if local {
w.localvar[ident.Name] = &ExprType{T: typ, X: ident}
}
if inRange(ident, p) {
return &TypeInfo{Kind: KindVar, X: ident, Name: ident.Name, T: vs.Type, Type: typ}, nil
}
}
} else if len(vs.Names) == len(vs.Values) {
for n, ident := range vs.Names {
typ := ""
if !local {
if t, ok := w.curPackage.vars[ident.Name]; ok {
typ = t.T
}
} else {
typ, err := w.varValueType(vs.Values[n], n)
if err != nil {
if apiVerbose {
log.Printf("unknown type of variable2 %q, type %T, error = %v, pos=%s",
ident.Name, vs.Values[n], err, w.fset.Position(vs.Pos()))
}
typ = "unknown-type"
}
w.localvar[ident.Name] = &ExprType{T: typ, X: ident}
}
if inRange(ident, p) {
return &TypeInfo{Kind: KindVar, X: ident, Name: ident.Name, T: ident, Type: typ}, nil
}
}
} else if len(vs.Values) == 1 {
for n, ident := range vs.Names {
typ := ""
if !local {
if t, ok := w.curPackage.vars[ident.Name]; ok {
typ = t.T
}
} else {
typ, err := w.varValueType(vs.Values[0], n)
if err != nil {
if apiVerbose {
log.Printf("unknown type of variable3 %q, type %T, error = %v, pos=%s",
ident.Name, vs.Values[0], err, w.fset.Position(vs.Pos()))
}
typ = "unknown-type"
}
w.localvar[ident.Name] = &ExprType{T: typ, X: ident}
}
if inRange(ident, p) {
return &TypeInfo{Kind: KindVar, X: ident, Name: ident.Name, T: ident, Type: typ}, nil
}
}
}
return nil, fmt.Errorf("not lookup var local:%v value:%v type:s%T", local, w.nodeString(vs), vs)
}
func (w *Walker) lookupConst(vs *ast.ValueSpec, p token.Pos, local bool) (*TypeInfo, error) {
if inRange(vs.Type, p) {
return w.lookupExprInfo(vs.Type, p)
}
for _, ident := range vs.Names {
typ := ""
if !local {
if t, ok := w.curPackage.consts[ident.Name]; ok {
typ = t.T
}
} else {
litType := ""
if vs.Type != nil {
litType = w.nodeString(vs.Type)
} else {
litType = w.lastConstType
if vs.Values != nil {
if len(vs.Values) != 1 {
if apiVerbose {
log.Printf("const %q, values: %#v", ident.Name, vs.Values)
}
return nil, nil
}
var err error
litType, err = w.constValueType(vs.Values[0])
if err != nil {
if apiVerbose {
log.Printf("unknown kind in const %q (%T): %v", ident.Name, vs.Values[0], err)
}
litType = "unknown-type"
}
}
}
w.lastConstType = litType
typ = litType
w.localvar[ident.Name] = &ExprType{T: typ, X: ident}
}
if inRange(ident, p) {
return &TypeInfo{Kind: KindConst, X: ident, Name: ident.Name, T: ident, Type: typ}, nil
}
}
return nil, nil
}
func (w *Walker) lookupType(ts *ast.TypeSpec, p token.Pos, local bool) (*TypeInfo, error) {
switch t := ts.Type.(type) {
case *ast.StructType:
if inRange(t.Fields, p) {
for _, fd := range t.Fields.List {
if inRange(fd.Type, p) {
return w.lookupExprInfo(fd.Type, p)
}
for _, ident := range fd.Names {
if inRange(ident, p) {
return &TypeInfo{Kind: KindField, X: ident, Name: ts.Name.Name + "." + ident.Name, T: fd.Type, Type: w.nodeString(w.namelessType(fd.Type))}, nil
}
}
}
}
return &TypeInfo{Kind: KindStruct, X: ts.Name, Name: ts.Name.Name, T: ts.Type, Type: "struct"}, nil
case *ast.InterfaceType:
if inRange(t.Methods, p) {
for _, fd := range t.Methods.List {
for _, ident := range fd.Names {
if inRange(ident, p) {
return &TypeInfo{Kind: KindMethod, X: ident, Name: ts.Name.Name + "." + ident.Name, T: ident, Type: w.nodeString(w.namelessType(fd.Type))}, nil
}
}
if inRange(fd.Type, p) {
return w.lookupExprInfo(fd.Type, p)
}
}
}
return &TypeInfo{Kind: KindInterface, X: ts.Name, Name: ts.Name.Name, T: ts.Type, Type: "interface"}, nil
default:
return &TypeInfo{Kind: KindType, X: ts.Name, Name: ts.Name.Name, T: ts.Type, Type: w.nodeString(w.namelessType(ts.Type))}, nil
}
return nil, nil
}
func (w *Walker) lookupDecl(di ast.Decl, p token.Pos, local bool) (*TypeInfo, error) {
switch d := di.(type) {
case *ast.GenDecl:
switch d.Tok {
case token.IMPORT:
for _, sp := range d.Specs {
is := sp.(*ast.ImportSpec)
fpath, err := strconv.Unquote(is.Path.Value)
if err != nil {
return nil, err
}
name := path.Base(fpath)
if is.Name != nil {
name = is.Name.Name
}
if inRange(sp, p) {
return &TypeInfo{Kind: KindImport, X: is.Name, Name: name, T: is.Name, Type: fpath}, nil
}
}
case token.CONST:
for _, sp := range d.Specs {
if inRange(sp, p) {
return w.lookupConst(sp.(*ast.ValueSpec), p, local)
} else {
w.lookupConst(sp.(*ast.ValueSpec), p, local)
}
}
return nil, nil
case token.TYPE:
for _, sp := range d.Specs {
if inRange(sp, p) {
return w.lookupType(sp.(*ast.TypeSpec), p, local)
} else {
w.lookupType(sp.(*ast.TypeSpec), p, local)
}
}
case token.VAR:
for _, sp := range d.Specs {
if inRange(sp, p) {
return w.lookupVar(sp.(*ast.ValueSpec), p, local)
} else {
w.lookupVar(sp.(*ast.ValueSpec), p, local)
}
}
return nil, nil
default:
return nil, fmt.Errorf("unknown token type %d %T in GenDecl", d.Tok, d)
}
case *ast.FuncDecl:
if d.Type.Params != nil {
for _, fd := range d.Type.Params.List {
if inRange(fd, p) {
return w.lookupExprInfo(fd.Type, p)
}
for _, ident := range fd.Names {
if inRange(ident, p) {
info, err := w.lookupExprInfo(fd.Type, p)
if err == nil {
return &TypeInfo{Kind: KindParam, X: ident, Name: ident.Name, T: info.T, Type: info.Type}, nil
}
}
typ, err := w.varValueType(fd.Type, 0)
if err == nil {
w.localvar[ident.Name] = &ExprType{T: typ, X: ident}
} else if apiVerbose {
log.Println(err)
}
}
}
}
if d.Type.Results != nil {
for _, fd := range d.Type.Results.List {
if inRange(fd, p) {
return w.lookupExprInfo(fd.Type, p)
}
for _, ident := range fd.Names {
typ, err := w.varValueType(fd.Type, 0)
if err == nil {
w.localvar[ident.Name] = &ExprType{T: typ, X: ident}
}
}
}
}
if d.Recv != nil {
for _, fd := range d.Recv.List {
if inRange(fd, p) {
return w.lookupExprInfo(fd.Type, p)
}
for _, ident := range fd.Names {
w.localvar[ident.Name] = &ExprType{T: w.nodeString(fd.Type), X: ident}
}
}
}
if inRange(d.Body, p) {
return w.lookupStmt(d.Body, p)
}
var fname = d.Name.Name
kind := KindFunc
if d.Recv != nil {
recvTypeName, imp := baseTypeName(d.Recv.List[0].Type)
if imp {
return nil, nil
}
fname = recvTypeName + "." + d.Name.Name
kind = KindMethod
}
return &TypeInfo{Kind: kind, X: d.Name, Name: fname, T: d.Type, Type: w.nodeString(w.namelessType(d.Type))}, nil
default:
return nil, fmt.Errorf("unhandled %T, %#v\n", di, di)
}
return nil, fmt.Errorf("not lookupDecl %v %T", w.nodeString(di), di)
}
func (w *Walker) lookupExprInfo(vi ast.Expr, p token.Pos) (*TypeInfo, error) {
_, info, err := w.lookupExpr(vi, p)
return info, err
}
// lookupExpr , return name,info,error
func (w *Walker) lookupExpr(vi ast.Expr, p token.Pos) (string, *TypeInfo, error) {
if apiVerbose {
log.Printf("lookup expr %v %T", w.nodeString(vi), vi)
}
switch v := vi.(type) {
case *ast.BasicLit:
litType, ok := varType[v.Kind]
if !ok {
return "", nil, fmt.Errorf("unknown basic literal kind %#v", v)
}
name := v.Value
if len(name) >= 128 {
name = name[:128] + "..."
}
return litType, &TypeInfo{Kind: KindBuiltin, X: v, Name: name, T: v, Type: litType}, nil
case *ast.StarExpr:
s, info, err := w.lookupExpr(v.X, p)
if err != nil {
return "", nil, err
}
return "*" + s, &TypeInfo{Kind: info.Kind, X: v, Name: "*" + info.Name, T: info.T, Type: "*" + info.Type}, err
case *ast.InterfaceType:
return "interface{}", &TypeInfo{Kind: KindInterface, X: v, Name: w.nodeString(v), T: v, Type: "interface{}"}, nil
case *ast.Ellipsis:
s, info, err := w.lookupExpr(v.Elt, p)
if err != nil {
return "", nil, err
}
return "[]" + s, &TypeInfo{Kind: KindArray, X: v.Elt, Name: "..." + s, T: info.T, Type: "[]" + info.Type}, nil
case *ast.KeyValueExpr:
if inRange(v.Key, p) {
return w.lookupExpr(v.Key, p)
} else if inRange(v.Value, p) {
return w.lookupExpr(v.Value, p)
}
case *ast.CompositeLit:
typ, err := w.varValueType(v.Type, 0)
if err == nil {
typ = strings.TrimLeft(typ, "*")
if strings.HasPrefix(typ, "[]") {
typ = strings.TrimLeft(typ[2:], "*")
}
pos := strings.Index(typ, ".")
var pt *Package = w.curPackage
var pkgdot string
if pos != -1 {
pkg := typ[:pos]
typ = typ[pos+1:]
pt = w.findPackage(pkg)
if pt != nil {
pkgdot = pkg + "."
}
}
if pt != nil {
if ss, ok := pt.structs[typ]; ok {
for _, elt := range v.Elts {
if inRange(elt, p) {
if cl, ok := elt.(*ast.CompositeLit); ok {
for _, elt := range cl.Elts {
if inRange(elt, p) {
if kv, ok := elt.(*ast.KeyValueExpr); ok {
if inRange(kv.Key, p) {
n, t := w.findStructField(ss, w.nodeString(kv.Key))
if n != nil {
return pkgdot + typ + "." + w.nodeString(kv.Key), &TypeInfo{Kind: KindField, X: kv.Key, Name: pkgdot + typ + "." + w.nodeString(kv.Key), T: n, Type: w.nodeString(w.namelessType(t))}, nil
}
} else if inRange(kv.Value, p) {
return w.lookupExpr(kv.Value, p)
}
}
}
}
}
if kv, ok := elt.(*ast.KeyValueExpr); ok {
if inRange(kv.Key, p) {
n, t := w.findStructField(ss, w.nodeString(kv.Key))
if n != nil {
return typ + "." + w.nodeString(kv.Key), &TypeInfo{Kind: KindField, X: kv.Key, Name: typ + "." + w.nodeString(kv.Key), T: n, Type: w.nodeString(w.namelessType(t))}, nil
}
} else if inRange(kv.Value, p) {
return w.lookupExpr(kv.Value, p)
}
}
}
}
}
}
}
for _, elt := range v.Elts {
if inRange(elt, p) {
return w.lookupExpr(elt, p)
}
}
return w.lookupExpr(v.Type, p)
case *ast.UnaryExpr:
s, info, err := w.lookupExpr(v.X, p)
return v.Op.String() + s, info, err
case *ast.TypeAssertExpr:
if inRange(v.X, p) {
return w.lookupExpr(v.X, p)
}
return w.lookupExpr(v.Type, p)
case *ast.BinaryExpr:
if inRange(v.X, p) {
return w.lookupExpr(v.X, p)
} else if inRange(v.Y, p) {
return w.lookupExpr(v.Y, p)
}
return "", nil, nil
case *ast.CallExpr:
for _, arg := range v.Args {
if inRange(arg, p) {
return w.lookupExpr(arg, p)
}
}
switch ft := v.Fun.(type) {
case *ast.Ident:
if typ, ok := w.localvar[ft.Name]; ok {
return ft.Name, &TypeInfo{Kind: KindVar, X: ft, Name: ft.Name, T: typ.X, Type: typ.T}, nil
}
if typ, ok := w.curPackage.vars[ft.Name]; ok {
return ft.Name, &TypeInfo{Kind: KindVar, X: v, Name: ft.Name, T: typ.X, Type: typ.T}, nil
}
if typ, ok := w.curPackage.functions[ft.Name]; ok {
return ft.Name, &TypeInfo{Kind: KindFunc, X: ft, Name: ft.Name, T: typ.ft, Type: typ.sig}, nil
}
if typ, ok := w.curPackage.interfaces[ft.Name]; ok {
return ft.Name, &TypeInfo{Kind: KindInterface, X: ft, Name: ft.Name, T: typ, Type: w.nodeString(w.namelessType(typ))}, nil
}
if typ, ok := w.curPackage.interfaces[ft.Name]; ok {
return ft.Name, &TypeInfo{Kind: KindInterface, X: ft, Name: ft.Name, T: typ, Type: w.nodeString(w.namelessType(typ))}, nil
}
if typ, ok := w.curPackage.structs[ft.Name]; ok {
return ft.Name, &TypeInfo{Kind: KindStruct, X: ft, Name: ft.Name, T: typ, Type: w.nodeString(w.namelessType(typ))}, nil
}
if typ, ok := w.curPackage.types[ft.Name]; ok {
return ft.Name, &TypeInfo{Kind: KindType, X: ft, Name: ft.Name, T: typ, Type: w.nodeString(w.namelessType(typ))}, nil
}
if isBuiltinType(ft.Name) {
return ft.Name, &TypeInfo{Kind: KindBuiltin, X: ft, Name: ft.Name}, nil
}
return "", nil, fmt.Errorf("lookup unknown ident %v", v)
case *ast.FuncLit:
if inRange(ft.Body, p) {
info, err := w.lookupStmt(ft.Body, p)
if err == nil {
return "", info, nil
}
return "", nil, err
}
return w.lookupExpr(ft.Type, p)
case *ast.ParenExpr:
return w.lookupExpr(ft.X, p)
case *ast.SelectorExpr:
switch st := ft.X.(type) {
case *ast.Ident:
if inRange(st, p) {
return w.lookupExpr(st, p)
}
s, info, err := w.lookupExpr(st, p)
if err != nil {
return "", nil, err
}
typ := info.Type
if typ == "" {
typ = s
}
fname := typ + "." + ft.Sel.Name
typ = strings.TrimLeft(typ, "*")
if fn, ok := w.curPackage.functions[fname]; ok {
return fname, &TypeInfo{Kind: KindMethod, X: st, Name: fname, T: fn.ft, Type: w.nodeString(w.namelessType(fn.ft))}, nil
}
info, e := w.lookupFunction(typ, ft.Sel.Name)
if e != nil {
return "", nil, e
}
return fname, info, nil
case *ast.SelectorExpr:
if inRange(st.X, p) {
return w.lookupExpr(st.X, p)
}
if inRange(st, p) {
return w.lookupExpr(st, p)
}
typ, err := w.varValueType(st, 0)
if err != nil {
return "", nil, err
}
/*
typ = strings.TrimLeft(typ, "*")
if t := w.curPackage.findType(typ); t != nil {
if ss, ok := t.(*ast.StructType); ok {
for _, fi := range ss.Fields.List {
for _, n := range fi.Names {
if n.Name == st.Sel.Name {
//return fname, &TypeInfo{Kind: KindField, X: n, Name: fname, T: fi.Type, Type: w.nodeString(w.namelessType(fi.Type))}, nil
typ = w.nodeString(w.namelessType(fi.Type))
}
}
}
}
}
*/
info, e := w.lookupFunction(typ, ft.Sel.Name)
if e != nil {
return "", nil, e
}
return typ + "." + st.Sel.Name, info, nil
case *ast.CallExpr:
if inRange(st, p) {
return w.lookupExpr(st, p)
}
if info, err := w.lookupExprInfo(st, p); err == nil {
if fn, ok := info.X.(*ast.FuncType); ok {
if fn.Results.NumFields() == 1 {
info, err := w.lookupFunction(w.nodeString(fn.Results.List[0].Type), ft.Sel.Name)
if err == nil {
return info.Name, info, err
}
return "", nil, err
}
}
}
//w.lookupFunction(w.nodeString(info.X))
typ, err := w.varValueType(st, 0)
if err != nil {
return "", nil, err
}
info, e := w.lookupFunction(typ, ft.Sel.Name)
if e != nil {
return "", nil, e
}
return typ + "." + ft.Sel.Name, info, nil
case *ast.TypeAssertExpr:
if inRange(st.X, p) {
return w.lookupExpr(st.X, p)
}
typ := w.nodeString(w.namelessType(st.Type))
info, e := w.lookupFunction(typ, ft.Sel.Name)
if e != nil {
return "", nil, e
}
return typ + "." + ft.Sel.Name, info, nil
default:
return "", nil, fmt.Errorf("not find select %v %T", v, st)
}
}
return "", nil, fmt.Errorf("not find call %v %T", w.nodeString(v), v.Fun)
case *ast.SelectorExpr:
switch st := v.X.(type) {
case *ast.Ident:
if inRange(st, p) {
return w.lookupExpr(st, p)
}
info, err := w.lookupSelector(st.Name, v.Sel.Name)
if err != nil {
return "", nil, err
}
return st.Name + "." + v.Sel.Name, info, nil
// case *ast.CallExpr:
// typ, err := w.varValueType(v.X, index)
// if err == nil {
// if strings.HasPrefix(typ, "*") {
// typ = typ[1:]
// }
// t := w.curPackage.findType(typ)
// if st, ok := t.(*ast.StructType); ok {
// for _, fi := range st.Fields.List {
// for _, n := range fi.Names {
// if n.Name == v.Sel.Name {
// return w.varValueType(fi.Type, index)
// }
// }
// }
// }
// }
case *ast.SelectorExpr:
if inRange(st.X, p) {
return w.lookupExpr(st.X, p)
}
if inRange(st, p) {
return w.lookupExpr(st, p)
}
typ, err := w.varValueType(st, 0)
if err == nil {
info, err := w.lookupSelector(typ, v.Sel.Name)
if err != nil {
return "", nil, err
}
return typ + v.Sel.Name, info, nil
}
// case *ast.IndexExpr:
// typ, err := w.varValueType(st.X, 0)
// log.Println(typ, err)
// if err == nil {
// if strings.HasPrefix(typ, "[]") {
// return w.varSelectorType(typ[2:], v.Sel.Name)
// }
// }
}
return "", nil, fmt.Errorf("unknown lookup selector expr: %T %s.%s", v.X, w.nodeString(v.X), v.Sel)
// s, info, err := w.lookupExpr(v.X, p)
// if err != nil {
// return "", "", err
// }
// if strings.HasPrefix(s, "*") {
// s = s[1:]
// }
// if inRange(v.X, p) {
// return s, info, err
// }
// t := w.curPackage.findType(s)
// fname := s + "." + v.Sel.Name
// if st, ok := t.(*ast.StructType); ok {
// for _, fi := range st.Fields.List {
// for _, n := range fi.Names {
// if n.Name == v.Sel.Name {
// return fname, fmt.Sprintf("var,%s,%s,%s", fname, w.nodeString(w.namelessType(fi.Type)), w.fset.Position(n.Pos())), nil
// }
// }
// }
// }
// log.Println(">>", s)
// info, e := w.lookupSelector(s, v.Sel.Name)
// return fname, info, e
case *ast.Ident:
if typ, ok := w.localvar[v.Name]; ok {
return typ.T, &TypeInfo{Kind: KindVar, X: v, Name: v.Name, T: typ.X, Type: typ.T}, nil
}
if typ, ok := w.curPackage.interfaces[v.Name]; ok {
return v.Name, &TypeInfo{Kind: KindInterface, X: v, Name: v.Name, T: typ, Type: "interface"}, nil
}
if typ, ok := w.curPackage.structs[v.Name]; ok {
return v.Name, &TypeInfo{Kind: KindStruct, X: v, Name: v.Name, T: typ, Type: "struct"}, nil
}
if typ, ok := w.curPackage.types[v.Name]; ok {
return v.Name, &TypeInfo{Kind: KindType, X: v, Name: v.Name, T: typ, Type: v.Name}, nil
}
if typ, ok := w.curPackage.vars[v.Name]; ok {
return v.Name, &TypeInfo{Kind: KindVar, X: v, Name: v.Name, T: typ.X, Type: typ.T}, nil
}
if typ, ok := w.curPackage.consts[v.Name]; ok {
return v.Name, &TypeInfo{Kind: KindConst, X: v, Name: v.Name, T: typ.X, Type: typ.T}, nil
}
if typ, ok := w.curPackage.functions[v.Name]; ok {
return v.Name, &TypeInfo{Kind: KindFunc, X: typ.ft, Name: v.Name, T: typ.ft, Type: typ.sig}, nil
}
if p := w.findPackage(v.Name); p != nil {
return v.Name, &TypeInfo{Kind: KindImport, X: v, Name: v.Name, Type: p.name}, nil
}
if isBuiltinType(v.Name) {
return v.Name, &TypeInfo{Kind: KindBuiltin, Name: v.Name}, nil
}
return "", nil, fmt.Errorf("lookup unknown ident %v", v)
//return v.Name, &TypeInfo{Kind: KindVar, X: v, Name: v.Name, T: v, Type: v.Name}, nil
case *ast.IndexExpr:
if inRange(v.Index, p) {
return w.lookupExpr(v.Index, p)
}
return w.lookupExpr(v.X, p)
case *ast.ParenExpr:
return w.lookupExpr(v.X, p)
case *ast.FuncLit:
if inRange(v.Type, p) {
return w.lookupExpr(v.Type, p)
} else {
w.lookupExpr(v.Type, p)
}
typ, err := w.varValueType(v.Type, 0)
if err != nil {
return "", nil, err
}
info, e := w.lookupStmt(v.Body, p)
if e != nil {
return "", nil, err
}
return typ, info, nil
case *ast.FuncType:
if v.Params != nil {
for _, fd := range v.Params.List {
if inRange(fd, p) {
return w.lookupExpr(fd.Type, p)
}
for _, ident := range fd.Names {
typ, err := w.varValueType(fd.Type, 0)
if err == nil {
w.localvar[ident.Name] = &ExprType{T: typ, X: ident}
}
}
}
}
if v.Results != nil {
for _, fd := range v.Results.List {
if inRange(fd, p) {
return w.lookupExpr(fd.Type, p)
}
for _, ident := range fd.Names {
typ, err := w.varValueType(fd.Type, 0)
if err == nil {
w.localvar[ident.Name] = &ExprType{T: typ, X: ident}
}
}
}
}
return "", nil, nil
case *ast.ArrayType:
s, info, err := w.lookupExpr(v.Elt, p)
if err != nil {
return "", nil, err
}
return "[]" + s, &TypeInfo{Kind: KindArray, Name: "[]" + info.Name, Type: "[]" + info.Type, T: info.T}, nil
case *ast.SliceExpr:
if inRange(v.High, p) {
return w.lookupExpr(v.High, p)
} else if inRange(v.Low, p) {
return w.lookupExpr(v.Low, p)
}
return w.lookupExpr(v.X, p)
case *ast.MapType:
if inRange(v.Key, p) {
return w.lookupExpr(v.Key, p)
} else if inRange(v.Value, p) {
return w.lookupExpr(v.Value, p)
}
typ, err := w.varValueType(v, 0)
if err != nil {
return "", nil, err
}
return typ, &TypeInfo{Kind: KindMap, X: v, Name: w.nodeString(v), T: v, Type: typ}, nil
case *ast.ChanType:
if inRange(v.Value, p) {
return w.lookupExpr(v.Value, p)
}
typ, err := w.varValueType(v, 0)
if err != nil {
return "", nil, err
}
return typ, &TypeInfo{Kind: KindChan, X: v, Name: w.nodeString(v), T: v, Type: typ}, nil
default:
return "", nil, fmt.Errorf("not lookupExpr %v %T", w.nodeString(v), v)
}
return "", nil, fmt.Errorf("not lookupExpr %v %T", w.nodeString(vi), vi)
}
func (w *Walker) walkFile(file *ast.File) {
// Not entering a scope here; file boundaries aren't interesting.
for _, di := range file.Decls {
switch d := di.(type) {
case *ast.GenDecl:
switch d.Tok {
case token.IMPORT:
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)
name := fpath
if i := strings.LastIndexAny(name, ".-/\\"); i > 0 {
name = name[i+1:]
}
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))
}
case token.TYPE:
for _, sp := range d.Specs {
w.walkTypeSpec(sp.(*ast.TypeSpec))
}
case token.VAR:
for _, sp := range d.Specs {
w.walkVar(sp.(*ast.ValueSpec))
}
default:
log.Fatalf("unknown token type %d in GenDecl", d.Tok)
}
case *ast.FuncDecl:
// Ignore. Handled in subsequent pass, by go/doc.
default:
log.Printf("unhandled %T, %#v\n", di, di)
printer.Fprint(os.Stderr, w.fset, di)
os.Stderr.Write([]byte("\n"))
}
}
}
var constType = map[token.Token]string{
token.INT: "ideal-int",
token.FLOAT: "ideal-float",
token.STRING: "ideal-string",
token.CHAR: "ideal-char",
token.IMAG: "ideal-imag",
}
var varType = map[token.Token]string{
token.INT: "int",
token.FLOAT: "float64",
token.STRING: "string",
token.CHAR: "rune",
token.IMAG: "complex128",
}
var builtinTypes = []string{
"bool", "byte", "complex64", "complex128", "error", "float32", "float64",
"int", "int8", "int16", "int32", "int64", "rune", "string",
"uint", "uint8", "uint16", "uint32", "uint64", "uintptr",
}
func isBuiltinType(typ string) bool {
for _, v := range builtinTypes {
if v == typ {
return true
}
}
return false
}
func constTypePriority(typ string) int {
switch typ {
case "complex128":
return 100
case "ideal-imag":
return 99
case "complex64":
return 98
case "float64":
return 97
case "ideal-float":
return 96
case "float32":
return 95
case "int64":
return 94
case "int", "uint", "uintptr":
return 93
case "ideal-int":
return 92
case "int16", "uint16", "int8", "uint8", "byte":
return 91
case "ideal-char":
return 90
}
return 101
}
func (w *Walker) constRealType(typ string) string {
pos := strings.Index(typ, ".")
if pos >= 0 {
pkg := typ[:pos]
if pkg == "C" {
return "int"
}
typ = typ[pos+1:]
if p := w.findPackage(pkg); p != nil {
ret := p.findType(typ)
if ret != nil {
return w.nodeString(w.namelessType(ret))
}
}
} else {
ret := w.curPackage.findType(typ)
if ret != nil {
return w.nodeString(w.namelessType(ret))
}
}
return typ
}
func (w *Walker) constValueType(vi interface{}) (string, error) {
switch v := vi.(type) {
case *ast.BasicLit:
litType, ok := constType[v.Kind]
if !ok {
return "", fmt.Errorf("unknown basic literal kind %#v", v)
}
return litType, nil
case *ast.UnaryExpr:
return w.constValueType(v.X)
case *ast.SelectorExpr:
lhs := w.nodeString(v.X)
rhs := w.nodeString(v.Sel)
//if CGO
if lhs == "C" {
return lhs + "." + rhs, nil
}
if p := w.findPackage(lhs); p != nil {
if ret, ok := p.consts[rhs]; ok {
return w.pkgRetType(p.name, ret.T), nil
}
}
return "", fmt.Errorf("unknown constant reference to %s.%s", lhs, rhs)
case *ast.Ident:
if v.Name == "iota" {
return "ideal-int", nil // hack.
}
if v.Name == "false" || v.Name == "true" {
return "bool", nil
}
if t, ok := w.curPackage.consts[v.Name]; ok {
return t.T, nil
}
return constDepPrefix + v.Name, nil
case *ast.BinaryExpr:
//== > < ! != >= <=
if v.Op == token.EQL || v.Op == token.LSS || v.Op == token.GTR || v.Op == token.NOT ||
v.Op == token.NEQ || v.Op == token.LEQ || v.Op == token.GEQ {
return "bool", nil
}
left, err := w.constValueType(v.X)
if err != nil {
return "", err
}
if v.Op == token.SHL || v.Op == token.SHR {
return left, err
}
right, err := w.constValueType(v.Y)
if err != nil {
return "", err
}
//const left != right , one or two is ideal-
if left != right {
if strings.HasPrefix(left, constDepPrefix) && strings.HasPrefix(right, constDepPrefix) {
// Just pick one.
// e.g. text/scanner GoTokens const-dependency:ScanIdents, const-dependency:ScanFloats
return left, nil
}
lp := constTypePriority(w.constRealType(left))
rp := constTypePriority(w.constRealType(right))
if lp >= rp {
return left, nil
} else {
return right, nil
}
return "", fmt.Errorf("in BinaryExpr, unhandled type mismatch; left=%q, right=%q", left, right)
}
return left, nil
case *ast.CallExpr:
// Not a call, but a type conversion.
typ := w.nodeString(v.Fun)
switch typ {
case "complex":
return "complex128", nil
case "real", "imag":
return "float64", nil
}
return typ, nil
case *ast.ParenExpr:
return w.constValueType(v.X)
}
return "", fmt.Errorf("unknown const value type %T", vi)
}
func (w *Walker) pkgRetType(pkg, ret string) string {
pkg = pkg[strings.LastIndex(pkg, "/")+1:]
if strings.HasPrefix(ret, "[]") {
return "[]" + w.pkgRetType(pkg, ret[2:])
}
if strings.HasPrefix(ret, "*") {
return "*" + w.pkgRetType(pkg, ret[1:])
}
if ast.IsExported(ret) {
return pkg + "." + ret
}
return ret
}
func (w *Walker) findStructFieldType(st ast.Expr, name string) ast.Expr {
_, expr := w.findStructField(st, name)
return expr
}
func (w *Walker) findStructFieldFunction(st ast.Expr, name string) (*TypeInfo, error) {
if s, ok := st.(*ast.StructType); ok {
for _, fi := range s.Fields.List {
typ := fi.Type
if fi.Names == nil {
switch v := typ.(type) {
case *ast.Ident:
if t := w.curPackage.findType(v.Name); t != nil {
return w.lookupFunction(v.Name, name)
}
case *ast.SelectorExpr:
pt := w.nodeString(typ)
pos := strings.Index(pt, ".")
if pos != -1 {
if p := w.findPackage(pt[:pos]); p != nil {
if t := p.findType(pt[pos+1:]); t != nil {
return w.lookupFunction(pt, name)
}
}
}
case *ast.StarExpr:
return w.findStructFieldFunction(v.X, name)
default:
if apiVerbose {
log.Printf("unable to handle embedded %T", typ)
}
}
}
}
}
return nil, nil
}
func (w *Walker) findStructField(st ast.Expr, name string) (*ast.Ident, ast.Expr) {
if s, ok := st.(*ast.StructType); ok {
for _, fi := range s.Fields.List {
typ := fi.Type
for _, n := range fi.Names {
if n.Name == name {
return n, fi.Type
}
}
if fi.Names == nil {
switch v := typ.(type) {
case *ast.Ident:
if t := w.curPackage.findType(v.Name); t != nil {
if v.Name == name {
return v, v
}
id, expr := w.findStructField(t, name)
if id != nil {
return id, expr
}
}
case *ast.StarExpr:
switch vv := v.X.(type) {
case *ast.Ident:
if t := w.curPackage.findType(vv.Name); t != nil {
if vv.Name == name {
return vv, v.X
}
id, expr := w.findStructField(t, name)
if id != nil {
return id, expr
}
}
case *ast.SelectorExpr:
pt := w.nodeString(typ)
pos := strings.Index(pt, ".")
if pos != -1 {
if p := w.findPackage(pt[:pos]); p != nil {
if t := p.findType(pt[pos+1:]); t != nil {
return w.findStructField(t, name)
}
}
}
default:
if apiVerbose {
log.Printf("unable to handle embedded starexpr before %T", typ)
}
}
case *ast.SelectorExpr:
pt := w.nodeString(typ)
pos := strings.Index(pt, ".")
if pos != -1 {
if p := w.findPackage(pt[:pos]); p != nil {
if t := p.findType(pt[pos+1:]); t != nil {
return w.findStructField(t, name)
}
}
}
default:
if apiVerbose {
log.Printf("unable to handle embedded %T", typ)
}
}
}
}
}
return nil, nil
}
func (w *Walker) lookupFunction(name, sel string) (*TypeInfo, error) {
name = strings.TrimLeft(name, "*")
if p := w.findPackage(name); p != nil {
fn := p.findCallFunc(sel)
if fn != nil {
return &TypeInfo{Kind: KindFunc, X: fn, Name: name + "." + sel, T: fn, Type: w.nodeString(w.namelessType(fn))}, nil
}
}
pos := strings.Index(name, ".")
if pos != -1 {
pkg := name[:pos]
typ := name[pos+1:]
if p := w.findPackage(pkg); p != nil {
if ident, fn := p.findMethod(typ, sel); fn != nil {
return &TypeInfo{Kind: KindMethod, X: fn, Name: name + "." + sel, T: ident, Type: w.nodeString(w.namelessType(fn))}, nil
}
}
return nil, fmt.Errorf("not lookup pkg type function pkg: %s, %s. %s. %s", name, pkg, typ, sel)
}
//find local var.func()
if ns, nt, n := w.resolveName(name); n >= 0 {
var vt string
if nt != nil {
vt = w.nodeString(w.namelessType(nt))
} else if ns != nil {
typ, err := w.varValueType(ns, n)
if err == nil {
vt = typ
}
} else {
typ := w.curPackage.findSelectorType(name)
if typ != nil {
vt = w.nodeString(w.namelessType(typ))
}
}
if strings.HasPrefix(vt, "*") {
vt = vt[1:]
}
if vt == "error" && sel == "Error" {
return &TypeInfo{Kind: KindBuiltin, Name: "error.Error", Type: "()string"}, nil
}
if fn, ok := w.curPackage.functions[vt+"."+sel]; ok {
return &TypeInfo{Kind: KindMethod, X: fn.ft, Name: name + "." + sel, T: fn.ft, Type: w.nodeString(w.namelessType(fn))}, nil
}
}
if typ, ok := w.curPackage.structs[name]; ok {
if fn, ok := w.curPackage.functions[name+"."+sel]; ok {
return &TypeInfo{Kind: KindMethod, X: fn.ft, Name: name + "." + sel, T: fn.ft, Type: w.nodeString(w.namelessType(fn.ft))}, nil
}
if info, err := w.findStructFieldFunction(typ, sel); err == nil {
return info, nil
}
// struct field is type function
if ft := w.findStructFieldType(typ, sel); ft != nil {
typ, err := w.varValueType(ft, 0)
if err != nil {
typ = w.nodeString(ft)
}
return &TypeInfo{Kind: KindField, X: ft, Name: name + "." + sel, T: ft, Type: typ}, nil
}
}
if ident, fn := w.curPackage.findMethod(name, sel); ident != nil && fn != nil {
return &TypeInfo{Kind: KindMethod, X: fn, Name: name + "." + sel, T: ident, Type: w.nodeString(w.namelessType(fn))}, nil
}
if p := w.findPackage(name); p != nil {
fn := p.findCallFunc(sel)
if fn != nil {
return &TypeInfo{Kind: KindFunc, X: fn, Name: name + "." + sel, T: fn, Type: w.nodeString(w.namelessType(fn))}, nil
}
return nil, fmt.Errorf("not find pkg func0 %v.%v", p.name, sel)
}
return nil, fmt.Errorf("not lookup func %v.%v", name, sel)
}
func (w *Walker) varFunctionType(name, sel string, index int) (string, error) {
name = strings.TrimLeft(name, "*")
pos := strings.Index(name, ".")
if pos != -1 {
pkg := name[:pos]
typ := name[pos+1:]
if p := w.findPackage(pkg); p != nil {
_, fn := p.findMethod(typ, sel)
if fn != nil {
ret := funcRetType(fn, index)
if ret != nil {
return w.pkgRetType(p.name, w.nodeString(w.namelessType(ret))), nil
}
}
}
return "", fmt.Errorf("unknown pkg type function pkg: %s.%s.%s", pkg, typ, sel)
}
//find local var
if v, ok := w.localvar[name]; ok {
vt := v.T
if strings.HasPrefix(vt, "*") {
vt = vt[1:]
}
if vt == "error" && sel == "Error" {
return "string", nil
}
typ, err := w.varFunctionType(vt, sel, 0)
if err == nil {
return typ, nil
}
}
//find global var.func()
if ns, nt, n := w.resolveName(name); n >= 0 {
var vt string
if nt != nil {
vt = w.nodeString(w.namelessType(nt))
} else if ns != nil {
typ, err := w.varValueType(ns, n)
if err == nil {
vt = typ
}
} else {
typ := w.curPackage.findSelectorType(name)
if typ != nil {
vt = w.nodeString(w.namelessType(typ))
}
}
if strings.HasPrefix(vt, "*") {
vt = vt[1:]
}
if vt == "error" && sel == "Error" {
return "string", nil
}
if fn, ok := w.curPackage.functions[vt+"."+sel]; ok {
return w.nodeString(w.namelessType(funcRetType(fn.ft, index))), nil
}
}
if typ, ok := w.curPackage.structs[name]; ok {
if ft := w.findStructFieldType(typ, sel); ft != nil {
return w.varValueType(ft, index)
}
}
//find pkg.func()
if p := w.findPackage(name); p != nil {
typ := p.findCallType(sel, index)
if typ != nil {
return w.pkgRetType(p.name, w.nodeString(w.namelessType(typ))), nil
}
//log.Println("->", p.functions)
return "", fmt.Errorf("not find pkg func1 %v . %v", p.name, sel)
}
return "", fmt.Errorf("not find func %v.%v", name, sel)
}
func (w *Walker) lookupSelector(name string, sel string) (*TypeInfo, error) {
name = strings.TrimLeft(name, "*")
pos := strings.Index(name, ".")
if pos != -1 {
pkg := name[:pos]
typ := name[pos+1:]
if p := w.findPackage(pkg); p != nil {
t := p.findType(typ)
if t != nil {
typ := w.findStructFieldType(t, sel)
if typ != nil {
return &TypeInfo{Kind: KindField, X: typ, Name: name + "." + sel, T: typ, Type: w.pkgRetType(p.name, w.nodeString(w.namelessType(typ)))}, nil
}
}
}
return nil, fmt.Errorf("lookup unknown pkg type selector pkg: %s.%s %s", pkg, typ, sel)
}
if lv, ok := w.localvar[name]; ok {
return w.lookupSelector(lv.T, sel)
}
vs, vt, n := w.resolveName(name)
if n >= 0 {
var typ string
if vt != nil {
typ = w.nodeString(w.namelessType(vt))
} else {
typ, _ = w.varValueType(vs, n)
}
if strings.HasPrefix(typ, "*") {
typ = typ[1:]
}
//typ is type, find real type
for k, v := range w.curPackage.types {
if k == typ {
typ = w.nodeString(w.namelessType(v))
}
}
pos := strings.Index(typ, ".")
if pos == -1 {
t := w.curPackage.findType(typ)
if t != nil {
typ := w.findStructFieldType(t, sel)
if typ != nil {
return &TypeInfo{Kind: KindField, X: typ, Name: name + "." + sel, T: typ, Type: w.nodeString(w.namelessType(typ))}, nil
}
}
} else {
name := typ[:pos]
typ = typ[pos+1:]
if p := w.findPackage(name); p != nil {
t := p.findType(typ)
if t != nil {
typ := w.findStructFieldType(t, sel)
if typ != nil {
return &TypeInfo{Kind: KindField, X: typ, Name: name + "." + sel, T: typ, Type: w.nodeString(w.namelessType(typ))}, nil
}
}
}
}
}
if p := w.findPackage(name); p != nil {
typ := p.findSelectorType(sel)
if typ != nil {
return &TypeInfo{Kind: KindType, X: typ, Name: name + "." + sel, T: typ, Type: w.pkgRetType(p.name, w.nodeString(w.namelessType(typ)))}, nil
}
}
t := w.curPackage.findType(name)
if t != nil {
typ := w.findStructFieldType(t, sel)
if typ != nil {
return &TypeInfo{Kind: KindField, X: typ, Name: name + "." + sel, T: typ, Type: w.nodeString(w.namelessType(typ))}, nil
}
}
if t, ok := w.curPackage.types[name]; ok {
return w.lookupSelector(w.nodeString(t), sel)
}
return nil, fmt.Errorf("unknown selector expr ident: %s.%s", name, sel)
}
func (w *Walker) varSelectorType(name string, sel string) (string, error) {
name = strings.TrimLeft(name, "*")
pos := strings.Index(name, ".")
if pos != -1 {
pkg := name[:pos]
typ := name[pos+1:]
if p := w.findPackage(pkg); p != nil {
t := p.findType(typ)
if t != nil {
typ := w.findStructFieldType(t, sel)
if typ != nil {
return w.pkgRetType(pkg, w.nodeString(w.namelessType(typ))), nil
}
}
}
return "", fmt.Errorf("unknown pkg type selector pkg: %s.%s.%s", pkg, typ, sel)
}
//check local
if lv, ok := w.localvar[name]; ok {
return w.varSelectorType(lv.T, sel)
}
//check struct
if t := w.curPackage.findType(name); t != nil {
typ := w.findStructFieldType(t, sel)
if typ != nil {
return w.nodeString(w.namelessType(typ)), nil
}
}
//check var
vs, vt, n := w.resolveName(name)
if n >= 0 {
var typ string
if vt != nil {
typ = w.nodeString(w.namelessType(vt))
} else {
typ, _ = w.varValueType(vs, n)
}
if strings.HasPrefix(typ, "*") {
typ = typ[1:]
}
//typ is type, find real type
for k, v := range w.curPackage.types {
if k == typ {
typ = w.nodeString(w.namelessType(v))
}
}
pos := strings.Index(typ, ".")
if pos == -1 {
t := w.curPackage.findType(typ)
if t != nil {
typ := w.findStructFieldType(t, sel)
if typ != nil {
return w.nodeString(w.namelessType(typ)), nil
}
}
} else {
name := typ[:pos]
typ = typ[pos+1:]
if p := w.findPackage(name); p != nil {
t := p.findType(typ)
if t != nil {
typ := w.findStructFieldType(t, sel)
if typ != nil {
return w.nodeString(w.namelessType(typ)), nil
}
}
}
}
}
if p := w.findPackage(name); p != nil {
typ := p.findSelectorType(sel)
if typ != nil {
return w.pkgRetType(p.name, w.nodeString(w.namelessType(typ))), nil
}
}
return "", fmt.Errorf("unknown var selector expr ident: %s.%s", name, sel)
}
func (w *Walker) varValueType(vi ast.Expr, index int) (string, error) {
if vi == nil {
return "", nil
}
switch v := vi.(type) {
case *ast.BasicLit:
litType, ok := varType[v.Kind]
if !ok {
return "", fmt.Errorf("unknown basic literal kind %#v", v)
}
return litType, nil
case *ast.CompositeLit:
return w.nodeString(v.Type), nil
case *ast.FuncLit:
return w.nodeString(w.namelessType(v.Type)), nil
case *ast.InterfaceType:
return w.nodeString(v), nil
case *ast.Ellipsis:
typ, err := w.varValueType(v.Elt, index)
if err != nil {
return "", err
}
return "[]" + typ, nil
case *ast.StarExpr:
typ, err := w.varValueType(v.X, index)
if err != nil {
return "", err
}
return "*" + typ, err
case *ast.UnaryExpr:
if v.Op == token.AND {
typ, err := w.varValueType(v.X, index)
return "*" + typ, err
}
return "", fmt.Errorf("unknown unary expr: %#v", v)
case *ast.SelectorExpr:
switch st := v.X.(type) {
case *ast.Ident:
return w.varSelectorType(st.Name, v.Sel.Name)
case *ast.CallExpr:
typ, err := w.varValueType(v.X, index)
if err == nil {
if strings.HasPrefix(typ, "*") {
typ = typ[1:]
}
t := w.curPackage.findType(typ)
if st, ok := t.(*ast.StructType); ok {
for _, fi := range st.Fields.List {
for _, n := range fi.Names {
if n.Name == v.Sel.Name {
return w.varValueType(fi.Type, index)
}
}
}
}
}
case *ast.SelectorExpr:
typ, err := w.varValueType(v.X, index)
if err == nil {
return w.varSelectorType(typ, v.Sel.Name)
}
case *ast.IndexExpr:
typ, err := w.varValueType(st.X, index)
if err == nil {
if strings.HasPrefix(typ, "[]") {
return w.varSelectorType(typ[2:], v.Sel.Name)
}
}
case *ast.CompositeLit:
typ, err := w.varValueType(st.Type, 0)
if err == nil {
//log.Println(typ, v.Sel.Name)
t, err := w.varSelectorType(typ, v.Sel.Name)
if err == nil {
return t, nil
}
}
}
return "", fmt.Errorf("var unknown selector expr: %T %s.%s", v.X, w.nodeString(v.X), v.Sel)
case *ast.Ident:
if v.Name == "true" || v.Name == "false" {
return "bool", nil
}
if isBuiltinType(v.Name) {
return v.Name, nil
}
if lv, ok := w.localvar[v.Name]; ok {
return lv.T, nil
}
vt := w.curPackage.findType(v.Name)
if vt != nil {
if _, ok := vt.(*ast.StructType); ok {
return v.Name, nil
}
return w.nodeString(vt), nil
}
vs, _, n := w.resolveName(v.Name)
if n >= 0 {
return w.varValueType(vs, n)
}
return "", fmt.Errorf("unresolved identifier: %q", v.Name)
case *ast.BinaryExpr:
//== > < ! != >= <=
if v.Op == token.EQL || v.Op == token.LSS || v.Op == token.GTR || v.Op == token.NOT ||
v.Op == token.NEQ || v.Op == token.LEQ || v.Op == token.GEQ {
return "bool", nil
}
left, err := w.varValueType(v.X, index)
if err != nil {
return "", err
}
right, err := w.varValueType(v.Y, index)
if err != nil {
return "", err
}
if left != right {
return "", fmt.Errorf("in BinaryExpr, unhandled type mismatch; left=%q, right=%q", left, right)
}
return left, nil
case *ast.ParenExpr:
return w.varValueType(v.X, index)
case *ast.CallExpr:
switch ft := v.Fun.(type) {
case *ast.ArrayType:
return w.nodeString(v.Fun), nil
case *ast.Ident:
switch ft.Name {
case "make":
return w.nodeString(w.namelessType(v.Args[0])), nil
case "new":
return "*" + w.nodeString(w.namelessType(v.Args[0])), nil
case "append":
return w.varValueType(v.Args[0], 0)
case "recover":
return "interface{}", nil
case "len", "cap", "copy":
return "int", nil
case "complex":
return "complex128", nil
case "real":
return "float64", nil
case "imag":
return "float64", nil
}
if isBuiltinType(ft.Name) {
return ft.Name, nil
}
typ := w.curPackage.findCallType(ft.Name, index)
if typ != nil {
return w.nodeString(w.namelessType(typ)), nil
}
//if local var type
if fn, ok := w.localvar[ft.Name]; ok {
typ := fn.T
if strings.HasPrefix(typ, "func(") {
expr, err := parser.ParseExpr(typ + "{}")
if err == nil {
if fl, ok := expr.(*ast.FuncLit); ok {
retType := funcRetType(fl.Type, index)
if retType != nil {
return w.nodeString(w.namelessType(retType)), nil
}
}
}
}
}
//if var is func() type
vs, _, n := w.resolveName(ft.Name)
if n >= 0 {
if vs != nil {
typ, err := w.varValueType(vs, n)
if err == nil {
if strings.HasPrefix(typ, "func(") {
expr, err := parser.ParseExpr(typ + "{}")
if err == nil {
if fl, ok := expr.(*ast.FuncLit); ok {
retType := funcRetType(fl.Type, index)
if retType != nil {
return w.nodeString(w.namelessType(retType)), nil
}
}
}
}
}
}
}
return "", fmt.Errorf("unknown funcion %s %s", w.curPackageName, ft.Name)
case *ast.SelectorExpr:
typ, err := w.varValueType(ft.X, index)
if err == nil {
if strings.HasPrefix(typ, "*") {
typ = typ[1:]
}
retType := w.curPackage.findCallType(typ+"."+ft.Sel.Name, index)
if retType != nil {
return w.nodeString(w.namelessType(retType)), nil
}
}
switch st := ft.X.(type) {
case *ast.Ident:
return w.varFunctionType(st.Name, ft.Sel.Name, index)
case *ast.CallExpr:
typ, err := w.varValueType(st, 0)
if err != nil {
return "", err
}
return w.varFunctionType(typ, ft.Sel.Name, index)
case *ast.SelectorExpr:
typ, err := w.varValueType(st, index)
if err == nil {
return w.varFunctionType(typ, ft.Sel.Name, index)
}
case *ast.IndexExpr:
typ, err := w.varValueType(st.X, index)
if err == nil {
if strings.HasPrefix(typ, "[]") {
return w.varFunctionType(typ[2:], ft.Sel.Name, index)
}
}
case *ast.TypeAssertExpr:
typ := w.nodeString(w.namelessType(st.Type))
typ = strings.TrimLeft(typ, "*")
return w.varFunctionType(typ, ft.Sel.Name, index)
}
return "", fmt.Errorf("unknown var function selector %v %T", w.nodeString(ft.X), ft.X)
case *ast.FuncLit:
retType := funcRetType(ft.Type, index)
if retType != nil {
return w.nodeString(w.namelessType(retType)), nil
}
case *ast.CallExpr:
typ, err := w.varValueType(v.Fun, 0)
if err == nil && strings.HasPrefix(typ, "func(") {
expr, err := parser.ParseExpr(typ + "{}")
if err == nil {
if fl, ok := expr.(*ast.FuncLit); ok {
retType := funcRetType(fl.Type, index)
if retType != nil {
return w.nodeString(w.namelessType(retType)), nil
}
}
}
}
}
return "", fmt.Errorf("not a known function %T %v", v.Fun, w.nodeString(v.Fun))
case *ast.MapType:
return fmt.Sprintf("map[%s](%s)", w.nodeString(w.namelessType(v.Key)), w.nodeString(w.namelessType(v.Value))), nil
case *ast.ArrayType:
return fmt.Sprintf("[]%s", w.nodeString(w.namelessType(v.Elt))), nil
case *ast.FuncType:
return w.nodeString(w.namelessType(v)), nil
case *ast.IndexExpr:
typ, err := w.varValueType(v.X, index)
typ = strings.TrimLeft(typ, "*")
if err == nil {
if index == 0 {
return typ, nil
} else if index == 1 {
return "bool", nil
}
if strings.HasPrefix(typ, "[]") {
return typ[2:], nil
} else if strings.HasPrefix(typ, "map[") {
node, err := parser.ParseExpr(typ + "{}")
if err == nil {
if cl, ok := node.(*ast.CompositeLit); ok {
if m, ok := cl.Type.(*ast.MapType); ok {
return w.nodeString(w.namelessType(m.Value)), nil
}
}
}
}
}
return "", fmt.Errorf("unknown index %v %v %v %v", typ, v.X, index, err)
case *ast.SliceExpr:
return w.varValueType(v.X, index)
case *ast.ChanType:
typ, err := w.varValueType(v.Value, index)
if err == nil {
if v.Dir == ast.RECV {
return "<-chan " + typ, nil
} else if v.Dir == ast.SEND {
return "chan<- " + typ, nil
}
return "chan " + typ, nil
}
case *ast.TypeAssertExpr:
if index == 1 {
return "bool", nil
}
return w.nodeString(w.namelessType(v.Type)), nil
default:
return "", fmt.Errorf("unknown value type %v %T", w.nodeString(vi), vi)
}
//panic("unreachable")
return "", fmt.Errorf("unreachable value type %v %T", vi, vi)
}
// resolveName finds a top-level node named name and returns the node
// v and its type t, if known.
func (w *Walker) resolveName(name string) (v ast.Expr, t interface{}, n int) {
for _, file := range w.curPackage.apkg.Files {
for _, di := range file.Decls {
switch d := di.(type) {
case *ast.GenDecl:
switch d.Tok {
case token.VAR:
for _, sp := range d.Specs {
vs := sp.(*ast.ValueSpec)
for i, vname := range vs.Names {
if vname.Name == name {
if len(vs.Values) == 1 {
return vs.Values[0], vs.Type, i
}
return nil, vs.Type, i
}
}
}
}
}
}
}
return nil, nil, -1
}
// constDepPrefix is a magic prefix that is used by constValueType
// and walkConst to signal that a type isn't known yet. These are
// resolved at the end of walking of a package's files.
const constDepPrefix = "const-dependency:"
func (w *Walker) walkConst(vs *ast.ValueSpec) {
for _, ident := range vs.Names {
if !w.isExtract(ident.Name) {
continue
}
litType := ""
if vs.Type != nil {
litType = w.nodeString(vs.Type)
} else {
litType = w.lastConstType
if vs.Values != nil {
if len(vs.Values) != 1 {
log.Fatalf("const %q, values: %#v", ident.Name, vs.Values)
}
var err error
litType, err = w.constValueType(vs.Values[0])
if err != nil {
if apiVerbose {
log.Printf("unknown kind in const %q (%T): %v", ident.Name, vs.Values[0], err)
}
litType = "unknown-type"
}
}
}
if strings.HasPrefix(litType, constDepPrefix) {
dep := litType[len(constDepPrefix):]
w.constDep[ident.Name] = &ExprType{T: dep, X: ident}
continue
}
if litType == "" {
if apiVerbose {
log.Printf("unknown kind in const %q", ident.Name)
}
continue
}
w.lastConstType = litType
w.curPackage.consts[ident.Name] = &ExprType{T: litType, X: ident}
if isExtract(ident.Name) {
w.emitFeature(fmt.Sprintf("const %s %s", ident, litType), ident.Pos())
}
}
}
func (w *Walker) resolveConstantDeps() {
var findConstType func(string) string
findConstType = func(ident string) string {
if dep, ok := w.constDep[ident]; ok {
return findConstType(dep.T)
}
if t, ok := w.curPackage.consts[ident]; ok {
return t.T
}
return ""
}
for ident, info := range w.constDep {
if !isExtract(ident) {
continue
}
t := findConstType(ident)
if t == "" {
if apiVerbose {
log.Printf("failed to resolve constant %q", ident)
}
continue
}
w.curPackage.consts[ident] = &ExprType{T: t, X: info.X}
w.emitFeature(fmt.Sprintf("const %s %s", ident, t), info.X.Pos())
}
}
func (w *Walker) walkVar(vs *ast.ValueSpec) {
if vs.Type != nil {
typ := w.nodeString(vs.Type)
for _, ident := range vs.Names {
w.curPackage.vars[ident.Name] = &ExprType{T: typ, X: ident}
if isExtract(ident.Name) {
w.emitFeature(fmt.Sprintf("var %s %s", ident, typ), ident.Pos())
}
}
} else if len(vs.Names) == len(vs.Values) {
for n, ident := range vs.Names {
if !w.isExtract(ident.Name) {
continue
}
typ, err := w.varValueType(vs.Values[n], n)
if err != nil {
if apiVerbose {
log.Printf("unknown type of variable0 %q, type %T, error = %v, pos=%s",
ident.Name, vs.Values[n], err, w.fset.Position(vs.Pos()))
}
typ = "unknown-type"
}
w.curPackage.vars[ident.Name] = &ExprType{T: typ, X: ident}
if isExtract(ident.Name) {
w.emitFeature(fmt.Sprintf("var %s %s", ident, typ), ident.Pos())
}
}
} else if len(vs.Values) == 1 {
for n, ident := range vs.Names {
if !w.isExtract(ident.Name) {
continue
}
typ, err := w.varValueType(vs.Values[0], n)
if err != nil {
if apiVerbose {
log.Printf("unknown type of variable1 %q, type %T, error = %v, pos=%s",
ident.Name, vs.Values[0], err, w.fset.Position(vs.Pos()))
}
typ = "unknown-type"
}
w.curPackage.vars[ident.Name] = &ExprType{T: typ, X: ident}
if isExtract(ident.Name) {
w.emitFeature(fmt.Sprintf("var %s %s", ident, typ), ident.Pos())
}
}
}
}
func (w *Walker) nodeString(node interface{}) string {
if node == nil {
return ""
}
var b bytes.Buffer
printer.Fprint(&b, w.fset, node)
return b.String()
}
func (w *Walker) nodeDebug(node interface{}) string {
if node == nil {
return ""
}
var b bytes.Buffer
ast.Fprint(&b, w.fset, node, nil)
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 !isExtract(name) {
return
}
switch t := ts.Type.(type) {
case *ast.StructType:
w.walkStructType(name, t)
case *ast.InterfaceType:
w.walkInterfaceType(name, t)
default:
w.emitFeature(fmt.Sprintf("type %s %s", name, w.nodeString(ts.Type)), t.Pos()-token.Pos(len(name)+1))
}
}
func (w *Walker) walkStructType(name string, t *ast.StructType) {
typeStruct := fmt.Sprintf("type %s struct", name)
w.emitFeature(typeStruct, t.Pos()-token.Pos(len(name)+1))
pop := w.pushScope(typeStruct)
defer pop()
for _, f := range t.Fields.List {
typ := f.Type
for _, name := range f.Names {
if isExtract(name.Name) {
w.emitFeature(fmt.Sprintf("%s %s", name, w.nodeString(w.namelessType(typ))), name.Pos())
}
}
if f.Names == nil {
switch v := typ.(type) {
case *ast.Ident:
if isExtract(v.Name) {
w.emitFeature(fmt.Sprintf("embedded %s", v.Name), v.Pos())
}
case *ast.StarExpr:
switch vv := v.X.(type) {
case *ast.Ident:
if isExtract(vv.Name) {
w.emitFeature(fmt.Sprintf("embedded *%s", vv.Name), vv.Pos())
}
case *ast.SelectorExpr:
w.emitFeature(fmt.Sprintf("embedded %s", w.nodeString(typ)), v.Pos())
default:
log.Fatalf("unable to handle embedded starexpr before %T", typ)
}
case *ast.SelectorExpr:
w.emitFeature(fmt.Sprintf("embedded %s", w.nodeString(typ)), v.Pos())
default:
if apiVerbose {
log.Printf("unable to handle embedded %T", typ)
}
}
}
}
}
// typeMethod is a method of an interface.
type typeMethod struct {
name string // "Read"
sig string // "([]byte) (int, error)", from funcSigString
ft *ast.FuncType
pos token.Pos
recv ast.Expr
}
// interfaceMethods returns the expanded list of exported methods for an interface.
// The boolean complete reports whether the list contains all methods (that is, the
// interface has no unexported methods).
// pkg is the complete package name ("net/http")
// iname is the interface name.
func (w *Walker) interfaceMethods(pkg, iname string) (methods []typeMethod, complete bool) {
t, ok := w.interfaces[pkgSymbol{pkg, iname}]
if !ok {
if apiVerbose {
log.Printf("failed to find interface %s.%s", pkg, iname)
}
return
}
complete = true
for _, f := range t.Methods.List {
typ := f.Type
switch tv := typ.(type) {
case *ast.FuncType:
for _, mname := range f.Names {
if isExtract(mname.Name) {
ft := typ.(*ast.FuncType)
methods = append(methods, typeMethod{
name: mname.Name,
sig: w.funcSigString(ft),
ft: ft,
pos: f.Pos(),
})
} else {
complete = false
}
}
case *ast.Ident:
embedded := typ.(*ast.Ident).Name
if embedded == "error" {
methods = append(methods, typeMethod{
name: "Error",
sig: "() string",
ft: &ast.FuncType{
Params: nil,
Results: &ast.FieldList{
List: []*ast.Field{
&ast.Field{
Type: &ast.Ident{
Name: "string",
},
},
},
},
},
pos: f.Pos(),
})
continue
}
if !isExtract(embedded) {
log.Fatalf("unexported embedded interface %q in exported interface %s.%s; confused",
embedded, pkg, iname)
}
m, c := w.interfaceMethods(pkg, embedded)
methods = append(methods, m...)
complete = complete && c
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)
}
m, c := w.interfaceMethods(fpkg, rhs)
methods = append(methods, m...)
complete = complete && c
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")
methods, complete := w.interfaceMethods(w.curPackageName, name)
w.packageMap[w.curPackageName].interfaceMethods[name] = methods
for _, m := range methods {
methNames = append(methNames, m.name)
w.emitFeature(fmt.Sprintf("%s%s", m.name, m.sig), m.pos)
}
if !complete {
// The method set has unexported methods, so all the
// implementations are provided by the same package,
// so the method set can be extended. Instead of recording
// the full set of names (below), record only that there were
// unexported methods. (If the interface shrinks, we will notice
// because a method signature emitted during the last loop,
// will disappear.)
w.emitFeature("unexported methods", 0)
}
pop()
if !complete {
return
}
sort.Strings(methNames)
if len(methNames) == 0 {
w.emitFeature(fmt.Sprintf("type %s interface {}", name), t.Pos()-token.Pos(len(name)+1))
} else {
w.emitFeature(fmt.Sprintf("type %s interface { %s }", name, strings.Join(methNames, ", ")), t.Pos()-token.Pos(len(name)+1))
}
}
func baseTypeName(x ast.Expr) (name string, imported bool) {
switch t := x.(type) {
case *ast.Ident:
return t.Name, false
case *ast.SelectorExpr:
if _, ok := t.X.(*ast.Ident); ok {
// only possible for qualified type names;
// assume type is imported
return t.Sel.Name, true
}
case *ast.StarExpr:
return baseTypeName(t.X)
}
return
}
func (w *Walker) peekFuncDecl(f *ast.FuncDecl) {
var fname = f.Name.Name
var recv ast.Expr
if f.Recv != nil {
recvTypeName, imp := baseTypeName(f.Recv.List[0].Type)
if imp {
return
}
fname = recvTypeName + "." + f.Name.Name
recv = f.Recv.List[0].Type
}
// Record return type for later use.
//if f.Type.Results != nil && len(f.Type.Results.List) >= 1 {
// record all function
w.curPackage.functions[fname] = typeMethod{
name: fname,
sig: w.funcSigString(f.Type),
ft: f.Type,
pos: f.Pos(),
recv: recv,
}
//}
}
func (w *Walker) walkFuncDecl(f *ast.FuncDecl) {
if !w.isExtract(f.Name.Name) {
return
}
if f.Recv != nil {
// Method.
recvType := w.nodeString(f.Recv.List[0].Type)
keep := isExtract(recvType) ||
(strings.HasPrefix(recvType, "*") &&
isExtract(recvType[1:]))
if !keep {
return
}
w.emitFeature(fmt.Sprintf("method (%s) %s%s", recvType, f.Name.Name, w.funcSigString(f.Type)), f.Name.Pos())
return
}
// Else, a function
w.emitFeature(fmt.Sprintf("func %s%s", f.Name.Name, w.funcSigString(f.Type)), f.Name.Pos())
}
func (w *Walker) funcSigString(ft *ast.FuncType) string {
var b bytes.Buffer
writeField := func(b *bytes.Buffer, f *ast.Field) {
if n := len(f.Names); n > 1 {
for i := 0; i < n; i++ {
if i > 0 {
b.WriteString(", ")
}
b.WriteString(w.nodeString(w.namelessType(f.Type)))
}
} else {
b.WriteString(w.nodeString(w.namelessType(f.Type)))
}
}
b.WriteByte('(')
if ft.Params != nil {
for i, f := range ft.Params.List {
if i > 0 {
b.WriteString(", ")
}
writeField(&b, f)
}
}
b.WriteByte(')')
if ft.Results != nil {
nr := 0
for _, f := range ft.Results.List {
if n := len(f.Names); n > 1 {
nr += n
} else {
nr++
}
}
if nr > 0 {
b.WriteByte(' ')
if nr > 1 {
b.WriteByte('(')
}
for i, f := range ft.Results.List {
if i > 0 {
b.WriteString(", ")
}
writeField(&b, f)
}
if nr > 1 {
b.WriteByte(')')
}
}
}
return b.String()
}
// namelessType returns a type node that lacks any variable names.
func (w *Walker) namelessType(t interface{}) interface{} {
ft, ok := t.(*ast.FuncType)
if !ok {
return t
}
return &ast.FuncType{
Params: w.namelessFieldList(ft.Params),
Results: w.namelessFieldList(ft.Results),
}
}
// namelessFieldList returns a deep clone of fl, with the cloned fields
// lacking names.
func (w *Walker) namelessFieldList(fl *ast.FieldList) *ast.FieldList {
fl2 := &ast.FieldList{}
if fl != nil {
for _, f := range fl.List {
n := len(f.Names)
if n >= 1 {
for i := 0; i < n; i++ {
fl2.List = append(fl2.List, w.namelessField(f))
}
} else {
fl2.List = append(fl2.List, w.namelessField(f))
}
}
}
return fl2
}
// namelessField clones f, but not preserving the names of fields.
// (comments and tags are also ignored)
func (w *Walker) namelessField(f *ast.Field) *ast.Field {
return &ast.Field{
Type: f.Type,
}
}
func (w *Walker) emitFeature(feature string, pos token.Pos) {
if !w.wantedPkg[w.curPackage.name] {
return
}
more := strings.Index(feature, "\n")
if more != -1 {
if len(feature) <= 1024 {
feature = strings.Replace(feature, "\n", " ", 1)
feature = strings.Replace(feature, "\n", ";", -1)
feature = strings.Replace(feature, "\t", " ", -1)
} else {
feature = feature[:more] + " ...more"
if apiVerbose {
log.Printf("feature contains newlines: %v, %s", feature, w.fset.Position(pos))
}
}
}
f := strings.Join(w.scope, w.sep) + w.sep + feature
if _, dup := w.curPackage.features[f]; dup {
return
}
w.curPackage.features[f] = pos
}
func strListContains(l []string, s string) bool {
for _, v := range l {
if v == s {
return true
}
}
return false
}
const goosList = "darwin freebsd linux netbsd openbsd plan9 windows "
const goarchList = "386 amd64 arm "
// goodOSArchFile returns false if the name contains a $GOOS or $GOARCH
// suffix which does not match the current system.
// The recognized name formats are:
//
// name_$(GOOS).*
// name_$(GOARCH).*
// name_$(GOOS)_$(GOARCH).*
// name_$(GOOS)_test.*
// name_$(GOARCH)_test.*
// name_$(GOOS)_$(GOARCH)_test.*
//
func isOSArchFile(ctxt *build.Context, name string) bool {
if dot := strings.Index(name, "."); dot != -1 {
name = name[:dot]
}
l := strings.Split(name, "_")
if n := len(l); n > 0 && l[n-1] == "test" {
l = l[:n-1]
}
n := len(l)
if n >= 2 && knownOS[l[n-2]] && knownArch[l[n-1]] {
return l[n-2] == ctxt.GOOS && l[n-1] == ctxt.GOARCH
}
if n >= 1 && knownOS[l[n-1]] {
return l[n-1] == ctxt.GOOS
}
if n >= 1 && knownArch[l[n-1]] {
return l[n-1] == ctxt.GOARCH
}
return false
}
var knownOS = make(map[string]bool)
var knownArch = make(map[string]bool)
func init() {
for _, v := range strings.Fields(goosList) {
knownOS[v] = true
}
for _, v := range strings.Fields(goarchList) {
knownArch[v] = true
}
}