all: update golang.org/x/tools to v0.27.0

Commands run:
	cd $GOROOT/src/cmd
	go get golang.org/x/tools@v0.27.0
	go mod tidy
	go mod vendor

Needed for CL 623475. Introduced ABIInternal syscall support.

Change-Id: I03d7576747826243c25658f360b24ef9b84f0f04
Reviewed-on: https://go-review.googlesource.com/c/go/+/620738
Reviewed-by: Meidan Li <limeidan@loongson.cn>
Reviewed-by: David Chase <drchase@google.com>
LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
Auto-Submit: David Chase <drchase@google.com>
Reviewed-by: Cherry Mui <cherryyz@google.com>
Reviewed-by: abner chenc <chenguoqi@loongson.cn>
This commit is contained in:
Xiaolin Zhao 2024-10-18 17:16:00 +08:00 committed by Gopher Robot
parent a1d62aa475
commit 5a22174eee
58 changed files with 621 additions and 2058 deletions

View File

@ -6,12 +6,12 @@ require (
github.com/google/pprof v0.0.0-20241101162523-b92577c0c142
golang.org/x/arch v0.11.1-0.20241106162200-f977c2e4e3f4
golang.org/x/build v0.0.0-20240722200705-b9910f320300
golang.org/x/mod v0.20.0
golang.org/x/sync v0.8.0
golang.org/x/mod v0.22.0
golang.org/x/sync v0.9.0
golang.org/x/sys v0.27.0
golang.org/x/telemetry v0.0.0-20240828202201-a797f331ea97
golang.org/x/term v0.22.1-0.20240716160707-d4346f0be292
golang.org/x/tools v0.24.1-0.20240904143311-70f56264139c
golang.org/x/tools v0.27.0
)
require (

View File

@ -10,10 +10,10 @@ golang.org/x/arch v0.11.1-0.20241106162200-f977c2e4e3f4 h1:B9d6SEXeIaY1QC4c7Gsf8
golang.org/x/arch v0.11.1-0.20241106162200-f977c2e4e3f4/go.mod h1:FEVrYAQjsQXMVJ1nsMoVVXPZg6p2JE2mx8psSWTDQys=
golang.org/x/build v0.0.0-20240722200705-b9910f320300 h1:2Cqg4LnvfD2ZpG8+6KbyYUkweWhNS3SgfcN/eeVseJ0=
golang.org/x/build v0.0.0-20240722200705-b9910f320300/go.mod h1:YsGhg4JUVUWLzdqU2wCrtpRrOveOql6w56FLDHq/CJ4=
golang.org/x/mod v0.20.0 h1:utOm6MM3R3dnawAiJgn0y+xvuYRsm1RKM/4giyfDgV0=
golang.org/x/mod v0.20.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ=
golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/mod v0.22.0 h1:D4nJWe9zXqHOmWqj4VMOJhvzj7bEZg4wEYa759z1pH4=
golang.org/x/mod v0.22.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY=
golang.org/x/sync v0.9.0 h1:fEo0HyrW1GIgZdpbhCRO0PkJajUS5H9IFUztCgEo2jQ=
golang.org/x/sync v0.9.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sys v0.27.0 h1:wBqf8DvsY9Y/2P8gAfPDEYNuS30J4lPHJxXSb/nJZ+s=
golang.org/x/sys v0.27.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/telemetry v0.0.0-20240828202201-a797f331ea97 h1:5xPN7d0u5VdgF2gFFXUDaeD3NP1pPgFMHocnCQGAh5M=
@ -22,7 +22,7 @@ golang.org/x/term v0.22.1-0.20240716160707-d4346f0be292 h1:BOrQi08eIX3cDgGcMgFON
golang.org/x/term v0.22.1-0.20240716160707-d4346f0be292/go.mod h1:F3qCibpT5AMpCRfhfT53vVJwhLtIVHhB9XDjfFvnMI4=
golang.org/x/text v0.19.0 h1:kTxAhCbGbxhK0IwgSKiMO5awPoDQ0RpfiVYBfK860YM=
golang.org/x/text v0.19.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY=
golang.org/x/tools v0.24.1-0.20240904143311-70f56264139c h1:JImdv91aqIPqamNg5sOTUjNQD++5KkvchZi2BcYlNoE=
golang.org/x/tools v0.24.1-0.20240904143311-70f56264139c/go.mod h1:IV2Kidsnn7A8K7hHxn/wcUfHXkViw0LLHdu8LnpT8LU=
golang.org/x/tools v0.27.0 h1:qEKojBykQkQ4EynWy4S8Weg69NumxKdn40Fce3uc/8o=
golang.org/x/tools v0.27.0/go.mod h1:sUi0ZgbwW9ZPAq26Ekut+weQPR5eIM6GQLQ1Yjm1H0Q=
rsc.io/markdown v0.0.0-20240306144322-0bf8f97ee8ef h1:mqLYrXCXYEZOop9/Dbo6RPX11539nwiCNBb1icVPmw8=
rsc.io/markdown v0.0.0-20240306144322-0bf8f97ee8ef/go.mod h1:8xcPgWmwlZONN1D9bjxtHEjrUtSEa3fakVF8iaewYKQ=

View File

@ -6,6 +6,7 @@
package sumdb
import (
"bytes"
"context"
"net/http"
"os"
@ -150,6 +151,8 @@ func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
// Data tiles contain formatted records without the first line with record ID.
_, msg, _ = bytes.Cut(msg, []byte{'\n'})
data = append(data, msg...)
}
w.Header().Set("Content-Type", "text/plain; charset=UTF-8")

View File

@ -73,13 +73,16 @@ func ParseTree(text []byte) (tree Tree, err error) {
var errMalformedRecord = errors.New("malformed record data")
// FormatRecord formats a record for serving to a client
// in a lookup response or data tile.
// in a lookup response.
//
// The encoded form is the record ID as a single number,
// then the text of the record, and then a terminating blank line.
// Record text must be valid UTF-8 and must not contain any ASCII control
// characters (those below U+0020) other than newline (U+000A).
// It must end in a terminating newline and not contain any blank lines.
//
// Responses to data tiles consist of concatenated formatted records from each of
// which the first line, with the record ID, is removed.
func FormatRecord(id int64, text []byte) (msg []byte, err error) {
if !isValidRecordText(text) {
return nil, errMalformedRecord

View File

@ -50,6 +50,7 @@ import (
"bytes"
"errors"
"fmt"
"go/version"
"io"
"os"
"os/exec"
@ -60,6 +61,7 @@ import (
"unicode"
"unicode/utf8"
"golang.org/x/mod/modfile"
"golang.org/x/mod/module"
)
@ -193,6 +195,20 @@ func CheckFiles(files []File) (CheckedFiles, error) {
return cf, cf.Err()
}
// parseGoVers extracts the Go version specified in the given go.mod file.
// It returns an empty string if the version is not found or if an error
// occurs during file parsing.
//
// The version string is in Go toolchain name syntax, prefixed with "go".
// Examples: "go1.21", "go1.22rc2", "go1.23.0"
func parseGoVers(file string, data []byte) string {
mfile, err := modfile.ParseLax(file, data, nil)
if err != nil || mfile.Go == nil {
return ""
}
return "go" + mfile.Go.Version
}
// checkFiles implements CheckFiles and also returns lists of valid files and
// their sizes, corresponding to cf.Valid. It omits files in submodules, files
// in vendored packages, symlinked files, and various other unwanted files.
@ -217,6 +233,7 @@ func checkFiles(files []File) (cf CheckedFiles, validFiles []File, validSizes []
// Files in these directories will be omitted.
// These directories will not be included in the output zip.
haveGoMod := make(map[string]bool)
var vers string
for _, f := range files {
p := f.Path()
dir, base := path.Split(p)
@ -226,8 +243,21 @@ func checkFiles(files []File) (cf CheckedFiles, validFiles []File, validSizes []
addError(p, false, err)
continue
}
if info.Mode().IsRegular() {
haveGoMod[dir] = true
if !info.Mode().IsRegular() {
continue
}
haveGoMod[dir] = true
// Extract the Go language version from the root "go.mod" file.
// This ensures we correctly interpret Go version-specific file omissions.
// We use f.Open() to handle potential custom Open() implementations
// that the underlying File type might have.
if base == "go.mod" && dir == "" {
if file, err := f.Open(); err == nil {
if data, err := io.ReadAll(file); err == nil {
vers = version.Lang(parseGoVers("go.mod", data))
}
file.Close()
}
}
}
}
@ -257,7 +287,7 @@ func checkFiles(files []File) (cf CheckedFiles, validFiles []File, validSizes []
addError(p, false, errPathNotRelative)
continue
}
if isVendoredPackage(p) {
if isVendoredPackage(p, vers) {
// Skip files in vendored packages.
addError(p, true, errVendored)
continue
@ -573,7 +603,9 @@ func CreateFromDir(w io.Writer, m module.Version, dir string) (err error) {
// VCS repository stored locally. The zip content is written to w.
//
// repoRoot must be an absolute path to the base of the repository, such as
// "/Users/some-user/some-repo".
// "/Users/some-user/some-repo". If the repository is a Git repository,
// this path is expected to point to its worktree: it can't be a bare git
// repo.
//
// revision is the revision of the repository to create the zip from. Examples
// include HEAD or SHA sums for git repositories.
@ -754,20 +786,42 @@ func (fi dataFileInfo) Sys() interface{} { return nil }
// in a package whose import path contains (but does not end with) the component
// "vendor".
//
// Unfortunately, isVendoredPackage reports false positives for files in any
// non-top-level package whose import path ends in "vendor".
func isVendoredPackage(name string) bool {
// The 'vers' parameter specifies the Go version declared in the module's
// go.mod file and must be a valid Go version according to the
// go/version.IsValid function.
// Vendoring behavior has evolved across Go versions, so this function adapts
// its logic accordingly.
func isVendoredPackage(name string, vers string) bool {
// vendor/modules.txt is a vendored package but was included in 1.23 and earlier.
// Remove vendor/modules.txt only for 1.24 and beyond to preserve older checksums.
if version.Compare(vers, "go1.24") >= 0 && name == "vendor/modules.txt" {
return true
}
var i int
if strings.HasPrefix(name, "vendor/") {
i += len("vendor/")
} else if j := strings.Index(name, "/vendor/"); j >= 0 {
// This offset looks incorrect; this should probably be
// Calculate the correct starting position within the import path
// to determine if a package is vendored.
//
// i = j + len("/vendor/")
// Due to a bug in Go versions before 1.24
// (see https://golang.org/issue/37397), the "/vendor/" prefix within
// a package path was not always correctly interpreted.
//
// (See https://golang.org/issue/31562 and https://golang.org/issue/37397.)
// Unfortunately, we can't fix it without invalidating module checksums.
i += len("/vendor/")
// This bug affected how vendored packages were identified in cases like:
//
// - "pkg/vendor/vendor.go" (incorrectly identified as vendored in pre-1.24)
// - "pkg/vendor/foo/foo.go" (correctly identified as vendored)
//
// To correct this, in Go 1.24 and later, we skip the entire "/vendor/" prefix
// when it's part of a nested package path (as in the first example above).
// In earlier versions, we only skipped the length of "/vendor/", leading
// to the incorrect behavior.
if version.Compare(vers, "go1.24") >= 0 {
i = j + len("/vendor/")
} else {
i += len("/vendor/")
}
} else {
return false
}
@ -892,6 +946,12 @@ func (cc collisionChecker) check(p string, isDir bool) error {
// files, as well as a list of directories and files that were skipped (for
// example, nested modules and symbolic links).
func listFilesInDir(dir string) (files []File, omitted []FileError, err error) {
// Extract the Go language version from the root "go.mod" file.
// This ensures we correctly interpret Go version-specific file omissions.
var vers string
if data, err := os.ReadFile(filepath.Join(dir, "go.mod")); err == nil {
vers = version.Lang(parseGoVers("go.mod", data))
}
err = filepath.Walk(dir, func(filePath string, info os.FileInfo, err error) error {
if err != nil {
return err
@ -902,11 +962,10 @@ func listFilesInDir(dir string) (files []File, omitted []FileError, err error) {
}
slashPath := filepath.ToSlash(relPath)
// Skip some subdirectories inside vendor, but maintain bug
// golang.org/issue/31562, described in isVendoredPackage.
// Skip some subdirectories inside vendor.
// We would like Create and CreateFromDir to produce the same result
// for a set of files, whether expressed as a directory tree or zip.
if isVendoredPackage(slashPath) {
if isVendoredPackage(slashPath, vers) {
omitted = append(omitted, FileError{Path: slashPath, Err: errVendored})
return nil
}

View File

@ -1,13 +0,0 @@
// Copyright 2023 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.20
package main
import "os/exec"
func cmdInterrupt(cmd *exec.Cmd) {
// cmd.Cancel and cmd.WaitDelay not available before Go 1.20.
}

View File

@ -2,8 +2,6 @@
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
//go:build go1.20
package main
import (

View File

@ -1,20 +0,0 @@
// Copyright 2023 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.
// Starting in Go 1.20, the global rand is auto-seeded,
// with a better value than the current Unix nanoseconds.
// Only seed if we're using older versions of Go.
//go:build !go1.20
package main
import (
"math/rand"
"time"
)
func init() {
rand.Seed(time.Now().UnixNano())
}

View File

@ -57,6 +57,8 @@ type asmArch struct {
// include the first integer register and first floating-point register. Accessing
// any of them counts as writing to result.
retRegs []string
// writeResult is a list of instructions that will change result register implicity.
writeResult []string
// calculated during initialization
sizes types.Sizes
intSize int
@ -85,18 +87,18 @@ type asmVar struct {
var (
asmArch386 = asmArch{name: "386", bigEndian: false, stack: "SP", lr: false}
asmArchArm = asmArch{name: "arm", bigEndian: false, stack: "R13", lr: true}
asmArchArm64 = asmArch{name: "arm64", bigEndian: false, stack: "RSP", lr: true, retRegs: []string{"R0", "F0"}}
asmArchAmd64 = asmArch{name: "amd64", bigEndian: false, stack: "SP", lr: false, retRegs: []string{"AX", "X0"}}
asmArchArm64 = asmArch{name: "arm64", bigEndian: false, stack: "RSP", lr: true, retRegs: []string{"R0", "F0"}, writeResult: []string{"SVC"}}
asmArchAmd64 = asmArch{name: "amd64", bigEndian: false, stack: "SP", lr: false, retRegs: []string{"AX", "X0"}, writeResult: []string{"SYSCALL"}}
asmArchMips = asmArch{name: "mips", bigEndian: true, stack: "R29", lr: true}
asmArchMipsLE = asmArch{name: "mipsle", bigEndian: false, stack: "R29", lr: true}
asmArchMips64 = asmArch{name: "mips64", bigEndian: true, stack: "R29", lr: true}
asmArchMips64LE = asmArch{name: "mips64le", bigEndian: false, stack: "R29", lr: true}
asmArchPpc64 = asmArch{name: "ppc64", bigEndian: true, stack: "R1", lr: true, retRegs: []string{"R3", "F1"}}
asmArchPpc64LE = asmArch{name: "ppc64le", bigEndian: false, stack: "R1", lr: true, retRegs: []string{"R3", "F1"}}
asmArchRISCV64 = asmArch{name: "riscv64", bigEndian: false, stack: "SP", lr: true, retRegs: []string{"X10", "F10"}}
asmArchPpc64 = asmArch{name: "ppc64", bigEndian: true, stack: "R1", lr: true, retRegs: []string{"R3", "F1"}, writeResult: []string{"SYSCALL"}}
asmArchPpc64LE = asmArch{name: "ppc64le", bigEndian: false, stack: "R1", lr: true, retRegs: []string{"R3", "F1"}, writeResult: []string{"SYSCALL"}}
asmArchRISCV64 = asmArch{name: "riscv64", bigEndian: false, stack: "SP", lr: true, retRegs: []string{"X10", "F10"}, writeResult: []string{"ECALL"}}
asmArchS390X = asmArch{name: "s390x", bigEndian: true, stack: "R15", lr: true}
asmArchWasm = asmArch{name: "wasm", bigEndian: false, stack: "SP", lr: false}
asmArchLoong64 = asmArch{name: "loong64", bigEndian: false, stack: "R3", lr: true, retRegs: []string{"R4", "F0"}}
asmArchLoong64 = asmArch{name: "loong64", bigEndian: false, stack: "R3", lr: true, retRegs: []string{"R4", "F0"}, writeResult: []string{"SYSCALL"}}
arches = []*asmArch{
&asmArch386,
@ -351,6 +353,12 @@ Files:
}
if abi == "ABIInternal" && !haveRetArg {
for _, ins := range archDef.writeResult {
if strings.Contains(line, ins) {
haveRetArg = true
break
}
}
for _, reg := range archDef.retRegs {
if strings.Contains(line, reg) {
haveRetArg = true

View File

@ -18,7 +18,6 @@ import (
"golang.org/x/tools/go/analysis"
"golang.org/x/tools/go/analysis/passes/inspect"
"golang.org/x/tools/go/analysis/passes/internal/analysisutil"
"golang.org/x/tools/go/ast/astutil"
"golang.org/x/tools/go/ast/inspector"
)
@ -78,7 +77,7 @@ func run(pass *analysis.Pass) (interface{}, error) {
// isMapIndex returns true if e is a map index expression.
func isMapIndex(info *types.Info, e ast.Expr) bool {
if idx, ok := astutil.Unparen(e).(*ast.IndexExpr); ok {
if idx, ok := ast.Unparen(e).(*ast.IndexExpr); ok {
if typ := info.Types[idx.X].Type; typ != nil {
_, ok := typ.Underlying().(*types.Map)
return ok

View File

@ -14,7 +14,6 @@ import (
"golang.org/x/tools/go/analysis"
"golang.org/x/tools/go/analysis/passes/inspect"
"golang.org/x/tools/go/analysis/passes/internal/analysisutil"
"golang.org/x/tools/go/ast/astutil"
"golang.org/x/tools/go/ast/inspector"
)
@ -169,7 +168,7 @@ func (op boolOp) checkSuspect(pass *analysis.Pass, exprs []ast.Expr) {
// seen[e] is already true; any newly processed exprs are added to seen.
func (op boolOp) split(e ast.Expr, seen map[*ast.BinaryExpr]bool) (exprs []ast.Expr) {
for {
e = astutil.Unparen(e)
e = ast.Unparen(e)
if b, ok := e.(*ast.BinaryExpr); ok && b.Op == op.tok {
seen[b] = true
exprs = append(exprs, op.split(b.Y, seen)...)

View File

@ -19,7 +19,6 @@ import (
"golang.org/x/tools/go/analysis"
"golang.org/x/tools/go/analysis/passes/internal/analysisutil"
"golang.org/x/tools/go/ast/astutil"
)
const debug = false
@ -65,7 +64,7 @@ func checkCgo(fset *token.FileSet, f *ast.File, info *types.Info, reportf func(t
// Is this a C.f() call?
var name string
if sel, ok := astutil.Unparen(call.Fun).(*ast.SelectorExpr); ok {
if sel, ok := ast.Unparen(call.Fun).(*ast.SelectorExpr); ok {
if id, ok := sel.X.(*ast.Ident); ok && id.Name == "C" {
name = sel.Sel.Name
}
@ -180,7 +179,7 @@ func typeCheckCgoSourceFiles(fset *token.FileSet, pkg *types.Package, files []*a
for _, raw := range files {
// If f is a cgo-generated file, Position reports
// the original file, honoring //line directives.
filename := fset.Position(raw.Pos()).Filename
filename := fset.Position(raw.Pos()).Filename // sic: Pos, not FileStart
f, err := parser.ParseFile(fset, filename, nil, parser.SkipObjectResolution)
if err != nil {
return nil, nil, fmt.Errorf("can't parse raw cgo file: %v", err)

View File

@ -15,7 +15,6 @@ import (
"golang.org/x/tools/go/analysis"
"golang.org/x/tools/go/analysis/passes/inspect"
"golang.org/x/tools/go/ast/inspector"
"golang.org/x/tools/internal/aliases"
"golang.org/x/tools/internal/typeparams"
)
@ -72,7 +71,7 @@ func run(pass *analysis.Pass) (interface{}, error) {
return
}
var structuralTypes []types.Type
switch typ := aliases.Unalias(typ).(type) {
switch typ := types.Unalias(typ).(type) {
case *types.TypeParam:
terms, err := typeparams.StructuralTerms(typ)
if err != nil {
@ -146,7 +145,7 @@ func run(pass *analysis.Pass) (interface{}, error) {
// isLocalType reports whether typ belongs to the same package as pass.
// TODO(adonovan): local means "internal to a function"; rename to isSamePackageType.
func isLocalType(pass *analysis.Pass, typ types.Type) bool {
switch x := aliases.Unalias(typ).(type) {
switch x := types.Unalias(typ).(type) {
case *types.Struct:
// struct literals are local types
return true

View File

@ -16,9 +16,7 @@ import (
"golang.org/x/tools/go/analysis"
"golang.org/x/tools/go/analysis/passes/inspect"
"golang.org/x/tools/go/analysis/passes/internal/analysisutil"
"golang.org/x/tools/go/ast/astutil"
"golang.org/x/tools/go/ast/inspector"
"golang.org/x/tools/internal/aliases"
"golang.org/x/tools/internal/typeparams"
"golang.org/x/tools/internal/versions"
)
@ -253,7 +251,7 @@ func (path typePath) String() string {
}
func lockPathRhs(pass *analysis.Pass, x ast.Expr) typePath {
x = astutil.Unparen(x) // ignore parens on rhs
x = ast.Unparen(x) // ignore parens on rhs
if _, ok := x.(*ast.CompositeLit); ok {
return nil
@ -263,7 +261,7 @@ func lockPathRhs(pass *analysis.Pass, x ast.Expr) typePath {
return nil
}
if star, ok := x.(*ast.StarExpr); ok {
if _, ok := astutil.Unparen(star.X).(*ast.CallExpr); ok {
if _, ok := ast.Unparen(star.X).(*ast.CallExpr); ok {
// A call may return a pointer to a zero value.
return nil
}
@ -287,7 +285,7 @@ func lockPath(tpkg *types.Package, typ types.Type, seen map[types.Type]bool) typ
}
seen[typ] = true
if tpar, ok := aliases.Unalias(typ).(*types.TypeParam); ok {
if tpar, ok := types.Unalias(typ).(*types.TypeParam); ok {
terms, err := typeparams.StructuralTerms(tpar)
if err != nil {
return nil // invalid type

View File

@ -14,7 +14,6 @@ import (
"golang.org/x/tools/go/analysis/passes/inspect"
"golang.org/x/tools/go/analysis/passes/internal/analysisutil"
"golang.org/x/tools/go/ast/inspector"
"golang.org/x/tools/internal/aliases"
"golang.org/x/tools/internal/typesinternal"
)
@ -137,7 +136,7 @@ func isHTTPFuncOrMethodOnClient(info *types.Info, expr *ast.CallExpr) bool {
if analysisutil.IsNamedType(typ, "net/http", "Client") {
return true // method on http.Client.
}
ptr, ok := aliases.Unalias(typ).(*types.Pointer)
ptr, ok := types.Unalias(typ).(*types.Pointer)
return ok && analysisutil.IsNamedType(ptr.Elem(), "net/http", "Client") // method on *http.Client.
}

View File

@ -15,7 +15,6 @@ import (
"os"
"golang.org/x/tools/go/analysis"
"golang.org/x/tools/internal/aliases"
"golang.org/x/tools/internal/analysisinternal"
)
@ -121,7 +120,7 @@ func Imports(pkg *types.Package, path string) bool {
// This function avoids allocating the concatenation of "pkg.Name",
// which is important for the performance of syntax matching.
func IsNamedType(t types.Type, pkgPath string, names ...string) bool {
n, ok := aliases.Unalias(t).(*types.Named)
n, ok := types.Unalias(t).(*types.Named)
if !ok {
return false
}

View File

@ -10,7 +10,7 @@
// lostcancel: check cancel func returned by context.WithCancel is called
//
// The cancellation function returned by context.WithCancel, WithTimeout,
// and WithDeadline must be called or the new context will remain live
// until its parent context is cancelled.
// WithDeadline and variants such as WithCancelCause must be called,
// or the new context will remain live until its parent context is cancelled.
// (The background context is never cancelled.)
package lostcancel

View File

@ -198,7 +198,9 @@ func isContextWithCancel(info *types.Info, n ast.Node) bool {
return false
}
switch sel.Sel.Name {
case "WithCancel", "WithTimeout", "WithDeadline":
case "WithCancel", "WithCancelCause",
"WithTimeout", "WithTimeoutCause",
"WithDeadline", "WithDeadlineCause":
default:
return false
}

View File

@ -24,7 +24,6 @@ import (
"golang.org/x/tools/go/analysis/passes/internal/analysisutil"
"golang.org/x/tools/go/ast/inspector"
"golang.org/x/tools/go/types/typeutil"
"golang.org/x/tools/internal/aliases"
"golang.org/x/tools/internal/typeparams"
)
@ -161,7 +160,7 @@ func maybePrintfWrapper(info *types.Info, decl ast.Decl) *printfWrapper {
// Check final parameter is "args ...interface{}".
args := params.At(nparams - 1)
iface, ok := aliases.Unalias(args.Type().(*types.Slice).Elem()).(*types.Interface)
iface, ok := types.Unalias(args.Type().(*types.Slice).Elem()).(*types.Interface)
if !ok || !iface.Empty() {
return nil
}
@ -515,7 +514,7 @@ func checkPrintf(pass *analysis.Pass, kind Kind, call *ast.CallExpr, fn *types.F
// non-constant format string and no arguments:
// if msg contains "%", misformatting occurs.
// Report the problem and suggest a fix: fmt.Printf("%s", msg).
if idx == len(call.Args)-1 {
if !suppressNonconstants && idx == len(call.Args)-1 {
pass.Report(analysis.Diagnostic{
Pos: formatArg.Pos(),
End: formatArg.End(),
@ -1013,7 +1012,7 @@ func checkPrint(pass *analysis.Pass, call *ast.CallExpr, fn *types.Func) {
typ := params.At(firstArg).Type()
typ = typ.(*types.Slice).Elem()
it, ok := aliases.Unalias(typ).(*types.Interface)
it, ok := types.Unalias(typ).(*types.Interface)
if !ok || !it.Empty() {
// Skip variadic functions accepting non-interface{} args.
return
@ -1101,3 +1100,12 @@ func (ss stringSet) Set(flag string) error {
}
return nil
}
// suppressNonconstants suppresses reporting printf calls with
// non-constant formatting strings (proposal #60529) when true.
//
// This variable is to allow for staging the transition to newer
// versions of x/tools by vendoring.
//
// Remove this after the 1.24 release.
var suppressNonconstants bool

View File

@ -10,7 +10,6 @@ import (
"go/types"
"golang.org/x/tools/go/analysis"
"golang.org/x/tools/internal/aliases"
"golang.org/x/tools/internal/typeparams"
)
@ -73,7 +72,7 @@ func (m *argMatcher) match(typ types.Type, topLevel bool) bool {
return true
}
if typ, _ := aliases.Unalias(typ).(*types.TypeParam); typ != nil {
if typ, _ := types.Unalias(typ).(*types.TypeParam); typ != nil {
// Avoid infinite recursion through type parameters.
if m.seen[typ] {
return true
@ -276,7 +275,7 @@ func (m *argMatcher) match(typ types.Type, topLevel bool) bool {
}
func isConvertibleToString(typ types.Type) bool {
if bt, ok := aliases.Unalias(typ).(*types.Basic); ok && bt.Kind() == types.UntypedNil {
if bt, ok := types.Unalias(typ).(*types.Basic); ok && bt.Kind() == types.UntypedNil {
// We explicitly don't want untyped nil, which is
// convertible to both of the interfaces below, as it
// would just panic anyway.

View File

@ -21,7 +21,6 @@ import (
"golang.org/x/tools/go/analysis/passes/inspect"
"golang.org/x/tools/go/analysis/passes/internal/analysisutil"
"golang.org/x/tools/go/ast/inspector"
"golang.org/x/tools/internal/aliases"
"golang.org/x/tools/internal/typeparams"
)
@ -100,7 +99,7 @@ func checkLongShift(pass *analysis.Pass, node ast.Node, x, y ast.Expr) {
return
}
var structuralTypes []types.Type
switch t := aliases.Unalias(t).(type) {
switch t := types.Unalias(t).(type) {
case *types.TypeParam:
terms, err := typeparams.StructuralTerms(t)
if err != nil {

View File

@ -203,7 +203,7 @@ func kvFuncSkipArgs(fn *types.Func) (int, bool) {
// order to get to the ones that match the ...any parameter.
// The first key is the dereferenced receiver type name, or "" for a function.
var kvFuncs = map[string]map[string]int{
"": map[string]int{
"": {
"Debug": 1,
"Info": 1,
"Warn": 1,
@ -215,7 +215,7 @@ var kvFuncs = map[string]map[string]int{
"Log": 3,
"Group": 1,
},
"Logger": map[string]int{
"Logger": {
"Debug": 1,
"Info": 1,
"Warn": 1,
@ -227,7 +227,7 @@ var kvFuncs = map[string]map[string]int{
"Log": 3,
"With": 0,
},
"Record": map[string]int{
"Record": {
"Add": 0,
},
}

View File

@ -15,7 +15,6 @@ import (
"golang.org/x/tools/go/analysis/passes/inspect"
"golang.org/x/tools/go/analysis/passes/internal/analysisutil"
"golang.org/x/tools/go/ast/inspector"
"golang.org/x/tools/internal/aliases"
"golang.org/x/tools/internal/analysisinternal"
"golang.org/x/tools/internal/typeparams"
)
@ -248,7 +247,7 @@ func run(pass *analysis.Pass) (interface{}, error) {
func structuralTypes(t types.Type) ([]types.Type, error) {
var structuralTypes []types.Type
if tp, ok := aliases.Unalias(t).(*types.TypeParam); ok {
if tp, ok := types.Unalias(t).(*types.TypeParam); ok {
terms, err := typeparams.StructuralTerms(tp)
if err != nil {
return nil, err

View File

@ -14,10 +14,8 @@ import (
"golang.org/x/tools/go/analysis"
"golang.org/x/tools/go/analysis/passes/inspect"
"golang.org/x/tools/go/analysis/passes/internal/analysisutil"
"golang.org/x/tools/go/ast/astutil"
"golang.org/x/tools/go/ast/inspector"
"golang.org/x/tools/go/types/typeutil"
"golang.org/x/tools/internal/aliases"
)
//go:embed doc.go
@ -186,7 +184,7 @@ func withinScope(scope ast.Node, x *types.Var) bool {
func goAsyncCall(info *types.Info, goStmt *ast.GoStmt, toDecl func(*types.Func) *ast.FuncDecl) *asyncCall {
call := goStmt.Call
fun := astutil.Unparen(call.Fun)
fun := ast.Unparen(call.Fun)
if id := funcIdent(fun); id != nil {
if lit := funcLitInScope(id); lit != nil {
return &asyncCall{region: lit, async: goStmt, scope: nil, fun: fun}
@ -213,7 +211,7 @@ func tRunAsyncCall(info *types.Info, call *ast.CallExpr) *asyncCall {
return nil
}
fun := astutil.Unparen(call.Args[1])
fun := ast.Unparen(call.Args[1])
if lit, ok := fun.(*ast.FuncLit); ok { // function lit?
return &asyncCall{region: lit, async: call, scope: lit, fun: fun}
}
@ -243,7 +241,7 @@ var forbidden = []string{
// Returns (nil, nil, nil) if call is not of this form.
func forbiddenMethod(info *types.Info, call *ast.CallExpr) (*types.Var, *types.Selection, *types.Func) {
// Compare to typeutil.StaticCallee.
fun := astutil.Unparen(call.Fun)
fun := ast.Unparen(call.Fun)
selExpr, ok := fun.(*ast.SelectorExpr)
if !ok {
return nil, nil, nil
@ -254,7 +252,7 @@ func forbiddenMethod(info *types.Info, call *ast.CallExpr) (*types.Var, *types.S
}
var x *types.Var
if id, ok := astutil.Unparen(selExpr.X).(*ast.Ident); ok {
if id, ok := ast.Unparen(selExpr.X).(*ast.Ident); ok {
x, _ = info.Uses[id].(*types.Var)
}
if x == nil {
@ -271,7 +269,7 @@ func forbiddenMethod(info *types.Info, call *ast.CallExpr) (*types.Var, *types.S
func formatMethod(sel *types.Selection, fn *types.Func) string {
var ptr string
rtype := sel.Recv()
if p, ok := aliases.Unalias(rtype).(*types.Pointer); ok {
if p, ok := types.Unalias(rtype).(*types.Pointer); ok {
ptr = "*"
rtype = p.Elem()
}

View File

@ -8,7 +8,6 @@ import (
"go/ast"
"go/types"
"golang.org/x/tools/go/ast/astutil"
"golang.org/x/tools/internal/typeparams"
)
@ -56,7 +55,7 @@ func isMethodNamed(f *types.Func, pkgPath string, names ...string) bool {
}
func funcIdent(fun ast.Expr) *ast.Ident {
switch fun := astutil.Unparen(fun).(type) {
switch fun := ast.Unparen(fun).(type) {
case *ast.IndexExpr, *ast.IndexListExpr:
x, _, _, _ := typeparams.UnpackIndexExpr(fun) // necessary?
id, _ := x.(*ast.Ident)

View File

@ -48,7 +48,7 @@ var acceptedFuzzTypes = []types.Type{
func run(pass *analysis.Pass) (interface{}, error) {
for _, f := range pass.Files {
if !strings.HasSuffix(pass.Fset.File(f.Pos()).Name(), "_test.go") {
if !strings.HasSuffix(pass.Fset.File(f.FileStart).Name(), "_test.go") {
continue
}
for _, decl := range f.Decls {

View File

@ -15,9 +15,7 @@ import (
"golang.org/x/tools/go/analysis"
"golang.org/x/tools/go/analysis/passes/inspect"
"golang.org/x/tools/go/analysis/passes/internal/analysisutil"
"golang.org/x/tools/go/ast/astutil"
"golang.org/x/tools/go/ast/inspector"
"golang.org/x/tools/internal/aliases"
)
//go:embed doc.go
@ -70,7 +68,7 @@ func isSafeUintptr(info *types.Info, x ast.Expr) bool {
// Check unsafe.Pointer safety rules according to
// https://golang.org/pkg/unsafe/#Pointer.
switch x := astutil.Unparen(x).(type) {
switch x := ast.Unparen(x).(type) {
case *ast.SelectorExpr:
// "(6) Conversion of a reflect.SliceHeader or
// reflect.StringHeader Data field to or from Pointer."
@ -89,7 +87,7 @@ func isSafeUintptr(info *types.Info, x ast.Expr) bool {
// by the time we get to the conversion at the end.
// For now approximate by saying that *Header is okay
// but Header is not.
pt, ok := aliases.Unalias(info.Types[x.X].Type).(*types.Pointer)
pt, ok := types.Unalias(info.Types[x.X].Type).(*types.Pointer)
if ok && isReflectHeader(pt.Elem()) {
return true
}
@ -119,7 +117,7 @@ func isSafeUintptr(info *types.Info, x ast.Expr) bool {
// isSafeArith reports whether x is a pointer arithmetic expression that is safe
// to convert to unsafe.Pointer.
func isSafeArith(info *types.Info, x ast.Expr) bool {
switch x := astutil.Unparen(x).(type) {
switch x := ast.Unparen(x).(type) {
case *ast.CallExpr:
// Base case: initial conversion from unsafe.Pointer to uintptr.
return len(x.Args) == 1 &&

View File

@ -24,7 +24,6 @@ import (
"golang.org/x/tools/go/analysis"
"golang.org/x/tools/go/analysis/passes/inspect"
"golang.org/x/tools/go/analysis/passes/internal/analysisutil"
"golang.org/x/tools/go/ast/astutil"
"golang.org/x/tools/go/ast/inspector"
"golang.org/x/tools/go/types/typeutil"
)
@ -101,7 +100,7 @@ func run(pass *analysis.Pass) (interface{}, error) {
(*ast.ExprStmt)(nil),
}
inspect.Preorder(nodeFilter, func(n ast.Node) {
call, ok := astutil.Unparen(n.(*ast.ExprStmt).X).(*ast.CallExpr)
call, ok := ast.Unparen(n.(*ast.ExprStmt).X).(*ast.CallExpr)
if !ok {
return // not a call statement
}

View File

@ -51,7 +51,6 @@ import (
"golang.org/x/tools/go/analysis/internal/analysisflags"
"golang.org/x/tools/internal/analysisinternal"
"golang.org/x/tools/internal/facts"
"golang.org/x/tools/internal/versions"
)
// A Config describes a compilation unit to be analyzed.
@ -257,15 +256,15 @@ func run(fset *token.FileSet, cfg *Config, analyzers []*analysis.Analyzer) ([]re
GoVersion: cfg.GoVersion,
}
info := &types.Info{
Types: make(map[ast.Expr]types.TypeAndValue),
Defs: make(map[*ast.Ident]types.Object),
Uses: make(map[*ast.Ident]types.Object),
Implicits: make(map[ast.Node]types.Object),
Instances: make(map[*ast.Ident]types.Instance),
Scopes: make(map[ast.Node]*types.Scope),
Selections: make(map[*ast.SelectorExpr]*types.Selection),
Types: make(map[ast.Expr]types.TypeAndValue),
Defs: make(map[*ast.Ident]types.Object),
Uses: make(map[*ast.Ident]types.Object),
Implicits: make(map[ast.Node]types.Object),
Instances: make(map[*ast.Ident]types.Instance),
Scopes: make(map[ast.Node]*types.Scope),
Selections: make(map[*ast.SelectorExpr]*types.Selection),
FileVersions: make(map[*ast.File]string),
}
versions.InitFileVersions(info)
pkg, err := tc.Check(cfg.ImportPath, fset, files, info)
if err != nil {

View File

@ -1,654 +0,0 @@
// 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 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
})
// 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:
children = append(children,
tok(n.Lbrace, len("{")),
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:
children = append(children,
tok(n.Opening, len("(")), // or len("[")
tok(n.Closing, len(")"))) // or 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
}

View File

@ -1,485 +0,0 @@
// 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"
"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
}
newImport.Path.ValuePos = 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 {
spec.(*ast.ImportSpec).Path.ValuePos = first.Pos()
first.Specs = append(first.Specs, spec)
}
f.Decls = append(f.Decls[:i], f.Decls[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 []*ast.ImportSpec
var delcomments []*ast.CommentGroup
// Find the import nodes that import path, if any.
for i := 0; i < len(f.Decls); i++ {
decl := f.Decls[i]
gen, ok := decl.(*ast.GenDecl)
if !ok || gen.Tok != token.IMPORT {
continue
}
for j := 0; j < len(gen.Specs); j++ {
spec := gen.Specs[j]
impspec := spec.(*ast.ImportSpec)
if importName(impspec) != name || importPath(impspec) != path {
continue
}
// We found an import spec that imports path.
// Delete it.
delspecs = append(delspecs, impspec)
deleted = true
copy(gen.Specs[j:], gen.Specs[j+1:])
gen.Specs = gen.Specs[:len(gen.Specs)-1]
// If this was the last import spec in this decl,
// delete the decl, too.
if len(gen.Specs) == 0 {
copy(f.Decls[i:], f.Decls[i+1:])
f.Decls = f.Decls[:len(f.Decls)-1]
i--
break
} else if len(gen.Specs) == 1 {
if impspec.Doc != nil {
delcomments = append(delcomments, impspec.Doc)
}
if impspec.Comment != nil {
delcomments = append(delcomments, impspec.Comment)
}
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 = append(delcomments, cg)
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.
for i := 0; i < len(f.Imports); i++ {
imp := f.Imports[i]
for j, del := range delspecs {
if imp == del {
copy(f.Imports[i:], f.Imports[i+1:])
f.Imports = f.Imports[:len(f.Imports)-1]
copy(delspecs[j:], delspecs[j+1:])
delspecs = delspecs[:len(delspecs)-1]
i--
break
}
}
}
// Delete comments from f.Comments.
for i := 0; i < len(f.Comments); i++ {
cg := f.Comments[i]
for j, del := range delcomments {
if cg == del {
copy(f.Comments[i:], f.Comments[i+1:])
f.Comments = f.Comments[:len(f.Comments)-1]
copy(delcomments[j:], delcomments[j+1:])
delcomments = delcomments[:len(delcomments)-1]
i--
break
}
}
}
if len(delspecs) > 0 {
panic(fmt.Sprintf("deleted specs from Decls but not Imports: %v", delspecs))
}
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.
func UsesImport(f *ast.File, path string) (used bool) {
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
}

View File

@ -1,486 +0,0 @@
// 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.
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.Ptr && 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
}

View File

@ -1,19 +0,0 @@
// 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.
// TODO(adonovan): use go1.22's ast.Unparen.
func Unparen(e ast.Expr) ast.Expr {
for {
p, ok := e.(*ast.ParenExpr)
if !ok {
return e
}
e = p.X
}
}

View File

@ -73,6 +73,15 @@ func (in *Inspector) Preorder(types []ast.Node, f func(ast.Node)) {
// 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 := 0; i < len(in.events); {
ev := in.events[i]
@ -171,7 +180,9 @@ func (in *Inspector) WithStack(types []ast.Node, f func(n ast.Node, push bool, s
// 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.
// 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 {

View File

@ -0,0 +1,85 @@
// 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 of events: only nodes whose type matches an
// element of the types slice are included in the sequence.
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 := 0; i < 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) { ... }
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 := 0; i < 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++
}
}
}

View File

@ -228,7 +228,7 @@ func (enc *Encoder) For(obj types.Object) (Path, error) {
// Reject obviously non-viable cases.
switch obj := obj.(type) {
case *types.TypeName:
if _, ok := aliases.Unalias(obj.Type()).(*types.TypeParam); !ok {
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)
@ -280,26 +280,26 @@ func (enc *Encoder) For(obj types.Object) (Path, error) {
path = append(path, opType)
T := o.Type()
if alias, ok := T.(*aliases.Alias); ok {
if r := findTypeParam(obj, aliases.TypeParams(alias), path, opTypeParam, nil); r != nil {
if alias, ok := T.(*types.Alias); ok {
if r := findTypeParam(obj, aliases.TypeParams(alias), path, opTypeParam); r != nil {
return Path(r), nil
}
if r := find(obj, aliases.Rhs(alias), append(path, opRhs), nil); r != nil {
if r := find(obj, aliases.Rhs(alias), append(path, opRhs)); r != nil {
return Path(r), nil
}
} else if tname.IsAlias() {
// legacy alias
if r := find(obj, T, path, nil); r != nil {
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, nil); r != nil {
if r := findTypeParam(obj, named.TypeParams(), path, opTypeParam); r != nil {
return Path(r), nil
}
if r := find(obj, named.Underlying(), append(path, opUnderlying), nil); r != nil {
if r := find(obj, named.Underlying(), append(path, opUnderlying)); r != nil {
return Path(r), nil
}
}
@ -312,7 +312,7 @@ func (enc *Encoder) For(obj types.Object) (Path, error) {
if _, ok := o.(*types.TypeName); !ok {
if o.Exported() {
// exported non-type (const, var, func)
if r := find(obj, o.Type(), append(path, opType), nil); r != nil {
if r := find(obj, o.Type(), append(path, opType)); r != nil {
return Path(r), nil
}
}
@ -320,7 +320,7 @@ func (enc *Encoder) For(obj types.Object) (Path, error) {
}
// Inspect declared methods of defined types.
if T, ok := aliases.Unalias(o.Type()).(*types.Named); ok {
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,
@ -332,7 +332,7 @@ func (enc *Encoder) For(obj types.Object) (Path, error) {
if m == obj {
return Path(path2), nil // found declared method
}
if r := find(obj, m.Type(), append(path2, opType), nil); r != nil {
if r := find(obj, m.Type(), append(path2, opType)); r != nil {
return Path(r), nil
}
}
@ -447,46 +447,64 @@ func (enc *Encoder) concreteMethod(meth *types.Func) (Path, bool) {
//
// The seen map is used to short circuit cycles through type parameters. If
// nil, it will be allocated as necessary.
func find(obj types.Object, T types.Type, path []byte, seen map[*types.TypeName]bool) []byte {
//
// 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 *aliases.Alias:
return find(obj, aliases.Unalias(T), path, seen)
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 find(obj, T.Elem(), append(path, opElem), seen)
return f.find(T.Elem(), append(path, opElem))
case *types.Slice:
return find(obj, T.Elem(), append(path, opElem), seen)
return f.find(T.Elem(), append(path, opElem))
case *types.Array:
return find(obj, T.Elem(), append(path, opElem), seen)
return f.find(T.Elem(), append(path, opElem))
case *types.Chan:
return find(obj, T.Elem(), append(path, opElem), seen)
return f.find(T.Elem(), append(path, opElem))
case *types.Map:
if r := find(obj, T.Key(), append(path, opKey), seen); r != nil {
if r := f.find(T.Key(), append(path, opKey)); r != nil {
return r
}
return find(obj, T.Elem(), append(path, opElem), seen)
return f.find(T.Elem(), append(path, opElem))
case *types.Signature:
if r := findTypeParam(obj, T.RecvTypeParams(), path, opRecvTypeParam, nil); r != nil {
if r := f.findTypeParam(T.RecvTypeParams(), path, opRecvTypeParam); r != nil {
return r
}
if r := findTypeParam(obj, T.TypeParams(), path, opTypeParam, seen); r != nil {
if r := f.findTypeParam(T.TypeParams(), path, opTypeParam); r != nil {
return r
}
if r := find(obj, T.Params(), append(path, opParams), seen); r != nil {
if r := f.find(T.Params(), append(path, opParams)); r != nil {
return r
}
return find(obj, T.Results(), append(path, opResults), seen)
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 == obj {
if fld == f.obj {
return path2 // found field var
}
if r := find(obj, fld.Type(), append(path2, opType), seen); r != nil {
if r := f.find(fld.Type(), append(path2, opType)); r != nil {
return r
}
}
@ -495,10 +513,10 @@ func find(obj types.Object, T types.Type, path []byte, seen map[*types.TypeName]
for i := 0; i < T.Len(); i++ {
v := T.At(i)
path2 := appendOpArg(path, opAt, i)
if v == obj {
if v == f.obj {
return path2 // found param/result var
}
if r := find(obj, v.Type(), append(path2, opType), seen); r != nil {
if r := f.find(v.Type(), append(path2, opType)); r != nil {
return r
}
}
@ -506,28 +524,35 @@ func find(obj types.Object, T types.Type, path []byte, seen map[*types.TypeName]
case *types.Interface:
for i := 0; i < T.NumMethods(); i++ {
m := T.Method(i)
if f.seenMethods[m] {
return nil
}
path2 := appendOpArg(path, opMethod, i)
if m == obj {
if m == f.obj {
return path2 // found interface method
}
if r := find(obj, m.Type(), append(path2, opType), seen); r != nil {
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 name == obj {
return append(path, opObj)
}
if seen[name] {
if f.seenTParamNames[name] {
return nil
}
if seen == nil {
seen = make(map[*types.TypeName]bool)
if name == f.obj {
return append(path, opObj)
}
seen[name] = true
if r := find(obj, T.Constraint(), append(path, opConstraint), seen); r != nil {
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
@ -535,11 +560,15 @@ func find(obj types.Object, T types.Type, path []byte, seen map[*types.TypeName]
panic(T)
}
func findTypeParam(obj types.Object, list *types.TypeParamList, path []byte, op byte, seen map[*types.TypeName]bool) []byte {
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 := find(obj, tparam, path2, seen); r != nil {
if r := f.find(tparam, path2); r != nil {
return r
}
}
@ -626,7 +655,7 @@ func Object(pkg *types.Package, p Path) (types.Object, error) {
// Inv: t != nil, obj == nil
t = aliases.Unalias(t)
t = types.Unalias(t)
switch code {
case opElem:
hasElem, ok := t.(hasElem) // Pointer, Slice, Array, Chan, Map
@ -664,7 +693,7 @@ func Object(pkg *types.Package, p Path) (types.Object, error) {
t = named.Underlying()
case opRhs:
if alias, ok := t.(*aliases.Alias); ok {
if alias, ok := t.(*types.Alias); ok {
t = aliases.Rhs(alias)
} else if false && aliases.Enabled() {
// The Enabled check is too expensive, so for now we

View File

@ -8,7 +8,6 @@ import (
"go/ast"
"go/types"
"golang.org/x/tools/go/ast/astutil"
"golang.org/x/tools/internal/typeparams"
)
@ -17,7 +16,7 @@ import (
//
// Functions and methods may potentially have type parameters.
func Callee(info *types.Info, call *ast.CallExpr) types.Object {
fun := astutil.Unparen(call.Fun)
fun := ast.Unparen(call.Fun)
// Look through type instantiation if necessary.
isInstance := false

View File

@ -12,7 +12,6 @@ import (
"go/types"
"reflect"
"golang.org/x/tools/internal/aliases"
"golang.org/x/tools/internal/typeparams"
)
@ -260,8 +259,8 @@ func (h Hasher) hashFor(t types.Type) uint32 {
case *types.Basic:
return uint32(t.Kind())
case *aliases.Alias:
return h.Hash(aliases.Unalias(t))
case *types.Alias:
return h.Hash(types.Unalias(t))
case *types.Array:
return 9043 + 2*uint32(t.Len()) + 3*h.Hash(t.Elem())
@ -461,8 +460,8 @@ func (h Hasher) shallowHash(t types.Type) uint32 {
// elements (mostly Slice, Pointer, Basic, Named),
// so there's no need to optimize anything else.
switch t := t.(type) {
case *aliases.Alias:
return h.shallowHash(aliases.Unalias(t))
case *types.Alias:
return h.shallowHash(types.Unalias(t))
case *types.Signature:
var hash uint32 = 604171

View File

@ -9,8 +9,6 @@ package typeutil
import (
"go/types"
"sync"
"golang.org/x/tools/internal/aliases"
)
// A MethodSetCache records the method set of each type T for which
@ -34,12 +32,12 @@ func (cache *MethodSetCache) MethodSet(T types.Type) *types.MethodSet {
cache.mu.Lock()
defer cache.mu.Unlock()
switch T := aliases.Unalias(T).(type) {
switch T := types.Unalias(T).(type) {
case *types.Named:
return cache.lookupNamed(T).value
case *types.Pointer:
if N, ok := aliases.Unalias(T.Elem()).(*types.Named); ok {
if N, ok := types.Unalias(T.Elem()).(*types.Named); ok {
return cache.lookupNamed(N).pointer
}
}

View File

@ -8,8 +8,6 @@ package typeutil
import (
"go/types"
"golang.org/x/tools/internal/aliases"
)
// IntuitiveMethodSet returns the intuitive method set of a type T,
@ -28,7 +26,7 @@ import (
// 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 := aliases.Unalias(T).(*types.Pointer)
ptr, ok := types.Unalias(T).(*types.Pointer)
return ok && !types.IsInterface(ptr.Elem())
}

View File

@ -28,7 +28,7 @@ import (
func NewAlias(enabled bool, pos token.Pos, pkg *types.Package, name string, rhs types.Type, tparams []*types.TypeParam) *types.TypeName {
if enabled {
tname := types.NewTypeName(pos, pkg, name, nil)
newAlias(tname, rhs, tparams)
SetTypeParams(types.NewAlias(tname, rhs), tparams)
return tname
}
if len(tparams) > 0 {

View File

@ -1,37 +0,0 @@
// 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.22
// +build !go1.22
package aliases
import (
"go/types"
)
// Alias is a placeholder for a go/types.Alias for <=1.21.
// It will never be created by go/types.
type Alias struct{}
func (*Alias) String() string { panic("unreachable") }
func (*Alias) Underlying() types.Type { panic("unreachable") }
func (*Alias) Obj() *types.TypeName { panic("unreachable") }
func Rhs(alias *Alias) types.Type { panic("unreachable") }
func TypeParams(alias *Alias) *types.TypeParamList { panic("unreachable") }
func SetTypeParams(alias *Alias, tparams []*types.TypeParam) { panic("unreachable") }
func TypeArgs(alias *Alias) *types.TypeList { panic("unreachable") }
func Origin(alias *Alias) *Alias { panic("unreachable") }
// Unalias returns the type t for go <=1.21.
func Unalias(t types.Type) types.Type { return t }
func newAlias(name *types.TypeName, rhs types.Type, tparams []*types.TypeParam) *Alias {
panic("unreachable")
}
// Enabled reports whether [NewAlias] should create [types.Alias] types.
//
// Before go1.22, this function always returns false.
func Enabled() bool { return false }

View File

@ -2,9 +2,6 @@
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
//go:build go1.22
// +build go1.22
package aliases
import (
@ -14,22 +11,19 @@ import (
"go/types"
)
// Alias is an alias of types.Alias.
type Alias = types.Alias
// Rhs returns the type on the right-hand side of the alias declaration.
func Rhs(alias *Alias) types.Type {
func Rhs(alias *types.Alias) types.Type {
if alias, ok := any(alias).(interface{ Rhs() types.Type }); ok {
return alias.Rhs() // go1.23+
}
// go1.22's Alias didn't have the Rhs method,
// so Unalias is the best we can do.
return Unalias(alias)
return types.Unalias(alias)
}
// TypeParams returns the type parameter list of the alias.
func TypeParams(alias *Alias) *types.TypeParamList {
func TypeParams(alias *types.Alias) *types.TypeParamList {
if alias, ok := any(alias).(interface{ TypeParams() *types.TypeParamList }); ok {
return alias.TypeParams() // go1.23+
}
@ -37,7 +31,7 @@ func TypeParams(alias *Alias) *types.TypeParamList {
}
// SetTypeParams sets the type parameters of the alias type.
func SetTypeParams(alias *Alias, tparams []*types.TypeParam) {
func SetTypeParams(alias *types.Alias, tparams []*types.TypeParam) {
if alias, ok := any(alias).(interface {
SetTypeParams(tparams []*types.TypeParam)
}); ok {
@ -48,7 +42,7 @@ func SetTypeParams(alias *Alias, tparams []*types.TypeParam) {
}
// TypeArgs returns the type arguments used to instantiate the Alias type.
func TypeArgs(alias *Alias) *types.TypeList {
func TypeArgs(alias *types.Alias) *types.TypeList {
if alias, ok := any(alias).(interface{ TypeArgs() *types.TypeList }); ok {
return alias.TypeArgs() // go1.23+
}
@ -57,25 +51,13 @@ func TypeArgs(alias *Alias) *types.TypeList {
// Origin returns the generic Alias type of which alias is an instance.
// If alias is not an instance of a generic alias, Origin returns alias.
func Origin(alias *Alias) *Alias {
func Origin(alias *types.Alias) *types.Alias {
if alias, ok := any(alias).(interface{ Origin() *types.Alias }); ok {
return alias.Origin() // go1.23+
}
return alias // not an instance of a generic alias (go1.22)
}
// Unalias is a wrapper of types.Unalias.
func Unalias(t types.Type) types.Type { return types.Unalias(t) }
// newAlias is an internal alias around types.NewAlias.
// Direct usage is discouraged as the moment.
// Try to use NewAlias instead.
func newAlias(tname *types.TypeName, rhs types.Type, tparams []*types.TypeParam) *Alias {
a := types.NewAlias(tname, rhs)
SetTypeParams(a, tparams)
return a
}
// Enabled reports whether [NewAlias] should create [types.Alias] types.
//
// This function is expensive! Call it sparingly.
@ -91,7 +73,7 @@ func Enabled() bool {
// many tests. Therefore any attempt to cache the result
// is just incorrect.
fset := token.NewFileSet()
f, _ := parser.ParseFile(fset, "a.go", "package p; type A = int", 0)
f, _ := parser.ParseFile(fset, "a.go", "package p; type A = int", parser.SkipObjectResolution)
pkg, _ := new(types.Config).Check("p", fset, []*ast.File{f}, nil)
_, enabled := pkg.Scope().Lookup("A").Type().(*types.Alias)
return enabled

View File

@ -10,6 +10,7 @@ import (
"bytes"
"fmt"
"go/ast"
"go/scanner"
"go/token"
"go/types"
"os"
@ -17,24 +18,57 @@ import (
"strconv"
"golang.org/x/tools/go/analysis"
"golang.org/x/tools/internal/aliases"
)
func TypeErrorEndPos(fset *token.FileSet, src []byte, start token.Pos) token.Pos {
// Get the end position for the type error.
offset, end := fset.PositionFor(start, false).Offset, start
if offset >= len(src) {
return end
file := fset.File(start)
if file == nil {
return start
}
if width := bytes.IndexAny(src[offset:], " \n,():;[]+-*"); width > 0 {
end = start + token.Pos(width)
if offset := file.PositionFor(start, false).Offset; offset > len(src) {
return start
} else {
src = src[offset:]
}
// Attempt to find a reasonable end position for the type error.
//
// TODO(rfindley): the heuristic implemented here is unclear. It looks like
// it seeks the end of the primary operand starting at start, but that is not
// quite implemented (for example, given a func literal this heuristic will
// return the range of the func keyword).
//
// We should formalize this heuristic, or deprecate it by finally proposing
// to add end position to all type checker errors.
//
// Nevertheless, ensure that the end position at least spans the current
// token at the cursor (this was golang/go#69505).
end := start
{
var s scanner.Scanner
fset := token.NewFileSet()
f := fset.AddFile("", fset.Base(), len(src))
s.Init(f, src, nil /* no error handler */, scanner.ScanComments)
pos, tok, lit := s.Scan()
if tok != token.SEMICOLON && token.Pos(f.Base()) <= pos && pos <= token.Pos(f.Base()+f.Size()) {
off := file.Offset(pos) + len(lit)
src = src[off:]
end += token.Pos(off)
}
}
// Look for bytes that might terminate the current operand. See note above:
// this is imprecise.
if width := bytes.IndexAny(src, " \n,():;[]+-*/"); width > 0 {
end += token.Pos(width)
}
return end
}
func ZeroValue(f *ast.File, pkg *types.Package, typ types.Type) ast.Expr {
// TODO(adonovan): think about generics, and also generic aliases.
under := aliases.Unalias(typ)
under := types.Unalias(typ)
// Don't call Underlying unconditionally: although it removes
// Named and Alias, it also removes TypeParam.
if n, ok := under.(*types.Named); ok {
@ -416,8 +450,7 @@ func CheckReadable(pass *analysis.Pass, filename string) error {
return nil
}
for _, f := range pass.Files {
// TODO(adonovan): use go1.20 f.FileStart
if pass.Fset.File(f.Pos()).Name() == filename {
if pass.Fset.File(f.FileStart).Name() == filename {
return nil
}
}

View File

@ -6,8 +6,6 @@ package facts
import (
"go/types"
"golang.org/x/tools/internal/aliases"
)
// importMap computes the import map for a package by traversing the
@ -47,8 +45,8 @@ func importMap(imports []*types.Package) map[string]*types.Package {
addType = func(T types.Type) {
switch T := T.(type) {
case *aliases.Alias:
addType(aliases.Unalias(T))
case *types.Alias:
addType(types.Unalias(T))
case *types.Basic:
// nop
case *types.Named:

View File

@ -16,8 +16,6 @@ import (
"go/ast"
"go/token"
"go/types"
"golang.org/x/tools/internal/aliases"
)
// UnpackIndexExpr extracts data from AST nodes that represent index
@ -65,7 +63,7 @@ func PackIndexExpr(x ast.Expr, lbrack token.Pos, indices []ast.Expr, rbrack toke
// IsTypeParam reports whether t is a type parameter (or an alias of one).
func IsTypeParam(t types.Type) bool {
_, ok := aliases.Unalias(t).(*types.TypeParam)
_, ok := types.Unalias(t).(*types.TypeParam)
return ok
}
@ -93,8 +91,8 @@ func IsTypeParam(t types.Type) bool {
// In this case, GenericAssignableTo reports that instantiations of Container
// are assignable to the corresponding instantiation of Interface.
func GenericAssignableTo(ctxt *types.Context, V, T types.Type) bool {
V = aliases.Unalias(V)
T = aliases.Unalias(T)
V = types.Unalias(V)
T = types.Unalias(T)
// If V and T are not both named, or do not have matching non-empty type
// parameter lists, fall back on types.AssignableTo.

View File

@ -37,8 +37,20 @@ func (w *Free) Has(typ types.Type) (res bool) {
case nil, *types.Basic: // TODO(gri) should nil be handled here?
break
case *aliases.Alias:
return w.Has(aliases.Unalias(t))
case *types.Alias:
if aliases.TypeParams(t).Len() > aliases.TypeArgs(t).Len() {
return true // This is an uninstantiated Alias.
}
// The expansion of an alias can have free type parameters,
// whether or not the alias itself has type parameters:
//
// func _[K comparable]() {
// type Set = map[K]bool // free(Set) = {K}
// type MapTo[V] = map[K]V // free(Map[foo]) = {V}
// }
//
// So, we must Unalias.
return w.Has(types.Unalias(t))
case *types.Array:
return w.Has(t.Elem())
@ -98,9 +110,8 @@ func (w *Free) Has(typ types.Type) (res bool) {
case *types.Named:
args := t.TypeArgs()
// TODO(taking): this does not match go/types/infer.go. Check with rfindley.
if params := t.TypeParams(); params.Len() > args.Len() {
return true
return true // this is an uninstantiated named type.
}
for i, n := 0, args.Len(); i < n; i++ {
if w.Has(args.At(i)) {

View File

@ -0,0 +1,133 @@
// 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.
package typesinternal
import (
"fmt"
"go/types"
"golang.org/x/tools/go/types/typeutil"
)
// ForEachElement calls f for type T and each type reachable from its
// type through reflection. It does this by recursively stripping off
// type constructors; in addition, for each named type N, the type *N
// is added to the result as it may have additional methods.
//
// The caller must provide an initially empty set used to de-duplicate
// identical types, potentially across multiple calls to ForEachElement.
// (Its final value holds all the elements seen, matching the arguments
// passed to f.)
//
// TODO(adonovan): share/harmonize with go/callgraph/rta.
func ForEachElement(rtypes *typeutil.Map, msets *typeutil.MethodSetCache, T types.Type, f func(types.Type)) {
var visit func(T types.Type, skip bool)
visit = func(T types.Type, skip bool) {
if !skip {
if seen, _ := rtypes.Set(T, true).(bool); seen {
return // de-dup
}
f(T) // notify caller of new element type
}
// Recursion over signatures of each method.
tmset := msets.MethodSet(T)
for i := 0; i < tmset.Len(); i++ {
sig := tmset.At(i).Type().(*types.Signature)
// It is tempting to call visit(sig, false)
// but, as noted in golang.org/cl/65450043,
// the Signature.Recv field is ignored by
// types.Identical and typeutil.Map, which
// is confusing at best.
//
// More importantly, the true signature rtype
// reachable from a method using reflection
// has no receiver but an extra ordinary parameter.
// For the Read method of io.Reader we want:
// func(Reader, []byte) (int, error)
// but here sig is:
// func([]byte) (int, error)
// with .Recv = Reader (though it is hard to
// notice because it doesn't affect Signature.String
// or types.Identical).
//
// TODO(adonovan): construct and visit the correct
// non-method signature with an extra parameter
// (though since unnamed func types have no methods
// there is essentially no actual demand for this).
//
// TODO(adonovan): document whether or not it is
// safe to skip non-exported methods (as RTA does).
visit(sig.Params(), true) // skip the Tuple
visit(sig.Results(), true) // skip the Tuple
}
switch T := T.(type) {
case *types.Alias:
visit(types.Unalias(T), skip) // emulates the pre-Alias behavior
case *types.Basic:
// nop
case *types.Interface:
// nop---handled by recursion over method set.
case *types.Pointer:
visit(T.Elem(), false)
case *types.Slice:
visit(T.Elem(), false)
case *types.Chan:
visit(T.Elem(), false)
case *types.Map:
visit(T.Key(), false)
visit(T.Elem(), false)
case *types.Signature:
if T.Recv() != nil {
panic(fmt.Sprintf("Signature %s has Recv %s", T, T.Recv()))
}
visit(T.Params(), true) // skip the Tuple
visit(T.Results(), true) // skip the Tuple
case *types.Named:
// A pointer-to-named type can be derived from a named
// type via reflection. It may have methods too.
visit(types.NewPointer(T), false)
// Consider 'type T struct{S}' where S has methods.
// Reflection provides no way to get from T to struct{S},
// only to S, so the method set of struct{S} is unwanted,
// so set 'skip' flag during recursion.
visit(T.Underlying(), true) // skip the unnamed type
case *types.Array:
visit(T.Elem(), false)
case *types.Struct:
for i, n := 0, T.NumFields(); i < n; i++ {
// TODO(adonovan): document whether or not
// it is safe to skip non-exported fields.
visit(T.Field(i).Type(), false)
}
case *types.Tuple:
for i, n := 0, T.Len(); i < n; i++ {
visit(T.At(i).Type(), false)
}
case *types.TypeParam, *types.Union:
// forEachReachable must not be called on parameterized types.
panic(T)
default:
panic(T)
}
}
visit(T, false)
}

View File

@ -6,8 +6,6 @@ package typesinternal
import (
"go/types"
"golang.org/x/tools/internal/aliases"
)
// ReceiverNamed returns the named type (if any) associated with the
@ -15,11 +13,11 @@ import (
// It also reports whether a Pointer was present.
func ReceiverNamed(recv *types.Var) (isPtr bool, named *types.Named) {
t := recv.Type()
if ptr, ok := aliases.Unalias(t).(*types.Pointer); ok {
if ptr, ok := types.Unalias(t).(*types.Pointer); ok {
isPtr = true
t = ptr.Elem()
}
named, _ = aliases.Unalias(t).(*types.Named)
named, _ = types.Unalias(t).(*types.Named)
return
}
@ -36,7 +34,7 @@ func ReceiverNamed(recv *types.Var) (isPtr bool, named *types.Named) {
// indirection from the type, regardless of named types (analogous to
// a LOAD instruction).
func Unpointer(t types.Type) types.Type {
if ptr, ok := aliases.Unalias(t).(*types.Pointer); ok {
if ptr, ok := types.Unalias(t).(*types.Pointer); ok {
return ptr.Elem()
}
return t

View File

@ -11,6 +11,8 @@ import (
"go/types"
"reflect"
"unsafe"
"golang.org/x/tools/internal/aliases"
)
func SetUsesCgo(conf *types.Config) bool {
@ -63,3 +65,57 @@ func NameRelativeTo(pkg *types.Package) types.Qualifier {
return other.Name()
}
}
// A NamedOrAlias is a [types.Type] that is named (as
// defined by the spec) and capable of bearing type parameters: it
// abstracts aliases ([types.Alias]) and defined types
// ([types.Named]).
//
// Every type declared by an explicit "type" declaration is a
// NamedOrAlias. (Built-in type symbols may additionally
// have type [types.Basic], which is not a NamedOrAlias,
// though the spec regards them as "named".)
//
// NamedOrAlias cannot expose the Origin method, because
// [types.Alias.Origin] and [types.Named.Origin] have different
// (covariant) result types; use [Origin] instead.
type NamedOrAlias interface {
types.Type
Obj() *types.TypeName
}
// TypeParams is a light shim around t.TypeParams().
// (go/types.Alias).TypeParams requires >= 1.23.
func TypeParams(t NamedOrAlias) *types.TypeParamList {
switch t := t.(type) {
case *types.Alias:
return aliases.TypeParams(t)
case *types.Named:
return t.TypeParams()
}
return nil
}
// TypeArgs is a light shim around t.TypeArgs().
// (go/types.Alias).TypeArgs requires >= 1.23.
func TypeArgs(t NamedOrAlias) *types.TypeList {
switch t := t.(type) {
case *types.Alias:
return aliases.TypeArgs(t)
case *types.Named:
return t.TypeArgs()
}
return nil
}
// Origin returns the generic type of the Named or Alias type t if it
// is instantiated, otherwise it returns t.
func Origin(t NamedOrAlias) NamedOrAlias {
switch t := t.(type) {
case *types.Alias:
return aliases.Origin(t)
case *types.Named:
return t.Origin()
}
return t
}

View File

@ -1,14 +0,0 @@
// 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.
package versions
// toolchain is maximum version (<1.22) that the go toolchain used
// to build the current tool is known to support.
//
// When a tool is built with >=1.22, the value of toolchain is unused.
//
// x/tools does not support building with go <1.18. So we take this
// as the minimum possible maximum.
var toolchain string = Go1_18

View File

@ -1,14 +0,0 @@
// 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.19
// +build go1.19
package versions
func init() {
if Compare(toolchain, Go1_19) < 0 {
toolchain = Go1_19
}
}

View File

@ -1,14 +0,0 @@
// 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.20
// +build go1.20
package versions
func init() {
if Compare(toolchain, Go1_20) < 0 {
toolchain = Go1_20
}
}

View File

@ -1,14 +0,0 @@
// 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.21
// +build go1.21
package versions
func init() {
if Compare(toolchain, Go1_21) < 0 {
toolchain = Go1_21
}
}

View File

@ -5,15 +5,29 @@
package versions
import (
"go/ast"
"go/types"
)
// GoVersion returns the Go version of the type package.
// It returns zero if no version can be determined.
func GoVersion(pkg *types.Package) string {
// TODO(taking): x/tools can call GoVersion() [from 1.21] after 1.25.
if pkg, ok := any(pkg).(interface{ GoVersion() string }); ok {
return pkg.GoVersion()
// FileVersion returns a file's Go version.
// The reported version is an unknown Future version if a
// version cannot be determined.
func FileVersion(info *types.Info, file *ast.File) string {
// In tools built with Go >= 1.22, the Go version of a file
// follow a cascades of sources:
// 1) types.Info.FileVersion, which follows the cascade:
// 1.a) file version (ast.File.GoVersion),
// 1.b) the package version (types.Config.GoVersion), or
// 2) is some unknown Future version.
//
// File versions require a valid package version to be provided to types
// in Config.GoVersion. Config.GoVersion is either from the package's module
// or the toolchain (go run). This value should be provided by go/packages
// or unitchecker.Config.GoVersion.
if v := info.FileVersions[file]; IsValid(v) {
return v
}
return ""
// Note: we could instead return runtime.Version() [if valid].
// This would act as a max version on what a tool can support.
return Future
}

View File

@ -1,30 +0,0 @@
// Copyright 2023 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.22
// +build !go1.22
package versions
import (
"go/ast"
"go/types"
)
// FileVersion returns a language version (<=1.21) derived from runtime.Version()
// or an unknown future version.
func FileVersion(info *types.Info, file *ast.File) string {
// In x/tools built with Go <= 1.21, we do not have Info.FileVersions
// available. We use a go version derived from the toolchain used to
// compile the tool by default.
// This will be <= go1.21. We take this as the maximum version that
// this tool can support.
//
// There are no features currently in x/tools that need to tell fine grained
// differences for versions <1.22.
return toolchain
}
// InitFileVersions is a noop when compiled with this Go version.
func InitFileVersions(*types.Info) {}

View File

@ -1,41 +0,0 @@
// Copyright 2023 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.22
// +build go1.22
package versions
import (
"go/ast"
"go/types"
)
// FileVersion returns a file's Go version.
// The reported version is an unknown Future version if a
// version cannot be determined.
func FileVersion(info *types.Info, file *ast.File) string {
// In tools built with Go >= 1.22, the Go version of a file
// follow a cascades of sources:
// 1) types.Info.FileVersion, which follows the cascade:
// 1.a) file version (ast.File.GoVersion),
// 1.b) the package version (types.Config.GoVersion), or
// 2) is some unknown Future version.
//
// File versions require a valid package version to be provided to types
// in Config.GoVersion. Config.GoVersion is either from the package's module
// or the toolchain (go run). This value should be provided by go/packages
// or unitchecker.Config.GoVersion.
if v := info.FileVersions[file]; IsValid(v) {
return v
}
// Note: we could instead return runtime.Version() [if valid].
// This would act as a max version on what a tool can support.
return Future
}
// InitFileVersions initializes info to record Go versions for Go files.
func InitFileVersions(info *types.Info) {
info.FileVersions = make(map[*ast.File]string)
}

View File

@ -28,8 +28,8 @@ golang.org/x/arch/x86/x86asm
# golang.org/x/build v0.0.0-20240722200705-b9910f320300
## explicit; go 1.21
golang.org/x/build/relnote
# golang.org/x/mod v0.20.0
## explicit; go 1.18
# golang.org/x/mod v0.22.0
## explicit; go 1.22.0
golang.org/x/mod/internal/lazyregexp
golang.org/x/mod/modfile
golang.org/x/mod/module
@ -39,7 +39,7 @@ golang.org/x/mod/sumdb/dirhash
golang.org/x/mod/sumdb/note
golang.org/x/mod/sumdb/tlog
golang.org/x/mod/zip
# golang.org/x/sync v0.8.0
# golang.org/x/sync v0.9.0
## explicit; go 1.18
golang.org/x/sync/errgroup
golang.org/x/sync/semaphore
@ -73,8 +73,8 @@ golang.org/x/text/internal/tag
golang.org/x/text/language
golang.org/x/text/transform
golang.org/x/text/unicode/norm
# golang.org/x/tools v0.24.1-0.20240904143311-70f56264139c
## explicit; go 1.22.6
# golang.org/x/tools v0.27.0
## explicit; go 1.22.0
golang.org/x/tools/cmd/bisect
golang.org/x/tools/cover
golang.org/x/tools/go/analysis
@ -116,7 +116,6 @@ golang.org/x/tools/go/analysis/passes/unreachable
golang.org/x/tools/go/analysis/passes/unsafeptr
golang.org/x/tools/go/analysis/passes/unusedresult
golang.org/x/tools/go/analysis/unitchecker
golang.org/x/tools/go/ast/astutil
golang.org/x/tools/go/ast/inspector
golang.org/x/tools/go/cfg
golang.org/x/tools/go/types/objectpath