// 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 -[-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 } }