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 (
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=
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=
package sumdb
import (
+ "bytes"
"context"
"net/http"
"os"
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")
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
"bytes"
"errors"
"fmt"
+ "go/version"
"io"
"os"
"os/exec"
"unicode"
"unicode/utf8"
+ "golang.org/x/mod/modfile"
"golang.org/x/mod/module"
)
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.
// 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)
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()
+ }
}
}
}
addError(p, false, errPathNotRelative)
continue
}
- if isVendoredPackage(p) {
+ if isVendoredPackage(p, vers) {
// Skip files in vendored packages.
addError(p, true, errVendored)
continue
// 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.
// 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.
+ //
+ // 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.
+ //
+ // This bug affected how vendored packages were identified in cases like:
//
- // i = j + len("/vendor/")
+ // - "pkg/vendor/vendor.go" (incorrectly identified as vendored in pre-1.24)
+ // - "pkg/vendor/foo/foo.go" (correctly identified as vendored)
//
- // (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/")
+ // 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
}
// 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
}
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
}
+++ /dev/null
-// 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.
-}
// 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 (
+++ /dev/null
-// 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())
-}
// 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
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,
}
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
"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"
)
// 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
"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"
)
// 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)...)
"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
// 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
}
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)
"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"
)
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 {
// 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
"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"
)
}
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
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
}
}
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
"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"
)
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.
}
"os"
"golang.org/x/tools/go/analysis"
- "golang.org/x/tools/internal/aliases"
"golang.org/x/tools/internal/analysisinternal"
)
// 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
}
// 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
return false
}
switch sel.Sel.Name {
- case "WithCancel", "WithTimeout", "WithDeadline":
+ case "WithCancel", "WithCancelCause",
+ "WithTimeout", "WithTimeoutCause",
+ "WithDeadline", "WithDeadlineCause":
default:
return false
}
"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"
)
// 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
}
// 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(),
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
}
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
"go/types"
"golang.org/x/tools/go/analysis"
- "golang.org/x/tools/internal/aliases"
"golang.org/x/tools/internal/typeparams"
)
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
}
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.
"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"
)
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 {
// 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,
"Log": 3,
"Group": 1,
},
- "Logger": map[string]int{
+ "Logger": {
"Debug": 1,
"Info": 1,
"Warn": 1,
"Log": 3,
"With": 0,
},
- "Record": map[string]int{
+ "Record": {
"Add": 0,
},
}
"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"
)
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
"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
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}
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}
}
// 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
}
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 {
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()
}
"go/ast"
"go/types"
- "golang.org/x/tools/go/ast/astutil"
"golang.org/x/tools/internal/typeparams"
)
}
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)
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 {
"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
// 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."
// 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
}
// 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 &&
"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"
)
(*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
}
"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.
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 {
+++ /dev/null
-// 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
-}
+++ /dev/null
-// 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
-}
+++ /dev/null
-// 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
-}
+++ /dev/null
-// 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
- }
-}
// 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]
// 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 {
--- /dev/null
+// 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++
+ }
+ }
+}
// 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)
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
}
}
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
}
}
}
// 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,
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
}
}
//
// 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
}
}
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
}
}
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
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
}
}
// 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
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
"go/ast"
"go/types"
- "golang.org/x/tools/go/ast/astutil"
"golang.org/x/tools/internal/typeparams"
)
//
// 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
"go/types"
"reflect"
- "golang.org/x/tools/internal/aliases"
"golang.org/x/tools/internal/typeparams"
)
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())
// 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
import (
"go/types"
"sync"
-
- "golang.org/x/tools/internal/aliases"
)
// A MethodSetCache records the method set of each type T for which
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
}
}
import (
"go/types"
-
- "golang.org/x/tools/internal/aliases"
)
// IntuitiveMethodSet returns the intuitive method set of a type T,
// 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())
}
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 {
+++ /dev/null
-// 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 }
// 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 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+
}
}
// 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 {
}
// 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+
}
// 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.
// 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
"bytes"
"fmt"
"go/ast"
+ "go/scanner"
"go/token"
"go/types"
"os"
"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 {
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
}
}
import (
"go/types"
-
- "golang.org/x/tools/internal/aliases"
)
// importMap computes the import map for a package by traversing the
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:
"go/ast"
"go/token"
"go/types"
-
- "golang.org/x/tools/internal/aliases"
)
// UnpackIndexExpr extracts data from AST nodes that represent index
// 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
}
// 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.
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())
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)) {
--- /dev/null
+// 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)
+}
import (
"go/types"
-
- "golang.org/x/tools/internal/aliases"
)
// ReceiverNamed returns the named type (if any) associated with the
// 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
}
// 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
"go/types"
"reflect"
"unsafe"
+
+ "golang.org/x/tools/internal/aliases"
)
func SetUsesCgo(conf *types.Config) bool {
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
+}
+++ /dev/null
-// 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
+++ /dev/null
-// 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
- }
-}
+++ /dev/null
-// 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
- }
-}
+++ /dev/null
-// 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
- }
-}
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
}
+++ /dev/null
-// 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) {}
+++ /dev/null
-// 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)
-}
# 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
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
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
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