3775 lines
94 KiB
Go
3775 lines
94 KiB
Go
// 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
|
|
}
|
|
}
|