Initial QSfera import

This commit is contained in:
Курнат Андрей
2026-06-07 10:20:04 +03:00
commit 2315f25754
16485 changed files with 4826827 additions and 0 deletions
+663
View File
@@ -0,0 +1,663 @@
// Copyright 2013 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.
package astutil
// This file defines utilities for working with source positions.
import (
"fmt"
"go/ast"
"go/token"
"sort"
)
// PathEnclosingInterval returns the node that encloses the source
// interval [start, end), and all its ancestors up to the AST root.
//
// The definition of "enclosing" used by this function considers
// additional whitespace abutting a node to be enclosed by it.
// In this example:
//
// z := x + y // add them
// <-A->
// <----B----->
//
// the ast.BinaryExpr(+) node is considered to enclose interval B
// even though its [Pos()..End()) is actually only interval A.
// This behaviour makes user interfaces more tolerant of imperfect
// input.
//
// This function treats tokens as nodes, though they are not included
// in the result. e.g. PathEnclosingInterval("+") returns the
// enclosing ast.BinaryExpr("x + y").
//
// If start==end, the 1-char interval following start is used instead.
//
// The 'exact' result is true if the interval contains only path[0]
// and perhaps some adjacent whitespace. It is false if the interval
// overlaps multiple children of path[0], or if it contains only
// interior whitespace of path[0].
// In this example:
//
// z := x + y // add them
// <--C--> <---E-->
// ^
// D
//
// intervals C, D and E are inexact. C is contained by the
// z-assignment statement, because it spans three of its children (:=,
// x, +). So too is the 1-char interval D, because it contains only
// interior whitespace of the assignment. E is considered interior
// whitespace of the BlockStmt containing the assignment.
//
// The resulting path is never empty; it always contains at least the
// 'root' *ast.File. Ideally PathEnclosingInterval would reject
// intervals that lie wholly or partially outside the range of the
// file, but unfortunately ast.File records only the token.Pos of
// the 'package' keyword, but not of the start of the file itself.
func PathEnclosingInterval(root *ast.File, start, end token.Pos) (path []ast.Node, exact bool) {
// fmt.Printf("EnclosingInterval %d %d\n", start, end) // debugging
// Precondition: node.[Pos..End) and adjoining whitespace contain [start, end).
var visit func(node ast.Node) bool
visit = func(node ast.Node) bool {
path = append(path, node)
nodePos := node.Pos()
nodeEnd := node.End()
// fmt.Printf("visit(%T, %d, %d)\n", node, nodePos, nodeEnd) // debugging
// Intersect [start, end) with interval of node.
if start < nodePos {
start = nodePos
}
if end > nodeEnd {
end = nodeEnd
}
// Find sole child that contains [start, end).
children := childrenOf(node)
l := len(children)
for i, child := range children {
// [childPos, childEnd) is unaugmented interval of child.
childPos := child.Pos()
childEnd := child.End()
// [augPos, augEnd) is whitespace-augmented interval of child.
augPos := childPos
augEnd := childEnd
if i > 0 {
augPos = children[i-1].End() // start of preceding whitespace
}
if i < l-1 {
nextChildPos := children[i+1].Pos()
// Does [start, end) lie between child and next child?
if start >= augEnd && end <= nextChildPos {
return false // inexact match
}
augEnd = nextChildPos // end of following whitespace
}
// fmt.Printf("\tchild %d: [%d..%d)\tcontains interval [%d..%d)?\n",
// i, augPos, augEnd, start, end) // debugging
// Does augmented child strictly contain [start, end)?
if augPos <= start && end <= augEnd {
if is[tokenNode](child) {
return true
}
// childrenOf elides the FuncType node beneath FuncDecl.
// Add it back here for TypeParams, Params, Results,
// all FieldLists). But we don't add it back for the "func" token
// even though it is the tree at FuncDecl.Type.Func.
if decl, ok := node.(*ast.FuncDecl); ok {
if fields, ok := child.(*ast.FieldList); ok && fields != decl.Recv {
path = append(path, decl.Type)
}
}
return visit(child)
}
// Does [start, end) overlap multiple children?
// i.e. left-augmented child contains start
// but LR-augmented child does not contain end.
if start < childEnd && end > augEnd {
break
}
}
// No single child contained [start, end),
// so node is the result. Is it exact?
// (It's tempting to put this condition before the
// child loop, but it gives the wrong result in the
// case where a node (e.g. ExprStmt) and its sole
// child have equal intervals.)
if start == nodePos && end == nodeEnd {
return true // exact match
}
return false // inexact: overlaps multiple children
}
// Ensure [start,end) is nondecreasing.
if start > end {
start, end = end, start
}
if start < root.End() && end > root.Pos() {
if start == end {
end = start + 1 // empty interval => interval of size 1
}
exact = visit(root)
// Reverse the path:
for i, l := 0, len(path); i < l/2; i++ {
path[i], path[l-1-i] = path[l-1-i], path[i]
}
} else {
// Selection lies within whitespace preceding the
// first (or following the last) declaration in the file.
// The result nonetheless always includes the ast.File.
path = append(path, root)
}
return
}
// tokenNode is a dummy implementation of ast.Node for a single token.
// They are used transiently by PathEnclosingInterval but never escape
// this package.
type tokenNode struct {
pos token.Pos
end token.Pos
}
func (n tokenNode) Pos() token.Pos {
return n.pos
}
func (n tokenNode) End() token.Pos {
return n.end
}
func tok(pos token.Pos, len int) ast.Node {
return tokenNode{pos, pos + token.Pos(len)}
}
// childrenOf returns the direct non-nil children of ast.Node n.
// It may include fake ast.Node implementations for bare tokens.
// it is not safe to call (e.g.) ast.Walk on such nodes.
func childrenOf(n ast.Node) []ast.Node {
var children []ast.Node
// First add nodes for all true subtrees.
ast.Inspect(n, func(node ast.Node) bool {
if node == n { // push n
return true // recur
}
if node != nil { // push child
children = append(children, node)
}
return false // no recursion
})
// TODO(adonovan): be more careful about missing (!Pos.Valid)
// tokens in trees produced from invalid input.
// Then add fake Nodes for bare tokens.
switch n := n.(type) {
case *ast.ArrayType:
children = append(children,
tok(n.Lbrack, len("[")),
tok(n.Elt.End(), len("]")))
case *ast.AssignStmt:
children = append(children,
tok(n.TokPos, len(n.Tok.String())))
case *ast.BasicLit:
children = append(children,
tok(n.ValuePos, len(n.Value)))
case *ast.BinaryExpr:
children = append(children, tok(n.OpPos, len(n.Op.String())))
case *ast.BlockStmt:
if n.Lbrace.IsValid() {
children = append(children, tok(n.Lbrace, len("{")))
}
if n.Rbrace.IsValid() {
children = append(children, tok(n.Rbrace, len("}")))
}
case *ast.BranchStmt:
children = append(children,
tok(n.TokPos, len(n.Tok.String())))
case *ast.CallExpr:
children = append(children,
tok(n.Lparen, len("(")),
tok(n.Rparen, len(")")))
if n.Ellipsis != 0 {
children = append(children, tok(n.Ellipsis, len("...")))
}
case *ast.CaseClause:
if n.List == nil {
children = append(children,
tok(n.Case, len("default")))
} else {
children = append(children,
tok(n.Case, len("case")))
}
children = append(children, tok(n.Colon, len(":")))
case *ast.ChanType:
switch n.Dir {
case ast.RECV:
children = append(children, tok(n.Begin, len("<-chan")))
case ast.SEND:
children = append(children, tok(n.Begin, len("chan<-")))
case ast.RECV | ast.SEND:
children = append(children, tok(n.Begin, len("chan")))
}
case *ast.CommClause:
if n.Comm == nil {
children = append(children,
tok(n.Case, len("default")))
} else {
children = append(children,
tok(n.Case, len("case")))
}
children = append(children, tok(n.Colon, len(":")))
case *ast.Comment:
// nop
case *ast.CommentGroup:
// nop
case *ast.CompositeLit:
children = append(children,
tok(n.Lbrace, len("{")),
tok(n.Rbrace, len("{")))
case *ast.DeclStmt:
// nop
case *ast.DeferStmt:
children = append(children,
tok(n.Defer, len("defer")))
case *ast.Ellipsis:
children = append(children,
tok(n.Ellipsis, len("...")))
case *ast.EmptyStmt:
// nop
case *ast.ExprStmt:
// nop
case *ast.Field:
// TODO(adonovan): Field.{Doc,Comment,Tag}?
case *ast.FieldList:
if n.Opening.IsValid() {
children = append(children, tok(n.Opening, len("(")))
}
if n.Closing.IsValid() {
children = append(children, tok(n.Closing, len(")")))
}
case *ast.File:
// TODO test: Doc
children = append(children,
tok(n.Package, len("package")))
case *ast.ForStmt:
children = append(children,
tok(n.For, len("for")))
case *ast.FuncDecl:
// TODO(adonovan): FuncDecl.Comment?
// Uniquely, FuncDecl breaks the invariant that
// preorder traversal yields tokens in lexical order:
// in fact, FuncDecl.Recv precedes FuncDecl.Type.Func.
//
// As a workaround, we inline the case for FuncType
// here and order things correctly.
// We also need to insert the elided FuncType just
// before the 'visit' recursion.
//
children = nil // discard ast.Walk(FuncDecl) info subtrees
children = append(children, tok(n.Type.Func, len("func")))
if n.Recv != nil {
children = append(children, n.Recv)
}
children = append(children, n.Name)
if tparams := n.Type.TypeParams; tparams != nil {
children = append(children, tparams)
}
if n.Type.Params != nil {
children = append(children, n.Type.Params)
}
if n.Type.Results != nil {
children = append(children, n.Type.Results)
}
if n.Body != nil {
children = append(children, n.Body)
}
case *ast.FuncLit:
// nop
case *ast.FuncType:
if n.Func != 0 {
children = append(children,
tok(n.Func, len("func")))
}
case *ast.GenDecl:
children = append(children,
tok(n.TokPos, len(n.Tok.String())))
if n.Lparen != 0 {
children = append(children,
tok(n.Lparen, len("(")),
tok(n.Rparen, len(")")))
}
case *ast.GoStmt:
children = append(children,
tok(n.Go, len("go")))
case *ast.Ident:
children = append(children,
tok(n.NamePos, len(n.Name)))
case *ast.IfStmt:
children = append(children,
tok(n.If, len("if")))
case *ast.ImportSpec:
// TODO(adonovan): ImportSpec.{Doc,EndPos}?
case *ast.IncDecStmt:
children = append(children,
tok(n.TokPos, len(n.Tok.String())))
case *ast.IndexExpr:
children = append(children,
tok(n.Lbrack, len("[")),
tok(n.Rbrack, len("]")))
case *ast.IndexListExpr:
children = append(children,
tok(n.Lbrack, len("[")),
tok(n.Rbrack, len("]")))
case *ast.InterfaceType:
children = append(children,
tok(n.Interface, len("interface")))
case *ast.KeyValueExpr:
children = append(children,
tok(n.Colon, len(":")))
case *ast.LabeledStmt:
children = append(children,
tok(n.Colon, len(":")))
case *ast.MapType:
children = append(children,
tok(n.Map, len("map")))
case *ast.ParenExpr:
children = append(children,
tok(n.Lparen, len("(")),
tok(n.Rparen, len(")")))
case *ast.RangeStmt:
children = append(children,
tok(n.For, len("for")),
tok(n.TokPos, len(n.Tok.String())))
case *ast.ReturnStmt:
children = append(children,
tok(n.Return, len("return")))
case *ast.SelectStmt:
children = append(children,
tok(n.Select, len("select")))
case *ast.SelectorExpr:
// nop
case *ast.SendStmt:
children = append(children,
tok(n.Arrow, len("<-")))
case *ast.SliceExpr:
children = append(children,
tok(n.Lbrack, len("[")),
tok(n.Rbrack, len("]")))
case *ast.StarExpr:
children = append(children, tok(n.Star, len("*")))
case *ast.StructType:
children = append(children, tok(n.Struct, len("struct")))
case *ast.SwitchStmt:
children = append(children, tok(n.Switch, len("switch")))
case *ast.TypeAssertExpr:
children = append(children,
tok(n.Lparen-1, len(".")),
tok(n.Lparen, len("(")),
tok(n.Rparen, len(")")))
case *ast.TypeSpec:
// TODO(adonovan): TypeSpec.{Doc,Comment}?
case *ast.TypeSwitchStmt:
children = append(children, tok(n.Switch, len("switch")))
case *ast.UnaryExpr:
children = append(children, tok(n.OpPos, len(n.Op.String())))
case *ast.ValueSpec:
// TODO(adonovan): ValueSpec.{Doc,Comment}?
case *ast.BadDecl, *ast.BadExpr, *ast.BadStmt:
// nop
}
// TODO(adonovan): opt: merge the logic of ast.Inspect() into
// the switch above so we can make interleaved callbacks for
// both Nodes and Tokens in the right order and avoid the need
// to sort.
sort.Sort(byPos(children))
return children
}
type byPos []ast.Node
func (sl byPos) Len() int {
return len(sl)
}
func (sl byPos) Less(i, j int) bool {
return sl[i].Pos() < sl[j].Pos()
}
func (sl byPos) Swap(i, j int) {
sl[i], sl[j] = sl[j], sl[i]
}
// NodeDescription returns a description of the concrete type of n suitable
// for a user interface.
//
// TODO(adonovan): in some cases (e.g. Field, FieldList, Ident,
// StarExpr) we could be much more specific given the path to the AST
// root. Perhaps we should do that.
func NodeDescription(n ast.Node) string {
switch n := n.(type) {
case *ast.ArrayType:
return "array type"
case *ast.AssignStmt:
return "assignment"
case *ast.BadDecl:
return "bad declaration"
case *ast.BadExpr:
return "bad expression"
case *ast.BadStmt:
return "bad statement"
case *ast.BasicLit:
return "basic literal"
case *ast.BinaryExpr:
return fmt.Sprintf("binary %s operation", n.Op)
case *ast.BlockStmt:
return "block"
case *ast.BranchStmt:
switch n.Tok {
case token.BREAK:
return "break statement"
case token.CONTINUE:
return "continue statement"
case token.GOTO:
return "goto statement"
case token.FALLTHROUGH:
return "fall-through statement"
}
case *ast.CallExpr:
if len(n.Args) == 1 && !n.Ellipsis.IsValid() {
return "function call (or conversion)"
}
return "function call"
case *ast.CaseClause:
return "case clause"
case *ast.ChanType:
return "channel type"
case *ast.CommClause:
return "communication clause"
case *ast.Comment:
return "comment"
case *ast.CommentGroup:
return "comment group"
case *ast.CompositeLit:
return "composite literal"
case *ast.DeclStmt:
return NodeDescription(n.Decl) + " statement"
case *ast.DeferStmt:
return "defer statement"
case *ast.Ellipsis:
return "ellipsis"
case *ast.EmptyStmt:
return "empty statement"
case *ast.ExprStmt:
return "expression statement"
case *ast.Field:
// Can be any of these:
// struct {x, y int} -- struct field(s)
// struct {T} -- anon struct field
// interface {I} -- interface embedding
// interface {f()} -- interface method
// func (A) func(B) C -- receiver, param(s), result(s)
return "field/method/parameter"
case *ast.FieldList:
return "field/method/parameter list"
case *ast.File:
return "source file"
case *ast.ForStmt:
return "for loop"
case *ast.FuncDecl:
return "function declaration"
case *ast.FuncLit:
return "function literal"
case *ast.FuncType:
return "function type"
case *ast.GenDecl:
switch n.Tok {
case token.IMPORT:
return "import declaration"
case token.CONST:
return "constant declaration"
case token.TYPE:
return "type declaration"
case token.VAR:
return "variable declaration"
}
case *ast.GoStmt:
return "go statement"
case *ast.Ident:
return "identifier"
case *ast.IfStmt:
return "if statement"
case *ast.ImportSpec:
return "import specification"
case *ast.IncDecStmt:
if n.Tok == token.INC {
return "increment statement"
}
return "decrement statement"
case *ast.IndexExpr:
return "index expression"
case *ast.IndexListExpr:
return "index list expression"
case *ast.InterfaceType:
return "interface type"
case *ast.KeyValueExpr:
return "key/value association"
case *ast.LabeledStmt:
return "statement label"
case *ast.MapType:
return "map type"
case *ast.Package:
return "package"
case *ast.ParenExpr:
return "parenthesized " + NodeDescription(n.X)
case *ast.RangeStmt:
return "range loop"
case *ast.ReturnStmt:
return "return statement"
case *ast.SelectStmt:
return "select statement"
case *ast.SelectorExpr:
return "selector"
case *ast.SendStmt:
return "channel send"
case *ast.SliceExpr:
return "slice expression"
case *ast.StarExpr:
return "*-operation" // load/store expr or pointer type
case *ast.StructType:
return "struct type"
case *ast.SwitchStmt:
return "switch statement"
case *ast.TypeAssertExpr:
return "type assertion"
case *ast.TypeSpec:
return "type specification"
case *ast.TypeSwitchStmt:
return "type switch"
case *ast.UnaryExpr:
return fmt.Sprintf("unary %s operation", n.Op)
case *ast.ValueSpec:
return "value specification"
}
panic(fmt.Sprintf("unexpected node type: %T", n))
}
func is[T any](x any) bool {
_, ok := x.(T)
return ok
}
+487
View File
@@ -0,0 +1,487 @@
// Copyright 2013 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.
// Package astutil contains common utilities for working with the Go AST.
package astutil // import "golang.org/x/tools/go/ast/astutil"
import (
"fmt"
"go/ast"
"go/token"
"reflect"
"slices"
"strconv"
"strings"
)
// AddImport adds the import path to the file f, if absent.
func AddImport(fset *token.FileSet, f *ast.File, path string) (added bool) {
return AddNamedImport(fset, f, "", path)
}
// AddNamedImport adds the import with the given name and path to the file f, if absent.
// If name is not empty, it is used to rename the import.
//
// For example, calling
//
// AddNamedImport(fset, f, "pathpkg", "path")
//
// adds
//
// import pathpkg "path"
func AddNamedImport(fset *token.FileSet, f *ast.File, name, path string) (added bool) {
if imports(f, name, path) {
return false
}
newImport := &ast.ImportSpec{
Path: &ast.BasicLit{
Kind: token.STRING,
Value: strconv.Quote(path),
},
}
if name != "" {
newImport.Name = &ast.Ident{Name: name}
}
// Find an import decl to add to.
// The goal is to find an existing import
// whose import path has the longest shared
// prefix with path.
var (
bestMatch = -1 // length of longest shared prefix
lastImport = -1 // index in f.Decls of the file's final import decl
impDecl *ast.GenDecl // import decl containing the best match
impIndex = -1 // spec index in impDecl containing the best match
isThirdPartyPath = isThirdParty(path)
)
for i, decl := range f.Decls {
gen, ok := decl.(*ast.GenDecl)
if ok && gen.Tok == token.IMPORT {
lastImport = i
// Do not add to import "C", to avoid disrupting the
// association with its doc comment, breaking cgo.
if declImports(gen, "C") {
continue
}
// Match an empty import decl if that's all that is available.
if len(gen.Specs) == 0 && bestMatch == -1 {
impDecl = gen
}
// Compute longest shared prefix with imports in this group and find best
// matched import spec.
// 1. Always prefer import spec with longest shared prefix.
// 2. While match length is 0,
// - for stdlib package: prefer first import spec.
// - for third party package: prefer first third party import spec.
// We cannot use last import spec as best match for third party package
// because grouped imports are usually placed last by goimports -local
// flag.
// See issue #19190.
seenAnyThirdParty := false
for j, spec := range gen.Specs {
impspec := spec.(*ast.ImportSpec)
p := importPath(impspec)
n := matchLen(p, path)
if n > bestMatch || (bestMatch == 0 && !seenAnyThirdParty && isThirdPartyPath) {
bestMatch = n
impDecl = gen
impIndex = j
}
seenAnyThirdParty = seenAnyThirdParty || isThirdParty(p)
}
}
}
// If no import decl found, add one after the last import.
if impDecl == nil {
impDecl = &ast.GenDecl{
Tok: token.IMPORT,
}
if lastImport >= 0 {
impDecl.TokPos = f.Decls[lastImport].End()
} else {
// There are no existing imports.
// Our new import, preceded by a blank line, goes after the package declaration
// and after the comment, if any, that starts on the same line as the
// package declaration.
impDecl.TokPos = f.Package
file := fset.File(f.Package)
pkgLine := file.Line(f.Package)
for _, c := range f.Comments {
if file.Line(c.Pos()) > pkgLine {
break
}
// +2 for a blank line
impDecl.TokPos = c.End() + 2
}
}
f.Decls = append(f.Decls, nil)
copy(f.Decls[lastImport+2:], f.Decls[lastImport+1:])
f.Decls[lastImport+1] = impDecl
}
// Insert new import at insertAt.
insertAt := 0
if impIndex >= 0 {
// insert after the found import
insertAt = impIndex + 1
}
impDecl.Specs = append(impDecl.Specs, nil)
copy(impDecl.Specs[insertAt+1:], impDecl.Specs[insertAt:])
impDecl.Specs[insertAt] = newImport
pos := impDecl.Pos()
if insertAt > 0 {
// If there is a comment after an existing import, preserve the comment
// position by adding the new import after the comment.
if spec, ok := impDecl.Specs[insertAt-1].(*ast.ImportSpec); ok && spec.Comment != nil {
pos = spec.Comment.End()
} else {
// Assign same position as the previous import,
// so that the sorter sees it as being in the same block.
pos = impDecl.Specs[insertAt-1].Pos()
}
}
if newImport.Name != nil {
newImport.Name.NamePos = pos
}
updateBasicLitPos(newImport.Path, pos)
newImport.EndPos = pos
// Clean up parens. impDecl contains at least one spec.
if len(impDecl.Specs) == 1 {
// Remove unneeded parens.
impDecl.Lparen = token.NoPos
} else if !impDecl.Lparen.IsValid() {
// impDecl needs parens added.
impDecl.Lparen = impDecl.Specs[0].Pos()
}
f.Imports = append(f.Imports, newImport)
if len(f.Decls) <= 1 {
return true
}
// Merge all the import declarations into the first one.
var first *ast.GenDecl
for i := 0; i < len(f.Decls); i++ {
decl := f.Decls[i]
gen, ok := decl.(*ast.GenDecl)
if !ok || gen.Tok != token.IMPORT || declImports(gen, "C") {
continue
}
if first == nil {
first = gen
continue // Don't touch the first one.
}
// We now know there is more than one package in this import
// declaration. Ensure that it ends up parenthesized.
first.Lparen = first.Pos()
// Move the imports of the other import declaration to the first one.
for _, spec := range gen.Specs {
updateBasicLitPos(spec.(*ast.ImportSpec).Path, first.Pos())
first.Specs = append(first.Specs, spec)
}
f.Decls = slices.Delete(f.Decls, i, i+1)
i--
}
return true
}
func isThirdParty(importPath string) bool {
// Third party package import path usually contains "." (".com", ".org", ...)
// This logic is taken from golang.org/x/tools/imports package.
return strings.Contains(importPath, ".")
}
// DeleteImport deletes the import path from the file f, if present.
// If there are duplicate import declarations, all matching ones are deleted.
func DeleteImport(fset *token.FileSet, f *ast.File, path string) (deleted bool) {
return DeleteNamedImport(fset, f, "", path)
}
// DeleteNamedImport deletes the import with the given name and path from the file f, if present.
// If there are duplicate import declarations, all matching ones are deleted.
func DeleteNamedImport(fset *token.FileSet, f *ast.File, name, path string) (deleted bool) {
var (
delspecs = make(map[*ast.ImportSpec]bool)
delcomments = make(map[*ast.CommentGroup]bool)
)
// Find the import nodes that import path, if any.
for i := 0; i < len(f.Decls); i++ {
gen, ok := f.Decls[i].(*ast.GenDecl)
if !ok || gen.Tok != token.IMPORT {
continue
}
for j := 0; j < len(gen.Specs); j++ {
impspec := gen.Specs[j].(*ast.ImportSpec)
if importName(impspec) != name || importPath(impspec) != path {
continue
}
// We found an import spec that imports path.
// Delete it.
delspecs[impspec] = true
deleted = true
gen.Specs = slices.Delete(gen.Specs, j, j+1)
// If this was the last import spec in this decl,
// delete the decl, too.
if len(gen.Specs) == 0 {
f.Decls = slices.Delete(f.Decls, i, i+1)
i--
break
} else if len(gen.Specs) == 1 {
if impspec.Doc != nil {
delcomments[impspec.Doc] = true
}
if impspec.Comment != nil {
delcomments[impspec.Comment] = true
}
for _, cg := range f.Comments {
// Found comment on the same line as the import spec.
if cg.End() < impspec.Pos() && fset.Position(cg.End()).Line == fset.Position(impspec.Pos()).Line {
delcomments[cg] = true
break
}
}
spec := gen.Specs[0].(*ast.ImportSpec)
// Move the documentation right after the import decl.
if spec.Doc != nil {
for fset.Position(gen.TokPos).Line+1 < fset.Position(spec.Doc.Pos()).Line {
fset.File(gen.TokPos).MergeLine(fset.Position(gen.TokPos).Line)
}
}
for _, cg := range f.Comments {
if cg.End() < spec.Pos() && fset.Position(cg.End()).Line == fset.Position(spec.Pos()).Line {
for fset.Position(gen.TokPos).Line+1 < fset.Position(spec.Pos()).Line {
fset.File(gen.TokPos).MergeLine(fset.Position(gen.TokPos).Line)
}
break
}
}
}
if j > 0 {
lastImpspec := gen.Specs[j-1].(*ast.ImportSpec)
lastLine := fset.PositionFor(lastImpspec.Path.ValuePos, false).Line
line := fset.PositionFor(impspec.Path.ValuePos, false).Line
// We deleted an entry but now there may be
// a blank line-sized hole where the import was.
if line-lastLine > 1 || !gen.Rparen.IsValid() {
// There was a blank line immediately preceding the deleted import,
// so there's no need to close the hole. The right parenthesis is
// invalid after AddImport to an import statement without parenthesis.
// Do nothing.
} else if line != fset.File(gen.Rparen).LineCount() {
// There was no blank line. Close the hole.
fset.File(gen.Rparen).MergeLine(line)
}
}
j--
}
}
// Delete imports from f.Imports.
before := len(f.Imports)
f.Imports = slices.DeleteFunc(f.Imports, func(imp *ast.ImportSpec) bool {
_, ok := delspecs[imp]
return ok
})
if len(f.Imports)+len(delspecs) != before {
// This can happen when the AST is invalid (i.e. imports differ between f.Decls and f.Imports).
panic(fmt.Sprintf("deleted specs from Decls but not Imports: %v", delspecs))
}
// Delete comments from f.Comments.
f.Comments = slices.DeleteFunc(f.Comments, func(cg *ast.CommentGroup) bool {
_, ok := delcomments[cg]
return ok
})
return
}
// RewriteImport rewrites any import of path oldPath to path newPath.
func RewriteImport(fset *token.FileSet, f *ast.File, oldPath, newPath string) (rewrote bool) {
for _, imp := range f.Imports {
if importPath(imp) == oldPath {
rewrote = true
// record old End, because the default is to compute
// it using the length of imp.Path.Value.
imp.EndPos = imp.End()
imp.Path.Value = strconv.Quote(newPath)
}
}
return
}
// UsesImport reports whether a given import is used.
// The provided File must have been parsed with syntactic object resolution
// (not using go/parser.SkipObjectResolution).
func UsesImport(f *ast.File, path string) (used bool) {
if f.Scope == nil {
panic("file f was not parsed with syntactic object resolution")
}
spec := importSpec(f, path)
if spec == nil {
return
}
name := spec.Name.String()
switch name {
case "<nil>":
// If the package name is not explicitly specified,
// make an educated guess. This is not guaranteed to be correct.
lastSlash := strings.LastIndex(path, "/")
if lastSlash == -1 {
name = path
} else {
name = path[lastSlash+1:]
}
case "_", ".":
// Not sure if this import is used - err on the side of caution.
return true
}
ast.Walk(visitFn(func(n ast.Node) {
sel, ok := n.(*ast.SelectorExpr)
if ok && isTopName(sel.X, name) {
used = true
}
}), f)
return
}
type visitFn func(node ast.Node)
func (fn visitFn) Visit(node ast.Node) ast.Visitor {
fn(node)
return fn
}
// imports reports whether f has an import with the specified name and path.
func imports(f *ast.File, name, path string) bool {
for _, s := range f.Imports {
if importName(s) == name && importPath(s) == path {
return true
}
}
return false
}
// importSpec returns the import spec if f imports path,
// or nil otherwise.
func importSpec(f *ast.File, path string) *ast.ImportSpec {
for _, s := range f.Imports {
if importPath(s) == path {
return s
}
}
return nil
}
// importName returns the name of s,
// or "" if the import is not named.
func importName(s *ast.ImportSpec) string {
if s.Name == nil {
return ""
}
return s.Name.Name
}
// importPath returns the unquoted import path of s,
// or "" if the path is not properly quoted.
func importPath(s *ast.ImportSpec) string {
t, err := strconv.Unquote(s.Path.Value)
if err != nil {
return ""
}
return t
}
// declImports reports whether gen contains an import of path.
func declImports(gen *ast.GenDecl, path string) bool {
if gen.Tok != token.IMPORT {
return false
}
for _, spec := range gen.Specs {
impspec := spec.(*ast.ImportSpec)
if importPath(impspec) == path {
return true
}
}
return false
}
// matchLen returns the length of the longest path segment prefix shared by x and y.
func matchLen(x, y string) int {
n := 0
for i := 0; i < len(x) && i < len(y) && x[i] == y[i]; i++ {
if x[i] == '/' {
n++
}
}
return n
}
// isTopName returns true if n is a top-level unresolved identifier with the given name.
func isTopName(n ast.Expr, name string) bool {
id, ok := n.(*ast.Ident)
return ok && id.Name == name && id.Obj == nil
}
// Imports returns the file imports grouped by paragraph.
func Imports(fset *token.FileSet, f *ast.File) [][]*ast.ImportSpec {
var groups [][]*ast.ImportSpec
for _, decl := range f.Decls {
genDecl, ok := decl.(*ast.GenDecl)
if !ok || genDecl.Tok != token.IMPORT {
break
}
group := []*ast.ImportSpec{}
var lastLine int
for _, spec := range genDecl.Specs {
importSpec := spec.(*ast.ImportSpec)
pos := importSpec.Path.ValuePos
line := fset.Position(pos).Line
if lastLine > 0 && pos > 0 && line-lastLine > 1 {
groups = append(groups, group)
group = []*ast.ImportSpec{}
}
group = append(group, importSpec)
lastLine = line
}
groups = append(groups, group)
}
return groups
}
// updateBasicLitPos updates lit.Pos,
// ensuring that lit.End (if set) is displaced by the same amount.
// (See https://go.dev/issue/76395.)
func updateBasicLitPos(lit *ast.BasicLit, pos token.Pos) {
len := lit.End() - lit.Pos()
lit.ValuePos = pos
// TODO(adonovan): after go1.26, simplify to:
// lit.ValueEnd = pos + len
v := reflect.ValueOf(lit).Elem().FieldByName("ValueEnd")
if v.IsValid() && v.Int() != 0 {
v.SetInt(int64(pos + len))
}
}
+490
View File
@@ -0,0 +1,490 @@
// Copyright 2017 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.
package astutil
import (
"fmt"
"go/ast"
"reflect"
"sort"
)
// An ApplyFunc is invoked by Apply for each node n, even if n is nil,
// before and/or after the node's children, using a Cursor describing
// the current node and providing operations on it.
//
// The return value of ApplyFunc controls the syntax tree traversal.
// See Apply for details.
type ApplyFunc func(*Cursor) bool
// Apply traverses a syntax tree recursively, starting with root,
// and calling pre and post for each node as described below.
// Apply returns the syntax tree, possibly modified.
//
// If pre is not nil, it is called for each node before the node's
// children are traversed (pre-order). If pre returns false, no
// children are traversed, and post is not called for that node.
//
// If post is not nil, and a prior call of pre didn't return false,
// post is called for each node after its children are traversed
// (post-order). If post returns false, traversal is terminated and
// Apply returns immediately.
//
// Only fields that refer to AST nodes are considered children;
// i.e., token.Pos, Scopes, Objects, and fields of basic types
// (strings, etc.) are ignored.
//
// Children are traversed in the order in which they appear in the
// respective node's struct definition. A package's files are
// traversed in the filenames' alphabetical order.
func Apply(root ast.Node, pre, post ApplyFunc) (result ast.Node) {
parent := &struct{ ast.Node }{root}
defer func() {
if r := recover(); r != nil && r != abort {
panic(r)
}
result = parent.Node
}()
a := &application{pre: pre, post: post}
a.apply(parent, "Node", nil, root)
return
}
var abort = new(int) // singleton, to signal termination of Apply
// A Cursor describes a node encountered during Apply.
// Information about the node and its parent is available
// from the Node, Parent, Name, and Index methods.
//
// If p is a variable of type and value of the current parent node
// c.Parent(), and f is the field identifier with name c.Name(),
// the following invariants hold:
//
// p.f == c.Node() if c.Index() < 0
// p.f[c.Index()] == c.Node() if c.Index() >= 0
//
// The methods Replace, Delete, InsertBefore, and InsertAfter
// can be used to change the AST without disrupting Apply.
//
// This type is not to be confused with [inspector.Cursor] from
// package [golang.org/x/tools/go/ast/inspector], which provides
// stateless navigation of immutable syntax trees.
type Cursor struct {
parent ast.Node
name string
iter *iterator // valid if non-nil
node ast.Node
}
// Node returns the current Node.
func (c *Cursor) Node() ast.Node { return c.node }
// Parent returns the parent of the current Node.
func (c *Cursor) Parent() ast.Node { return c.parent }
// Name returns the name of the parent Node field that contains the current Node.
// If the parent is a *ast.Package and the current Node is a *ast.File, Name returns
// the filename for the current Node.
func (c *Cursor) Name() string { return c.name }
// Index reports the index >= 0 of the current Node in the slice of Nodes that
// contains it, or a value < 0 if the current Node is not part of a slice.
// The index of the current node changes if InsertBefore is called while
// processing the current node.
func (c *Cursor) Index() int {
if c.iter != nil {
return c.iter.index
}
return -1
}
// field returns the current node's parent field value.
func (c *Cursor) field() reflect.Value {
return reflect.Indirect(reflect.ValueOf(c.parent)).FieldByName(c.name)
}
// Replace replaces the current Node with n.
// The replacement node is not walked by Apply.
func (c *Cursor) Replace(n ast.Node) {
if _, ok := c.node.(*ast.File); ok {
file, ok := n.(*ast.File)
if !ok {
panic("attempt to replace *ast.File with non-*ast.File")
}
c.parent.(*ast.Package).Files[c.name] = file
return
}
v := c.field()
if i := c.Index(); i >= 0 {
v = v.Index(i)
}
v.Set(reflect.ValueOf(n))
}
// Delete deletes the current Node from its containing slice.
// If the current Node is not part of a slice, Delete panics.
// As a special case, if the current node is a package file,
// Delete removes it from the package's Files map.
func (c *Cursor) Delete() {
if _, ok := c.node.(*ast.File); ok {
delete(c.parent.(*ast.Package).Files, c.name)
return
}
i := c.Index()
if i < 0 {
panic("Delete node not contained in slice")
}
v := c.field()
l := v.Len()
reflect.Copy(v.Slice(i, l), v.Slice(i+1, l))
v.Index(l - 1).Set(reflect.Zero(v.Type().Elem()))
v.SetLen(l - 1)
c.iter.step--
}
// InsertAfter inserts n after the current Node in its containing slice.
// If the current Node is not part of a slice, InsertAfter panics.
// Apply does not walk n.
func (c *Cursor) InsertAfter(n ast.Node) {
i := c.Index()
if i < 0 {
panic("InsertAfter node not contained in slice")
}
v := c.field()
v.Set(reflect.Append(v, reflect.Zero(v.Type().Elem())))
l := v.Len()
reflect.Copy(v.Slice(i+2, l), v.Slice(i+1, l))
v.Index(i + 1).Set(reflect.ValueOf(n))
c.iter.step++
}
// InsertBefore inserts n before the current Node in its containing slice.
// If the current Node is not part of a slice, InsertBefore panics.
// Apply will not walk n.
func (c *Cursor) InsertBefore(n ast.Node) {
i := c.Index()
if i < 0 {
panic("InsertBefore node not contained in slice")
}
v := c.field()
v.Set(reflect.Append(v, reflect.Zero(v.Type().Elem())))
l := v.Len()
reflect.Copy(v.Slice(i+1, l), v.Slice(i, l))
v.Index(i).Set(reflect.ValueOf(n))
c.iter.index++
}
// application carries all the shared data so we can pass it around cheaply.
type application struct {
pre, post ApplyFunc
cursor Cursor
iter iterator
}
func (a *application) apply(parent ast.Node, name string, iter *iterator, n ast.Node) {
// convert typed nil into untyped nil
if v := reflect.ValueOf(n); v.Kind() == reflect.Pointer && v.IsNil() {
n = nil
}
// avoid heap-allocating a new cursor for each apply call; reuse a.cursor instead
saved := a.cursor
a.cursor.parent = parent
a.cursor.name = name
a.cursor.iter = iter
a.cursor.node = n
if a.pre != nil && !a.pre(&a.cursor) {
a.cursor = saved
return
}
// walk children
// (the order of the cases matches the order of the corresponding node types in go/ast)
switch n := n.(type) {
case nil:
// nothing to do
// Comments and fields
case *ast.Comment:
// nothing to do
case *ast.CommentGroup:
if n != nil {
a.applyList(n, "List")
}
case *ast.Field:
a.apply(n, "Doc", nil, n.Doc)
a.applyList(n, "Names")
a.apply(n, "Type", nil, n.Type)
a.apply(n, "Tag", nil, n.Tag)
a.apply(n, "Comment", nil, n.Comment)
case *ast.FieldList:
a.applyList(n, "List")
// Expressions
case *ast.BadExpr, *ast.Ident, *ast.BasicLit:
// nothing to do
case *ast.Ellipsis:
a.apply(n, "Elt", nil, n.Elt)
case *ast.FuncLit:
a.apply(n, "Type", nil, n.Type)
a.apply(n, "Body", nil, n.Body)
case *ast.CompositeLit:
a.apply(n, "Type", nil, n.Type)
a.applyList(n, "Elts")
case *ast.ParenExpr:
a.apply(n, "X", nil, n.X)
case *ast.SelectorExpr:
a.apply(n, "X", nil, n.X)
a.apply(n, "Sel", nil, n.Sel)
case *ast.IndexExpr:
a.apply(n, "X", nil, n.X)
a.apply(n, "Index", nil, n.Index)
case *ast.IndexListExpr:
a.apply(n, "X", nil, n.X)
a.applyList(n, "Indices")
case *ast.SliceExpr:
a.apply(n, "X", nil, n.X)
a.apply(n, "Low", nil, n.Low)
a.apply(n, "High", nil, n.High)
a.apply(n, "Max", nil, n.Max)
case *ast.TypeAssertExpr:
a.apply(n, "X", nil, n.X)
a.apply(n, "Type", nil, n.Type)
case *ast.CallExpr:
a.apply(n, "Fun", nil, n.Fun)
a.applyList(n, "Args")
case *ast.StarExpr:
a.apply(n, "X", nil, n.X)
case *ast.UnaryExpr:
a.apply(n, "X", nil, n.X)
case *ast.BinaryExpr:
a.apply(n, "X", nil, n.X)
a.apply(n, "Y", nil, n.Y)
case *ast.KeyValueExpr:
a.apply(n, "Key", nil, n.Key)
a.apply(n, "Value", nil, n.Value)
// Types
case *ast.ArrayType:
a.apply(n, "Len", nil, n.Len)
a.apply(n, "Elt", nil, n.Elt)
case *ast.StructType:
a.apply(n, "Fields", nil, n.Fields)
case *ast.FuncType:
if tparams := n.TypeParams; tparams != nil {
a.apply(n, "TypeParams", nil, tparams)
}
a.apply(n, "Params", nil, n.Params)
a.apply(n, "Results", nil, n.Results)
case *ast.InterfaceType:
a.apply(n, "Methods", nil, n.Methods)
case *ast.MapType:
a.apply(n, "Key", nil, n.Key)
a.apply(n, "Value", nil, n.Value)
case *ast.ChanType:
a.apply(n, "Value", nil, n.Value)
// Statements
case *ast.BadStmt:
// nothing to do
case *ast.DeclStmt:
a.apply(n, "Decl", nil, n.Decl)
case *ast.EmptyStmt:
// nothing to do
case *ast.LabeledStmt:
a.apply(n, "Label", nil, n.Label)
a.apply(n, "Stmt", nil, n.Stmt)
case *ast.ExprStmt:
a.apply(n, "X", nil, n.X)
case *ast.SendStmt:
a.apply(n, "Chan", nil, n.Chan)
a.apply(n, "Value", nil, n.Value)
case *ast.IncDecStmt:
a.apply(n, "X", nil, n.X)
case *ast.AssignStmt:
a.applyList(n, "Lhs")
a.applyList(n, "Rhs")
case *ast.GoStmt:
a.apply(n, "Call", nil, n.Call)
case *ast.DeferStmt:
a.apply(n, "Call", nil, n.Call)
case *ast.ReturnStmt:
a.applyList(n, "Results")
case *ast.BranchStmt:
a.apply(n, "Label", nil, n.Label)
case *ast.BlockStmt:
a.applyList(n, "List")
case *ast.IfStmt:
a.apply(n, "Init", nil, n.Init)
a.apply(n, "Cond", nil, n.Cond)
a.apply(n, "Body", nil, n.Body)
a.apply(n, "Else", nil, n.Else)
case *ast.CaseClause:
a.applyList(n, "List")
a.applyList(n, "Body")
case *ast.SwitchStmt:
a.apply(n, "Init", nil, n.Init)
a.apply(n, "Tag", nil, n.Tag)
a.apply(n, "Body", nil, n.Body)
case *ast.TypeSwitchStmt:
a.apply(n, "Init", nil, n.Init)
a.apply(n, "Assign", nil, n.Assign)
a.apply(n, "Body", nil, n.Body)
case *ast.CommClause:
a.apply(n, "Comm", nil, n.Comm)
a.applyList(n, "Body")
case *ast.SelectStmt:
a.apply(n, "Body", nil, n.Body)
case *ast.ForStmt:
a.apply(n, "Init", nil, n.Init)
a.apply(n, "Cond", nil, n.Cond)
a.apply(n, "Post", nil, n.Post)
a.apply(n, "Body", nil, n.Body)
case *ast.RangeStmt:
a.apply(n, "Key", nil, n.Key)
a.apply(n, "Value", nil, n.Value)
a.apply(n, "X", nil, n.X)
a.apply(n, "Body", nil, n.Body)
// Declarations
case *ast.ImportSpec:
a.apply(n, "Doc", nil, n.Doc)
a.apply(n, "Name", nil, n.Name)
a.apply(n, "Path", nil, n.Path)
a.apply(n, "Comment", nil, n.Comment)
case *ast.ValueSpec:
a.apply(n, "Doc", nil, n.Doc)
a.applyList(n, "Names")
a.apply(n, "Type", nil, n.Type)
a.applyList(n, "Values")
a.apply(n, "Comment", nil, n.Comment)
case *ast.TypeSpec:
a.apply(n, "Doc", nil, n.Doc)
a.apply(n, "Name", nil, n.Name)
if tparams := n.TypeParams; tparams != nil {
a.apply(n, "TypeParams", nil, tparams)
}
a.apply(n, "Type", nil, n.Type)
a.apply(n, "Comment", nil, n.Comment)
case *ast.BadDecl:
// nothing to do
case *ast.GenDecl:
a.apply(n, "Doc", nil, n.Doc)
a.applyList(n, "Specs")
case *ast.FuncDecl:
a.apply(n, "Doc", nil, n.Doc)
a.apply(n, "Recv", nil, n.Recv)
a.apply(n, "Name", nil, n.Name)
a.apply(n, "Type", nil, n.Type)
a.apply(n, "Body", nil, n.Body)
// Files and packages
case *ast.File:
a.apply(n, "Doc", nil, n.Doc)
a.apply(n, "Name", nil, n.Name)
a.applyList(n, "Decls")
// Don't walk n.Comments; they have either been walked already if
// they are Doc comments, or they can be easily walked explicitly.
case *ast.Package:
// collect and sort names for reproducible behavior
var names []string
for name := range n.Files {
names = append(names, name)
}
sort.Strings(names)
for _, name := range names {
a.apply(n, name, nil, n.Files[name])
}
default:
panic(fmt.Sprintf("Apply: unexpected node type %T", n))
}
if a.post != nil && !a.post(&a.cursor) {
panic(abort)
}
a.cursor = saved
}
// An iterator controls iteration over a slice of nodes.
type iterator struct {
index, step int
}
func (a *application) applyList(parent ast.Node, name string) {
// avoid heap-allocating a new iterator for each applyList call; reuse a.iter instead
saved := a.iter
a.iter.index = 0
for {
// must reload parent.name each time, since cursor modifications might change it
v := reflect.Indirect(reflect.ValueOf(parent)).FieldByName(name)
if a.iter.index >= v.Len() {
break
}
// element x may be nil in a bad AST - be cautious
var x ast.Node
if e := v.Index(a.iter.index); e.IsValid() {
x = e.Interface().(ast.Node)
}
a.iter.step = 1
a.apply(parent, name, &a.iter, x)
a.iter.index += a.iter.step
}
a.iter = saved
}
+13
View File
@@ -0,0 +1,13 @@
// Copyright 2015 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.
package astutil
import "go/ast"
// Unparen returns e with any enclosing parentheses stripped.
// Deprecated: use [ast.Unparen].
//
//go:fix inline
func Unparen(e ast.Expr) ast.Expr { return ast.Unparen(e) }
+295
View File
@@ -0,0 +1,295 @@
// Copyright 2025 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.
// Package edge defines identifiers for each field of an ast.Node
// struct type that refers to another Node.
package edge
import (
"fmt"
"go/ast"
"reflect"
)
// A Kind describes a field of an ast.Node struct.
type Kind uint8
// String returns a description of the edge kind.
func (k Kind) String() string {
if k == Invalid {
return "<invalid>"
}
info := fieldInfos[k]
return fmt.Sprintf("%v.%s", info.nodeType.Elem().Name(), info.name)
}
// NodeType returns the pointer-to-struct type of the ast.Node implementation.
func (k Kind) NodeType() reflect.Type { return fieldInfos[k].nodeType }
// FieldName returns the name of the field.
func (k Kind) FieldName() string { return fieldInfos[k].name }
// FieldType returns the declared type of the field.
func (k Kind) FieldType() reflect.Type { return fieldInfos[k].fieldType }
// Get returns the direct child of n identified by (k, idx).
// n's type must match k.NodeType().
// idx must be a valid slice index, or -1 for a non-slice.
func (k Kind) Get(n ast.Node, idx int) ast.Node {
if k.NodeType() != reflect.TypeOf(n) {
panic(fmt.Sprintf("%v.Get(%T): invalid node type", k, n))
}
v := reflect.ValueOf(n).Elem().Field(fieldInfos[k].index)
if idx != -1 {
v = v.Index(idx) // asserts valid index
} else {
// (The type assertion below asserts that v is not a slice.)
}
return v.Interface().(ast.Node) // may be nil
}
const (
Invalid Kind = iota // for nodes at the root of the traversal
// Kinds are sorted alphabetically.
// Numbering is not stable.
// Each is named Type_Field, where Type is the
// ast.Node struct type and Field is the name of the field
ArrayType_Elt
ArrayType_Len
AssignStmt_Lhs
AssignStmt_Rhs
BinaryExpr_X
BinaryExpr_Y
BlockStmt_List
BranchStmt_Label
CallExpr_Args
CallExpr_Fun
CaseClause_Body
CaseClause_List
ChanType_Value
CommClause_Body
CommClause_Comm
CommentGroup_List
CompositeLit_Elts
CompositeLit_Type
DeclStmt_Decl
DeferStmt_Call
Ellipsis_Elt
ExprStmt_X
FieldList_List
Field_Comment
Field_Doc
Field_Names
Field_Tag
Field_Type
File_Decls
File_Doc
File_Name
ForStmt_Body
ForStmt_Cond
ForStmt_Init
ForStmt_Post
FuncDecl_Body
FuncDecl_Doc
FuncDecl_Name
FuncDecl_Recv
FuncDecl_Type
FuncLit_Body
FuncLit_Type
FuncType_Params
FuncType_Results
FuncType_TypeParams
GenDecl_Doc
GenDecl_Specs
GoStmt_Call
IfStmt_Body
IfStmt_Cond
IfStmt_Else
IfStmt_Init
ImportSpec_Comment
ImportSpec_Doc
ImportSpec_Name
ImportSpec_Path
IncDecStmt_X
IndexExpr_Index
IndexExpr_X
IndexListExpr_Indices
IndexListExpr_X
InterfaceType_Methods
KeyValueExpr_Key
KeyValueExpr_Value
LabeledStmt_Label
LabeledStmt_Stmt
MapType_Key
MapType_Value
ParenExpr_X
RangeStmt_Body
RangeStmt_Key
RangeStmt_Value
RangeStmt_X
ReturnStmt_Results
SelectStmt_Body
SelectorExpr_Sel
SelectorExpr_X
SendStmt_Chan
SendStmt_Value
SliceExpr_High
SliceExpr_Low
SliceExpr_Max
SliceExpr_X
StarExpr_X
StructType_Fields
SwitchStmt_Body
SwitchStmt_Init
SwitchStmt_Tag
TypeAssertExpr_Type
TypeAssertExpr_X
TypeSpec_Comment
TypeSpec_Doc
TypeSpec_Name
TypeSpec_Type
TypeSpec_TypeParams
TypeSwitchStmt_Assign
TypeSwitchStmt_Body
TypeSwitchStmt_Init
UnaryExpr_X
ValueSpec_Comment
ValueSpec_Doc
ValueSpec_Names
ValueSpec_Type
ValueSpec_Values
maxKind
)
// Assert that the encoding fits in 7 bits,
// as the inspector relies on this.
// (We are currently at 104.)
var _ = [1 << 7]struct{}{}[maxKind]
type fieldInfo struct {
nodeType reflect.Type // pointer-to-struct type of ast.Node implementation
name string
index int
fieldType reflect.Type
}
func info[N ast.Node](fieldName string) fieldInfo {
nodePtrType := reflect.TypeFor[N]()
f, ok := nodePtrType.Elem().FieldByName(fieldName)
if !ok {
panic(fieldName)
}
return fieldInfo{nodePtrType, fieldName, f.Index[0], f.Type}
}
var fieldInfos = [...]fieldInfo{
Invalid: {},
ArrayType_Elt: info[*ast.ArrayType]("Elt"),
ArrayType_Len: info[*ast.ArrayType]("Len"),
AssignStmt_Lhs: info[*ast.AssignStmt]("Lhs"),
AssignStmt_Rhs: info[*ast.AssignStmt]("Rhs"),
BinaryExpr_X: info[*ast.BinaryExpr]("X"),
BinaryExpr_Y: info[*ast.BinaryExpr]("Y"),
BlockStmt_List: info[*ast.BlockStmt]("List"),
BranchStmt_Label: info[*ast.BranchStmt]("Label"),
CallExpr_Args: info[*ast.CallExpr]("Args"),
CallExpr_Fun: info[*ast.CallExpr]("Fun"),
CaseClause_Body: info[*ast.CaseClause]("Body"),
CaseClause_List: info[*ast.CaseClause]("List"),
ChanType_Value: info[*ast.ChanType]("Value"),
CommClause_Body: info[*ast.CommClause]("Body"),
CommClause_Comm: info[*ast.CommClause]("Comm"),
CommentGroup_List: info[*ast.CommentGroup]("List"),
CompositeLit_Elts: info[*ast.CompositeLit]("Elts"),
CompositeLit_Type: info[*ast.CompositeLit]("Type"),
DeclStmt_Decl: info[*ast.DeclStmt]("Decl"),
DeferStmt_Call: info[*ast.DeferStmt]("Call"),
Ellipsis_Elt: info[*ast.Ellipsis]("Elt"),
ExprStmt_X: info[*ast.ExprStmt]("X"),
FieldList_List: info[*ast.FieldList]("List"),
Field_Comment: info[*ast.Field]("Comment"),
Field_Doc: info[*ast.Field]("Doc"),
Field_Names: info[*ast.Field]("Names"),
Field_Tag: info[*ast.Field]("Tag"),
Field_Type: info[*ast.Field]("Type"),
File_Decls: info[*ast.File]("Decls"),
File_Doc: info[*ast.File]("Doc"),
File_Name: info[*ast.File]("Name"),
ForStmt_Body: info[*ast.ForStmt]("Body"),
ForStmt_Cond: info[*ast.ForStmt]("Cond"),
ForStmt_Init: info[*ast.ForStmt]("Init"),
ForStmt_Post: info[*ast.ForStmt]("Post"),
FuncDecl_Body: info[*ast.FuncDecl]("Body"),
FuncDecl_Doc: info[*ast.FuncDecl]("Doc"),
FuncDecl_Name: info[*ast.FuncDecl]("Name"),
FuncDecl_Recv: info[*ast.FuncDecl]("Recv"),
FuncDecl_Type: info[*ast.FuncDecl]("Type"),
FuncLit_Body: info[*ast.FuncLit]("Body"),
FuncLit_Type: info[*ast.FuncLit]("Type"),
FuncType_Params: info[*ast.FuncType]("Params"),
FuncType_Results: info[*ast.FuncType]("Results"),
FuncType_TypeParams: info[*ast.FuncType]("TypeParams"),
GenDecl_Doc: info[*ast.GenDecl]("Doc"),
GenDecl_Specs: info[*ast.GenDecl]("Specs"),
GoStmt_Call: info[*ast.GoStmt]("Call"),
IfStmt_Body: info[*ast.IfStmt]("Body"),
IfStmt_Cond: info[*ast.IfStmt]("Cond"),
IfStmt_Else: info[*ast.IfStmt]("Else"),
IfStmt_Init: info[*ast.IfStmt]("Init"),
ImportSpec_Comment: info[*ast.ImportSpec]("Comment"),
ImportSpec_Doc: info[*ast.ImportSpec]("Doc"),
ImportSpec_Name: info[*ast.ImportSpec]("Name"),
ImportSpec_Path: info[*ast.ImportSpec]("Path"),
IncDecStmt_X: info[*ast.IncDecStmt]("X"),
IndexExpr_Index: info[*ast.IndexExpr]("Index"),
IndexExpr_X: info[*ast.IndexExpr]("X"),
IndexListExpr_Indices: info[*ast.IndexListExpr]("Indices"),
IndexListExpr_X: info[*ast.IndexListExpr]("X"),
InterfaceType_Methods: info[*ast.InterfaceType]("Methods"),
KeyValueExpr_Key: info[*ast.KeyValueExpr]("Key"),
KeyValueExpr_Value: info[*ast.KeyValueExpr]("Value"),
LabeledStmt_Label: info[*ast.LabeledStmt]("Label"),
LabeledStmt_Stmt: info[*ast.LabeledStmt]("Stmt"),
MapType_Key: info[*ast.MapType]("Key"),
MapType_Value: info[*ast.MapType]("Value"),
ParenExpr_X: info[*ast.ParenExpr]("X"),
RangeStmt_Body: info[*ast.RangeStmt]("Body"),
RangeStmt_Key: info[*ast.RangeStmt]("Key"),
RangeStmt_Value: info[*ast.RangeStmt]("Value"),
RangeStmt_X: info[*ast.RangeStmt]("X"),
ReturnStmt_Results: info[*ast.ReturnStmt]("Results"),
SelectStmt_Body: info[*ast.SelectStmt]("Body"),
SelectorExpr_Sel: info[*ast.SelectorExpr]("Sel"),
SelectorExpr_X: info[*ast.SelectorExpr]("X"),
SendStmt_Chan: info[*ast.SendStmt]("Chan"),
SendStmt_Value: info[*ast.SendStmt]("Value"),
SliceExpr_High: info[*ast.SliceExpr]("High"),
SliceExpr_Low: info[*ast.SliceExpr]("Low"),
SliceExpr_Max: info[*ast.SliceExpr]("Max"),
SliceExpr_X: info[*ast.SliceExpr]("X"),
StarExpr_X: info[*ast.StarExpr]("X"),
StructType_Fields: info[*ast.StructType]("Fields"),
SwitchStmt_Body: info[*ast.SwitchStmt]("Body"),
SwitchStmt_Init: info[*ast.SwitchStmt]("Init"),
SwitchStmt_Tag: info[*ast.SwitchStmt]("Tag"),
TypeAssertExpr_Type: info[*ast.TypeAssertExpr]("Type"),
TypeAssertExpr_X: info[*ast.TypeAssertExpr]("X"),
TypeSpec_Comment: info[*ast.TypeSpec]("Comment"),
TypeSpec_Doc: info[*ast.TypeSpec]("Doc"),
TypeSpec_Name: info[*ast.TypeSpec]("Name"),
TypeSpec_Type: info[*ast.TypeSpec]("Type"),
TypeSpec_TypeParams: info[*ast.TypeSpec]("TypeParams"),
TypeSwitchStmt_Assign: info[*ast.TypeSwitchStmt]("Assign"),
TypeSwitchStmt_Body: info[*ast.TypeSwitchStmt]("Body"),
TypeSwitchStmt_Init: info[*ast.TypeSwitchStmt]("Init"),
UnaryExpr_X: info[*ast.UnaryExpr]("X"),
ValueSpec_Comment: info[*ast.ValueSpec]("Comment"),
ValueSpec_Doc: info[*ast.ValueSpec]("Doc"),
ValueSpec_Names: info[*ast.ValueSpec]("Names"),
ValueSpec_Type: info[*ast.ValueSpec]("Type"),
ValueSpec_Values: info[*ast.ValueSpec]("Values"),
}
+551
View File
@@ -0,0 +1,551 @@
// Copyright 2025 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.
package inspector
import (
"fmt"
"go/ast"
"go/token"
"iter"
"reflect"
"golang.org/x/tools/go/ast/edge"
)
// A Cursor represents an [ast.Node]. It is immutable.
//
// Two Cursors compare equal if they represent the same node.
//
// The zero value of Cursor is not valid.
//
// Call [Inspector.Root] to obtain a cursor for the virtual root node
// of the traversal. This is the sole valid cursor for which [Cursor.Node]
// returns nil.
//
// Use the following methods to navigate efficiently around the tree:
// - for ancestors, use [Cursor.Parent] and [Cursor.Enclosing];
// - for children, use [Cursor.Child], [Cursor.Children],
// [Cursor.FirstChild], and [Cursor.LastChild];
// - for siblings, use [Cursor.PrevSibling] and [Cursor.NextSibling];
// - for descendants, use [Cursor.FindByPos], [Cursor.FindNode],
// [Cursor.Inspect], and [Cursor.Preorder].
//
// Use the [Cursor.ChildAt] and [Cursor.ParentEdge] methods for
// information about the edges in a tree: which field (and slice
// element) of the parent node holds the child.
type Cursor struct {
in *Inspector
index int32 // index of push node; -1 for virtual root node
}
// Root returns a valid cursor for the virtual root node,
// whose children are the files provided to [New].
//
// Its [Cursor.Node] method return nil.
func (in *Inspector) Root() Cursor {
return Cursor{in, -1}
}
// At returns the cursor at the specified index in the traversal,
// which must have been obtained from [Cursor.Index] on a Cursor
// belonging to the same Inspector (see [Cursor.Inspector]).
func (in *Inspector) At(index int32) Cursor {
if index < 0 {
panic("negative index")
}
if int(index) >= len(in.events) {
panic("index out of range for this inspector")
}
if in.events[index].index < index {
panic("invalid index") // (a push, not a pop)
}
return Cursor{in, index}
}
// Valid reports whether the cursor is valid.
// The zero value of cursor is invalid.
// Unless otherwise documented, it is not safe to call
// any other method on an invalid cursor.
func (c Cursor) Valid() bool {
return c.in != nil
}
// Inspector returns the cursor's Inspector.
// It returns nil if the Cursor is not valid.
func (c Cursor) Inspector() *Inspector { return c.in }
// Index returns the index of this cursor position within the package.
//
// Clients should not assume anything about the numeric Index value
// except that it increases monotonically throughout the traversal.
// It is provided for use with [Inspector.At].
//
// Index must not be called on the Root node.
func (c Cursor) Index() int32 {
if c.index < 0 {
panic("Index called on Root node")
}
return c.index
}
// Node returns the node at the current cursor position,
// or nil for the cursor returned by [Inspector.Root].
func (c Cursor) Node() ast.Node {
if c.index < 0 {
return nil
}
return c.in.events[c.index].node
}
// String returns information about the cursor's node, if any.
func (c Cursor) String() string {
if !c.Valid() {
return "(invalid)"
}
if c.index < 0 {
return "(root)"
}
return reflect.TypeOf(c.Node()).String()
}
// indices return the [start, end) half-open interval of event indices.
func (c Cursor) indices() (int32, int32) {
if c.index < 0 {
return 0, int32(len(c.in.events)) // root: all events
} else {
return c.index, c.in.events[c.index].index + 1 // just one subtree
}
}
// Preorder returns an iterator over the nodes of the subtree
// represented by c in depth-first order. Each node in the sequence is
// represented by a Cursor that allows access to the Node, but may
// also be used to start a new traversal, or to obtain the stack of
// nodes enclosing the cursor.
//
// The traversal sequence is determined by [ast.Inspect]. The types
// argument, if non-empty, enables type-based filtering of events. The
// function f if is called only for nodes whose type matches an
// element of the types slice.
//
// If you need control over descent into subtrees,
// or need both pre- and post-order notifications, use [Cursor.Inspect]
func (c Cursor) Preorder(types ...ast.Node) iter.Seq[Cursor] {
mask := maskOf(types)
return func(yield func(Cursor) bool) {
events := c.in.events
for i, limit := c.indices(); i < limit; {
ev := events[i]
if ev.index > i { // push?
if ev.typ&mask != 0 && !yield(Cursor{c.in, i}) {
break
}
pop := ev.index
if events[pop].typ&mask == 0 {
// Subtree does not contain types: skip.
i = pop + 1
continue
}
}
i++
}
}
}
// Inspect visits the nodes of the subtree represented by c in
// depth-first order. It calls f(n) for each node n before it
// visits n's children. If f returns true, Inspect invokes f
// recursively for each of the non-nil children of the node.
//
// Each node is represented by a Cursor that allows access to the
// Node, but may also be used to start a new traversal, or to obtain
// the stack of nodes enclosing the cursor.
//
// The complete traversal sequence is determined by [ast.Inspect].
// The types argument, if non-empty, enables type-based filtering of
// events. The function f if is called only for nodes whose type
// matches an element of the types slice.
func (c Cursor) Inspect(types []ast.Node, f func(c Cursor) (descend bool)) {
mask := maskOf(types)
events := c.in.events
for i, limit := c.indices(); i < limit; {
ev := events[i]
if ev.index > i {
// push
pop := ev.index
if ev.typ&mask != 0 && !f(Cursor{c.in, i}) ||
events[pop].typ&mask == 0 {
// The user opted not to descend, or the
// subtree does not contain types:
// skip past the pop.
i = pop + 1
continue
}
}
i++
}
}
// Enclosing returns an iterator over the nodes enclosing the current
// current node, starting with the Cursor itself.
//
// Enclosing must not be called on the Root node (whose [Cursor.Node] returns nil).
//
// The types argument, if non-empty, enables type-based filtering of
// events: the sequence includes only enclosing nodes whose type
// matches an element of the types slice.
func (c Cursor) Enclosing(types ...ast.Node) iter.Seq[Cursor] {
if c.index < 0 {
panic("Cursor.Enclosing called on Root node")
}
mask := maskOf(types)
return func(yield func(Cursor) bool) {
events := c.in.events
for i := c.index; i >= 0; i = events[i].parent {
if events[i].typ&mask != 0 && !yield(Cursor{c.in, i}) {
break
}
}
}
}
// Parent returns the parent of the current node.
//
// Parent must not be called on the Root node (whose [Cursor.Node] returns nil).
func (c Cursor) Parent() Cursor {
if c.index < 0 {
panic("Cursor.Parent called on Root node")
}
return Cursor{c.in, c.in.events[c.index].parent}
}
// ParentEdge returns the identity of the field in the parent node
// that holds this cursor's node, and if it is a list, the index within it.
//
// For example, f(x, y) is a CallExpr whose three children are Idents.
// f has edge kind [edge.CallExpr_Fun] and index -1.
// x and y have kind [edge.CallExpr_Args] and indices 0 and 1, respectively.
//
// If called on a child of the Root node, it returns ([edge.Invalid], -1).
//
// ParentEdge must not be called on the Root node (whose [Cursor.Node] returns nil).
func (c Cursor) ParentEdge() (edge.Kind, int) {
if c.index < 0 {
panic("Cursor.ParentEdge called on Root node")
}
events := c.in.events
pop := events[c.index].index
return unpackEdgeKindAndIndex(events[pop].parent)
}
// ParentEdgeKind returns the kind component of the result of [Cursor.ParentEdge].
func (c Cursor) ParentEdgeKind() edge.Kind {
ek, _ := c.ParentEdge()
return ek
}
// ParentEdgeIndex returns the index component of the result of [Cursor.ParentEdge].
func (c Cursor) ParentEdgeIndex() int {
_, index := c.ParentEdge()
return index
}
// ChildAt returns the cursor for the child of the
// current node identified by its edge and index.
// The index must be -1 if the edge.Kind is not a slice.
// The indicated child node must exist.
//
// ChildAt must not be called on the Root node (whose [Cursor.Node] returns nil).
//
// Invariant: c.Parent().ChildAt(c.ParentEdge()) == c.
func (c Cursor) ChildAt(k edge.Kind, idx int) Cursor {
target := packEdgeKindAndIndex(k, idx)
// Unfortunately there's no shortcut to looping.
events := c.in.events
i := c.index + 1
for {
pop := events[i].index
if pop < i {
break
}
if events[pop].parent == target {
return Cursor{c.in, i}
}
i = pop + 1
}
panic(fmt.Sprintf("ChildAt(%v, %d): no such child of %v", k, idx, c))
}
// Child returns the cursor for n, which must be a direct child of c's Node.
//
// Child must not be called on the Root node (whose [Cursor.Node] returns nil).
func (c Cursor) Child(n ast.Node) Cursor {
if c.index < 0 {
panic("Cursor.Child called on Root node")
}
if false {
// reference implementation
for child := range c.Children() {
if child.Node() == n {
return child
}
}
} else {
// optimized implementation
events := c.in.events
for i := c.index + 1; events[i].index > i; i = events[i].index + 1 {
if events[i].node == n {
return Cursor{c.in, i}
}
}
}
panic(fmt.Sprintf("Child(%T): not a child of %v", n, c))
}
// NextSibling returns the cursor for the next sibling node in the same list
// (for example, of files, decls, specs, statements, fields, or expressions) as
// the current node. It returns (zero, false) if the node is the last node in
// the list, or is not part of a list.
//
// NextSibling must not be called on the Root node.
//
// See note at [Cursor.Children].
func (c Cursor) NextSibling() (Cursor, bool) {
if c.index < 0 {
panic("Cursor.NextSibling called on Root node")
}
events := c.in.events
i := events[c.index].index + 1 // after corresponding pop
if i < int32(len(events)) {
if events[i].index > i { // push?
return Cursor{c.in, i}, true
}
}
return Cursor{}, false
}
// PrevSibling returns the cursor for the previous sibling node in the
// same list (for example, of files, decls, specs, statements, fields,
// or expressions) as the current node. It returns zero if the node is
// the first node in the list, or is not part of a list.
//
// It must not be called on the Root node.
//
// See note at [Cursor.Children].
func (c Cursor) PrevSibling() (Cursor, bool) {
if c.index < 0 {
panic("Cursor.PrevSibling called on Root node")
}
events := c.in.events
i := c.index - 1
if i >= 0 {
if j := events[i].index; j < i { // pop?
return Cursor{c.in, j}, true
}
}
return Cursor{}, false
}
// FirstChild returns the first direct child of the current node,
// or zero if it has no children.
func (c Cursor) FirstChild() (Cursor, bool) {
events := c.in.events
i := c.index + 1 // i=0 if c is root
if i < int32(len(events)) && events[i].index > i { // push?
return Cursor{c.in, i}, true
}
return Cursor{}, false
}
// LastChild returns the last direct child of the current node,
// or zero if it has no children.
func (c Cursor) LastChild() (Cursor, bool) {
events := c.in.events
if c.index < 0 { // root?
if len(events) > 0 {
// return push of final event (a pop)
return Cursor{c.in, events[len(events)-1].index}, true
}
} else {
j := events[c.index].index - 1 // before corresponding pop
// Inv: j == c.index if c has no children
// or j is last child's pop.
if j > c.index { // c has children
return Cursor{c.in, events[j].index}, true
}
}
return Cursor{}, false
}
// Children returns an iterator over the direct children of the
// current node, if any.
//
// When using Children, NextChild, and PrevChild, bear in mind that a
// Node's children may come from different fields, some of which may
// be lists of nodes without a distinguished intervening container
// such as [ast.BlockStmt].
//
// For example, [ast.CaseClause] has a field List of expressions and a
// field Body of statements, so the children of a CaseClause are a mix
// of expressions and statements. Other nodes that have "uncontained"
// list fields include:
//
// - [ast.ValueSpec] (Names, Values)
// - [ast.CompositeLit] (Type, Elts)
// - [ast.IndexListExpr] (X, Indices)
// - [ast.CallExpr] (Fun, Args)
// - [ast.AssignStmt] (Lhs, Rhs)
//
// So, do not assume that the previous sibling of an ast.Stmt is also
// an ast.Stmt, or if it is, that they are executed sequentially,
// unless you have established that, say, its parent is a BlockStmt
// or its [Cursor.ParentEdge] is [edge.BlockStmt_List].
// For example, given "for S1; ; S2 {}", the predecessor of S2 is S1,
// even though they are not executed in sequence.
func (c Cursor) Children() iter.Seq[Cursor] {
return func(yield func(Cursor) bool) {
c, ok := c.FirstChild()
for ok && yield(c) {
c, ok = c.NextSibling()
}
}
}
// Contains reports whether c contains or is equal to c2.
//
// Both Cursors must belong to the same [Inspector];
// neither may be its Root node.
func (c Cursor) Contains(c2 Cursor) bool {
if c.in != c2.in {
panic("different inspectors")
}
events := c.in.events
return c.index <= c2.index && events[c2.index].index <= events[c.index].index
}
// FindNode returns the cursor for node n if it belongs to the subtree
// rooted at c. It returns zero if n is not found.
func (c Cursor) FindNode(n ast.Node) (Cursor, bool) {
// FindNode is equivalent to this code,
// but more convenient and 15-20% faster:
if false {
for candidate := range c.Preorder(n) {
if candidate.Node() == n {
return candidate, true
}
}
return Cursor{}, false
}
// TODO(adonovan): opt: should we assume Node.Pos is accurate
// and combine type-based filtering with position filtering
// like FindByPos?
mask := maskOf([]ast.Node{n})
events := c.in.events
for i, limit := c.indices(); i < limit; i++ {
ev := events[i]
if ev.index > i { // push?
if ev.typ&mask != 0 && ev.node == n {
return Cursor{c.in, i}, true
}
pop := ev.index
if events[pop].typ&mask == 0 {
// Subtree does not contain type of n: skip.
i = pop
}
}
}
return Cursor{}, false
}
// FindByPos returns the cursor for the innermost node n in the tree
// rooted at c such that n.Pos() <= start && end <= n.End().
// (For an *ast.File, it uses the bounds n.FileStart-n.FileEnd.)
//
// An empty range (start == end) between two adjacent nodes is
// considered to belong to the first node.
//
// It returns zero if none is found.
// Precondition: start <= end.
//
// See also [astutil.PathEnclosingInterval], which
// tolerates adjoining whitespace.
func (c Cursor) FindByPos(start, end token.Pos) (Cursor, bool) {
if end < start {
panic("end < start")
}
events := c.in.events
// This algorithm could be implemented using c.Inspect,
// but it is about 2.5x slower.
// best is the push-index of the latest (=innermost) node containing range.
// (Beware: latest is not always innermost because FuncDecl.{Name,Type} overlap.)
best := int32(-1)
for i, limit := c.indices(); i < limit; i++ {
ev := events[i]
if ev.index > i { // push?
n := ev.node
var nodeEnd token.Pos
if file, ok := n.(*ast.File); ok {
nodeEnd = file.FileEnd
// Note: files may be out of Pos order.
if file.FileStart > start {
i = ev.index // disjoint, after; skip to next file
continue
}
} else {
// Edge case: FuncDecl.Name and .Type overlap:
// Don't update best from Name to FuncDecl.Type.
//
// The condition can be read as:
// - n is FuncType
// - n.parent is FuncDecl
// - best is strictly beneath the FuncDecl
if ev.typ == 1<<nFuncType &&
events[ev.parent].typ == 1<<nFuncDecl &&
best > ev.parent {
continue
}
nodeEnd = n.End()
if n.Pos() > start {
break // disjoint, after; stop
}
}
// Inv: node.{Pos,FileStart} <= start
if end <= nodeEnd {
// node fully contains target range
best = i
// Don't search beyond end of the first match.
// This is important only for an empty range (start=end)
// between two adjoining nodes, which would otherwise
// match both nodes; we want to match only the first.
limit = ev.index
} else if nodeEnd < start {
i = ev.index // disjoint, before; skip forward
}
}
}
if best >= 0 {
return Cursor{c.in, best}, true
}
return Cursor{}, false
}
+311
View File
@@ -0,0 +1,311 @@
// Copyright 2018 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.
// Package inspector provides helper functions for traversal over the
// syntax trees of a package, including node filtering by type, and
// materialization of the traversal stack.
//
// During construction, the inspector does a complete traversal and
// builds a list of push/pop events and their node type. Subsequent
// method calls that request a traversal scan this list, rather than walk
// the AST, and perform type filtering using efficient bit sets.
// This representation is sometimes called a "balanced parenthesis tree."
//
// Experiments suggest the inspector's traversals are about 2.5x faster
// than [ast.Inspect], but it may take around 5 traversals for this
// benefit to amortize the inspector's construction cost.
// If efficiency is the primary concern, do not use Inspector for
// one-off traversals.
//
// The [Cursor] type provides a more flexible API for efficient
// navigation of syntax trees in all four "cardinal directions". For
// example, traversals may be nested, so you can find each node of
// type A and then search within it for nodes of type B. Or you can
// traverse from a node to its immediate neighbors: its parent, its
// previous and next sibling, or its first and last child. We
// recommend using methods of Cursor in preference to Inspector where
// possible.
package inspector
// There are four orthogonal features in a traversal:
// 1 type filtering
// 2 pruning
// 3 postorder calls to f
// 4 stack
// Rather than offer all of them in the API,
// only a few combinations are exposed:
// - Preorder is the fastest and has fewest features,
// but is the most commonly needed traversal.
// - Nodes and WithStack both provide pruning and postorder calls,
// even though few clients need it, because supporting two versions
// is not justified.
// More combinations could be supported by expressing them as
// wrappers around a more generic traversal, but this was measured
// and found to degrade performance significantly (30%).
import (
"go/ast"
"golang.org/x/tools/go/ast/edge"
)
// An Inspector provides methods for inspecting
// (traversing) the syntax trees of a package.
type Inspector struct {
events []event
}
func packEdgeKindAndIndex(ek edge.Kind, index int) int32 {
return int32(uint32(index+1)<<7 | uint32(ek))
}
// unpackEdgeKindAndIndex unpacks the edge kind and edge index (within
// an []ast.Node slice) from the parent field of a pop event.
func unpackEdgeKindAndIndex(x int32) (edge.Kind, int) {
// The "parent" field of a pop node holds the
// edge Kind in the lower 7 bits and the index+1
// in the upper 25.
return edge.Kind(x & 0x7f), int(x>>7) - 1
}
// New returns an Inspector for the specified syntax trees.
func New(files []*ast.File) *Inspector {
return &Inspector{traverse(files)}
}
// An event represents a push or a pop
// of an ast.Node during a traversal.
type event struct {
node ast.Node
typ uint64 // typeOf(node) on push event, or union of typ strictly between push and pop events on pop events
index int32 // index of corresponding push or pop event
parent int32 // index of parent's push node (push nodes only), or packed edge kind/index (pop nodes only)
}
// TODO: Experiment with storing only the second word of event.node (unsafe.Pointer).
// Type can be recovered from the sole bit in typ.
// [Tried this, wasn't faster. --adonovan]
// Preorder visits all the nodes of the files supplied to [New] in
// depth-first order. It calls f(n) for each node n before it visits
// n's children.
//
// The complete traversal sequence is determined by [ast.Inspect].
// The types argument, if non-empty, enables type-based filtering of
// events. The function f is called only for nodes whose type
// matches an element of the types slice.
//
// The [Cursor.Preorder] method provides a richer alternative interface.
// Example:
//
// for c := range in.Root().Preorder(types) { ... }
func (in *Inspector) Preorder(types []ast.Node, f func(ast.Node)) {
// Because it avoids postorder calls to f, and the pruning
// check, Preorder is almost twice as fast as Nodes. The two
// features seem to contribute similar slowdowns (~1.4x each).
// This function is equivalent to the PreorderSeq call below,
// but to avoid the additional dynamic call (which adds 13-35%
// to the benchmarks), we expand it out.
//
// in.PreorderSeq(types...)(func(n ast.Node) bool {
// f(n)
// return true
// })
mask := maskOf(types)
for i := int32(0); i < int32(len(in.events)); {
ev := in.events[i]
if ev.index > i {
// push
if ev.typ&mask != 0 {
f(ev.node)
}
pop := ev.index
if in.events[pop].typ&mask == 0 {
// Subtrees do not contain types: skip them and pop.
i = pop + 1
continue
}
}
i++
}
}
// Nodes visits the nodes of the files supplied to [New] in depth-first
// order. It calls f(n, true) for each node n before it visits n's
// children. If f returns true, Nodes invokes f recursively for each
// of the non-nil children of the node, followed by a call of
// f(n, false).
//
// The complete traversal sequence is determined by [ast.Inspect].
// The types argument, if non-empty, enables type-based filtering of
// events. The function f if is called only for nodes whose type
// matches an element of the types slice.
//
// The [Cursor.Inspect] method provides a richer alternative interface.
// Example:
//
// in.Root().Inspect(types, func(c Cursor) bool {
// ...
// return true
// }
func (in *Inspector) Nodes(types []ast.Node, f func(n ast.Node, push bool) (proceed bool)) {
mask := maskOf(types)
for i := int32(0); i < int32(len(in.events)); {
ev := in.events[i]
if ev.index > i {
// push
pop := ev.index
if ev.typ&mask != 0 {
if !f(ev.node, true) {
i = pop + 1 // jump to corresponding pop + 1
continue
}
}
if in.events[pop].typ&mask == 0 {
// Subtrees do not contain types: skip them.
i = pop
continue
}
} else {
// pop
push := ev.index
if in.events[push].typ&mask != 0 {
f(ev.node, false)
}
}
i++
}
}
// WithStack visits nodes in a similar manner to Nodes, but it
// supplies each call to f an additional argument, the current
// traversal stack. The stack's first element is the outermost node,
// an *ast.File; its last is the innermost, n.
//
// The [Cursor.Inspect] method provides a richer alternative interface.
// Example:
//
// in.Root().Inspect(types, func(c Cursor) bool {
// stack := slices.Collect(c.Enclosing())
// ...
// return true
// })
func (in *Inspector) WithStack(types []ast.Node, f func(n ast.Node, push bool, stack []ast.Node) (proceed bool)) {
mask := maskOf(types)
var stack []ast.Node
for i := int32(0); i < int32(len(in.events)); {
ev := in.events[i]
if ev.index > i {
// push
pop := ev.index
stack = append(stack, ev.node)
if ev.typ&mask != 0 {
if !f(ev.node, true, stack) {
i = pop + 1
stack = stack[:len(stack)-1]
continue
}
}
if in.events[pop].typ&mask == 0 {
// Subtrees does not contain types: skip them.
i = pop
continue
}
} else {
// pop
push := ev.index
if in.events[push].typ&mask != 0 {
f(ev.node, false, stack)
}
stack = stack[:len(stack)-1]
}
i++
}
}
// traverse builds the table of events representing a traversal.
func traverse(files []*ast.File) []event {
// Preallocate approximate number of events
// based on source file extent of the declarations.
// (We use End-Pos not FileStart-FileEnd to neglect
// the effect of long doc comments.)
// This makes traverse faster by 4x (!).
var extent int
for _, f := range files {
extent += int(f.End() - f.Pos())
}
// This estimate is based on the net/http package.
capacity := min(extent*33/100, 1e6) // impose some reasonable maximum (1M)
v := &visitor{
events: make([]event, 0, capacity),
stack: []item{{index: -1}}, // include an extra event so file nodes have a parent
}
for _, file := range files {
walk(v, edge.Invalid, -1, file)
}
return v.events
}
type visitor struct {
events []event
stack []item
}
type item struct {
index int32 // index of current node's push event
parentIndex int32 // index of parent node's push event
typAccum uint64 // accumulated type bits of current node's descendants
edgeKindAndIndex int32 // edge.Kind and index, bit packed
}
func (v *visitor) push(ek edge.Kind, eindex int, node ast.Node) {
var (
index = int32(len(v.events))
parentIndex = v.stack[len(v.stack)-1].index
)
v.events = append(v.events, event{
node: node,
parent: parentIndex,
typ: typeOf(node),
index: 0, // (pop index is set later by visitor.pop)
})
v.stack = append(v.stack, item{
index: index,
parentIndex: parentIndex,
edgeKindAndIndex: packEdgeKindAndIndex(ek, eindex),
})
// 2B nodes ought to be enough for anyone!
if int32(len(v.events)) < 0 {
panic("event index exceeded int32")
}
// 32M elements in an []ast.Node ought to be enough for anyone!
if ek2, eindex2 := unpackEdgeKindAndIndex(packEdgeKindAndIndex(ek, eindex)); ek2 != ek || eindex2 != eindex {
panic("Node slice index exceeded uint25")
}
}
func (v *visitor) pop(node ast.Node) {
top := len(v.stack) - 1
current := v.stack[top]
push := &v.events[current.index]
parent := &v.stack[top-1]
push.index = int32(len(v.events)) // make push event refer to pop
parent.typAccum |= current.typAccum | push.typ // accumulate type bits into parent
v.stack = v.stack[:top]
v.events = append(v.events, event{
node: node,
typ: current.typAccum,
index: current.index,
parent: current.edgeKindAndIndex, // see [unpackEdgeKindAndIndex]
})
}
+113
View File
@@ -0,0 +1,113 @@
// Copyright 2024 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.
//go:build go1.23
package inspector
import (
"go/ast"
"iter"
)
// PreorderSeq returns an iterator that visits all the
// nodes of the files supplied to [New] in depth-first order.
// It visits each node n before n's children.
// The complete traversal sequence is determined by ast.Inspect.
//
// The types argument, if non-empty, enables type-based filtering:
// only nodes whose type matches an element of the types slice are
// included in the sequence.
//
// Example:
//
// for call := range in.PreorderSeq((*ast.CallExpr)(nil)) { ... }
//
// The [All] function is more convenient if there is exactly one node type:
//
// for call := range All[*ast.CallExpr](in) { ... }
//
// See also the newer and more flexible [Cursor] API, which lets you
// start the traversal at an arbitrary node, and reports each matching
// node by its Cursor, enabling easier navigation.
// The above example would be written thus:
//
// for curCall := range in.Root().Preorder((*ast.CallExpr)(nil)) {
// call := curCall.Node().(*ast.CallExpr)
// ...
// }
func (in *Inspector) PreorderSeq(types ...ast.Node) iter.Seq[ast.Node] {
// This implementation is identical to Preorder,
// except that it supports breaking out of the loop.
return func(yield func(ast.Node) bool) {
mask := maskOf(types)
for i := int32(0); i < int32(len(in.events)); {
ev := in.events[i]
if ev.index > i {
// push
if ev.typ&mask != 0 {
if !yield(ev.node) {
break
}
}
pop := ev.index
if in.events[pop].typ&mask == 0 {
// Subtrees do not contain types: skip them and pop.
i = pop + 1
continue
}
}
i++
}
}
}
// All[N] returns an iterator over all the nodes of type N.
// N must be a pointer-to-struct type that implements ast.Node.
//
// Example:
//
// for call := range All[*ast.CallExpr](in) { ... }
//
// See also the newer and more flexible [Cursor] API, which lets you
// start the traversal at an arbitrary node, and reports each matching
// node by its Cursor, enabling easier navigation.
// The above example would be written thus:
//
// for curCall := range in.Root().Preorder((*ast.CallExpr)(nil)) {
// call := curCall.Node().(*ast.CallExpr)
// ...
// }
func All[N interface {
*S
ast.Node
}, S any](in *Inspector) iter.Seq[N] {
// To avoid additional dynamic call overheads,
// we duplicate rather than call the logic of PreorderSeq.
mask := typeOf((N)(nil))
return func(yield func(N) bool) {
for i := int32(0); i < int32(len(in.events)); {
ev := in.events[i]
if ev.index > i {
// push
if ev.typ&mask != 0 {
if !yield(ev.node.(N)) {
break
}
}
pop := ev.index
if in.events[pop].typ&mask == 0 {
// Subtrees do not contain types: skip them and pop.
i = pop + 1
continue
}
}
i++
}
}
}
+227
View File
@@ -0,0 +1,227 @@
// Copyright 2018 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.
package inspector
// This file defines func typeOf(ast.Node) uint64.
//
// The initial map-based implementation was too slow;
// see https://go-review.googlesource.com/c/tools/+/135655/1/go/ast/inspector/inspector.go#196
import (
"go/ast"
"math"
)
const (
nArrayType = iota
nAssignStmt
nBadDecl
nBadExpr
nBadStmt
nBasicLit
nBinaryExpr
nBlockStmt
nBranchStmt
nCallExpr
nCaseClause
nChanType
nCommClause
nComment
nCommentGroup
nCompositeLit
nDeclStmt
nDeferStmt
nEllipsis
nEmptyStmt
nExprStmt
nField
nFieldList
nFile
nForStmt
nFuncDecl
nFuncLit
nFuncType
nGenDecl
nGoStmt
nIdent
nIfStmt
nImportSpec
nIncDecStmt
nIndexExpr
nIndexListExpr
nInterfaceType
nKeyValueExpr
nLabeledStmt
nMapType
nPackage
nParenExpr
nRangeStmt
nReturnStmt
nSelectStmt
nSelectorExpr
nSendStmt
nSliceExpr
nStarExpr
nStructType
nSwitchStmt
nTypeAssertExpr
nTypeSpec
nTypeSwitchStmt
nUnaryExpr
nValueSpec
)
// typeOf returns a distinct single-bit value that represents the type of n.
//
// Various implementations were benchmarked with BenchmarkNewInspector:
//
// GOGC=off
// - type switch 4.9-5.5ms 2.1ms
// - binary search over a sorted list of types 5.5-5.9ms 2.5ms
// - linear scan, frequency-ordered list 5.9-6.1ms 2.7ms
// - linear scan, unordered list 6.4ms 2.7ms
// - hash table 6.5ms 3.1ms
//
// A perfect hash seemed like overkill.
//
// The compiler's switch statement is the clear winner
// as it produces a binary tree in code,
// with constant conditions and good branch prediction.
// (Sadly it is the most verbose in source code.)
// Binary search suffered from poor branch prediction.
func typeOf(n ast.Node) uint64 {
// Fast path: nearly half of all nodes are identifiers.
if _, ok := n.(*ast.Ident); ok {
return 1 << nIdent
}
// These cases include all nodes encountered by ast.Inspect.
switch n.(type) {
case *ast.ArrayType:
return 1 << nArrayType
case *ast.AssignStmt:
return 1 << nAssignStmt
case *ast.BadDecl:
return 1 << nBadDecl
case *ast.BadExpr:
return 1 << nBadExpr
case *ast.BadStmt:
return 1 << nBadStmt
case *ast.BasicLit:
return 1 << nBasicLit
case *ast.BinaryExpr:
return 1 << nBinaryExpr
case *ast.BlockStmt:
return 1 << nBlockStmt
case *ast.BranchStmt:
return 1 << nBranchStmt
case *ast.CallExpr:
return 1 << nCallExpr
case *ast.CaseClause:
return 1 << nCaseClause
case *ast.ChanType:
return 1 << nChanType
case *ast.CommClause:
return 1 << nCommClause
case *ast.Comment:
return 1 << nComment
case *ast.CommentGroup:
return 1 << nCommentGroup
case *ast.CompositeLit:
return 1 << nCompositeLit
case *ast.DeclStmt:
return 1 << nDeclStmt
case *ast.DeferStmt:
return 1 << nDeferStmt
case *ast.Ellipsis:
return 1 << nEllipsis
case *ast.EmptyStmt:
return 1 << nEmptyStmt
case *ast.ExprStmt:
return 1 << nExprStmt
case *ast.Field:
return 1 << nField
case *ast.FieldList:
return 1 << nFieldList
case *ast.File:
return 1 << nFile
case *ast.ForStmt:
return 1 << nForStmt
case *ast.FuncDecl:
return 1 << nFuncDecl
case *ast.FuncLit:
return 1 << nFuncLit
case *ast.FuncType:
return 1 << nFuncType
case *ast.GenDecl:
return 1 << nGenDecl
case *ast.GoStmt:
return 1 << nGoStmt
case *ast.Ident:
return 1 << nIdent
case *ast.IfStmt:
return 1 << nIfStmt
case *ast.ImportSpec:
return 1 << nImportSpec
case *ast.IncDecStmt:
return 1 << nIncDecStmt
case *ast.IndexExpr:
return 1 << nIndexExpr
case *ast.IndexListExpr:
return 1 << nIndexListExpr
case *ast.InterfaceType:
return 1 << nInterfaceType
case *ast.KeyValueExpr:
return 1 << nKeyValueExpr
case *ast.LabeledStmt:
return 1 << nLabeledStmt
case *ast.MapType:
return 1 << nMapType
case *ast.Package:
return 1 << nPackage
case *ast.ParenExpr:
return 1 << nParenExpr
case *ast.RangeStmt:
return 1 << nRangeStmt
case *ast.ReturnStmt:
return 1 << nReturnStmt
case *ast.SelectStmt:
return 1 << nSelectStmt
case *ast.SelectorExpr:
return 1 << nSelectorExpr
case *ast.SendStmt:
return 1 << nSendStmt
case *ast.SliceExpr:
return 1 << nSliceExpr
case *ast.StarExpr:
return 1 << nStarExpr
case *ast.StructType:
return 1 << nStructType
case *ast.SwitchStmt:
return 1 << nSwitchStmt
case *ast.TypeAssertExpr:
return 1 << nTypeAssertExpr
case *ast.TypeSpec:
return 1 << nTypeSpec
case *ast.TypeSwitchStmt:
return 1 << nTypeSwitchStmt
case *ast.UnaryExpr:
return 1 << nUnaryExpr
case *ast.ValueSpec:
return 1 << nValueSpec
}
return 0
}
func maskOf(nodes []ast.Node) uint64 {
if len(nodes) == 0 {
return math.MaxUint64 // match all node types
}
var mask uint64
for _, n := range nodes {
mask |= typeOf(n)
}
return mask
}
+341
View File
@@ -0,0 +1,341 @@
// Copyright 2025 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.
package inspector
// This file is a fork of ast.Inspect to reduce unnecessary dynamic
// calls and to gather edge information.
//
// Consistency with the original is ensured by TestInspectAllNodes.
import (
"fmt"
"go/ast"
"golang.org/x/tools/go/ast/edge"
)
func walkList[N ast.Node](v *visitor, ek edge.Kind, list []N) {
for i, node := range list {
walk(v, ek, i, node)
}
}
func walk(v *visitor, ek edge.Kind, index int, node ast.Node) {
v.push(ek, index, node)
// walk children
// (the order of the cases matches the order
// of the corresponding node types in ast.go)
switch n := node.(type) {
// Comments and fields
case *ast.Comment:
// nothing to do
case *ast.CommentGroup:
walkList(v, edge.CommentGroup_List, n.List)
case *ast.Field:
if n.Doc != nil {
walk(v, edge.Field_Doc, -1, n.Doc)
}
walkList(v, edge.Field_Names, n.Names)
if n.Type != nil {
walk(v, edge.Field_Type, -1, n.Type)
}
if n.Tag != nil {
walk(v, edge.Field_Tag, -1, n.Tag)
}
if n.Comment != nil {
walk(v, edge.Field_Comment, -1, n.Comment)
}
case *ast.FieldList:
walkList(v, edge.FieldList_List, n.List)
// Expressions
case *ast.BadExpr, *ast.Ident, *ast.BasicLit:
// nothing to do
case *ast.Ellipsis:
if n.Elt != nil {
walk(v, edge.Ellipsis_Elt, -1, n.Elt)
}
case *ast.FuncLit:
walk(v, edge.FuncLit_Type, -1, n.Type)
walk(v, edge.FuncLit_Body, -1, n.Body)
case *ast.CompositeLit:
if n.Type != nil {
walk(v, edge.CompositeLit_Type, -1, n.Type)
}
walkList(v, edge.CompositeLit_Elts, n.Elts)
case *ast.ParenExpr:
walk(v, edge.ParenExpr_X, -1, n.X)
case *ast.SelectorExpr:
walk(v, edge.SelectorExpr_X, -1, n.X)
walk(v, edge.SelectorExpr_Sel, -1, n.Sel)
case *ast.IndexExpr:
walk(v, edge.IndexExpr_X, -1, n.X)
walk(v, edge.IndexExpr_Index, -1, n.Index)
case *ast.IndexListExpr:
walk(v, edge.IndexListExpr_X, -1, n.X)
walkList(v, edge.IndexListExpr_Indices, n.Indices)
case *ast.SliceExpr:
walk(v, edge.SliceExpr_X, -1, n.X)
if n.Low != nil {
walk(v, edge.SliceExpr_Low, -1, n.Low)
}
if n.High != nil {
walk(v, edge.SliceExpr_High, -1, n.High)
}
if n.Max != nil {
walk(v, edge.SliceExpr_Max, -1, n.Max)
}
case *ast.TypeAssertExpr:
walk(v, edge.TypeAssertExpr_X, -1, n.X)
if n.Type != nil {
walk(v, edge.TypeAssertExpr_Type, -1, n.Type)
}
case *ast.CallExpr:
walk(v, edge.CallExpr_Fun, -1, n.Fun)
walkList(v, edge.CallExpr_Args, n.Args)
case *ast.StarExpr:
walk(v, edge.StarExpr_X, -1, n.X)
case *ast.UnaryExpr:
walk(v, edge.UnaryExpr_X, -1, n.X)
case *ast.BinaryExpr:
walk(v, edge.BinaryExpr_X, -1, n.X)
walk(v, edge.BinaryExpr_Y, -1, n.Y)
case *ast.KeyValueExpr:
walk(v, edge.KeyValueExpr_Key, -1, n.Key)
walk(v, edge.KeyValueExpr_Value, -1, n.Value)
// Types
case *ast.ArrayType:
if n.Len != nil {
walk(v, edge.ArrayType_Len, -1, n.Len)
}
walk(v, edge.ArrayType_Elt, -1, n.Elt)
case *ast.StructType:
walk(v, edge.StructType_Fields, -1, n.Fields)
case *ast.FuncType:
if n.TypeParams != nil {
walk(v, edge.FuncType_TypeParams, -1, n.TypeParams)
}
if n.Params != nil {
walk(v, edge.FuncType_Params, -1, n.Params)
}
if n.Results != nil {
walk(v, edge.FuncType_Results, -1, n.Results)
}
case *ast.InterfaceType:
walk(v, edge.InterfaceType_Methods, -1, n.Methods)
case *ast.MapType:
walk(v, edge.MapType_Key, -1, n.Key)
walk(v, edge.MapType_Value, -1, n.Value)
case *ast.ChanType:
walk(v, edge.ChanType_Value, -1, n.Value)
// Statements
case *ast.BadStmt:
// nothing to do
case *ast.DeclStmt:
walk(v, edge.DeclStmt_Decl, -1, n.Decl)
case *ast.EmptyStmt:
// nothing to do
case *ast.LabeledStmt:
walk(v, edge.LabeledStmt_Label, -1, n.Label)
walk(v, edge.LabeledStmt_Stmt, -1, n.Stmt)
case *ast.ExprStmt:
walk(v, edge.ExprStmt_X, -1, n.X)
case *ast.SendStmt:
walk(v, edge.SendStmt_Chan, -1, n.Chan)
walk(v, edge.SendStmt_Value, -1, n.Value)
case *ast.IncDecStmt:
walk(v, edge.IncDecStmt_X, -1, n.X)
case *ast.AssignStmt:
walkList(v, edge.AssignStmt_Lhs, n.Lhs)
walkList(v, edge.AssignStmt_Rhs, n.Rhs)
case *ast.GoStmt:
walk(v, edge.GoStmt_Call, -1, n.Call)
case *ast.DeferStmt:
walk(v, edge.DeferStmt_Call, -1, n.Call)
case *ast.ReturnStmt:
walkList(v, edge.ReturnStmt_Results, n.Results)
case *ast.BranchStmt:
if n.Label != nil {
walk(v, edge.BranchStmt_Label, -1, n.Label)
}
case *ast.BlockStmt:
walkList(v, edge.BlockStmt_List, n.List)
case *ast.IfStmt:
if n.Init != nil {
walk(v, edge.IfStmt_Init, -1, n.Init)
}
walk(v, edge.IfStmt_Cond, -1, n.Cond)
walk(v, edge.IfStmt_Body, -1, n.Body)
if n.Else != nil {
walk(v, edge.IfStmt_Else, -1, n.Else)
}
case *ast.CaseClause:
walkList(v, edge.CaseClause_List, n.List)
walkList(v, edge.CaseClause_Body, n.Body)
case *ast.SwitchStmt:
if n.Init != nil {
walk(v, edge.SwitchStmt_Init, -1, n.Init)
}
if n.Tag != nil {
walk(v, edge.SwitchStmt_Tag, -1, n.Tag)
}
walk(v, edge.SwitchStmt_Body, -1, n.Body)
case *ast.TypeSwitchStmt:
if n.Init != nil {
walk(v, edge.TypeSwitchStmt_Init, -1, n.Init)
}
walk(v, edge.TypeSwitchStmt_Assign, -1, n.Assign)
walk(v, edge.TypeSwitchStmt_Body, -1, n.Body)
case *ast.CommClause:
if n.Comm != nil {
walk(v, edge.CommClause_Comm, -1, n.Comm)
}
walkList(v, edge.CommClause_Body, n.Body)
case *ast.SelectStmt:
walk(v, edge.SelectStmt_Body, -1, n.Body)
case *ast.ForStmt:
if n.Init != nil {
walk(v, edge.ForStmt_Init, -1, n.Init)
}
if n.Cond != nil {
walk(v, edge.ForStmt_Cond, -1, n.Cond)
}
if n.Post != nil {
walk(v, edge.ForStmt_Post, -1, n.Post)
}
walk(v, edge.ForStmt_Body, -1, n.Body)
case *ast.RangeStmt:
if n.Key != nil {
walk(v, edge.RangeStmt_Key, -1, n.Key)
}
if n.Value != nil {
walk(v, edge.RangeStmt_Value, -1, n.Value)
}
walk(v, edge.RangeStmt_X, -1, n.X)
walk(v, edge.RangeStmt_Body, -1, n.Body)
// Declarations
case *ast.ImportSpec:
if n.Doc != nil {
walk(v, edge.ImportSpec_Doc, -1, n.Doc)
}
if n.Name != nil {
walk(v, edge.ImportSpec_Name, -1, n.Name)
}
walk(v, edge.ImportSpec_Path, -1, n.Path)
if n.Comment != nil {
walk(v, edge.ImportSpec_Comment, -1, n.Comment)
}
case *ast.ValueSpec:
if n.Doc != nil {
walk(v, edge.ValueSpec_Doc, -1, n.Doc)
}
walkList(v, edge.ValueSpec_Names, n.Names)
if n.Type != nil {
walk(v, edge.ValueSpec_Type, -1, n.Type)
}
walkList(v, edge.ValueSpec_Values, n.Values)
if n.Comment != nil {
walk(v, edge.ValueSpec_Comment, -1, n.Comment)
}
case *ast.TypeSpec:
if n.Doc != nil {
walk(v, edge.TypeSpec_Doc, -1, n.Doc)
}
walk(v, edge.TypeSpec_Name, -1, n.Name)
if n.TypeParams != nil {
walk(v, edge.TypeSpec_TypeParams, -1, n.TypeParams)
}
walk(v, edge.TypeSpec_Type, -1, n.Type)
if n.Comment != nil {
walk(v, edge.TypeSpec_Comment, -1, n.Comment)
}
case *ast.BadDecl:
// nothing to do
case *ast.GenDecl:
if n.Doc != nil {
walk(v, edge.GenDecl_Doc, -1, n.Doc)
}
walkList(v, edge.GenDecl_Specs, n.Specs)
case *ast.FuncDecl:
if n.Doc != nil {
walk(v, edge.FuncDecl_Doc, -1, n.Doc)
}
if n.Recv != nil {
walk(v, edge.FuncDecl_Recv, -1, n.Recv)
}
walk(v, edge.FuncDecl_Name, -1, n.Name)
walk(v, edge.FuncDecl_Type, -1, n.Type)
if n.Body != nil {
walk(v, edge.FuncDecl_Body, -1, n.Body)
}
case *ast.File:
if n.Doc != nil {
walk(v, edge.File_Doc, -1, n.Doc)
}
walk(v, edge.File_Name, -1, n.Name)
walkList(v, edge.File_Decls, n.Decls)
// don't walk n.Comments - they have been
// visited already through the individual
// nodes
default:
// (includes *ast.Package)
panic(fmt.Sprintf("Walk: unexpected node type %T", n))
}
v.pop(node)
}
+236
View File
@@ -0,0 +1,236 @@
// Copyright 2016 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.
// Package gcexportdata provides functions for reading and writing
// export data, which is a serialized description of the API of a Go
// package including the names, kinds, types, and locations of all
// exported declarations.
//
// The standard Go compiler (cmd/compile) writes an export data file
// for each package it compiles, which it later reads when compiling
// packages that import the earlier one. The compiler must thus
// contain logic to both write and read export data.
// (See the "Export" section in the cmd/compile/README file.)
//
// The [Read] function in this package can read files produced by the
// compiler, producing [go/types] data structures. As a matter of
// policy, Read supports export data files produced by only the last
// two Go releases plus tip; see https://go.dev/issue/68898. The
// export data files produced by the compiler contain additional
// details related to generics, inlining, and other optimizations that
// cannot be decoded by the [Read] function.
//
// In files written by the compiler, the export data is not at the
// start of the file. Before calling Read, use [NewReader] to locate
// the desired portion of the file.
//
// The [Write] function in this package encodes the exported API of a
// Go package ([types.Package]) as a file. Such files can be later
// decoded by Read, but cannot be consumed by the compiler.
//
// # Future changes
//
// Although Read supports the formats written by both Write and the
// compiler, the two are quite different, and there is an open
// proposal (https://go.dev/issue/69491) to separate these APIs.
//
// Under that proposal, this package would ultimately provide only the
// Read operation for compiler export data, which must be defined in
// this module (golang.org/x/tools), not in the standard library, to
// avoid version skew for developer tools that need to read compiler
// export data both before and after a Go release, such as from Go
// 1.23 to Go 1.24. Because this package lives in the tools module,
// clients can update their version of the module some time before the
// Go 1.24 release and rebuild and redeploy their tools, which will
// then be able to consume both Go 1.23 and Go 1.24 export data files,
// so they will work before and after the Go update. (See discussion
// at https://go.dev/issue/15651.)
//
// The operations to import and export [go/types] data structures
// would be defined in the go/types package as Import and Export.
// [Write] would (eventually) delegate to Export,
// and [Read], when it detects a file produced by Export,
// would delegate to Import.
//
// # Deprecations
//
// The [NewImporter] and [Find] functions are deprecated and should
// not be used in new code. The [WriteBundle] and [ReadBundle]
// functions are experimental, and there is an open proposal to
// deprecate them (https://go.dev/issue/69573).
package gcexportdata
import (
"bufio"
"bytes"
"encoding/json"
"fmt"
"go/token"
"go/types"
"io"
"os/exec"
"golang.org/x/tools/internal/gcimporter"
)
// Find returns the name of an object (.o) or archive (.a) file
// containing type information for the specified import path,
// using the go command.
// If no file was found, an empty filename is returned.
//
// A relative srcDir is interpreted relative to the current working directory.
//
// Find also returns the package's resolved (canonical) import path,
// reflecting the effects of srcDir and vendoring on importPath.
//
// Deprecated: Use the higher-level API in golang.org/x/tools/go/packages,
// which is more efficient.
func Find(importPath, srcDir string) (filename, path string) {
cmd := exec.Command("go", "list", "-json", "-export", "--", importPath)
cmd.Dir = srcDir
out, err := cmd.Output()
if err != nil {
return "", ""
}
var data struct {
ImportPath string
Export string
}
json.Unmarshal(out, &data)
return data.Export, data.ImportPath
}
// NewReader returns a reader for the export data section of an object
// (.o) or archive (.a) file read from r. The new reader may provide
// additional trailing data beyond the end of the export data.
func NewReader(r io.Reader) (io.Reader, error) {
buf := bufio.NewReader(r)
size, err := gcimporter.FindExportData(buf)
if err != nil {
return nil, err
}
// We were given an archive and found the __.PKGDEF in it.
// This tells us the size of the export data, and we don't
// need to return the entire file.
return &io.LimitedReader{
R: buf,
N: size,
}, nil
}
// readAll works the same way as io.ReadAll, but avoids allocations and copies
// by preallocating a byte slice of the necessary size if the size is known up
// front. This is always possible when the input is an archive. In that case,
// NewReader will return the known size using an io.LimitedReader.
func readAll(r io.Reader) ([]byte, error) {
if lr, ok := r.(*io.LimitedReader); ok {
data := make([]byte, lr.N)
_, err := io.ReadFull(lr, data)
return data, err
}
return io.ReadAll(r)
}
// Read reads export data from in, decodes it, and returns type
// information for the package.
//
// Read is capable of reading export data produced by [Write] at the
// same source code version, or by the last two Go releases (plus tip)
// of the standard Go compiler. Reading files from older compilers may
// produce an error.
//
// The package path (effectively its linker symbol prefix) is
// specified by path, since unlike the package name, this information
// may not be recorded in the export data.
//
// File position information is added to fset.
//
// Read may inspect and add to the imports map to ensure that references
// within the export data to other packages are consistent. The caller
// must ensure that imports[path] does not exist, or exists but is
// incomplete (see types.Package.Complete), and Read inserts the
// resulting package into this map entry.
//
// On return, the state of the reader is undefined.
func Read(in io.Reader, fset *token.FileSet, imports map[string]*types.Package, path string) (*types.Package, error) {
data, err := readAll(in)
if err != nil {
return nil, fmt.Errorf("reading export data for %q: %v", path, err)
}
if bytes.HasPrefix(data, []byte("!<arch>")) {
return nil, fmt.Errorf("can't read export data for %q directly from an archive file (call gcexportdata.NewReader first to extract export data)", path)
}
// The indexed export format starts with an 'i'; the older
// binary export format starts with a 'c', 'd', or 'v'
// (from "version"). Select appropriate importer.
if len(data) > 0 {
switch data[0] {
case 'v', 'c', 'd':
// binary, produced by cmd/compile till go1.10
return nil, fmt.Errorf("binary (%c) import format is no longer supported", data[0])
case 'i':
// indexed, produced by cmd/compile till go1.19,
// and also by [Write].
//
// If proposal #69491 is accepted, go/types
// serialization will be implemented by
// types.Export, to which Write would eventually
// delegate (explicitly dropping any pretence at
// inter-version Write-Read compatibility).
// This [Read] function would delegate to types.Import
// when it detects that the file was produced by Export.
_, pkg, err := gcimporter.IImportData(fset, imports, data[1:], path)
return pkg, err
case 'u':
// unified, produced by cmd/compile since go1.20
_, pkg, err := gcimporter.UImportData(fset, imports, data[1:], path)
return pkg, err
default:
l := min(len(data), 10)
return nil, fmt.Errorf("unexpected export data with prefix %q for path %s", string(data[:l]), path)
}
}
return nil, fmt.Errorf("empty export data for %s", path)
}
// Write writes encoded type information for the specified package to out.
// The FileSet provides file position information for named objects.
func Write(out io.Writer, fset *token.FileSet, pkg *types.Package) error {
if _, err := io.WriteString(out, "i"); err != nil {
return err
}
return gcimporter.IExportData(out, fset, pkg)
}
// ReadBundle reads an export bundle from in, decodes it, and returns type
// information for the packages.
// File position information is added to fset.
//
// ReadBundle may inspect and add to the imports map to ensure that references
// within the export bundle to other packages are consistent.
//
// On return, the state of the reader is undefined.
//
// Experimental: This API is experimental and may change in the future.
func ReadBundle(in io.Reader, fset *token.FileSet, imports map[string]*types.Package) ([]*types.Package, error) {
data, err := readAll(in)
if err != nil {
return nil, fmt.Errorf("reading export bundle: %v", err)
}
return gcimporter.IImportBundle(fset, imports, data)
}
// WriteBundle writes encoded type information for the specified packages to out.
// The FileSet provides file position information for named objects.
//
// Experimental: This API is experimental and may change in the future.
func WriteBundle(out io.Writer, fset *token.FileSet, pkgs []*types.Package) error {
return gcimporter.IExportBundle(out, fset, pkgs)
}
+75
View File
@@ -0,0 +1,75 @@
// Copyright 2016 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.
package gcexportdata
import (
"fmt"
"go/token"
"go/types"
"os"
)
// NewImporter returns a new instance of the types.Importer interface
// that reads type information from export data files written by gc.
// The Importer also satisfies types.ImporterFrom.
//
// Export data files are located using "go build" workspace conventions
// and the build.Default context.
//
// Use this importer instead of go/importer.For("gc", ...) to avoid the
// version-skew problems described in the documentation of this package,
// or to control the FileSet or access the imports map populated during
// package loading.
//
// Deprecated: Use the higher-level API in golang.org/x/tools/go/packages,
// which is more efficient.
func NewImporter(fset *token.FileSet, imports map[string]*types.Package) types.ImporterFrom {
return importer{fset, imports}
}
type importer struct {
fset *token.FileSet
imports map[string]*types.Package
}
func (imp importer) Import(importPath string) (*types.Package, error) {
return imp.ImportFrom(importPath, "", 0)
}
func (imp importer) ImportFrom(importPath, srcDir string, mode types.ImportMode) (_ *types.Package, err error) {
filename, path := Find(importPath, srcDir)
if filename == "" {
if importPath == "unsafe" {
// Even for unsafe, call Find first in case
// the package was vendored.
return types.Unsafe, nil
}
return nil, fmt.Errorf("can't find import: %s", importPath)
}
if pkg, ok := imp.imports[path]; ok && pkg.Complete() {
return pkg, nil // cache hit
}
// open file
f, err := os.Open(filename)
if err != nil {
return nil, err
}
defer func() {
f.Close()
if err != nil {
// add file name to error
err = fmt.Errorf("reading export data: %s: %v", filename, err)
}
}()
r, err := NewReader(f)
if err != nil {
return nil, err
}
return Read(r, imp.fset, imp.imports, path)
}
+253
View File
@@ -0,0 +1,253 @@
// Copyright 2018 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.
/*
Package packages loads Go packages for inspection and analysis.
The [Load] function takes as input a list of patterns and returns a
list of [Package] values describing individual packages matched by those
patterns.
A [Config] specifies configuration options, the most important of which is
the [LoadMode], which controls the amount of detail in the loaded packages.
Load passes most patterns directly to the underlying build tool.
The default build tool is the go command.
Its supported patterns are described at
https://pkg.go.dev/cmd/go#hdr-Package_lists_and_patterns.
Other build systems may be supported by providing a "driver";
see [The driver protocol].
All patterns with the prefix "query=", where query is a
non-empty string of letters from [a-z], are reserved and may be
interpreted as query operators.
Two query operators are currently supported: "file" and "pattern".
The query "file=path/to/file.go" matches the package or packages enclosing
the Go source file path/to/file.go. For example "file=~/go/src/fmt/print.go"
might return the packages "fmt" and "fmt [fmt.test]".
The query "pattern=string" causes "string" to be passed directly to
the underlying build tool. In most cases this is unnecessary,
but an application can use Load("pattern=" + x) as an escaping mechanism
to ensure that x is not interpreted as a query operator if it contains '='.
All other query operators are reserved for future use and currently
cause Load to report an error.
The Package struct provides basic information about the package, including
- ID, a unique identifier for the package in the returned set;
- GoFiles, the names of the package's Go source files;
- Imports, a map from source import strings to the Packages they name;
- Types, the type information for the package's exported symbols;
- Syntax, the parsed syntax trees for the package's source code; and
- TypesInfo, the result of a complete type-check of the package syntax trees.
(See the documentation for type Package for the complete list of fields
and more detailed descriptions.)
For example,
Load(nil, "bytes", "unicode...")
returns four Package structs describing the standard library packages
bytes, unicode, unicode/utf16, and unicode/utf8. Note that one pattern
can match multiple packages and that a package might be matched by
multiple patterns: in general it is not possible to determine which
packages correspond to which patterns.
Note that the list returned by Load contains only the packages matched
by the patterns. Their dependencies can be found by walking the import
graph using the Imports fields.
The Load function can be configured by passing a pointer to a Config as
the first argument. A nil Config is equivalent to the zero Config, which
causes Load to run in [LoadFiles] mode, collecting minimal information.
See the documentation for type Config for details.
As noted earlier, the Config.Mode controls the amount of detail
reported about the loaded packages. See the documentation for type LoadMode
for details.
Most tools should pass their command-line arguments (after any flags)
uninterpreted to Load, so that it can interpret them
according to the conventions of the underlying build system.
See the Example function for typical usage.
See also [golang.org/x/tools/go/packages/internal/linecount]
for an example application.
# The driver protocol
Load may be used to load Go packages even in Go projects that use
alternative build systems, by installing an appropriate "driver"
program for the build system and specifying its location in the
GOPACKAGESDRIVER environment variable.
For example,
https://github.com/bazelbuild/rules_go/wiki/Editor-and-tool-integration
explains how to use the driver for Bazel.
The driver program is responsible for interpreting patterns in its
preferred notation and reporting information about the packages that
those patterns identify. Drivers must also support the special "file="
and "pattern=" patterns described above.
The patterns are provided as positional command-line arguments. A
JSON-encoded [DriverRequest] message providing additional information
is written to the driver's standard input. The driver must write a
JSON-encoded [DriverResponse] message to its standard output. (This
message differs from the JSON schema produced by 'go list'.)
The value of the PWD environment variable seen by the driver process
is the preferred name of its working directory. (The working directory
may have other aliases due to symbolic links; see the comment on the
Dir field of [exec.Cmd] for related information.)
When the driver process emits in its response the name of a file
that is a descendant of this directory, it must use an absolute path
that has the value of PWD as a prefix, to ensure that the returned
filenames satisfy the original query.
*/
package packages // import "golang.org/x/tools/go/packages"
/*
Motivation and design considerations
The new package's design solves problems addressed by two existing
packages: go/build, which locates and describes packages, and
golang.org/x/tools/go/loader, which loads, parses and type-checks them.
The go/build.Package structure encodes too much of the 'go build' way
of organizing projects, leaving us in need of a data type that describes a
package of Go source code independent of the underlying build system.
We wanted something that works equally well with go build and vgo, and
also other build systems such as Bazel and Blaze, making it possible to
construct analysis tools that work in all these environments.
Tools such as errcheck and staticcheck were essentially unavailable to
the Go community at Google, and some of Google's internal tools for Go
are unavailable externally.
This new package provides a uniform way to obtain package metadata by
querying each of these build systems, optionally supporting their
preferred command-line notations for packages, so that tools integrate
neatly with users' build environments. The Metadata query function
executes an external query tool appropriate to the current workspace.
Loading packages always returns the complete import graph "all the way down",
even if all you want is information about a single package, because the query
mechanisms of all the build systems we currently support ({go,vgo} list, and
blaze/bazel aspect-based query) cannot provide detailed information
about one package without visiting all its dependencies too, so there is
no additional asymptotic cost to providing transitive information.
(This property might not be true of a hypothetical 5th build system.)
In calls to TypeCheck, all initial packages, and any package that
transitively depends on one of them, must be loaded from source.
Consider A->B->C->D->E: if A,C are initial, A,B,C must be loaded from
source; D may be loaded from export data, and E may not be loaded at all
(though it's possible that D's export data mentions it, so a
types.Package may be created for it and exposed.)
The old loader had a feature to suppress type-checking of function
bodies on a per-package basis, primarily intended to reduce the work of
obtaining type information for imported packages. Now that imports are
satisfied by export data, the optimization no longer seems necessary.
Despite some early attempts, the old loader did not exploit export data,
instead always using the equivalent of WholeProgram mode. This was due
to the complexity of mixing source and export data packages (now
resolved by the upward traversal mentioned above), and because export data
files were nearly always missing or stale. Now that 'go build' supports
caching, all the underlying build systems can guarantee to produce
export data in a reasonable (amortized) time.
Test "main" packages synthesized by the build system are now reported as
first-class packages, avoiding the need for clients (such as go/ssa) to
reinvent this generation logic.
One way in which go/packages is simpler than the old loader is in its
treatment of in-package tests. In-package tests are packages that
consist of all the files of the library under test, plus the test files.
The old loader constructed in-package tests by a two-phase process of
mutation called "augmentation": first it would construct and type check
all the ordinary library packages and type-check the packages that
depend on them; then it would add more (test) files to the package and
type-check again. This two-phase approach had four major problems:
1) in processing the tests, the loader modified the library package,
leaving no way for a client application to see both the test
package and the library package; one would mutate into the other.
2) because test files can declare additional methods on types defined in
the library portion of the package, the dispatch of method calls in
the library portion was affected by the presence of the test files.
This should have been a clue that the packages were logically
different.
3) this model of "augmentation" assumed at most one in-package test
per library package, which is true of projects using 'go build',
but not other build systems.
4) because of the two-phase nature of test processing, all packages that
import the library package had to be processed before augmentation,
forcing a "one-shot" API and preventing the client from calling Load
in several times in sequence as is now possible in WholeProgram mode.
(TypeCheck mode has a similar one-shot restriction for a different reason.)
Early drafts of this package supported "multi-shot" operation.
Although it allowed clients to make a sequence of calls (or concurrent
calls) to Load, building up the graph of Packages incrementally,
it was of marginal value: it complicated the API
(since it allowed some options to vary across calls but not others),
it complicated the implementation,
it cannot be made to work in Types mode, as explained above,
and it was less efficient than making one combined call (when this is possible).
Among the clients we have inspected, none made multiple calls to load
but could not be easily and satisfactorily modified to make only a single call.
However, applications changes may be required.
For example, the ssadump command loads the user-specified packages
and in addition the runtime package. It is tempting to simply append
"runtime" to the user-provided list, but that does not work if the user
specified an ad-hoc package such as [a.go b.go].
Instead, ssadump no longer requests the runtime package,
but seeks it among the dependencies of the user-specified packages,
and emits an error if it is not found.
Questions & Tasks
- Add GOARCH/GOOS?
They are not portable concepts, but could be made portable.
Our goal has been to allow users to express themselves using the conventions
of the underlying build system: if the build system honors GOARCH
during a build and during a metadata query, then so should
applications built atop that query mechanism.
Conversely, if the target architecture of the build is determined by
command-line flags, the application can pass the relevant
flags through to the build system using a command such as:
myapp -query_flag="--cpu=amd64" -query_flag="--os=darwin"
However, this approach is low-level, unwieldy, and non-portable.
GOOS and GOARCH seem important enough to warrant a dedicated option.
- How should we handle partial failures such as a mixture of good and
malformed patterns, existing and non-existent packages, successful and
failed builds, import failures, import cycles, and so on, in a call to
Load?
- Support bazel, blaze, and go1.10 list, not just go1.11 list.
- Handle (and test) various partial success cases, e.g.
a mixture of good packages and:
invalid patterns
nonexistent packages
empty packages
packages with malformed package or import declarations
unreadable files
import cycles
other parse errors
type errors
Make sure we record errors at the correct place in the graph.
- Missing packages among initial arguments are not reported.
Return bogus packages for them, like golist does.
- "undeclared name" errors (for example) are reported out of source file
order. I suspect this is due to the breadth-first resolution now used
by go/types. Is that a bug? Discuss with gri.
*/
+153
View File
@@ -0,0 +1,153 @@
// Copyright 2018 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.
package packages
// This file defines the protocol that enables an external "driver"
// tool to supply package metadata in place of 'go list'.
import (
"bytes"
"encoding/json"
"fmt"
"os"
"os/exec"
"slices"
"strings"
)
// DriverRequest defines the schema of a request for package metadata
// from an external driver program. The JSON-encoded DriverRequest
// message is provided to the driver program's standard input. The
// query patterns are provided as command-line arguments.
//
// See the package documentation for an overview.
type DriverRequest struct {
Mode LoadMode `json:"mode"`
// Env specifies the environment the underlying build system should be run in.
Env []string `json:"env"`
// BuildFlags are flags that should be passed to the underlying build system.
BuildFlags []string `json:"build_flags"`
// Tests specifies whether the patterns should also return test packages.
Tests bool `json:"tests"`
// Overlay maps file paths (relative to the driver's working directory)
// to the contents of overlay files (see Config.Overlay).
Overlay map[string][]byte `json:"overlay"`
}
// DriverResponse defines the schema of a response from an external
// driver program, providing the results of a query for package
// metadata. The driver program must write a JSON-encoded
// DriverResponse message to its standard output.
//
// See the package documentation for an overview.
type DriverResponse struct {
// NotHandled is returned if the request can't be handled by the current
// driver. If an external driver returns a response with NotHandled, the
// rest of the DriverResponse is ignored, and go/packages will fallback
// to the next driver. If go/packages is extended in the future to support
// lists of multiple drivers, go/packages will fall back to the next driver.
NotHandled bool
// Compiler and Arch are the arguments pass of types.SizesFor
// to get a types.Sizes to use when type checking.
Compiler string
Arch string
// Roots is the set of package IDs that make up the root packages.
// We have to encode this separately because when we encode a single package
// we cannot know if it is one of the roots as that requires knowledge of the
// graph it is part of.
Roots []string `json:",omitempty"`
// Packages is the full set of packages in the graph.
// The packages are not connected into a graph.
// The Imports if populated will be stubs that only have their ID set.
// Imports will be connected and then type and syntax information added in a
// later pass (see refine).
Packages []*Package
// GoVersion is the minor version number used by the driver
// (e.g. the go command on the PATH) when selecting .go files.
// Zero means unknown.
GoVersion int
}
// driver is the type for functions that query the build system for the
// packages named by the patterns.
type driver func(cfg *Config, patterns []string) (*DriverResponse, error)
// findExternalDriver returns the file path of a tool that supplies
// the build system package structure, or "" if not found.
// If GOPACKAGESDRIVER is set in the environment findExternalTool returns its
// value, otherwise it searches for a binary named gopackagesdriver on the PATH.
func findExternalDriver(cfg *Config) driver {
const toolPrefix = "GOPACKAGESDRIVER="
tool := ""
for _, env := range cfg.Env {
if val, ok := strings.CutPrefix(env, toolPrefix); ok {
tool = val
}
}
if tool != "" && tool == "off" {
return nil
}
if tool == "" {
var err error
tool, err = exec.LookPath("gopackagesdriver")
if err != nil {
return nil
}
}
return func(cfg *Config, patterns []string) (*DriverResponse, error) {
req, err := json.Marshal(DriverRequest{
Mode: cfg.Mode,
Env: cfg.Env,
BuildFlags: cfg.BuildFlags,
Tests: cfg.Tests,
Overlay: cfg.Overlay,
})
if err != nil {
return nil, fmt.Errorf("failed to encode message to driver tool: %v", err)
}
buf := new(bytes.Buffer)
stderr := new(bytes.Buffer)
cmd := exec.CommandContext(cfg.Context, tool, patterns...)
cmd.Dir = cfg.Dir
// The cwd gets resolved to the real path. On Darwin, where
// /tmp is a symlink, this breaks anything that expects the
// working directory to keep the original path, including the
// go command when dealing with modules.
//
// os.Getwd stdlib has a special feature where if the
// cwd and the PWD are the same node then it trusts
// the PWD, so by setting it in the env for the child
// process we fix up all the paths returned by the go
// command.
//
// (See similar trick in Invocation.run in ../../internal/gocommand/invoke.go)
cmd.Env = append(slices.Clip(cfg.Env), "PWD="+cfg.Dir)
cmd.Stdin = bytes.NewReader(req)
cmd.Stdout = buf
cmd.Stderr = stderr
if err := cmd.Run(); err != nil {
return nil, fmt.Errorf("%v: %v: %s", tool, err, cmd.Stderr)
}
if len(stderr.Bytes()) != 0 && os.Getenv("GOPACKAGESPRINTDRIVERERRORS") != "" {
fmt.Fprintf(os.Stderr, "%s stderr: <<%s>>\n", cmdDebugStr(cmd), stderr)
}
var response DriverResponse
if err := json.Unmarshal(buf.Bytes(), &response); err != nil {
return nil, err
}
return &response, nil
}
}
File diff suppressed because it is too large Load Diff
+83
View File
@@ -0,0 +1,83 @@
// Copyright 2018 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.
package packages
import (
"encoding/json"
"path/filepath"
"golang.org/x/tools/internal/gocommand"
)
// determineRootDirs returns a mapping from absolute directories that could
// contain code to their corresponding import path prefixes.
func (state *golistState) determineRootDirs() (map[string]string, error) {
env, err := state.getEnv()
if err != nil {
return nil, err
}
if env["GOMOD"] != "" {
state.rootsOnce.Do(func() {
state.rootDirs, state.rootDirsError = state.determineRootDirsModules()
})
} else {
state.rootsOnce.Do(func() {
state.rootDirs, state.rootDirsError = state.determineRootDirsGOPATH()
})
}
return state.rootDirs, state.rootDirsError
}
func (state *golistState) determineRootDirsModules() (map[string]string, error) {
// List all of the modules--the first will be the directory for the main
// module. Any replaced modules will also need to be treated as roots.
// Editing files in the module cache isn't a great idea, so we don't
// plan to ever support that.
out, err := state.invokeGo("list", "-m", "-json", "all")
if err != nil {
// 'go list all' will fail if we're outside of a module and
// GO111MODULE=on. Try falling back without 'all'.
var innerErr error
out, innerErr = state.invokeGo("list", "-m", "-json")
if innerErr != nil {
return nil, err
}
}
roots := map[string]string{}
modules := map[string]string{}
var i int
for dec := json.NewDecoder(out); dec.More(); {
mod := new(gocommand.ModuleJSON)
if err := dec.Decode(mod); err != nil {
return nil, err
}
if mod.Dir != "" && mod.Path != "" {
// This is a valid module; add it to the map.
absDir, err := state.cfg.abs(mod.Dir)
if err != nil {
return nil, err
}
modules[absDir] = mod.Path
// The first result is the main module.
if i == 0 || mod.Replace != nil && mod.Replace.Path != "" {
roots[absDir] = mod.Path
}
}
i++
}
return roots, nil
}
func (state *golistState) determineRootDirsGOPATH() (map[string]string, error) {
m := map[string]string{}
for _, dir := range filepath.SplitList(state.mustGetEnv()["GOPATH"]) {
absDir, err := filepath.Abs(dir)
if err != nil {
return nil, err
}
m[filepath.Join(absDir, "src")] = ""
}
return m, nil
}
+56
View File
@@ -0,0 +1,56 @@
// Copyright 2019 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.
package packages
import (
"fmt"
"strings"
)
var modes = [...]struct {
mode LoadMode
name string
}{
{NeedName, "NeedName"},
{NeedFiles, "NeedFiles"},
{NeedCompiledGoFiles, "NeedCompiledGoFiles"},
{NeedImports, "NeedImports"},
{NeedDeps, "NeedDeps"},
{NeedExportFile, "NeedExportFile"},
{NeedTypes, "NeedTypes"},
{NeedSyntax, "NeedSyntax"},
{NeedTypesInfo, "NeedTypesInfo"},
{NeedTypesSizes, "NeedTypesSizes"},
{NeedForTest, "NeedForTest"},
{NeedModule, "NeedModule"},
{NeedEmbedFiles, "NeedEmbedFiles"},
{NeedEmbedPatterns, "NeedEmbedPatterns"},
{NeedTarget, "NeedTarget"},
}
func (mode LoadMode) String() string {
if mode == 0 {
return "LoadMode(0)"
}
var out []string
// named bits
for _, item := range modes {
if (mode & item.mode) != 0 {
mode ^= item.mode
out = append(out, item.name)
}
}
// unnamed residue
if mode != 0 {
if out == nil {
return fmt.Sprintf("LoadMode(%#x)", int(mode))
}
out = append(out, fmt.Sprintf("%#x", int(mode)))
}
if len(out) == 1 {
return out[0]
}
return "(" + strings.Join(out, "|") + ")"
}
File diff suppressed because it is too large Load Diff
+133
View File
@@ -0,0 +1,133 @@
// Copyright 2018 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.
package packages
import (
"cmp"
"fmt"
"iter"
"os"
"slices"
)
// Visit visits all the packages in the import graph whose roots are
// pkgs, calling the optional pre function the first time each package
// is encountered (preorder), and the optional post function after a
// package's dependencies have been visited (postorder).
// The boolean result of pre(pkg) determines whether
// the imports of package pkg are visited.
//
// Example:
//
// pkgs, err := Load(...)
// if err != nil { ... }
// Visit(pkgs, nil, func(pkg *Package) {
// log.Println(pkg)
// })
//
// In most cases, it is more convenient to use [Postorder]:
//
// for pkg := range Postorder(pkgs) {
// log.Println(pkg)
// }
func Visit(pkgs []*Package, pre func(*Package) bool, post func(*Package)) {
seen := make(map[*Package]bool)
var visit func(*Package)
visit = func(pkg *Package) {
if !seen[pkg] {
seen[pkg] = true
if pre == nil || pre(pkg) {
for _, imp := range sorted(pkg.Imports) { // for determinism
visit(imp)
}
}
if post != nil {
post(pkg)
}
}
}
for _, pkg := range pkgs {
visit(pkg)
}
}
// PrintErrors prints to os.Stderr the accumulated errors of all
// packages in the import graph rooted at pkgs, dependencies first.
// PrintErrors returns the number of errors printed.
func PrintErrors(pkgs []*Package) int {
var n int
errModules := make(map[*Module]bool)
for pkg := range Postorder(pkgs) {
for _, err := range pkg.Errors {
fmt.Fprintln(os.Stderr, err)
n++
}
// Print pkg.Module.Error once if present.
mod := pkg.Module
if mod != nil && mod.Error != nil && !errModules[mod] {
errModules[mod] = true
fmt.Fprintln(os.Stderr, mod.Error.Err)
n++
}
}
return n
}
// Postorder returns an iterator over the packages in
// the import graph whose roots are pkg.
// Packages are enumerated in dependencies-first order.
func Postorder(pkgs []*Package) iter.Seq[*Package] {
return func(yield func(*Package) bool) {
seen := make(map[*Package]bool)
var visit func(*Package) bool
visit = func(pkg *Package) bool {
if !seen[pkg] {
seen[pkg] = true
for _, imp := range sorted(pkg.Imports) { // for determinism
if !visit(imp) {
return false
}
}
if !yield(pkg) {
return false
}
}
return true
}
for _, pkg := range pkgs {
if !visit(pkg) {
break
}
}
}
}
// -- copied from golang.org.x/tools/gopls/internal/util/moremaps --
// sorted returns an iterator over the entries of m in key order.
func sorted[M ~map[K]V, K cmp.Ordered, V any](m M) iter.Seq2[K, V] {
// TODO(adonovan): use maps.Sorted if proposal #68598 is accepted.
return func(yield func(K, V) bool) {
keys := keySlice(m)
slices.Sort(keys)
for _, k := range keys {
if !yield(k, m[k]) {
break
}
}
}
}
// KeySlice returns the keys of the map M, like slices.Collect(maps.Keys(m)).
func keySlice[M ~map[K]V, K comparable, V any](m M) []K {
r := make([]K, 0, len(m))
for k := range m {
r = append(r, k)
}
return r
}
+816
View File
@@ -0,0 +1,816 @@
// Copyright 2018 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.
// Package objectpath defines a naming scheme for types.Objects
// (that is, named entities in Go programs) relative to their enclosing
// package.
//
// Type-checker objects are canonical, so they are usually identified by
// their address in memory (a pointer), but a pointer has meaning only
// within one address space. By contrast, objectpath names allow the
// identity of an object to be sent from one program to another,
// establishing a correspondence between types.Object variables that are
// distinct but logically equivalent.
//
// A single object may have multiple paths. In this example,
//
// type A struct{ X int }
// type B A
//
// the field X has two paths due to its membership of both A and B.
// The For(obj) function always returns one of these paths, arbitrarily
// but consistently.
package objectpath
import (
"fmt"
"go/types"
"strconv"
"strings"
"golang.org/x/tools/internal/typesinternal"
)
// TODO(adonovan): think about generic aliases.
// A Path is an opaque name that identifies a types.Object
// relative to its package. Conceptually, the name consists of a
// sequence of destructuring operations applied to the package scope
// to obtain the original object.
// The name does not include the package itself.
type Path string
// Encoding
//
// An object path is a textual and (with training) human-readable encoding
// of a sequence of destructuring operators, starting from a types.Package.
// The sequences represent a path through the package/object/type graph.
// We classify these operators by their type:
//
// PO package->object Package.Scope.Lookup
// OT object->type Object.Type
// TT type->type Type.{Elem,Key,{,{,Recv}Type}Params,Results,Underlying,Rhs} [EKPRUTrCa]
// TO type->object Type.{At,Field,Method,Obj} [AFMO]
//
// All valid paths start with a package and end at an object
// and thus may be defined by the regular language:
//
// objectpath = PO (OT TT* TO)*
//
// The concrete encoding follows directly:
// - The only PO operator is Package.Scope.Lookup, which requires an identifier.
// - The only OT operator is Object.Type,
// which we encode as '.' because dot cannot appear in an identifier.
// - The TT operators are encoded as [EKPRUTrCa];
// two of these ({,Recv}TypeParams) require an integer operand,
// which is encoded as a string of decimal digits.
// - The TO operators are encoded as [AFMO];
// three of these (At,Field,Method) require an integer operand,
// which is encoded as a string of decimal digits.
// These indices are stable across different representations
// of the same package, even source and export data.
// The indices used are implementation specific and may not correspond to
// the argument to the go/types function.
//
// In the example below,
//
// package p
//
// type T interface {
// f() (a string, b struct{ X int })
// }
//
// field X has the path "T.UM0.RA1.F0",
// representing the following sequence of operations:
//
// p.Lookup("T") T
// .Type().Underlying().Method(0). f
// .Type().Results().At(1) b
// .Type().Field(0) X
//
// The encoding is not maximally compact---every R or P is
// followed by an A, for example---but this simplifies the
// encoder and decoder.
const (
// object->type operators
opType = '.' // .Type() (Object)
// type->type operators
opElem = 'E' // .Elem() (Pointer, Slice, Array, Chan, Map)
opKey = 'K' // .Key() (Map)
opParams = 'P' // .Params() (Signature)
opResults = 'R' // .Results() (Signature)
opUnderlying = 'U' // .Underlying() (Named)
opTypeParam = 'T' // .TypeParams.At(i) (Named, Signature)
opRecvTypeParam = 'r' // .RecvTypeParams.At(i) (Signature)
opConstraint = 'C' // .Constraint() (TypeParam)
opRhs = 'a' // .Rhs() (Alias)
// type->object operators
opAt = 'A' // .At(i) (Tuple)
opField = 'F' // .Field(i) (Struct)
opMethod = 'M' // .Method(i) (Named or Interface; not Struct: "promoted" names are ignored)
opObj = 'O' // .Obj() (Named, TypeParam)
)
// For is equivalent to new(Encoder).For(obj).
//
// It may be more efficient to reuse a single Encoder across several calls.
func For(obj types.Object) (Path, error) {
return new(Encoder).For(obj)
}
// An Encoder amortizes the cost of encoding the paths of multiple objects.
// The zero value of an Encoder is ready to use.
type Encoder struct {
scopeMemo map[*types.Scope][]types.Object // memoization of scopeObjects
}
// For returns the path to an object relative to its package,
// or an error if the object is not accessible from the package's Scope.
//
// The For function guarantees to return a path only for the following objects:
// - package-level types
// - exported package-level non-types
// - methods
// - parameter and result variables
// - struct fields
// These objects are sufficient to define the API of their package.
// The objects described by a package's export data are drawn from this set.
//
// The set of objects accessible from a package's Scope depends on
// whether the package was produced by type-checking syntax, or
// reading export data; the latter may have a smaller Scope since
// export data trims objects that are not reachable from an exported
// declaration. For example, the For function will return a path for
// an exported method of an unexported type that is not reachable
// from any public declaration; this path will cause the Object
// function to fail if called on a package loaded from export data.
// TODO(adonovan): is this a bug or feature? Should this package
// compute accessibility in the same way?
//
// For does not return a path for predeclared names, imported package
// names, local names, and unexported package-level names (except
// types).
//
// Example: given this definition,
//
// package p
//
// type T interface {
// f() (a string, b struct{ X int })
// }
//
// For(X) would return a path that denotes the following sequence of operations:
//
// p.Scope().Lookup("T") (TypeName T)
// .Type().Underlying().Method(0). (method Func f)
// .Type().Results().At(1) (field Var b)
// .Type().Field(0) (field Var X)
//
// where p is the package (*types.Package) to which X belongs.
func (enc *Encoder) For(obj types.Object) (Path, error) {
pkg := obj.Pkg()
// This table lists the cases of interest.
//
// Object Action
// ------ ------
// nil reject
// builtin reject
// pkgname reject
// label reject
// var
// package-level accept
// func param/result accept
// local reject
// struct field accept
// const
// package-level accept
// local reject
// func
// package-level accept
// init functions reject
// concrete method accept
// interface method accept
// type
// package-level accept
// local reject
//
// The only accessible package-level objects are members of pkg itself.
//
// The cases are handled in four steps:
//
// 1. reject nil and builtin
// 2. accept package-level objects
// 3. reject obviously invalid objects
// 4. search the API for the path to the param/result/field/method.
// 1. reference to nil or builtin?
if pkg == nil {
return "", fmt.Errorf("predeclared %s has no path", obj)
}
scope := pkg.Scope()
// 2. package-level object?
if scope.Lookup(obj.Name()) == obj {
// Only exported objects (and non-exported types) have a path.
// Non-exported types may be referenced by other objects.
if _, ok := obj.(*types.TypeName); !ok && !obj.Exported() {
return "", fmt.Errorf("no path for non-exported %v", obj)
}
return Path(obj.Name()), nil
}
// 3. Not a package-level object.
// Reject obviously non-viable cases.
switch obj := obj.(type) {
case *types.TypeName:
if _, ok := types.Unalias(obj.Type()).(*types.TypeParam); !ok {
// With the exception of type parameters, only package-level type names
// have a path.
return "", fmt.Errorf("no path for %v", obj)
}
case *types.Const, // Only package-level constants have a path.
*types.Label, // Labels are function-local.
*types.PkgName: // PkgNames are file-local.
return "", fmt.Errorf("no path for %v", obj)
case *types.Var:
// Could be:
// - a field (obj.IsField())
// - a func parameter or result
// - a local var.
// Sadly there is no way to distinguish
// a param/result from a local
// so we must proceed to the find.
case *types.Func:
// A func, if not package-level, must be a method.
if recv := obj.Signature().Recv(); recv == nil {
return "", fmt.Errorf("func is not a method: %v", obj)
}
if path, ok := enc.concreteMethod(obj); ok {
// Fast path for concrete methods that avoids looping over scope.
return path, nil
}
default:
panic(obj)
}
// 4. Search the API for the path to the var (field/param/result) or method.
// First inspect package-level named types.
// In the presence of path aliases, these give
// the best paths because non-types may
// refer to types, but not the reverse.
empty := make([]byte, 0, 48) // initial space
objs := enc.scopeObjects(scope)
for _, o := range objs {
tname, ok := o.(*types.TypeName)
if !ok {
continue // handle non-types in second pass
}
path := append(empty, o.Name()...)
path = append(path, opType)
T := o.Type()
if alias, ok := T.(*types.Alias); ok {
if r := findTypeParam(obj, alias.TypeParams(), path, opTypeParam); r != nil {
return Path(r), nil
}
if r := find(obj, alias.Rhs(), append(path, opRhs)); r != nil {
return Path(r), nil
}
} else if tname.IsAlias() {
// legacy alias
if r := find(obj, T, path); r != nil {
return Path(r), nil
}
} else if named, ok := T.(*types.Named); ok {
// defined (named) type
if r := findTypeParam(obj, named.TypeParams(), path, opTypeParam); r != nil {
return Path(r), nil
}
if r := find(obj, named.Underlying(), append(path, opUnderlying)); r != nil {
return Path(r), nil
}
}
}
// Then inspect everything else:
// non-types, and declared methods of defined types.
for _, o := range objs {
path := append(empty, o.Name()...)
if _, ok := o.(*types.TypeName); !ok {
if o.Exported() {
// exported non-type (const, var, func)
if r := find(obj, o.Type(), append(path, opType)); r != nil {
return Path(r), nil
}
}
continue
}
// Inspect declared methods of defined types.
if T, ok := types.Unalias(o.Type()).(*types.Named); ok {
path = append(path, opType)
// The method index here is always with respect
// to the underlying go/types data structures,
// which ultimately derives from source order
// and must be preserved by export data.
for i := 0; i < T.NumMethods(); i++ {
m := T.Method(i)
path2 := appendOpArg(path, opMethod, i)
if m == obj {
return Path(path2), nil // found declared method
}
if r := find(obj, m.Type(), append(path2, opType)); r != nil {
return Path(r), nil
}
}
}
}
return "", fmt.Errorf("can't find path for %v in %s", obj, pkg.Path())
}
func appendOpArg(path []byte, op byte, arg int) []byte {
path = append(path, op)
path = strconv.AppendInt(path, int64(arg), 10)
return path
}
// concreteMethod returns the path for meth, which must have a non-nil receiver.
// The second return value indicates success and may be false if the method is
// an interface method or if it is an instantiated method.
//
// This function is just an optimization that avoids the general scope walking
// approach. You are expected to fall back to the general approach if this
// function fails.
func (enc *Encoder) concreteMethod(meth *types.Func) (Path, bool) {
// Concrete methods can only be declared on package-scoped named types. For
// that reason we can skip the expensive walk over the package scope: the
// path will always be package -> named type -> method. We can trivially get
// the type name from the receiver, and only have to look over the type's
// methods to find the method index.
//
// Methods on generic types require special consideration, however. Consider
// the following package:
//
// L1: type S[T any] struct{}
// L2: func (recv S[A]) Foo() { recv.Bar() }
// L3: func (recv S[B]) Bar() { }
// L4: type Alias = S[int]
// L5: func _[T any]() { var s S[int]; s.Foo() }
//
// The receivers of methods on generic types are instantiations. L2 and L3
// instantiate S with the type-parameters A and B, which are scoped to the
// respective methods. L4 and L5 each instantiate S with int. Each of these
// instantiations has its own method set, full of methods (and thus objects)
// with receivers whose types are the respective instantiations. In other
// words, we have
//
// S[A].Foo, S[A].Bar
// S[B].Foo, S[B].Bar
// S[int].Foo, S[int].Bar
//
// We may thus be trying to produce object paths for any of these objects.
//
// S[A].Foo and S[B].Bar are the origin methods, and their paths are S.Foo
// and S.Bar, which are the paths that this function naturally produces.
//
// S[A].Bar, S[B].Foo, and both methods on S[int] are instantiations that
// don't correspond to the origin methods. For S[int], this is significant.
// The most precise object path for S[int].Foo, for example, is Alias.Foo,
// not S.Foo. Our function, however, would produce S.Foo, which would
// resolve to a different object.
//
// For S[A].Bar and S[B].Foo it could be argued that S.Bar and S.Foo are
// still the correct paths, since only the origin methods have meaningful
// paths. But this is likely only true for trivial cases and has edge cases.
// Since this function is only an optimization, we err on the side of giving
// up, deferring to the slower but definitely correct algorithm. Most users
// of objectpath will only be giving us origin methods, anyway, as referring
// to instantiated methods is usually not useful.
if meth.Origin() != meth {
return "", false
}
_, named := typesinternal.ReceiverNamed(meth.Signature().Recv())
if named == nil {
return "", false
}
if types.IsInterface(named) {
// Named interfaces don't have to be package-scoped
//
// TODO(dominikh): opt: if scope.Lookup(name) == named, then we can apply this optimization to interface
// methods, too, I think.
return "", false
}
// Preallocate space for the name, opType, opMethod, and some digits.
name := named.Obj().Name()
path := make([]byte, 0, len(name)+8)
path = append(path, name...)
path = append(path, opType)
// Method indices are w.r.t. the go/types data structures,
// ultimately deriving from source order,
// which is preserved by export data.
for i := 0; i < named.NumMethods(); i++ {
if named.Method(i) == meth {
path = appendOpArg(path, opMethod, i)
return Path(path), true
}
}
// Due to golang/go#59944, go/types fails to associate the receiver with
// certain methods on cgo types.
//
// TODO(rfindley): replace this panic once golang/go#59944 is fixed in all Go
// versions gopls supports.
return "", false
// panic(fmt.Sprintf("couldn't find method %s on type %s; methods: %#v", meth, named, enc.namedMethods(named)))
}
// find finds obj within type T, returning the path to it, or nil if not found.
//
// The seen map is used to short circuit cycles through type parameters. If
// nil, it will be allocated as necessary.
//
// The seenMethods map is used internally to short circuit cycles through
// interface methods, such as occur in the following example:
//
// type I interface { f() interface{I} }
//
// See golang/go#68046 for details.
func find(obj types.Object, T types.Type, path []byte) []byte {
return (&finder{obj: obj}).find(T, path)
}
// finder closes over search state for a call to find.
type finder struct {
obj types.Object // the sought object
seenTParamNames map[*types.TypeName]bool // for cycle breaking through type parameters
seenMethods map[*types.Func]bool // for cycle breaking through recursive interfaces
}
func (f *finder) find(T types.Type, path []byte) []byte {
switch T := T.(type) {
case *types.Alias:
return f.find(types.Unalias(T), path)
case *types.Basic, *types.Named:
// Named types belonging to pkg were handled already,
// so T must belong to another package. No path.
return nil
case *types.Pointer:
return f.find(T.Elem(), append(path, opElem))
case *types.Slice:
return f.find(T.Elem(), append(path, opElem))
case *types.Array:
return f.find(T.Elem(), append(path, opElem))
case *types.Chan:
return f.find(T.Elem(), append(path, opElem))
case *types.Map:
if r := f.find(T.Key(), append(path, opKey)); r != nil {
return r
}
return f.find(T.Elem(), append(path, opElem))
case *types.Signature:
if r := f.findTypeParam(T.RecvTypeParams(), path, opRecvTypeParam); r != nil {
return r
}
if r := f.findTypeParam(T.TypeParams(), path, opTypeParam); r != nil {
return r
}
if r := f.find(T.Params(), append(path, opParams)); r != nil {
return r
}
return f.find(T.Results(), append(path, opResults))
case *types.Struct:
for i := 0; i < T.NumFields(); i++ {
fld := T.Field(i)
path2 := appendOpArg(path, opField, i)
if fld == f.obj {
return path2 // found field var
}
if r := f.find(fld.Type(), append(path2, opType)); r != nil {
return r
}
}
return nil
case *types.Tuple:
for i := 0; i < T.Len(); i++ {
v := T.At(i)
path2 := appendOpArg(path, opAt, i)
if v == f.obj {
return path2 // found param/result var
}
if r := f.find(v.Type(), append(path2, opType)); r != nil {
return r
}
}
return nil
case *types.Interface:
for i := 0; i < T.NumMethods(); i++ {
m := T.Method(i)
if f.seenMethods[m] {
continue // break cycles (see TestIssue70418)
}
path2 := appendOpArg(path, opMethod, i)
if m == f.obj {
return path2 // found interface method
}
if f.seenMethods == nil {
f.seenMethods = make(map[*types.Func]bool)
}
f.seenMethods[m] = true
if r := f.find(m.Type(), append(path2, opType)); r != nil {
return r
}
}
return nil
case *types.TypeParam:
name := T.Obj()
if f.seenTParamNames[name] {
return nil
}
if name == f.obj {
return append(path, opObj)
}
if f.seenTParamNames == nil {
f.seenTParamNames = make(map[*types.TypeName]bool)
}
f.seenTParamNames[name] = true
if r := f.find(T.Constraint(), append(path, opConstraint)); r != nil {
return r
}
return nil
}
panic(T)
}
func findTypeParam(obj types.Object, list *types.TypeParamList, path []byte, op byte) []byte {
return (&finder{obj: obj}).findTypeParam(list, path, op)
}
func (f *finder) findTypeParam(list *types.TypeParamList, path []byte, op byte) []byte {
for i := 0; i < list.Len(); i++ {
tparam := list.At(i)
path2 := appendOpArg(path, op, i)
if r := f.find(tparam, path2); r != nil {
return r
}
}
return nil
}
// Object returns the object denoted by path p within the package pkg.
func Object(pkg *types.Package, p Path) (types.Object, error) {
pathstr := string(p)
if pathstr == "" {
return nil, fmt.Errorf("empty path")
}
var pkgobj, suffix string
if dot := strings.IndexByte(pathstr, opType); dot < 0 {
pkgobj = pathstr
} else {
pkgobj = pathstr[:dot]
suffix = pathstr[dot:] // suffix starts with "."
}
obj := pkg.Scope().Lookup(pkgobj)
if obj == nil {
return nil, fmt.Errorf("package %s does not contain %q", pkg.Path(), pkgobj)
}
// abstraction of *types.{Pointer,Slice,Array,Chan,Map}
type hasElem interface {
Elem() types.Type
}
// abstraction of *types.{Named,Signature}
type hasTypeParams interface {
TypeParams() *types.TypeParamList
}
// abstraction of *types.{Alias,Named,TypeParam}
type hasObj interface {
Obj() *types.TypeName
}
// The loop state is the pair (t, obj),
// exactly one of which is non-nil, initially obj.
// All suffixes start with '.' (the only object->type operation),
// followed by optional type->type operations,
// then a type->object operation.
// The cycle then repeats.
var t types.Type
for suffix != "" {
code := suffix[0]
suffix = suffix[1:]
// Codes [AFMTr] have an integer operand.
var index int
switch code {
case opAt, opField, opMethod, opTypeParam, opRecvTypeParam:
rest := strings.TrimLeft(suffix, "0123456789")
numerals := suffix[:len(suffix)-len(rest)]
suffix = rest
i, err := strconv.Atoi(numerals)
if err != nil {
return nil, fmt.Errorf("invalid path: bad numeric operand %q for code %q", numerals, code)
}
index = int(i)
case opObj:
// no operand
default:
// The suffix must end with a type->object operation.
if suffix == "" {
return nil, fmt.Errorf("invalid path: ends with %q, want [AFMO]", code)
}
}
if code == opType {
if t != nil {
return nil, fmt.Errorf("invalid path: unexpected %q in type context", opType)
}
t = obj.Type()
obj = nil
continue
}
if t == nil {
return nil, fmt.Errorf("invalid path: code %q in object context", code)
}
// Inv: t != nil, obj == nil
t = types.Unalias(t)
switch code {
case opElem:
hasElem, ok := t.(hasElem) // Pointer, Slice, Array, Chan, Map
if !ok {
return nil, fmt.Errorf("cannot apply %q to %s (got %T, want pointer, slice, array, chan or map)", code, t, t)
}
t = hasElem.Elem()
case opKey:
mapType, ok := t.(*types.Map)
if !ok {
return nil, fmt.Errorf("cannot apply %q to %s (got %T, want map)", code, t, t)
}
t = mapType.Key()
case opParams:
sig, ok := t.(*types.Signature)
if !ok {
return nil, fmt.Errorf("cannot apply %q to %s (got %T, want signature)", code, t, t)
}
t = sig.Params()
case opResults:
sig, ok := t.(*types.Signature)
if !ok {
return nil, fmt.Errorf("cannot apply %q to %s (got %T, want signature)", code, t, t)
}
t = sig.Results()
case opUnderlying:
named, ok := t.(*types.Named)
if !ok {
return nil, fmt.Errorf("cannot apply %q to %s (got %T, want named)", code, t, t)
}
t = named.Underlying()
case opRhs:
if alias, ok := t.(*types.Alias); ok {
t = alias.Rhs()
} else if false {
// Now that go1.24 is assured, we should be able to
// replace this with "if true {", but it causes objectpath
// tests to fail. TODO(adonovan): investigate.
return nil, fmt.Errorf("cannot apply %q to %s (got %T, want alias)", code, t, t)
}
case opTypeParam:
hasTypeParams, ok := t.(hasTypeParams) // Named, Signature
if !ok {
return nil, fmt.Errorf("cannot apply %q to %s (got %T, want named or signature)", code, t, t)
}
tparams := hasTypeParams.TypeParams()
if n := tparams.Len(); index >= n {
return nil, fmt.Errorf("tuple index %d out of range [0-%d)", index, n)
}
t = tparams.At(index)
case opRecvTypeParam:
sig, ok := t.(*types.Signature) // Signature
if !ok {
return nil, fmt.Errorf("cannot apply %q to %s (got %T, want signature)", code, t, t)
}
rtparams := sig.RecvTypeParams()
if n := rtparams.Len(); index >= n {
return nil, fmt.Errorf("tuple index %d out of range [0-%d)", index, n)
}
t = rtparams.At(index)
case opConstraint:
tparam, ok := t.(*types.TypeParam)
if !ok {
return nil, fmt.Errorf("cannot apply %q to %s (got %T, want type parameter)", code, t, t)
}
t = tparam.Constraint()
case opAt:
tuple, ok := t.(*types.Tuple)
if !ok {
return nil, fmt.Errorf("cannot apply %q to %s (got %T, want tuple)", code, t, t)
}
if n := tuple.Len(); index >= n {
return nil, fmt.Errorf("tuple index %d out of range [0-%d)", index, n)
}
obj = tuple.At(index)
t = nil
case opField:
structType, ok := t.(*types.Struct)
if !ok {
return nil, fmt.Errorf("cannot apply %q to %s (got %T, want struct)", code, t, t)
}
if n := structType.NumFields(); index >= n {
return nil, fmt.Errorf("field index %d out of range [0-%d)", index, n)
}
obj = structType.Field(index)
t = nil
case opMethod:
switch t := t.(type) {
case *types.Interface:
if index >= t.NumMethods() {
return nil, fmt.Errorf("method index %d out of range [0-%d)", index, t.NumMethods())
}
obj = t.Method(index) // Id-ordered
case *types.Named:
if index >= t.NumMethods() {
return nil, fmt.Errorf("method index %d out of range [0-%d)", index, t.NumMethods())
}
obj = t.Method(index)
default:
return nil, fmt.Errorf("cannot apply %q to %s (got %T, want interface or named)", code, t, t)
}
t = nil
case opObj:
hasObj, ok := t.(hasObj)
if !ok {
return nil, fmt.Errorf("cannot apply %q to %s (got %T, want named or type param)", code, t, t)
}
obj = hasObj.Obj()
t = nil
default:
return nil, fmt.Errorf("invalid path: unknown code %q", code)
}
}
if obj == nil {
panic(p) // path does not end in an object-valued operator
}
if obj.Pkg() != pkg {
return nil, fmt.Errorf("path denotes %s, which belongs to a different package", obj)
}
return obj, nil // success
}
// scopeObjects is a memoization of scope objects.
// Callers must not modify the result.
func (enc *Encoder) scopeObjects(scope *types.Scope) []types.Object {
m := enc.scopeMemo
if m == nil {
m = make(map[*types.Scope][]types.Object)
enc.scopeMemo = m
}
objs, ok := m[scope]
if !ok {
names := scope.Names() // allocates and sorts
objs = make([]types.Object, len(names))
for i, name := range names {
objs[i] = scope.Lookup(name)
}
m[scope] = objs
}
return objs
}
+86
View File
@@ -0,0 +1,86 @@
// Copyright 2018 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.
package typeutil
import (
"go/ast"
"go/types"
_ "unsafe" // for linkname
)
// Callee returns the named target of a function call, if any:
// a function, method, builtin, or variable.
// It returns nil for a T(x) conversion.
//
// Functions and methods may potentially have type parameters.
//
// Note: for calls of instantiated functions and methods, Callee returns
// the corresponding generic function or method on the generic type.
func Callee(info *types.Info, call *ast.CallExpr) types.Object {
obj := info.Uses[usedIdent(info, call.Fun)]
if obj == nil {
return nil
}
if _, ok := obj.(*types.TypeName); ok {
return nil
}
return obj
}
// StaticCallee returns the target (function or method) of a static function
// call, if any. It returns nil for calls to builtins.
//
// Note: for calls of instantiated functions and methods, StaticCallee returns
// the corresponding generic function or method on the generic type.
func StaticCallee(info *types.Info, call *ast.CallExpr) *types.Func {
obj := info.Uses[usedIdent(info, call.Fun)]
fn, _ := obj.(*types.Func)
if fn == nil || interfaceMethod(fn) {
return nil
}
return fn
}
// usedIdent is the implementation of [internal/typesinternal.UsedIdent].
// It returns the identifier associated with e.
// See typesinternal.UsedIdent for a fuller description.
// This function should live in typesinternal, but cannot because it would
// create an import cycle.
//
//go:linkname usedIdent golang.org/x/tools/go/types/typeutil.usedIdent
func usedIdent(info *types.Info, e ast.Expr) *ast.Ident {
if info.Types == nil || info.Uses == nil {
panic("one of info.Types or info.Uses is nil; both must be populated")
}
// Look through type instantiation if necessary.
switch d := ast.Unparen(e).(type) {
case *ast.IndexExpr:
if info.Types[d.Index].IsType() {
e = d.X
}
case *ast.IndexListExpr:
e = d.X
}
switch e := ast.Unparen(e).(type) {
// info.Uses always has the object we want, even for selector expressions.
// We don't need info.Selections.
// See go/types/recording.go:recordSelection.
case *ast.Ident:
return e
case *ast.SelectorExpr:
return e.Sel
}
return nil
}
// interfaceMethod reports whether its argument is a method of an interface.
// This function should live in typesinternal, but cannot because it would create an import cycle.
//
//go:linkname interfaceMethod golang.org/x/tools/go/types/typeutil.interfaceMethod
func interfaceMethod(f *types.Func) bool {
recv := f.Signature().Recv()
return recv != nil && types.IsInterface(recv.Type())
}
+30
View File
@@ -0,0 +1,30 @@
// Copyright 2014 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.
package typeutil
import "go/types"
// Dependencies returns all dependencies of the specified packages.
//
// Dependent packages appear in topological order: if package P imports
// package Q, Q appears earlier than P in the result.
// The algorithm follows import statements in the order they
// appear in the source code, so the result is a total order.
func Dependencies(pkgs ...*types.Package) []*types.Package {
var result []*types.Package
seen := make(map[*types.Package]bool)
var visit func(pkgs []*types.Package)
visit = func(pkgs []*types.Package) {
for _, p := range pkgs {
if !seen[p] {
seen[p] = true
visit(p.Imports())
result = append(result, p)
}
}
}
visit(pkgs)
return result
}
+459
View File
@@ -0,0 +1,459 @@
// Copyright 2014 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.
// Package typeutil defines various utilities for types, such as [Map],
// a hash table that maps [types.Type] to any value.
package typeutil
import (
"bytes"
"fmt"
"go/types"
"hash/maphash"
"golang.org/x/tools/internal/typeparams"
)
// Map is a hash-table-based mapping from types (types.Type) to
// arbitrary values. The concrete types that implement
// the Type interface are pointers. Since they are not canonicalized,
// == cannot be used to check for equivalence, and thus we cannot
// simply use a Go map.
//
// Just as with map[K]V, a nil *Map is a valid empty map.
//
// Read-only map operations ([Map.At], [Map.Len], and so on) may
// safely be called concurrently.
//
// TODO(adonovan): deprecate in favor of https://go.dev/issues/69420
// and 69559, if the latter proposals for a generic hash-map type and
// a types.Hash function are accepted.
type Map struct {
table map[uint32][]entry // maps hash to bucket; entry.key==nil means unused
length int // number of map entries
}
// entry is an entry (key/value association) in a hash bucket.
type entry struct {
key types.Type
value any
}
// SetHasher has no effect.
//
// It is a relic of an optimization that is no longer profitable. Do
// not use [Hasher], [MakeHasher], or [SetHasher] in new code.
func (m *Map) SetHasher(Hasher) {}
// Delete removes the entry with the given key, if any.
// It returns true if the entry was found.
func (m *Map) Delete(key types.Type) bool {
if m != nil && m.table != nil {
hash := hash(key)
bucket := m.table[hash]
for i, e := range bucket {
if e.key != nil && types.Identical(key, e.key) {
// We can't compact the bucket as it
// would disturb iterators.
bucket[i] = entry{}
m.length--
return true
}
}
}
return false
}
// At returns the map entry for the given key.
// The result is nil if the entry is not present.
func (m *Map) At(key types.Type) any {
if m != nil && m.table != nil {
for _, e := range m.table[hash(key)] {
if e.key != nil && types.Identical(key, e.key) {
return e.value
}
}
}
return nil
}
// Set sets the map entry for key to val,
// and returns the previous entry, if any.
func (m *Map) Set(key types.Type, value any) (prev any) {
if m.table != nil {
hash := hash(key)
bucket := m.table[hash]
var hole *entry
for i, e := range bucket {
if e.key == nil {
hole = &bucket[i]
} else if types.Identical(key, e.key) {
prev = e.value
bucket[i].value = value
return
}
}
if hole != nil {
*hole = entry{key, value} // overwrite deleted entry
} else {
m.table[hash] = append(bucket, entry{key, value})
}
} else {
hash := hash(key)
m.table = map[uint32][]entry{hash: {entry{key, value}}}
}
m.length++
return
}
// Len returns the number of map entries.
func (m *Map) Len() int {
if m != nil {
return m.length
}
return 0
}
// Iterate calls function f on each entry in the map in unspecified order.
//
// If f should mutate the map, Iterate provides the same guarantees as
// Go maps: if f deletes a map entry that Iterate has not yet reached,
// f will not be invoked for it, but if f inserts a map entry that
// Iterate has not yet reached, whether or not f will be invoked for
// it is unspecified.
func (m *Map) Iterate(f func(key types.Type, value any)) {
if m != nil {
for _, bucket := range m.table {
for _, e := range bucket {
if e.key != nil {
f(e.key, e.value)
}
}
}
}
}
// Keys returns a new slice containing the set of map keys.
// The order is unspecified.
func (m *Map) Keys() []types.Type {
keys := make([]types.Type, 0, m.Len())
m.Iterate(func(key types.Type, _ any) {
keys = append(keys, key)
})
return keys
}
func (m *Map) toString(values bool) string {
if m == nil {
return "{}"
}
var buf bytes.Buffer
fmt.Fprint(&buf, "{")
sep := ""
m.Iterate(func(key types.Type, value any) {
fmt.Fprint(&buf, sep)
sep = ", "
fmt.Fprint(&buf, key)
if values {
fmt.Fprintf(&buf, ": %q", value)
}
})
fmt.Fprint(&buf, "}")
return buf.String()
}
// String returns a string representation of the map's entries.
// Values are printed using fmt.Sprintf("%v", v).
// Order is unspecified.
func (m *Map) String() string {
return m.toString(true)
}
// KeysString returns a string representation of the map's key set.
// Order is unspecified.
func (m *Map) KeysString() string {
return m.toString(false)
}
// -- Hasher --
// hash returns the hash of type t.
// TODO(adonovan): replace by types.Hash when Go proposal #69420 is accepted.
func hash(t types.Type) uint32 {
return theHasher.Hash(t)
}
// A Hasher provides a [Hasher.Hash] method to map a type to its hash value.
// Hashers are stateless, and all are equivalent.
type Hasher struct{}
var theHasher Hasher
// MakeHasher returns Hasher{}.
// Hashers are stateless; all are equivalent.
func MakeHasher() Hasher { return theHasher }
// Hash computes a hash value for the given type t such that
// Identical(t, t') => Hash(t) == Hash(t').
func (h Hasher) Hash(t types.Type) uint32 {
return hasher{inGenericSig: false}.hash(t)
}
// hasher holds the state of a single Hash traversal: whether we are
// inside the signature of a generic function; this is used to
// optimize [hasher.hashTypeParam].
type hasher struct{ inGenericSig bool }
// hashString computes the FowlerNollVo hash of s.
func hashString(s string) uint32 {
var h uint32
for i := 0; i < len(s); i++ {
h ^= uint32(s[i])
h *= 16777619
}
return h
}
// hash computes the hash of t.
func (h hasher) hash(t types.Type) uint32 {
// See Identical for rationale.
switch t := t.(type) {
case *types.Basic:
return uint32(t.Kind())
case *types.Alias:
return h.hash(types.Unalias(t))
case *types.Array:
return 9043 + 2*uint32(t.Len()) + 3*h.hash(t.Elem())
case *types.Slice:
return 9049 + 2*h.hash(t.Elem())
case *types.Struct:
var hash uint32 = 9059
for i, n := 0, t.NumFields(); i < n; i++ {
f := t.Field(i)
if f.Anonymous() {
hash += 8861
}
hash += hashString(t.Tag(i))
hash += hashString(f.Name()) // (ignore f.Pkg)
hash += h.hash(f.Type())
}
return hash
case *types.Pointer:
return 9067 + 2*h.hash(t.Elem())
case *types.Signature:
var hash uint32 = 9091
if t.Variadic() {
hash *= 8863
}
tparams := t.TypeParams()
if n := tparams.Len(); n > 0 {
h.inGenericSig = true // affects constraints, params, and results
for i := range n {
tparam := tparams.At(i)
hash += 7 * h.hash(tparam.Constraint())
}
}
return hash + 3*h.hashTuple(t.Params()) + 5*h.hashTuple(t.Results())
case *types.Union:
return h.hashUnion(t)
case *types.Interface:
// Interfaces are identical if they have the same set of methods, with
// identical names and types, and they have the same set of type
// restrictions. See go/types.identical for more details.
var hash uint32 = 9103
// Hash methods.
for i, n := 0, t.NumMethods(); i < n; i++ {
// Method order is not significant.
// Ignore m.Pkg().
m := t.Method(i)
// Use shallow hash on method signature to
// avoid anonymous interface cycles.
hash += 3*hashString(m.Name()) + 5*h.shallowHash(m.Type())
}
// Hash type restrictions.
terms, err := typeparams.InterfaceTermSet(t)
// if err != nil t has invalid type restrictions.
if err == nil {
hash += h.hashTermSet(terms)
}
return hash
case *types.Map:
return 9109 + 2*h.hash(t.Key()) + 3*h.hash(t.Elem())
case *types.Chan:
return 9127 + 2*uint32(t.Dir()) + 3*h.hash(t.Elem())
case *types.Named:
hash := h.hashTypeName(t.Obj())
targs := t.TypeArgs()
for targ := range targs.Types() {
hash += 2 * h.hash(targ)
}
return hash
case *types.TypeParam:
return h.hashTypeParam(t)
case *types.Tuple:
return h.hashTuple(t)
}
panic(fmt.Sprintf("%T: %v", t, t))
}
func (h hasher) hashTuple(tuple *types.Tuple) uint32 {
// See go/types.identicalTypes for rationale.
n := tuple.Len()
hash := 9137 + 2*uint32(n)
for i := range n {
hash += 3 * h.hash(tuple.At(i).Type())
}
return hash
}
func (h hasher) hashUnion(t *types.Union) uint32 {
// Hash type restrictions.
terms, err := typeparams.UnionTermSet(t)
// if err != nil t has invalid type restrictions. Fall back on a non-zero
// hash.
if err != nil {
return 9151
}
return h.hashTermSet(terms)
}
func (h hasher) hashTermSet(terms []*types.Term) uint32 {
hash := 9157 + 2*uint32(len(terms))
for _, term := range terms {
// term order is not significant.
termHash := h.hash(term.Type())
if term.Tilde() {
termHash *= 9161
}
hash += 3 * termHash
}
return hash
}
// hashTypeParam returns the hash of a type parameter.
func (h hasher) hashTypeParam(t *types.TypeParam) uint32 {
// Within the signature of a generic function, TypeParams are
// identical if they have the same index and constraint, so we
// hash them based on index.
//
// When we are outside a generic function, free TypeParams are
// identical iff they are the same object, so we can use a
// more discriminating hash consistent with object identity.
// This optimization saves [Map] about 4% when hashing all the
// types.Info.Types in the forward closure of net/http.
if !h.inGenericSig {
// Optimization: outside a generic function signature,
// use a more discrimating hash consistent with object identity.
return h.hashTypeName(t.Obj())
}
return 9173 + 3*uint32(t.Index())
}
var theSeed = maphash.MakeSeed()
// hashTypeName hashes the pointer of tname.
func (hasher) hashTypeName(tname *types.TypeName) uint32 {
// Since types.Identical uses == to compare TypeNames,
// the Hash function uses maphash.Comparable.
hash := maphash.Comparable(theSeed, tname)
return uint32(hash ^ (hash >> 32))
}
// shallowHash computes a hash of t without looking at any of its
// element Types, to avoid potential anonymous cycles in the types of
// interface methods.
//
// When an unnamed non-empty interface type appears anywhere among the
// arguments or results of an interface method, there is a potential
// for endless recursion. Consider:
//
// type X interface { m() []*interface { X } }
//
// The problem is that the Methods of the interface in m's result type
// include m itself; there is no mention of the named type X that
// might help us break the cycle.
// (See comment in go/types.identical, case *Interface, for more.)
func (h hasher) shallowHash(t types.Type) uint32 {
// t is the type of an interface method (Signature),
// its params or results (Tuples), or their immediate
// elements (mostly Slice, Pointer, Basic, Named),
// so there's no need to optimize anything else.
switch t := t.(type) {
case *types.Alias:
return h.shallowHash(types.Unalias(t))
case *types.Signature:
var hash uint32 = 604171
if t.Variadic() {
hash *= 971767
}
// The Signature/Tuple recursion is always finite
// and invariably shallow.
return hash + 1062599*h.shallowHash(t.Params()) + 1282529*h.shallowHash(t.Results())
case *types.Tuple:
n := t.Len()
hash := 9137 + 2*uint32(n)
for i := range n {
hash += 53471161 * h.shallowHash(t.At(i).Type())
}
return hash
case *types.Basic:
return 45212177 * uint32(t.Kind())
case *types.Array:
return 1524181 + 2*uint32(t.Len())
case *types.Slice:
return 2690201
case *types.Struct:
return 3326489
case *types.Pointer:
return 4393139
case *types.Union:
return 562448657
case *types.Interface:
return 2124679 // no recursion here
case *types.Map:
return 9109
case *types.Chan:
return 9127
case *types.Named:
return h.hashTypeName(t.Obj())
case *types.TypeParam:
return h.hashTypeParam(t)
}
panic(fmt.Sprintf("shallowHash: %T: %v", t, t))
}
+71
View File
@@ -0,0 +1,71 @@
// Copyright 2014 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.
// This file implements a cache of method sets.
package typeutil
import (
"go/types"
"sync"
)
// A MethodSetCache records the method set of each type T for which
// MethodSet(T) is called so that repeat queries are fast.
// The zero value is a ready-to-use cache instance.
type MethodSetCache struct {
mu sync.Mutex
named map[*types.Named]struct{ value, pointer *types.MethodSet } // method sets for named N and *N
others map[types.Type]*types.MethodSet // all other types
}
// MethodSet returns the method set of type T. It is thread-safe.
//
// If cache is nil, this function is equivalent to types.NewMethodSet(T).
// Utility functions can thus expose an optional *MethodSetCache
// parameter to clients that care about performance.
func (cache *MethodSetCache) MethodSet(T types.Type) *types.MethodSet {
if cache == nil {
return types.NewMethodSet(T)
}
cache.mu.Lock()
defer cache.mu.Unlock()
switch T := types.Unalias(T).(type) {
case *types.Named:
return cache.lookupNamed(T).value
case *types.Pointer:
if N, ok := types.Unalias(T.Elem()).(*types.Named); ok {
return cache.lookupNamed(N).pointer
}
}
// all other types
// (The map uses pointer equivalence, not type identity.)
mset := cache.others[T]
if mset == nil {
mset = types.NewMethodSet(T)
if cache.others == nil {
cache.others = make(map[types.Type]*types.MethodSet)
}
cache.others[T] = mset
}
return mset
}
func (cache *MethodSetCache) lookupNamed(named *types.Named) struct{ value, pointer *types.MethodSet } {
if cache.named == nil {
cache.named = make(map[*types.Named]struct{ value, pointer *types.MethodSet })
}
// Avoid recomputing mset(*T) for each distinct Pointer
// instance whose underlying type is a named type.
msets, ok := cache.named[named]
if !ok {
msets.value = types.NewMethodSet(named)
msets.pointer = types.NewMethodSet(types.NewPointer(named))
cache.named[named] = msets
}
return msets
}
+53
View File
@@ -0,0 +1,53 @@
// Copyright 2014 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.
package typeutil
// This file defines utilities for user interfaces that display types.
import (
"go/types"
)
// IntuitiveMethodSet returns the intuitive method set of a type T,
// which is the set of methods you can call on an addressable value of
// that type.
//
// The result always contains MethodSet(T), and is exactly MethodSet(T)
// for interface types and for pointer-to-concrete types.
// For all other concrete types T, the result additionally
// contains each method belonging to *T if there is no identically
// named method on T itself.
//
// This corresponds to user intuition about method sets;
// this function is intended only for user interfaces.
//
// The order of the result is as for types.MethodSet(T).
func IntuitiveMethodSet(T types.Type, msets *MethodSetCache) []*types.Selection {
isPointerToConcrete := func(T types.Type) bool {
ptr, ok := types.Unalias(T).(*types.Pointer)
return ok && !types.IsInterface(ptr.Elem())
}
var result []*types.Selection
mset := msets.MethodSet(T)
if types.IsInterface(T) || isPointerToConcrete(T) {
for i, n := 0, mset.Len(); i < n; i++ {
result = append(result, mset.At(i))
}
} else {
// T is some other concrete type.
// Report methods of T and *T, preferring those of T.
pmset := msets.MethodSet(types.NewPointer(T))
for i, n := 0, pmset.Len(); i < n; i++ {
meth := pmset.At(i)
if m := mset.Lookup(meth.Obj().Pkg(), meth.Obj().Name()); m != nil {
meth = m
}
result = append(result, meth)
}
}
return result
}